diff --git a/README.md b/README.md index 98531236..d0f2a773 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ playground for learning and exploring. - **[MD054](doc/md054.md)** *link-image-style* - Link and image style - **[MD055](doc/md055.md)** *table-pipe-style* - Table pipe style - **[MD056](doc/md056.md)** *table-column-count* - Table column count +- **[MD058](doc/md058.md)** *blanks-around-tables* - Tables should be surrounded by blank lines @@ -177,7 +178,7 @@ rules at once. - **`ol`** - `MD029`, `MD030`, `MD032` - **`spaces`** - `MD018`, `MD019`, `MD020`, `MD021`, `MD023` - **`spelling`** - `MD044` -- **`table`** - `MD055`, `MD056` +- **`table`** - `MD055`, `MD056`, `MD058` - **`ul`** - `MD004`, `MD005`, `MD007`, `MD030`, `MD032` - **`url`** - `MD034` - **`whitespace`** - `MD009`, `MD010`, `MD012`, `MD027`, `MD028`, `MD030`, diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 4d8b0700..f71bd6c6 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -607,8 +607,20 @@ function addError(onError, lineNumber, detail, context, range, fixInfo) { } module.exports.addError = addError; -// Adds an error object with details conditionally via the onError callback -module.exports.addErrorDetailIf = function addErrorDetailIf( +/** + * Adds an error object with details conditionally via the onError callback. + * + * @param {Object} onError RuleOnError instance. + * @param {number} lineNumber Line number. + * @param {Object} expected Expected value. + * @param {Object} actual Actual value. + * @param {string} [detail] Error details. + * @param {string} [context] Error context. + * @param {number[]} [range] Column and length of error. + * @param {Object} [fixInfo] RuleOnErrorFixInfo instance. + * @returns {void} + */ +function addErrorDetailIf( onError, lineNumber, expected, actual, detail, context, range, fixInfo) { if (expected !== actual) { addError( @@ -620,14 +632,55 @@ module.exports.addErrorDetailIf = function addErrorDetailIf( range, fixInfo); } -}; +} +module.exports.addErrorDetailIf = addErrorDetailIf; -// Adds an error object with context via the onError callback -module.exports.addErrorContext = function addErrorContext( - onError, lineNumber, context, left, right, range, fixInfo) { - context = ellipsify(context, left, right); +/** + * Adds an error object with context via the onError callback. + * + * @param {Object} onError RuleOnError instance. + * @param {number} lineNumber Line number. + * @param {string} context Error context. + * @param {boolean} [start] True iff the start of the text is important. + * @param {boolean} [end] True iff the end of the text is important. + * @param {number[]} [range] Column and length of error. + * @param {Object} [fixInfo] RuleOnErrorFixInfo instance. + * @returns {void} + */ +function addErrorContext( + onError, lineNumber, context, start, end, range, fixInfo) { + context = ellipsify(context, start, end); addError(onError, lineNumber, undefined, context, range, fixInfo); -}; +} +module.exports.addErrorContext = addErrorContext; + +/** + * Adds an error object with context for a construct missing a blank line. + * + * @param {Object} onError RuleOnError instance. + * @param {string[]} lines Lines of Markdown content. + * @param {number} lineIndex Line index of line. + * @param {number} [lineNumber] Line number for override. + * @returns {void} + */ +function addErrorContextForLine(onError, lines, lineIndex, lineNumber) { + const line = lines[lineIndex]; + // @ts-ignore + const quotePrefix = line.match(blockquotePrefixRe)[0].trimEnd(); + addErrorContext( + onError, + lineIndex + 1, + line.trim(), + undefined, + undefined, + undefined, + { + lineNumber, + "insertText": `${quotePrefix}\n` + } + ); +} +module.exports.addErrorContextForLine = addErrorContextForLine; /** * Returns an array of code block and span content ranges. @@ -1673,7 +1726,8 @@ module.exports.fixableRuleNames = [ "MD012", "MD014", "MD018", "MD019", "MD020", "MD021", "MD022", "MD023", "MD026", "MD027", "MD030", "MD031", "MD032", "MD034", "MD037", "MD038", "MD039", "MD044", - "MD047", "MD049", "MD050", "MD051", "MD053", "MD054" + "MD047", "MD049", "MD050", "MD051", "MD053", "MD054", + "MD058" ]; module.exports.homepage = "https://github.com/DavidAnson/markdownlint"; module.exports.version = "0.34.0"; @@ -4058,6 +4112,7 @@ module.exports = { onError, // @ts-ignore i + 1, + // @ts-ignore lineTrim, null, null, @@ -4219,7 +4274,7 @@ module.exports = { const leftHashLength = leftHash.length; const rightHashLength = rightHash.length; const left = !leftSpaceLength; - const right = !rightSpaceLength || rightEscape; + const right = !rightSpaceLength || !!rightEscape; const rightEscapeReplacement = rightEscape ? `${rightEscape} ` : ""; if (left || right) { const range = left ? @@ -5011,28 +5066,12 @@ module.exports = { -const { addErrorContext, blockquotePrefixRe, isBlankLine } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"); +const { addErrorContextForLine, isBlankLine } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"); const { filterByPredicate, nonContentTokens } = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"); const isList = (token) => ( (token.type === "listOrdered") || (token.type === "listUnordered") ); -const addBlankLineError = (onError, lines, lineIndex, lineNumber) => { - const line = lines[lineIndex]; - const quotePrefix = line.match(blockquotePrefixRe)[0].trimEnd(); - addErrorContext( - onError, - lineIndex + 1, - line.trim(), - null, - null, - null, - { - lineNumber, - "insertText": `${quotePrefix}\n` - } - ); -}; // eslint-disable-next-line jsdoc/valid-types /** @type import("./markdownlint").Rule */ @@ -5062,7 +5101,12 @@ module.exports = { // Look for a blank line above the list const firstIndex = list.startLine - 1; if (!isBlankLine(lines[firstIndex - 1])) { - addBlankLineError(onError, lines, firstIndex); + addErrorContextForLine( + onError, + // @ts-ignore + lines, + firstIndex + ); } // Find the "visual" end of the list @@ -5078,7 +5122,13 @@ module.exports = { // Look for a blank line below the list const lastIndex = endLine - 1; if (!isBlankLine(lines[lastIndex + 1])) { - addBlankLineError(onError, lines, lastIndex, lastIndex + 2); + addErrorContextForLine( + onError, + // @ts-ignore + lines, + lastIndex, + lastIndex + 2 + ); } } } @@ -6991,6 +7041,65 @@ module.exports = { } +/***/ }), + +/***/ "../lib/md058.js": +/*!***********************!*\ + !*** ../lib/md058.js ***! + \***********************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +"use strict"; +// @ts-check + + + +const { addErrorContextForLine, isBlankLine } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"); +const { filterByTypes } = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"); + +// eslint-disable-next-line jsdoc/valid-types +/** @type import("./markdownlint").Rule */ +module.exports = { + "names": [ "MD058", "blanks-around-tables" ], + "description": "Tables should be surrounded by blank lines", + "tags": [ "table" ], + "parser": "micromark", + "function": function MD058(params, onError) { + // eslint-disable-next-line jsdoc/valid-types + /** @type import("../helpers/micromark.cjs").Token[] */ + const micromarkTokens = + // @ts-ignore + params.parsers.micromark.tokens; + const { lines } = params; + // For every table... + const tables = filterByTypes(micromarkTokens, [ "table" ]); + for (const table of tables) { + // Look for a blank line above the table + const firstIndex = table.startLine - 1; + if (!isBlankLine(lines[firstIndex - 1])) { + addErrorContextForLine( + onError, + // @ts-ignore + lines, + firstIndex + ); + } + // Look for a blank line below the table + const lastIndex = table.endLine - 1; + if (!isBlankLine(lines[lastIndex + 1])) { + addErrorContextForLine( + onError, + // @ts-ignore + lines, + lastIndex, + lastIndex + 2 + ); + } + } + } +}; + + /***/ }), /***/ "../lib/rules.js": @@ -7057,8 +7166,9 @@ const rules = [ __webpack_require__(/*! ./md053 */ "../lib/md053.js"), __webpack_require__(/*! ./md054 */ "../lib/md054.js"), __webpack_require__(/*! ./md055 */ "../lib/md055.js"), - __webpack_require__(/*! ./md056 */ "../lib/md056.js") + __webpack_require__(/*! ./md056 */ "../lib/md056.js"), // md057: See https://github.com/markdownlint/markdownlint + __webpack_require__(/*! ./md058 */ "../lib/md058.js") ]; for (const rule of rules) { const name = rule.names[0].toLowerCase(); diff --git a/doc-build/md058.md b/doc-build/md058.md new file mode 100644 index 00000000..f836adc1 --- /dev/null +++ b/doc-build/md058.md @@ -0,0 +1,40 @@ +This rule is triggered when tables are either not preceded or not followed by a +blank line: + +```markdown +Some text +| Header | Header | +| ------ | ------ | +| Cell | Cell | +> Blockquote +``` + +To fix violations of this rule, ensure that all tables have a blank line both +before and after (except when the table is at the very beginning or end of the +document): + +```markdown +Some text + +| Header | Header | +| ------ | ------ | +| Cell | Cell | + +> Blockquote +``` + +Note that text immediately following a table (i.e., not separated by an empty +line) is treated as part of the table (per the specification) and will not +trigger this rule: + +```markdown +| Header | Header | +| ------ | ------ | +| Cell | Cell | +This text is part of the table and the next line is blank + +Some text +``` + +Rationale: In addition to aesthetic reasons, some parsers will incorrectly parse +tables that don't have blank lines before and after them. diff --git a/doc/Rules.md b/doc/Rules.md index 6e7f24a8..4d517adb 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -2458,6 +2458,57 @@ Missing cells in a row create holes in the table and suggest an omission. [gfm-table-056]: https://github.github.com/gfm/#tables-extension- + + +## `MD058` - Tables should be surrounded by blank lines + +Tags: `table` + +Aliases: `blanks-around-tables` + +Fixable: Some violations can be fixed by tooling + +This rule is triggered when tables are either not preceded or not followed by a +blank line: + +```markdown +Some text +| Header | Header | +| ------ | ------ | +| Cell | Cell | +> Blockquote +``` + +To fix violations of this rule, ensure that all tables have a blank line both +before and after (except when the table is at the very beginning or end of the +document): + +```markdown +Some text + +| Header | Header | +| ------ | ------ | +| Cell | Cell | + +> Blockquote +``` + +Note that text immediately following a table (i.e., not separated by an empty +line) is treated as part of the table (per the specification) and will not +trigger this rule: + +```markdown +| Header | Header | +| ------ | ------ | +| Cell | Cell | +This text is part of the table and the next line is blank + +Some text +``` + +Rationale: In addition to aesthetic reasons, some parsers will incorrectly parse +tables that don't have blank lines before and after them. +