From d7c0d195d7cef4747d66aaa3111a6a176a2c3d4a Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 26 Mar 2019 22:34:19 -0700 Subject: [PATCH] Update MD013/line-length with heading_line_length parameter (fixes #170). --- doc/Rules.md | 7 +- lib/md013.js | 72 +++++++++++-------- schema/build-config-schema.js | 5 ++ schema/markdownlint-config-schema.json | 10 +++ test/detailed-results-MD011-MD021.json | 6 ++ test/detailed-results-MD011-MD021.md | 2 + .../detailed-results-MD011-MD021.results.json | 21 ++++-- test/long-lines-short-headings.json | 7 ++ test/long-lines-short-headings.md | 20 ++++++ 9 files changed, 110 insertions(+), 40 deletions(-) create mode 100644 test/detailed-results-MD011-MD021.json create mode 100644 test/long-lines-short-headings.json create mode 100644 test/long-lines-short-headings.md diff --git a/doc/Rules.md b/doc/Rules.md index 58c362aa..6aae1816 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -443,13 +443,14 @@ Tags: line_length Aliases: line-length -Parameters: line_length, code_blocks, tables, headings, headers (number; default 80, boolean; default true) +Parameters: line_length, heading_line_length, code_blocks, tables, headings, headers (number; default 80, boolean; default true) > If `headings` is not provided, `headers` (deprecated) will be used. This rule is triggered when there are lines that are longer than the -configured line length (default: 80 characters). To fix this, split the line -up into multiple lines. +configured `line_length` (default: 80 characters). To fix this, split the line +up into multiple lines. To set a different maximum length for headings, use +`heading_line_length`. This rule has an exception where there is no whitespace beyond the configured line length. This allows you to still include items such as long URLs without diff --git a/lib/md013.js b/lib/md013.js index eecef223..ac36d692 100644 --- a/lib/md013.js +++ b/lib/md013.js @@ -3,8 +3,23 @@ "use strict"; const shared = require("./shared"); +const { + addErrorDetailIf, filterTokens, forEachHeading, forEachLine, rangeFromRegExp +} = shared; +const longLineRePrefix = "^(.{"; +const longLineRePostfix = "})(.*\\s.*)$"; const labelRe = /^\s*\[.*[^\\]]:/; +const linkOnlyLineRe = /^[es]*lT?L[ES]*$/; +const tokenTypeMap = { + "em_open": "e", + "em_close": "E", + "link_open": "l", + "link_close": "L", + "strong_open": "s", + "strong_close": "S", + "text": "T" +}; module.exports = { "names": [ "MD013", "line-length" ], @@ -12,6 +27,11 @@ module.exports = { "tags": [ "line_length" ], "function": function MD013(params, onError) { const lineLength = params.config.line_length || 80; + const headingLineLength = params.config.heading_line_length || lineLength; + const longLineRe = + new RegExp(longLineRePrefix + lineLength + longLineRePostfix); + const longHeadingLineRe = + new RegExp(longLineRePrefix + headingLineLength + longLineRePostfix); const codeBlocks = params.config.code_blocks; const includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks; const tables = params.config.tables; @@ -22,45 +42,35 @@ module.exports = { } const includeHeadings = (headings === undefined) ? true : !!headings; const headingLineNumbers = []; - if (!includeHeadings) { - shared.forEachHeading(params, function forHeading(heading) { - headingLineNumbers.push(heading.lineNumber); - }); - } - const tokenTypeMap = { - "em_open": "e", - "em_close": "E", - "link_open": "l", - "link_close": "L", - "strong_open": "s", - "strong_close": "S", - "text": "T" - }; + forEachHeading(params, (heading) => { + headingLineNumbers.push(heading.lineNumber); + }); const linkOnlyLineNumbers = []; - shared.filterTokens(params, "inline", function forToken(token) { + filterTokens(params, "inline", (token) => { let childTokenTypes = ""; - token.children.forEach(function forChild(child) { + token.children.forEach((child) => { if (child.type !== "text" || child.content !== "") { childTokenTypes += tokenTypeMap[child.type] || "x"; } }); - if (/^[es]*lT?L[ES]*$/.test(childTokenTypes)) { + if (linkOnlyLineRe.test(childTokenTypes)) { linkOnlyLineNumbers.push(token.lineNumber); } }); - const longLineRe = new RegExp("^(.{" + lineLength + "})(.*\\s.*)$"); - shared.forEachLine( - function forLine(line, lineIndex, inCode, onFence, inTable) { - const lineNumber = lineIndex + 1; - if ((includeCodeBlocks || !inCode) && - (includeTables || !inTable) && - (includeHeadings || (headingLineNumbers.indexOf(lineNumber)) < 0) && - (linkOnlyLineNumbers.indexOf(lineNumber) < 0) && - longLineRe.test(line) && - !labelRe.test(line)) { - shared.addErrorDetailIf(onError, lineNumber, lineLength, - line.length, null, null, shared.rangeFromRegExp(line, longLineRe)); - } - }); + forEachLine((line, lineIndex, inCode, onFence, inTable) => { + const lineNumber = lineIndex + 1; + const isHeading = headingLineNumbers.indexOf(lineNumber) >= 0; + const length = isHeading ? headingLineLength : lineLength; + const lengthRe = isHeading ? longHeadingLineRe : longLineRe; + if ((includeCodeBlocks || !inCode) && + (includeTables || !inTable) && + (includeHeadings || !isHeading) && + (linkOnlyLineNumbers.indexOf(lineNumber) < 0) && + lengthRe.test(line) && + !labelRe.test(line)) { + addErrorDetailIf(onError, lineNumber, length, line.length, + null, null, rangeFromRegExp(line, lengthRe)); + } + }); } }; diff --git a/schema/build-config-schema.js b/schema/build-config-schema.js index 92576f0f..b5eb0c2b 100644 --- a/schema/build-config-schema.js +++ b/schema/build-config-schema.js @@ -129,6 +129,11 @@ rules.forEach(function forRule(rule) { "type": "integer", "default": 80 }, + "heading_line_length": { + "description": "Number of characters for headings", + "type": "integer", + "default": 80 + }, "code_blocks": { "description": "Include code blocks", "type": "boolean", diff --git a/schema/markdownlint-config-schema.json b/schema/markdownlint-config-schema.json index 58a2f706..a68c84e7 100644 --- a/schema/markdownlint-config-schema.json +++ b/schema/markdownlint-config-schema.json @@ -374,6 +374,11 @@ "type": "integer", "default": 80 }, + "heading_line_length": { + "description": "Number of characters for headings", + "type": "integer", + "default": 80 + }, "code_blocks": { "description": "Include code blocks", "type": "boolean", @@ -410,6 +415,11 @@ "type": "integer", "default": 80 }, + "heading_line_length": { + "description": "Number of characters for headings", + "type": "integer", + "default": 80 + }, "code_blocks": { "description": "Include code blocks", "type": "boolean", diff --git a/test/detailed-results-MD011-MD021.json b/test/detailed-results-MD011-MD021.json new file mode 100644 index 00000000..44cc2ac0 --- /dev/null +++ b/test/detailed-results-MD011-MD021.json @@ -0,0 +1,6 @@ +{ + "default": true, + "MD013": { + "heading_line_length": 40 + } +} diff --git a/test/detailed-results-MD011-MD021.md b/test/detailed-results-MD011-MD021.md index bc494275..2e49bd73 100644 --- a/test/detailed-results-MD011-MD021.md +++ b/test/detailed-results-MD011-MD021.md @@ -7,6 +7,8 @@ A (reversed)[link] example. 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 +## 123456789 123456789 123456789 123456789 123456789 123456789 + $ command with no output ##No space A diff --git a/test/detailed-results-MD011-MD021.results.json b/test/detailed-results-MD011-MD021.results.json index 318f6543..acca18f4 100644 --- a/test/detailed-results-MD011-MD021.results.json +++ b/test/detailed-results-MD011-MD021.results.json @@ -28,6 +28,15 @@ }, { "lineNumber": 10, + "ruleNames": [ "MD013", "line-length" ], + "ruleDescription": "Line length", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md013", + "errorDetail": "Expected: 40; Actual: 62", + "errorContext": null, + "errorRange": [41, 22] + }, + { + "lineNumber": 12, "ruleNames": [ "MD014", "commands-show-output" ], "ruleDescription": "Dollar signs used before commands without showing output", "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md014", @@ -36,7 +45,7 @@ "errorRange": [5, 2] }, { - "lineNumber": 12, + "lineNumber": 14, "ruleNames": [ "MD018", "no-missing-space-atx" ], "ruleDescription": "No space after hash on atx style heading", "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md018", @@ -45,7 +54,7 @@ "errorRange": [1, 3] }, { - "lineNumber": 14, + "lineNumber": 16, "ruleNames": [ "MD019", "no-multiple-space-atx" ], "ruleDescription": "Multiple spaces after hash on atx style heading", "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md019", @@ -54,7 +63,7 @@ "errorRange": [1, 5] }, { - "lineNumber": 16, + "lineNumber": 18, "ruleNames": [ "MD020", "no-missing-space-closed-atx" ], "ruleDescription": "No space inside hashes on closed atx style heading", "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md020", @@ -63,7 +72,7 @@ "errorRange": [1, 3] }, { - "lineNumber": 18, + "lineNumber": 20, "ruleNames": [ "MD020", "no-missing-space-closed-atx" ], "ruleDescription": "No space inside hashes on closed atx style heading", "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md020", @@ -72,7 +81,7 @@ "errorRange": [13, 3] }, { - "lineNumber": 20, + "lineNumber": 22, "ruleNames": [ "MD021", "no-multiple-space-closed-atx" ], "ruleDescription": "Multiple spaces inside hashes on closed atx style heading", "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md021", @@ -81,7 +90,7 @@ "errorRange": [1, 5] }, { - "lineNumber": 22, + "lineNumber": 24, "ruleNames": [ "MD021", "no-multiple-space-closed-atx" ], "ruleDescription": "Multiple spaces inside hashes on closed atx style heading", "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md021", diff --git a/test/long-lines-short-headings.json b/test/long-lines-short-headings.json new file mode 100644 index 00000000..82a9608c --- /dev/null +++ b/test/long-lines-short-headings.json @@ -0,0 +1,7 @@ +{ + "default": true, + "MD003": false, + "MD013": { + "heading_line_length": 30 + } +} diff --git a/test/long-lines-short-headings.md b/test/long-lines-short-headings.md new file mode 100644 index 00000000..667bf2d3 --- /dev/null +++ b/test/long-lines-short-headings.md @@ -0,0 +1,20 @@ +# Long Lines, Short Headings + +Text text text text text text text text text text text text text text text text text {MD013} + +## Short heading text text text + +Text + +## Long heading text text text {MD013} + +Text + +## Long heading text text {MD013} ## + +Text + +Long heading of text text text text text text {MD013} +----------------------------------------------------- + +Text