();
+
+ /**
+ * Check if a line contains a URL (following @stylistic/max-len behavior)
+ * URLs are identified by the URL_PATTERN
+ */
+ function containsUrl(line: string): boolean {
+ // Reset regex state
+ URL_PATTERN.lastIndex = 0;
+ return URL_PATTERN.test(line);
+ }
+
+ /**
+ * Check lines in a node against a maximum length
+ */
+ function checkLines(
+ node: Heading | Paragraph | Table | Html | Code | Yaml | Toml | Math,
+ ): void {
+ const maxLength = options.maxLength(node);
+ if (maxLength === "ignore") return;
+
+ const nodeLoc = sourceCode.getLoc(node);
+ const startLine = nodeLoc.start.line;
+ const endLine = nodeLoc.end.line;
+
+ for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++) {
+ // Skip if we've already checked this line
+ if (checkedLines.has(lineNumber)) {
+ continue;
+ }
+
+ const line = sourceCode.lines[lineNumber - 1];
+ const width = getTextWidth(line);
+
+ if (width > maxLength) {
+ if (options.ignoreUrls && containsUrl(line)) {
+ continue;
+ }
+
+ context.report({
+ node,
+ loc: {
+ start: { line: lineNumber, column: 1 },
+ end: { line: lineNumber, column: line.length + 1 },
+ },
+ messageId: "maxLenExceeded",
+ data: {
+ actual: String(width),
+ max: String(maxLength),
+ },
+ });
+
+ checkedLines.add(lineNumber);
+ }
+ }
+ }
+
+ return {
+ heading: checkLines,
+ paragraph: checkLines,
+ table: checkLines,
+ html(node) {
+ const parent = getParent(sourceCode, node);
+ if (
+ parent.type !== "root" &&
+ parent.type !== "blockquote" &&
+ parent.type !== "listItem" &&
+ parent.type !== "footnoteDefinition" &&
+ parent.type !== "customContainer"
+ ) {
+ // HTML node is inside phrasing content, so it's not a block-level HTML node
+ return;
+ }
+ checkLines(node);
+ },
+ code: checkLines,
+ yaml: checkLines,
+ toml: checkLines,
+ math: checkLines,
+ };
+ },
+});
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index bb325a8c..6eb92395 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -26,6 +26,7 @@ import linkParenNewline from "../rules/link-paren-newline.ts";
import linkParenSpacing from "../rules/link-paren-spacing.ts";
import linkTitleStyle from "../rules/link-title-style.ts";
import listMarkerAlignment from "../rules/list-marker-alignment.ts";
+import maxLen from "../rules/max-len.ts";
import noHeadingTrailingPunctuation from "../rules/no-heading-trailing-punctuation.ts";
import noImplicitBlockClosing from "../rules/no-implicit-block-closing.ts";
import noLazinessBlockquotes from "../rules/no-laziness-blockquotes.ts";
@@ -80,6 +81,7 @@ export const rules = [
linkParenSpacing,
linkTitleStyle,
listMarkerAlignment,
+ maxLen,
noHeadingTrailingPunctuation,
noImplicitBlockClosing,
noLazinessBlockquotes,
diff --git a/tests/fixtures/rules/max-len/invalid/blockquote-long-input.md b/tests/fixtures/rules/max-len/invalid/blockquote-long-input.md
new file mode 100644
index 00000000..a44bdb37
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/blockquote-long-input.md
@@ -0,0 +1,3 @@
+> This is a very long blockquote that definitely exceeds the default one hundred and twenty character maximum length limit and should be reported.
+> Short line.
+> Another very long blockquote line that goes on and on and on and should definitely be caught by the rule as a violation of the maximum length.
diff --git a/tests/fixtures/rules/max-len/invalid/code-block-checked-config.json b/tests/fixtures/rules/max-len/invalid/code-block-checked-config.json
new file mode 100644
index 00000000..af59d84b
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/code-block-checked-config.json
@@ -0,0 +1,7 @@
+{
+ "options": [
+ {
+ "code": 60
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/invalid/code-block-checked-input.md b/tests/fixtures/rules/max-len/invalid/code-block-checked-input.md
new file mode 100644
index 00000000..1edb722f
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/code-block-checked-input.md
@@ -0,0 +1,5 @@
+Code blocks should be checked when the option is set:
+
+```js
+const veryLongVariableName = "this is a very long string that exceeds the maximum length limit of sixty characters";
+```
diff --git a/tests/fixtures/rules/max-len/invalid/code-language-specific-config.json b/tests/fixtures/rules/max-len/invalid/code-language-specific-config.json
new file mode 100644
index 00000000..c2f80605
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/code-language-specific-config.json
@@ -0,0 +1,10 @@
+{
+ "options": [
+ {
+ "code": {
+ "javascript": 60,
+ "python": 50
+ }
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/invalid/code-language-specific-input.md b/tests/fixtures/rules/max-len/invalid/code-language-specific-input.md
new file mode 100644
index 00000000..76afd9ac
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/code-language-specific-input.md
@@ -0,0 +1,13 @@
+Code blocks with language-specific limits:
+
+```javascript
+const veryLongVariableName = "this line exceeds the 60 character limit for javascript";
+```
+
+```python
+very_long_variable_name = "this exceeds 50 chars for python"
+```
+
+```shell
+echo "This shell script line is very long but shell is not configured so it's ignored"
+```
diff --git a/tests/fixtures/rules/max-len/invalid/custom-max-config.json b/tests/fixtures/rules/max-len/invalid/custom-max-config.json
new file mode 100644
index 00000000..71f81c55
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/custom-max-config.json
@@ -0,0 +1,8 @@
+{
+ "options": [
+ {
+ "heading": 40,
+ "paragraph": 40
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/invalid/custom-max-input.md b/tests/fixtures/rules/max-len/invalid/custom-max-input.md
new file mode 100644
index 00000000..a938b5cb
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/custom-max-input.md
@@ -0,0 +1,3 @@
+# This heading is longer than 40 characters
+
+This paragraph is too long for the custom limit.
diff --git a/tests/fixtures/rules/max-len/invalid/footnote-definition-config.json b/tests/fixtures/rules/max-len/invalid/footnote-definition-config.json
new file mode 100644
index 00000000..01c9a323
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/footnote-definition-config.json
@@ -0,0 +1,7 @@
+{
+ "options": [
+ {
+ "footnoteDefinition": 60
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/invalid/footnote-definition-input.md b/tests/fixtures/rules/max-len/invalid/footnote-definition-input.md
new file mode 100644
index 00000000..5dc5244e
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/footnote-definition-input.md
@@ -0,0 +1,3 @@
+Here is a reference to a footnote[^1].
+
+[^1]: This is a footnote definition with a very long line that exceeds sixty characters limit.
diff --git a/tests/fixtures/rules/max-len/invalid/frontmatter-yaml-config.json b/tests/fixtures/rules/max-len/invalid/frontmatter-yaml-config.json
new file mode 100644
index 00000000..9a225cd0
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/frontmatter-yaml-config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "frontmatter": {
+ "yaml": 50
+ }
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/invalid/frontmatter-yaml-input.md b/tests/fixtures/rules/max-len/invalid/frontmatter-yaml-input.md
new file mode 100644
index 00000000..7d9c32c8
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/frontmatter-yaml-input.md
@@ -0,0 +1,8 @@
+---
+title: "This is a very long title that exceeds the 50 character limit"
+description: Short
+---
+
+# Content
+
+This is the content.
diff --git a/tests/fixtures/rules/max-len/invalid/heading-long-input.md b/tests/fixtures/rules/max-len/invalid/heading-long-input.md
new file mode 100644
index 00000000..321c64d1
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/heading-long-input.md
@@ -0,0 +1,3 @@
+# This is a very long heading that definitely exceeds the default eighty character limit
+
+## This is another heading that is way too long and should be reported as a violation
diff --git a/tests/fixtures/rules/max-len/invalid/html-block-config.json b/tests/fixtures/rules/max-len/invalid/html-block-config.json
new file mode 100644
index 00000000..e3fdebc8
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/html-block-config.json
@@ -0,0 +1,7 @@
+{
+ "options": [
+ {
+ "html": 60
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/invalid/html-block-input.md b/tests/fixtures/rules/max-len/invalid/html-block-input.md
new file mode 100644
index 00000000..55478c0c
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/html-block-input.md
@@ -0,0 +1,7 @@
+# Document with HTML
+
+
+ Content here
+
+
+Regular paragraph.
diff --git a/tests/fixtures/rules/max-len/invalid/list-long-input.md b/tests/fixtures/rules/max-len/invalid/list-long-input.md
new file mode 100644
index 00000000..b57a93f8
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/list-long-input.md
@@ -0,0 +1,3 @@
+- This is a very long list item that definitely exceeds the default one hundred and twenty character maximum length limit and should be reported
+- Short item
+- Another very long list item that goes on and on and on and should definitely be caught by the rule as a violation of the maximum length
diff --git a/tests/fixtures/rules/max-len/invalid/math-block-config.json b/tests/fixtures/rules/max-len/invalid/math-block-config.json
new file mode 100644
index 00000000..a2bae8a6
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/math-block-config.json
@@ -0,0 +1,7 @@
+{
+ "options": [
+ {
+ "math": 50
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/invalid/math-block-input.md b/tests/fixtures/rules/max-len/invalid/math-block-input.md
new file mode 100644
index 00000000..ebd6e69e
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/math-block-input.md
@@ -0,0 +1,7 @@
+Here is a math block:
+
+$$
+\text{This is a very long mathematical expression that exceeds the fifty character limit}
+$$
+
+Regular content.
diff --git a/tests/fixtures/rules/max-len/invalid/nested-blockquote-config.json b/tests/fixtures/rules/max-len/invalid/nested-blockquote-config.json
new file mode 100644
index 00000000..1caaeb3a
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/nested-blockquote-config.json
@@ -0,0 +1,12 @@
+{
+ "options": [
+ {
+ "heading": 80,
+ "paragraph": 120,
+ "blockquote": {
+ "heading": 50,
+ "paragraph": 60
+ }
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/invalid/nested-blockquote-input.md b/tests/fixtures/rules/max-len/invalid/nested-blockquote-input.md
new file mode 100644
index 00000000..f81eaf96
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/nested-blockquote-input.md
@@ -0,0 +1,7 @@
+# This standalone heading fits in the 80 character limit
+
+This standalone paragraph fits comfortably within the 120 character maximum length limit.
+
+> ## This blockquote heading exceeds the 50 character limit
+>
+> This blockquote paragraph is way too long and exceeds the 60 character limit set for blockquote content.
diff --git a/tests/fixtures/rules/max-len/invalid/paragraph-long-input.md b/tests/fixtures/rules/max-len/invalid/paragraph-long-input.md
new file mode 100644
index 00000000..67ff95b1
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/paragraph-long-input.md
@@ -0,0 +1,5 @@
+This is a very long paragraph that definitely exceeds the default one hundred and twenty character maximum length limit and should be reported.
+
+Another paragraph with short text.
+
+This is yet another very long paragraph that goes on and on and on and should definitely be caught by the rule as a violation of the maximum length.
diff --git a/tests/fixtures/rules/max-len/invalid/table-long-input.md b/tests/fixtures/rules/max-len/invalid/table-long-input.md
new file mode 100644
index 00000000..875ee717
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/table-long-input.md
@@ -0,0 +1,3 @@
+| Column 1 | This is a very long table row that definitely exceeds the default one hundred and twenty character maximum length limit |
+| --- | --- |
+| Short | Short |
diff --git a/tests/fixtures/rules/max-len/invalid/url-not-ignored-config.json b/tests/fixtures/rules/max-len/invalid/url-not-ignored-config.json
new file mode 100644
index 00000000..ee3d33a6
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/url-not-ignored-config.json
@@ -0,0 +1,7 @@
+{
+ "options": [
+ {
+ "ignoreUrls": false
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/invalid/url-not-ignored-input.md b/tests/fixtures/rules/max-len/invalid/url-not-ignored-input.md
new file mode 100644
index 00000000..89fc4c20
--- /dev/null
+++ b/tests/fixtures/rules/max-len/invalid/url-not-ignored-input.md
@@ -0,0 +1 @@
+This line has a long URL that should be reported: https://github.com/ota-meshi/eslint-plugin-markdown-preferences/blob/main/CONTRIBUTING.md
diff --git a/tests/fixtures/rules/max-len/valid/code-block-long-input.md b/tests/fixtures/rules/max-len/valid/code-block-long-input.md
new file mode 100644
index 00000000..3479c33d
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/code-block-long-input.md
@@ -0,0 +1,7 @@
+Code blocks are ignored by default:
+
+```js
+const veryLongVariableName = "this is a very long string that exceeds the maximum length limit but it's in a code block";
+```
+
+This paragraph is fine.
diff --git a/tests/fixtures/rules/max-len/valid/code-language-specific-config.json b/tests/fixtures/rules/max-len/valid/code-language-specific-config.json
new file mode 100644
index 00000000..890ed4a0
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/code-language-specific-config.json
@@ -0,0 +1,11 @@
+{
+ "options": [
+ {
+ "code": {
+ "javascript": 100,
+ "python": 80,
+ "shell": "ignore"
+ }
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/valid/code-language-specific-input.md b/tests/fixtures/rules/max-len/valid/code-language-specific-input.md
new file mode 100644
index 00000000..1a4a624d
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/code-language-specific-input.md
@@ -0,0 +1,13 @@
+Code blocks with language-specific limits:
+
+```javascript
+const shortVar = "this fits in 100 chars for javascript";
+```
+
+```python
+short_var = "this fits in 80 chars for python"
+```
+
+```shell
+echo "This shell script line is very long but shell is configured as ignore so it's fine"
+```
diff --git a/tests/fixtures/rules/max-len/valid/footnote-definition-input.md b/tests/fixtures/rules/max-len/valid/footnote-definition-input.md
new file mode 100644
index 00000000..f9dbd2fe
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/footnote-definition-input.md
@@ -0,0 +1,3 @@
+Here is a reference to a footnote[^1].
+
+[^1]: This is a short footnote.
diff --git a/tests/fixtures/rules/max-len/valid/frontmatter-yaml-config.json b/tests/fixtures/rules/max-len/valid/frontmatter-yaml-config.json
new file mode 100644
index 00000000..842f3fc7
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/frontmatter-yaml-config.json
@@ -0,0 +1,9 @@
+{
+ "options": [
+ {
+ "frontmatter": {
+ "yaml": "ignore"
+ }
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/valid/frontmatter-yaml-input.md b/tests/fixtures/rules/max-len/valid/frontmatter-yaml-input.md
new file mode 100644
index 00000000..d196a2fc
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/frontmatter-yaml-input.md
@@ -0,0 +1,8 @@
+---
+title: "This is a very long title but yaml frontmatter is set to ignore so it's fine"
+description: "Another long line that would normally be too long"
+---
+
+# Content
+
+This is the content.
diff --git a/tests/fixtures/rules/max-len/valid/heading-short-input.md b/tests/fixtures/rules/max-len/valid/heading-short-input.md
new file mode 100644
index 00000000..9f3c1cfa
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/heading-short-input.md
@@ -0,0 +1,5 @@
+# Short heading
+
+## Another short heading
+
+### Even shorter
diff --git a/tests/fixtures/rules/max-len/valid/html-block-input.md b/tests/fixtures/rules/max-len/valid/html-block-input.md
new file mode 100644
index 00000000..9bbc69be
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/html-block-input.md
@@ -0,0 +1,7 @@
+# Document with HTML
+
+
+ Content here
+
+
+Regular paragraph.
diff --git a/tests/fixtures/rules/max-len/valid/math-block-input.md b/tests/fixtures/rules/max-len/valid/math-block-input.md
new file mode 100644
index 00000000..03c2fbb8
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/math-block-input.md
@@ -0,0 +1,7 @@
+Here is a math block:
+
+$$
+E = mc^2
+$$
+
+Regular content.
diff --git a/tests/fixtures/rules/max-len/valid/nested-blockquote-config.json b/tests/fixtures/rules/max-len/valid/nested-blockquote-config.json
new file mode 100644
index 00000000..b635e214
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/nested-blockquote-config.json
@@ -0,0 +1,12 @@
+{
+ "options": [
+ {
+ "heading": 50,
+ "paragraph": 60,
+ "blockquote": {
+ "heading": 80,
+ "paragraph": 120
+ }
+ }
+ ]
+}
diff --git a/tests/fixtures/rules/max-len/valid/nested-blockquote-input.md b/tests/fixtures/rules/max-len/valid/nested-blockquote-input.md
new file mode 100644
index 00000000..868e8756
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/nested-blockquote-input.md
@@ -0,0 +1,7 @@
+# Short
+
+Short.
+
+> ## This blockquote heading fits in the 80 character limit
+>
+> This blockquote paragraph fits comfortably within the 120 character maximum length limit.
diff --git a/tests/fixtures/rules/max-len/valid/paragraph-short-input.md b/tests/fixtures/rules/max-len/valid/paragraph-short-input.md
new file mode 100644
index 00000000..22e3caea
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/paragraph-short-input.md
@@ -0,0 +1,3 @@
+This is a short paragraph that fits within the default maximum length limit.
+
+This is another paragraph.
diff --git a/tests/fixtures/rules/max-len/valid/table-short-input.md b/tests/fixtures/rules/max-len/valid/table-short-input.md
new file mode 100644
index 00000000..d1618c29
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/table-short-input.md
@@ -0,0 +1,4 @@
+| Column 1 | Column 2 |
+| --- | --- |
+| Row 1 | Data |
+| Row 2 | More data |
diff --git a/tests/fixtures/rules/max-len/valid/url-long-input.md b/tests/fixtures/rules/max-len/valid/url-long-input.md
new file mode 100644
index 00000000..ed545b99
--- /dev/null
+++ b/tests/fixtures/rules/max-len/valid/url-long-input.md
@@ -0,0 +1,3 @@
+This line has a very long URL that should be ignored by default: https://github.com/ota-meshi/eslint-plugin-markdown-preferences/blob/main/CONTRIBUTING.md
+
+Another line with link: [Documentation](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/max-len.html)
diff --git a/tests/src/rules/__snapshots__/max-len.ts.eslintsnap b/tests/src/rules/__snapshots__/max-len.ts.eslintsnap
new file mode 100644
index 00000000..57ba254c
--- /dev/null
+++ b/tests/src/rules/__snapshots__/max-len.ts.eslintsnap
@@ -0,0 +1,322 @@
+# eslint-snapshot-rule-tester format: v1
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/blockquote-long-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Plugins: unable to serialize
+
+Code:
+ 1 | > This is a very long blockquote that definitely exceeds the default one hundred and twenty character maximum length limit and should be reported.
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 2 | > Short line.
+ 3 | > Another very long blockquote line that goes on and on and on and should definitely be caught by the rule as a violation of the maximum length.
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [2]
+ 4 |
+
+[1] Line length of 146 exceeds the maximum of 120.
+[2] Line length of 144 exceeds the maximum of 120.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/code-block-checked-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Options:
+ - code: 60
+Plugins: unable to serialize
+
+Code:
+ 1 | Code blocks should be checked when the option is set:
+ 2 |
+ 3 | ```js
+ 4 | const veryLongVariableName = "this is a very long string that exceeds the maximum length limit of sixty characters";
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 5 | ```
+ 6 |
+
+[1] Line length of 116 exceeds the maximum of 60.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/code-language-specific-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Options:
+ - code:
+ javascript: 60
+ python: 50
+Plugins: unable to serialize
+
+Code:
+ 1 | Code blocks with language-specific limits:
+ 2 |
+ 3 | ```javascript
+ 4 | const veryLongVariableName = "this line exceeds the 60 character limit for javascript";
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 5 | ```
+ 6 |
+ 7 | ```python
+ 8 | very_long_variable_name = "this exceeds 50 chars for python"
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [2]
+ 9 | ```
+ 10 |
+ 11 | ```shell
+ 12 | echo "This shell script line is very long but shell is not configured so it's ignored"
+ 13 | ```
+ 14 |
+
+[1] Line length of 87 exceeds the maximum of 60.
+[2] Line length of 60 exceeds the maximum of 50.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/custom-max-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Options:
+ - heading: 40
+ paragraph: 40
+Plugins: unable to serialize
+
+Code:
+ 1 | # This heading is longer than 40 characters
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 2 |
+ 3 | This paragraph is too long for the custom limit.
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [2]
+ 4 |
+
+[1] Line length of 43 exceeds the maximum of 40.
+[2] Line length of 48 exceeds the maximum of 40.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/footnote-definition-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Options:
+ - footnoteDefinition: 60
+Plugins: unable to serialize
+
+Code:
+ 1 | Here is a reference to a footnote[^1].
+ 2 |
+ 3 | [^1]: This is a footnote definition with a very long line that exceeds sixty characters limit.
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 4 |
+
+[1] Line length of 94 exceeds the maximum of 60.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/frontmatter-yaml-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Options:
+ - frontmatter:
+ yaml: 50
+Plugins: unable to serialize
+
+Code:
+ 1 | ---
+ 2 | title: "This is a very long title that exceeds the 50 character limit"
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 3 | description: Short
+ 4 | ---
+ 5 |
+ 6 | # Content
+ 7 |
+ 8 | This is the content.
+ 9 |
+
+[1] Line length of 70 exceeds the maximum of 50.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/heading-long-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Plugins: unable to serialize
+
+Code:
+ 1 | # This is a very long heading that definitely exceeds the default eighty character limit
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 2 |
+ 3 | ## This is another heading that is way too long and should be reported as a violation
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [2]
+ 4 |
+
+[1] Line length of 88 exceeds the maximum of 80.
+[2] Line length of 85 exceeds the maximum of 80.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/html-block-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Options:
+ - html: 60
+Plugins: unable to serialize
+
+Code:
+ 1 | # Document with HTML
+ 2 |
+ 3 |
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 4 | Content here
+ 5 |
+ 6 |
+ 7 | Regular paragraph.
+ 8 |
+
+[1] Line length of 81 exceeds the maximum of 60.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/list-long-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Plugins: unable to serialize
+
+Code:
+ 1 | - This is a very long list item that definitely exceeds the default one hundred and twenty character maximum length limit and should be reported
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 2 | - Short item
+ 3 | - Another very long list item that goes on and on and on and should definitely be caught by the rule as a violation of the maximum length
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [2]
+ 4 |
+
+[1] Line length of 144 exceeds the maximum of 120.
+[2] Line length of 137 exceeds the maximum of 120.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/math-block-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Options:
+ - math: 50
+Plugins: unable to serialize
+
+Code:
+ 1 | Here is a math block:
+ 2 |
+ 3 | $$
+ 4 | \text{This is a very long mathematical expression that exceeds the fifty character limit}
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 5 | $$
+ 6 |
+ 7 | Regular content.
+ 8 |
+
+[1] Line length of 89 exceeds the maximum of 50.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/nested-blockquote-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Options:
+ - heading: 80
+ paragraph: 120
+ blockquote:
+ heading: 50
+ paragraph: 60
+Plugins: unable to serialize
+
+Code:
+ 1 | # This standalone heading fits in the 80 character limit
+ 2 |
+ 3 | This standalone paragraph fits comfortably within the 120 character maximum length limit.
+ 4 |
+ 5 | > ## This blockquote heading exceeds the 50 character limit
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 6 | >
+ 7 | > This blockquote paragraph is way too long and exceeds the 60 character limit set for blockquote content.
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [2]
+ 8 |
+
+[1] Line length of 59 exceeds the maximum of 50.
+[2] Line length of 106 exceeds the maximum of 60.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/paragraph-long-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Plugins: unable to serialize
+
+Code:
+ 1 | This is a very long paragraph that definitely exceeds the default one hundred and twenty character maximum length limit and should be reported.
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 2 |
+ 3 | Another paragraph with short text.
+ 4 |
+ 5 | This is yet another very long paragraph that goes on and on and on and should definitely be caught by the rule as a violation of the maximum length.
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [2]
+ 6 |
+
+[1] Line length of 143 exceeds the maximum of 120.
+[2] Line length of 148 exceeds the maximum of 120.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/table-long-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Plugins: unable to serialize
+
+Code:
+ 1 | | Column 1 | This is a very long table row that definitely exceeds the default one hundred and twenty character maximum length limit |
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 2 | | --- | --- |
+ 3 | | Short | Short |
+ 4 |
+
+[1] Line length of 134 exceeds the maximum of 120.
+---
+
+
+Test: max-len >> invalid
+Filename: tests/fixtures/rules/max-len/invalid/url-not-ignored-input.md
+Language: markdown-preferences/extended-syntax
+LanguageOptions:
+ frontmatter: yaml
+Options:
+ - ignoreUrls: false
+Plugins: unable to serialize
+
+Code:
+ 1 | This line has a long URL that should be reported: https://github.com/ota-meshi/eslint-plugin-markdown-preferences/blob/main/CONTRIBUTING.md
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [1]
+ 2 |
+
+[1] Line length of 139 exceeds the maximum of 120.
+---
diff --git a/tests/src/rules/max-len.ts b/tests/src/rules/max-len.ts
new file mode 100644
index 00000000..be3676d9
--- /dev/null
+++ b/tests/src/rules/max-len.ts
@@ -0,0 +1,7 @@
+import { SnapshotRuleTester } from "eslint-snapshot-rule-tester";
+import rule from "../../../src/rules/max-len.ts";
+import { loadTestCases } from "../../utils/utils.ts";
+
+const tester = new SnapshotRuleTester();
+
+tester.run("max-len", rule as any, await loadTestCases("max-len"));