diff --git a/.changeset/max-len.md b/.changeset/max-len.md new file mode 100644 index 00000000..92affa9e --- /dev/null +++ b/.changeset/max-len.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-markdown-preferences": minor +--- + +feat: add `markdown-preferences/max-len` rule diff --git a/README.md b/README.md index cf02543c..2e09082a 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,8 @@ The rules with the following πŸ’„ are included in the `standard` config. - Rules related to visual or stylistic decorations in Markdown. + + | Rule ID | Description | Fixable | Config | @@ -223,6 +225,7 @@ The rules with the following πŸ’„ are included in the `standard` config. | [markdown-preferences/atx-heading-closing-sequence-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-heading-closing-sequence-length.html) | enforce consistent length for the closing sequence (trailing #s) in ATX headings. | πŸ”§ | πŸ’„ | | [markdown-preferences/atx-heading-closing-sequence](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-heading-closing-sequence.html) | enforce consistent use of closing sequence in ATX headings. | πŸ”§ | πŸ’„ | | [markdown-preferences/code-fence-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-length.html) | enforce consistent code fence length in fenced code blocks. | πŸ”§ | πŸ’„ | +| [markdown-preferences/max-len](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/max-len.html) | enforce maximum length for various Markdown entities | | | | [markdown-preferences/no-laziness-blockquotes](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-laziness-blockquotes.html) | disallow laziness in blockquotes | | β­πŸ’„ | | [markdown-preferences/ordered-list-marker-sequence](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-sequence.html) | enforce consistent ordered list marker numbering (sequential or flat) | πŸ”§ | πŸ’„ | | [markdown-preferences/setext-heading-underline-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/setext-heading-underline-length.html) | enforce setext heading underline length | πŸ”§ | πŸ’„ | @@ -234,6 +237,8 @@ The rules with the following πŸ’„ are included in the `standard` config. + + diff --git a/docs/appendix/comparison-with-markdownlint-rules.md b/docs/appendix/comparison-with-markdownlint-rules.md index 75d4760a..4b40cbf6 100644 --- a/docs/appendix/comparison-with-markdownlint-rules.md +++ b/docs/appendix/comparison-with-markdownlint-rules.md @@ -159,10 +159,10 @@ Please note that each OSS is constantly evolving, so this list is not exhaustive ## Rules Related to Documents -| Description | [markdownlint] Rules | [@eslint/markdown] Rules | `eslint-plugin-markdown-preferences` Rules | -| ----------------------------- | ----------------------------------------------------------------------------- | ------------------------ | ------------------------------------------ | -| Enforce a maximum line length | [MD013] _line-length_
Line length | -- | -- | -| Proper names | [MD044] _proper-names_
Proper names should have the correct capitalization | -- | -- | +| Description | [markdownlint] Rules | [@eslint/markdown] Rules | `eslint-plugin-markdown-preferences` Rules | +| ----------------------------- | ----------------------------------------------------------------------------- | ------------------------ | -------------------------------------------------------------------------------------- | +| Enforce a maximum line length | [MD013] _line-length_
Line length | -- | [markdown-preferences/max-len]
enforce maximum length for various Markdown entities | +| Proper names | [MD044] _proper-names_
Proper names should have the correct capitalization | -- | -- | ## Rules Related to Syntax @@ -256,6 +256,7 @@ Please note that each OSS is constantly evolving, so this list is not exhaustive [markdown-preferences/link-paren-spacing]: ./../rules/link-paren-spacing.md [markdown-preferences/link-title-style]: ./../rules/link-title-style.md [markdown-preferences/list-marker-alignment]: ../rules/list-marker-alignment.md +[markdown-preferences/max-len]: ./../rules/max-len.md [markdown-preferences/no-heading-trailing-punctuation]: ./../rules/no-heading-trailing-punctuation.md [markdown-preferences/no-implicit-block-closing]: ../rules/no-implicit-block-closing.md [markdown-preferences/no-laziness-blockquotes]: ../rules/no-laziness-blockquotes.md diff --git a/docs/rules/index.md b/docs/rules/index.md index 775582c2..110701af 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -95,6 +95,7 @@ The rules with the following πŸ’„ are included in the `plugin.configs.standard` | [markdown-preferences/atx-heading-closing-sequence-length](./atx-heading-closing-sequence-length.md) | enforce consistent length for the closing sequence (trailing #s) in ATX headings. | πŸ”§ | πŸ’„ | | [markdown-preferences/atx-heading-closing-sequence](./atx-heading-closing-sequence.md) | enforce consistent use of closing sequence in ATX headings. | πŸ”§ | πŸ’„ | | [markdown-preferences/code-fence-length](./code-fence-length.md) | enforce consistent code fence length in fenced code blocks. | πŸ”§ | πŸ’„ | +| [markdown-preferences/max-len](./max-len.md) | enforce maximum length for various Markdown entities | | | | [markdown-preferences/no-laziness-blockquotes](./no-laziness-blockquotes.md) | disallow laziness in blockquotes | | β­πŸ’„ | | [markdown-preferences/ordered-list-marker-sequence](./ordered-list-marker-sequence.md) | enforce consistent ordered list marker numbering (sequential or flat) | πŸ”§ | πŸ’„ | | [markdown-preferences/setext-heading-underline-length](./setext-heading-underline-length.md) | enforce setext heading underline length | πŸ”§ | πŸ’„ | diff --git a/docs/rules/max-len.md b/docs/rules/max-len.md new file mode 100644 index 00000000..fd2f9760 --- /dev/null +++ b/docs/rules/max-len.md @@ -0,0 +1,192 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "markdown-preferences/max-len" +description: "enforce maximum length for various Markdown entities" +--- + +# markdown-preferences/max-len + +> enforce maximum length for various Markdown entities + +- ❗ **_This rule has not been released yet._** + +## πŸ“– Rule Details + +This rule enforces a configurable maximum length for different Markdown entities such as headings, paragraphs, lists, blockquotes, tables, code blocks, frontmatter, footnote definitions, HTML blocks, and math blocks. + +Overly long Markdown elements reduce readability, make diffs harder to review, and complicate maintenance. By enforcing reasonable maximum lengths for various entities, documentation remains cleaner, easier to navigate, and more consistent across the project. + + + + + +```md + + + + +## This heading is within the limit + +This is a paragraph that fits comfortably within the specified maximum length limit. + +- A list item that is short enough + + + +## This is a very long heading that exceeds the default eighty character maximum length + +This is an extremely long paragraph that goes on and on and on well beyond the one hundred and twenty character maximum length limit and should be broken up. + +- This is a very long list item that definitely exceeds the default one hundred and twenty character maximum length limit and should be reported +``` + + + +## πŸ”§ Options + +This rule accepts an object with the following properties: + +```json +{ + "markdown-preferences/max-len": [ + "error", + { + "heading": 80, + "paragraph": 120, + "list": 120, + "blockquote": 120, + "table": 120, + "footnoteDefinition": 120, + "html": 120, + "code": "ignore", + "frontmatter": "ignore", + "math": "ignore", + "ignoreUrls": true + } + ] +} +``` + +### Entity Options + +These options control the maximum line length for specific Markdown entity types: + +- `heading` (default: `80`): Maximum line length for headings. Set to `"ignore"` to skip checking. +- `paragraph` (default: `120`): Maximum line length for paragraphs. Set to `"ignore"` to skip checking. +- `table` (default: `120`): Maximum line length for tables. Set to `"ignore"` to skip checking. +- `html` (default: `120`): Maximum line length for HTML blocks. Set to `"ignore"` to skip checking. +- `code` (default: `"ignore"`): Maximum line length for code blocks. Set to `"ignore"` to skip checking (recommended). See [notes on code blocks](#notes-on-code-blocks) below. +- `frontmatter` (default: `"ignore"`): Maximum line length for frontmatter. Set to `"ignore"` to skip checking (recommended). +- `math` (default: `"ignore"`): Maximum line length for math blocks. Set to `"ignore"` to skip checking. + +### Container Options + +These options control line length checking for entities within container elements. Containers can either have a simple numeric limit (applied to all nested content) or an object specifying different limits for different entity types within that container: + +- `list`: Override limits for content within lists. When not specified, nested content inherits from entity-level defaults. +- `blockquote`: Override limits for content within blockquotes. When not specified, nested content inherits from entity-level defaults. +- `footnoteDefinition`: Override limits for content within footnote definitions. When not specified, nested content inherits from entity-level defaults. + +#### Nested Configuration for Containers + +For `list`, `blockquote`, and `footnoteDefinition`, you can specify different limits for any entity types within these containers (including `heading`, `paragraph`, `table`, `html`, `math`, `code`, and `frontmatter`): + +```json +{ + "markdown-preferences/max-len": [ + "error", + { + "heading": 80, + "paragraph": 120, + "table": 100, + "blockquote": { + "heading": 70, + "paragraph": 100, + "table": 90 + } + } + ] +} +``` + +In this example: + +- Standalone headings are limited to 80 characters, but headings inside blockquotes are limited to 70 characters +- Standalone tables are limited to 100 characters, but tables inside blockquotes are limited to 90 characters +- All entity types (`heading`, `paragraph`, `table`, `html`, `math`, `code`, `frontmatter`) can be configured within containers + +### Language-specific Code Block Configuration + +For `code` and `frontmatter`, you can specify different limits per language: + +```json +{ + "markdown-preferences/max-len": [ + "error", + { + "code": { + "javascript": 100, + "python": 80, + "shell": "ignore" + }, + "frontmatter": { + "yaml": 120, + "toml": "ignore" + } + } + ] +} +``` + +### URL Handling + +- `ignoreUrls` (default: `true`): When enabled, lines containing URLs are ignored. + +This option works similarly to [`@stylistic/eslint-plugin`'s `max-len` rule](https://eslint.style/rules/max-len#ignoreurls). + +### Notes on Code Blocks + +**Note:** Code blocks are ignored by default (`code: "ignore"`) because: + +1. Code inside code blocks should be linted using language-specific linters via ESLint's language plugins +2. The [`@eslint/markdown`](https://github.com/eslint/markdown) plugin supports linting code blocks as their respective languages +3. This rule cannot understand the syntax inside code blocks (e.g., comments, strings) + +If you need to enforce line length for code blocks in Markdown, you can: + +- Use the language-specific ESLint configuration for code blocks (recommended): + + ```js + { + files: ["**/*.md/*.js"], + rules: { + "@stylistic/max-len": ["error", { "ignoreComments": true }] + } + } + ``` + + **However**, this feature is not currently available with this plugin. It may be possible in the future. The RFC is at: + +- Set the `code` option to enforce a simple line length limit (not syntax-aware) + +For more information, see the [advanced configuration guide](https://github.com/eslint/markdown/blob/main/docs/processors/markdown.md#advanced-configuration). + +## πŸ“š Further Reading + +- [Markdownlint MD013](https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md013.md) - Similar rule in markdownlint +- [@stylistic/eslint-plugin max-len](https://eslint.style/rules/max-len) - ESLint stylistic rule for code + +## πŸ‘« Related Rules + +- [no-multiple-empty-lines](./no-multiple-empty-lines.md) - Disallow multiple empty lines + +## πŸ” Implementation + + + +- [Rule source](https://github.com/ota-meshi/eslint-plugin-markdown-preferences/blob/main/src/rules/max-len.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-markdown-preferences/blob/main/tests/src/rules/max-len.ts) +- [Test fixture sources](https://github.com/ota-meshi/eslint-plugin-markdown-preferences/tree/main/tests/fixtures/rules/max-len) + + diff --git a/eslint.config.mjs b/eslint.config.mjs index 1da556b3..bfc8cf56 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -158,6 +158,7 @@ export default defineConfig([ ...Object.fromEntries( rules.map((rule) => [rule.meta.docs.ruleId, "error"]), ), + "markdown-preferences/max-len": "off", // δ»Šγ―η„‘εŠΉεŒ–γ—γΎγ™ "markdown-preferences/prefer-linked-words": [ "error", { @@ -197,8 +198,12 @@ export default defineConfig([ "/^https:\\/\\/www\\.npmtrends\\.com\\//u", ], allowedAnchors: { + // https://eslint-online-playground.netlify.app/ "/^https:\\/\\/eslint-online-playground\\.netlify\\.app\\//u": "/.*/u", + // https://github.com/eslint/markdown/blob/main/docs/processors/markdown.md + "/^https:\\/\\/github\\.com\\/eslint\\/markdown\\/blob\\/main\\/docs\\/processors\\/markdown\\.md/u": + "/.*/u", }, }, ], diff --git a/src/rule-types.ts b/src/rule-types.ts index 621aad54..bbdc1663 100644 --- a/src/rule-types.ts +++ b/src/rule-types.ts @@ -139,6 +139,11 @@ export interface RuleOptions { * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/list-marker-alignment.html */ 'markdown-preferences/list-marker-alignment'?: Linter.RuleEntry + /** + * enforce maximum length for various Markdown entities + * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/max-len.html + */ + 'markdown-preferences/max-len'?: Linter.RuleEntry /** * disallow trailing punctuation in headings. * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-heading-trailing-punctuation.html @@ -433,6 +438,60 @@ type MarkdownPreferencesLinkTitleStyle = []|[{ type MarkdownPreferencesListMarkerAlignment = []|[{ align?: ("left" | "right") }] +// ----- markdown-preferences/max-len ----- +type MarkdownPreferencesMaxLen = []|[{ + heading?: (number | "ignore") + paragraph?: (number | "ignore") + table?: (number | "ignore") + html?: (number | "ignore") + math?: (number | "ignore") + code?: ((number | "ignore") | { + [k: string]: (number | "ignore") + }) + frontmatter?: ((number | "ignore") | { + [k: string]: (number | "ignore") + }) + list?: ((number | "ignore") | { + heading?: (number | "ignore") + paragraph?: (number | "ignore") + table?: (number | "ignore") + html?: (number | "ignore") + math?: (number | "ignore") + code?: ((number | "ignore") | { + [k: string]: (number | "ignore") + }) + frontmatter?: ((number | "ignore") | { + [k: string]: (number | "ignore") + }) + }) + blockquote?: ((number | "ignore") | { + heading?: (number | "ignore") + paragraph?: (number | "ignore") + table?: (number | "ignore") + html?: (number | "ignore") + math?: (number | "ignore") + code?: ((number | "ignore") | { + [k: string]: (number | "ignore") + }) + frontmatter?: ((number | "ignore") | { + [k: string]: (number | "ignore") + }) + }) + footnoteDefinition?: ((number | "ignore") | { + heading?: (number | "ignore") + paragraph?: (number | "ignore") + table?: (number | "ignore") + html?: (number | "ignore") + math?: (number | "ignore") + code?: ((number | "ignore") | { + [k: string]: (number | "ignore") + }) + frontmatter?: ((number | "ignore") | { + [k: string]: (number | "ignore") + }) + }) + ignoreUrls?: boolean +}] // ----- markdown-preferences/no-heading-trailing-punctuation ----- type MarkdownPreferencesNoHeadingTrailingPunctuation = []|[{ punctuation?: (string | { diff --git a/src/rules/max-len.ts b/src/rules/max-len.ts new file mode 100644 index 00000000..a0927299 --- /dev/null +++ b/src/rules/max-len.ts @@ -0,0 +1,356 @@ +import type { + Code, + Heading, + Html, + Math, + Paragraph, + Table, + Toml, + Yaml, +} from "../language/ast-types.ts"; +import type { ExtendedMarkdownSourceCode } from "../language/extended-markdown-language.ts"; +import type { MDNode } from "../utils/ast.ts"; +import { getParent } from "../utils/ast.ts"; +import { createRule } from "../utils/index.ts"; +import { getTextWidth } from "../utils/text-width.ts"; + +type MaxLengthOption = number | "ignore"; +type MaxLengthParLangOption = MaxLengthOption | Record; +type MaxLengthParNodeOptions = { + heading?: MaxLengthOption; + paragraph?: MaxLengthOption; + table?: MaxLengthOption; + html?: MaxLengthOption; + math?: MaxLengthOption; + code?: MaxLengthParLangOption; + frontmatter?: MaxLengthParLangOption; +}; +type MaxLengthOptionForContainer = MaxLengthOption | MaxLengthParNodeOptions; +type Options = MaxLengthParNodeOptions & { + // Options for containers that can have nested entities + list?: MaxLengthOptionForContainer; + blockquote?: MaxLengthOptionForContainer; + footnoteDefinition?: MaxLengthOptionForContainer; + // Whether to ignore lines containing URLs + ignoreUrls?: boolean; +}; + +const URL_PATTERN = + /https?:\/\/(?:w{3}\.)?[\w#%+\-.:=@~]{1,256}\.[\w()]{1,6}\b[\w#%&()+\-./:=?@~]*/gu; + +const maxLengthSchema = { + oneOf: [ + { + type: "integer", + minimum: 1, + }, + { + enum: ["ignore"], + }, + ], +}; +const maxLengthParLangSchema = { + oneOf: [ + maxLengthSchema, + { + type: "object", + patternProperties: { + ".*": maxLengthSchema, + }, + additionalProperties: false, + }, + ], +}; +const maxLengthParPhrasingNodeOptionsSchema = { + type: "object", + properties: { + heading: maxLengthSchema, + paragraph: maxLengthSchema, + table: maxLengthSchema, + html: maxLengthSchema, + math: maxLengthSchema, + code: maxLengthParLangSchema, + frontmatter: maxLengthParLangSchema, + }, + additionalProperties: false, +}; +const maxLengthOptionForContainerSchema = { + oneOf: [maxLengthSchema, maxLengthParPhrasingNodeOptionsSchema], +}; + +/** + * Parse options + */ +function parseOptions( + options: Options, + sourceCode: ExtendedMarkdownSourceCode, +): { + maxLength: ( + node: Heading | Paragraph | Table | Html | Code | Yaml | Toml | Math, + ) => number | "ignore"; + ignoreUrls: boolean; +} { + const ignoreUrls = options.ignoreUrls ?? true; + + const maxLengthForNode = parseOptionsParNode({ + heading: options.heading ?? 80, + paragraph: options.paragraph ?? 120, + table: options.table ?? 120, + html: options.html ?? 120, + math: options.math ?? 120, + code: options.code ?? "ignore", + frontmatter: options.frontmatter ?? "ignore", + }); + + const maxLengthForInBlockquote = parseContainerOption( + options.blockquote ?? {}, + ); + const maxLengthForInList = parseContainerOption(options.list ?? {}); + const maxLengthForInFootnoteDefinition = parseContainerOption( + options.footnoteDefinition ?? {}, + ); + + return { + maxLength: (node) => { + let maxLength: number | null = null; + let parent: MDNode | null = getParent(sourceCode, node); + while (parent) { + const len = + parent.type === "blockquote" + ? maxLengthForInBlockquote(node) + : parent.type === "listItem" + ? maxLengthForInList(node) + : parent.type === "footnoteDefinition" + ? maxLengthForInFootnoteDefinition(node) + : null; + if (len === "ignore") return "ignore"; + if (len && (maxLength == null || len < maxLength)) { + maxLength = len; + } + + parent = getParent(sourceCode, parent); + } + + return maxLength ?? maxLengthForNode(node); + }, + ignoreUrls, + }; +} + +/** + * Parse code option + */ +function parseCodeOption( + option: MaxLengthParLangOption, +): (node: Code) => number | "ignore" { + if (typeof option === "number" || option === "ignore") { + return () => option; + } + return (node) => { + if (node.lang && option[node.lang] != null) { + return option[node.lang]; + } + return "ignore"; + }; +} + +/** + * Parse frontmatter option + */ +function parseFrontmatterOption( + option: MaxLengthParLangOption, +): (node: Yaml | Toml) => number | "ignore" { + if (typeof option === "number" || option === "ignore") { + return () => option; + } + return (node) => { + if (option[node.type] != null) { + return option[node.type]; + } + return "ignore"; + }; +} + +/** + * Parse container option + */ +function parseContainerOption( + option: MaxLengthOptionForContainer, +): ( + node: Heading | Paragraph | Table | Html | Code | Yaml | Toml | Math, +) => number | "ignore" | null { + if (typeof option === "number" || option === "ignore") { + return () => option; + } + return parseOptionsParNode(option); +} + +/** + * Parse options for phrasing nodes + */ +function parseOptionsParNode( + option: T, +): ( + node: Heading | Paragraph | Table | Html | Code | Yaml | Toml | Math, +) => + | (number | "ignore") + | (T extends Required ? never : null) { + const headingMax = option.heading; + const paragraphMax = option.paragraph; + const tableMax = option.table; + const htmlMax = option.html; + const mathMax = option.math; + + const maxLengthForCode = option.code ? parseCodeOption(option.code) : null; + const maxLengthForFrontmatter = option.frontmatter + ? parseFrontmatterOption(option.frontmatter) + : null; + + /** + * Get max length for a specific node type + */ + function getMaxLength( + node: Heading | Paragraph | Table | Html | Code | Yaml | Toml | Math, + ): number | "ignore" | null { + switch (node.type) { + case "heading": + return headingMax ?? null; + case "paragraph": + return paragraphMax ?? null; + case "table": + return tableMax ?? null; + case "html": + return htmlMax ?? null; + case "math": + return mathMax ?? null; + case "code": + return maxLengthForCode?.(node) ?? null; + case "yaml": + case "toml": + return maxLengthForFrontmatter?.(node) ?? null; + } + return null; + } + + return (node) => getMaxLength(node)!; +} + +export default createRule<[Options?]>("max-len", { + meta: { + type: "layout", + docs: { + description: "enforce maximum length for various Markdown entities", + categories: [], + listCategory: "Decorative", + }, + fixable: undefined, + hasSuggestions: false, + schema: [ + { + type: "object", + properties: { + ...maxLengthParPhrasingNodeOptionsSchema.properties, + + list: maxLengthOptionForContainerSchema, + blockquote: maxLengthOptionForContainerSchema, + footnoteDefinition: maxLengthOptionForContainerSchema, + ignoreUrls: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + messages: { + maxLenExceeded: + "Line length of {{actual}} exceeds the maximum of {{max}}.", + }, + }, + create(context) { + const sourceCode = context.sourceCode; + + const options = parseOptions(context.options[0] ?? {}, sourceCode); + + // Track which lines we've already checked to avoid double-checking + const checkedLines = new Set(); + + /** + * 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"));