From a5bfaa8a7d06b12568040cd23fa521fcb10baf09 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 25 Sep 2021 16:23:37 -0700 Subject: [PATCH 01/69] Correct typo-ed references to RuleOnErrorInfo.details to detail. --- doc/CustomRules.md | 2 +- example/typescript/type-check.ts | 2 +- lib/markdownlint.d.ts | 4 ++-- lib/markdownlint.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/CustomRules.md b/doc/CustomRules.md index 874bf9d5..6eb4273c 100644 --- a/doc/CustomRules.md +++ b/doc/CustomRules.md @@ -51,7 +51,7 @@ A rule is implemented as an `Object` with one optional and four required propert - `config` is an `Object` corresponding to the rule's entry in `options.config` (if present). - `onError` is a function that takes a single `Object` parameter with one required and four optional properties: - `lineNumber` is a required `Number` specifying the 1-based line number of the error. - - `details` is an optional `String` with information about what caused the error. + - `detail` is an optional `String` with information about what caused the error. - `context` is an optional `String` with relevant text surrounding the error location. - `range` is an optional `Array` with two `Number` values identifying the 1-based column and length of the error. - `fixInfo` is an optional `Object` with information about how to fix the error (all properties are optional, but diff --git a/example/typescript/type-check.ts b/example/typescript/type-check.ts index 3d0ee90b..05229066 100644 --- a/example/typescript/type-check.ts +++ b/example/typescript/type-check.ts @@ -139,7 +139,7 @@ const testRule = { let ruleOnErrorInfo: markdownlint.RuleOnErrorInfo; ruleOnErrorInfo = { "lineNumber": 1, - "details": "details", + "detail": "detail", "context": "context", "range": [ 1, 2 ], "fixInfo": { diff --git a/lib/markdownlint.d.ts b/lib/markdownlint.d.ts index f59ef569..c19bcfc4 100644 --- a/lib/markdownlint.d.ts +++ b/lib/markdownlint.d.ts @@ -206,9 +206,9 @@ type RuleOnErrorInfo = { */ lineNumber: number; /** - * Details about the error. + * Detail about the error. */ - details?: string; + detail?: string; /** * Context for the error. */ diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 7f181142..435cf140 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -1172,7 +1172,7 @@ module.exports = markdownlint; * * @typedef {Object} RuleOnErrorInfo * @property {number} lineNumber Line number (1-based). - * @property {string} [details] Details about the error. + * @property {string} [detail] Detail about the error. * @property {string} [context] Context for the error. * @property {number[]} [range] Column number (1-based) and length. * @property {RuleOnErrorFixInfo} [fixInfo] Fix information. From 6294ad3ef04cb6cd3b749d362b503df9a79daefb Mon Sep 17 00:00:00 2001 From: Janosh Riebesell Date: Fri, 8 Oct 2021 16:35:37 +0100 Subject: [PATCH 02/69] readme add pre-commit sample links for both markdownlint CLIs (#440) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3439ca31..82b49522 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ and test cases came directly from that project. ### Related * CLI - * [markdownlint-cli command-line interface for Node.js](https://github.com/igorshubovych/markdownlint-cli) - * [markdownlint-cli2 command-line interface for Node.js](https://github.com/DavidAnson/markdownlint-cli2) + * [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli) command-line interface for Node.js ([works with pre-commit](https://github.com/igorshubovych/markdownlint-cli#use-with-pre-commit)) + * [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) command-line interface for Node.js ([works with pre-commit](https://github.com/DavidAnson/markdownlint-cli2#pre-commit)) * GitHub * [GitHub Super-Linter Action](https://github.com/github/super-linter) * [GitHub Actions problem matcher for markdownlint-cli](https://github.com/xt0rted/markdownlint-problem-matcher) From ab9e5875a2cc669218e74f041641bc6a85558c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20R=C3=A8gne?= Date: Thu, 21 Oct 2021 06:42:48 +0200 Subject: [PATCH 03/69] Add MD050/strong-style (fixes #150). --- .eslintignore | 1 + README.md | 3 +- demo/markdownlint-browser.js | 48 +++++++++++++++++++++++- doc/Rules.md | 30 +++++++++++++++ helpers/helpers.js | 16 ++++++++ lib/md050.js | 28 ++++++++++++++ lib/rules.js | 3 +- schema/.markdownlint.jsonc | 6 +++ schema/.markdownlint.yaml | 5 +++ schema/build-config-schema.js | 14 +++++++ schema/markdownlint-config-schema.json | 44 +++++++++++++++++++++- test/break-all-the-rules.md | 4 ++ test/emphasis_instead_of_headings.md | 2 +- test/fix_102_extra_nodes_in_link_text.md | 2 +- test/long_lines.md | 2 +- test/markdownlint-test-repos.js | 20 +++++++++- test/markdownlint-test.js | 16 ++++---- test/mixed-emphasis-markers.md | 6 +-- test/spaces_inside_emphasis_markers.md | 2 + test/strong_style_asterisk.json | 6 +++ test/strong_style_asterisk.md | 5 +++ test/strong_style_underscore.json | 6 +++ test/strong_style_underscore.md | 5 +++ 23 files changed, 255 insertions(+), 19 deletions(-) create mode 100644 lib/md050.js create mode 100644 test/strong_style_asterisk.json create mode 100644 test/strong_style_asterisk.md create mode 100644 test/strong_style_underscore.json create mode 100644 test/strong_style_underscore.md diff --git a/.eslintignore b/.eslintignore index 97235743..a41c216c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,4 @@ demo/markdownlint-browser.min.js demo/markdownlint-rule-helpers-browser.js demo/markdownlint-rule-helpers-browser.min.js example/typescript/type-check.js +test-repos/ diff --git a/README.md b/README.md index 82b49522..63910255 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ playground for learning and exploring. * **[MD046](doc/Rules.md#md046)** *code-block-style* - Code block style * **[MD047](doc/Rules.md#md047)** *single-trailing-newline* - Files should end with a single newline character * **[MD048](doc/Rules.md#md048)** *code-fence-style* - Code fence style +* **[MD050](doc/Rules.md#md050)** *strong-style* - Strong style should be consistent @@ -125,7 +126,7 @@ rules at once. * **blockquote** - MD027, MD028 * **bullet** - MD004, MD005, MD006, MD007, MD032 * **code** - MD014, MD031, MD038, MD040, MD046, MD048 -* **emphasis** - MD036, MD037 +* **emphasis** - MD036, MD037, MD050 * **hard_tab** - MD010 * **headers** - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023, MD024, MD025, MD026, MD036, MD041, MD043 diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 3fd61ef7..fc483429 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -190,6 +190,21 @@ module.exports.fencedCodeBlockStyleFor = return "backtick"; } }; +/** + * Return the string representation of a emphasis or strong markup character. + * + * @param {string} markup Emphasis or strong string. + * @returns {string} String representation. + */ +module.exports.emphasisOrStrongStyleFor = + function emphasisOrStrongStyleFor(markup) { + switch (markup[0]) { + case "*": + return "asterisk"; + default: + return "underscore"; + } + }; /** * Return the number of characters of indent for a token. * @@ -4024,6 +4039,36 @@ module.exports = { }; +/***/ }), + +/***/ "../lib/md050.js": +/*!***********************!*\ + !*** ../lib/md050.js ***! + \***********************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +"use strict"; +// @ts-check + +var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, emphasisOrStrongStyleFor = _a.emphasisOrStrongStyleFor, forEachInlineChild = _a.forEachInlineChild; +module.exports = { + "names": ["MD050", "strong-style"], + "description": "Strong style should be consistent", + "tags": ["emphasis"], + "function": function MD050(params, onError) { + var expectedStyle = String(params.config.style || "consistent"); + forEachInlineChild(params, "strong_open", function (token) { + var lineNumber = token.lineNumber, markup = token.markup; + var markupStyle = emphasisOrStrongStyleFor(markup); + if (expectedStyle === "consistent") { + expectedStyle = markupStyle; + } + addErrorDetailIf(onError, lineNumber, expectedStyle, markupStyle); + }); + } +}; + + /***/ }), /***/ "../lib/rules.js": @@ -4082,7 +4127,8 @@ var rules = [ __webpack_require__(/*! ./md045 */ "../lib/md045.js"), __webpack_require__(/*! ./md046 */ "../lib/md046.js"), __webpack_require__(/*! ./md047 */ "../lib/md047.js"), - __webpack_require__(/*! ./md048 */ "../lib/md048.js") + __webpack_require__(/*! ./md048 */ "../lib/md048.js"), + __webpack_require__(/*! ./md050 */ "../lib/md050.js") ]; rules.forEach(function (rule) { var name = rule.names[0].toLowerCase(); diff --git a/doc/Rules.md b/doc/Rules.md index 7ca6afb8..1c9236a9 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -1911,3 +1911,33 @@ The configured list style can be a specific symbol to use (backtick, tilde), or can require that usage be consistent within the document. Rationale: Consistent formatting makes it easier to understand a document. + + + +## MD050 - Strong style should be consistent + +Tags: emphasis + +Aliases: strong-style + +Parameters: style ("consistent", "asterisk", "underscore"; default "consistent") + +This rule is triggered when the symbols used in the document for strong do not +match the configured strong style: + +```markdown +**Text** +__Text__ +``` + +To fix this issue, use the configured strong style throughout the document: + +```markdown +**Text** +**Text** +``` + +The configured strong style can be a specific symbol to use ("asterisk", +"underscore"), or can require that usage be consistent within the document. + +Rationale: Consistent formatting makes it easier to understand a document. diff --git a/helpers/helpers.js b/helpers/helpers.js index a1b10952..302bb921 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -181,6 +181,22 @@ module.exports.fencedCodeBlockStyleFor = } }; +/** + * Return the string representation of a emphasis or strong markup character. + * + * @param {string} markup Emphasis or strong string. + * @returns {string} String representation. + */ +module.exports.emphasisOrStrongStyleFor = + function emphasisOrStrongStyleFor(markup) { + switch (markup[0]) { + case "*": + return "asterisk"; + default: + return "underscore"; + } + }; + /** * Return the number of characters of indent for a token. * diff --git a/lib/md050.js b/lib/md050.js new file mode 100644 index 00000000..79ae8a18 --- /dev/null +++ b/lib/md050.js @@ -0,0 +1,28 @@ +// @ts-check + +"use strict"; + +const { addErrorDetailIf, emphasisOrStrongStyleFor, forEachInlineChild } = + require("../helpers"); + +module.exports = { + "names": [ "MD050", "strong-style" ], + "description": "Strong style should be consistent", + "tags": [ "emphasis" ], + "function": function MD050(params, onError) { + let expectedStyle = String(params.config.style || "consistent"); + forEachInlineChild(params, "strong_open", (token) => { + const { lineNumber, markup } = token; + const markupStyle = emphasisOrStrongStyleFor(markup); + if (expectedStyle === "consistent") { + expectedStyle = markupStyle; + } + addErrorDetailIf( + onError, + lineNumber, + expectedStyle, + markupStyle + ); + }); + } +}; diff --git a/lib/rules.js b/lib/rules.js index 2b957061..4a457db8 100644 --- a/lib/rules.js +++ b/lib/rules.js @@ -50,7 +50,8 @@ const rules = [ require("./md045"), require("./md046"), require("./md047"), - require("./md048") + require("./md048"), + require("./md050") ]; rules.forEach((rule) => { const name = rule.names[0].toLowerCase(); diff --git a/schema/.markdownlint.jsonc b/schema/.markdownlint.jsonc index 1f2493b1..59ab2239 100644 --- a/schema/.markdownlint.jsonc +++ b/schema/.markdownlint.jsonc @@ -248,5 +248,11 @@ "MD048": { // Code fence style "style": "consistent" + }, + + // MD050/strong-style - Strong style should be consistent + "MD050": { + // Strong style should be consistent + "style": "consistent" } } \ No newline at end of file diff --git a/schema/.markdownlint.yaml b/schema/.markdownlint.yaml index ba61bef1..6beaa7c5 100644 --- a/schema/.markdownlint.yaml +++ b/schema/.markdownlint.yaml @@ -224,4 +224,9 @@ MD047: true # MD048/code-fence-style - Code fence style MD048: # Code fence style + style: "consistent" + +# MD050/strong-style - Strong style should be consistent +MD050: + # Strong style should be consistent style: "consistent" \ No newline at end of file diff --git a/schema/build-config-schema.js b/schema/build-config-schema.js index fbebe5af..17d0c01f 100644 --- a/schema/build-config-schema.js +++ b/schema/build-config-schema.js @@ -396,6 +396,20 @@ rules.forEach(function forRule(rule) { } }; break; + case "MD050": + scheme.properties = { + "style": { + "description": "Strong style should be consistent", + "type": "string", + "enum": [ + "consistent", + "asterisk", + "underscore" + ], + "default": "consistent" + } + }; + break; default: custom = false; break; diff --git a/schema/markdownlint-config-schema.json b/schema/markdownlint-config-schema.json index a434e7cd..4ec02f87 100644 --- a/schema/markdownlint-config-schema.json +++ b/schema/markdownlint-config-schema.json @@ -1439,6 +1439,48 @@ }, "additionalProperties": false }, + "MD050": { + "description": "MD050/strong-style - Strong style should be consistent", + "type": [ + "boolean", + "object" + ], + "default": true, + "properties": { + "style": { + "description": "Strong style should be consistent", + "type": "string", + "enum": [ + "consistent", + "asterisk", + "underscore" + ], + "default": "consistent" + } + }, + "additionalProperties": false + }, + "strong-style": { + "description": "MD050/strong-style - Strong style should be consistent", + "type": [ + "boolean", + "object" + ], + "default": true, + "properties": { + "style": { + "description": "Strong style should be consistent", + "type": "string", + "enum": [ + "consistent", + "asterisk", + "underscore" + ], + "default": "consistent" + } + }, + "additionalProperties": false + }, "headings": { "description": "headings - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023, MD024, MD025, MD026, MD036, MD041, MD043", "type": "boolean", @@ -1535,7 +1577,7 @@ "default": true }, "emphasis": { - "description": "emphasis - MD036, MD037", + "description": "emphasis - MD036, MD037, MD050", "type": "boolean", "default": true }, diff --git a/test/break-all-the-rules.md b/test/break-all-the-rules.md index c3f83234..a5ea2ea2 100644 --- a/test/break-all-the-rules.md +++ b/test/break-all-the-rules.md @@ -85,4 +85,8 @@ markdownLint {MD044} ![](image.jpg) {MD045} ## Heading 10 {MD022} +Strong __with__ underscore style + +Strong **with** different style {MD050} + EOF {MD047} \ No newline at end of file diff --git a/test/emphasis_instead_of_headings.md b/test/emphasis_instead_of_headings.md index 72b8148d..e2488236 100644 --- a/test/emphasis_instead_of_headings.md +++ b/test/emphasis_instead_of_headings.md @@ -9,7 +9,7 @@ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -__Section 1.1: another section {MD036}__ +__Section 1.1: another section {MD036} {MD050}__ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis diff --git a/test/fix_102_extra_nodes_in_link_text.md b/test/fix_102_extra_nodes_in_link_text.md index 74cc494e..b3a7df06 100644 --- a/test/fix_102_extra_nodes_in_link_text.md +++ b/test/fix_102_extra_nodes_in_link_text.md @@ -6,5 +6,5 @@ [test *test* *test* test](www.test.com) [test *test* *test* *test* test](www.test.com) [test **test** test](www.test.com) -[test __test__ test](www.test.com) +[test __test__ test](www.test.com) {MD050} [this should not raise](www.shouldnotraise.com) diff --git a/test/long_lines.md b/test/long_lines.md index 17b61371..c42090eb 100644 --- a/test/long_lines.md +++ b/test/long_lines.md @@ -36,7 +36,7 @@ _[This long line is comprised of an emphasized link](https://example.com "This i **[This long line is comprised of a bolded link](https://example.com "This is the long link's title")** -__[This long line is comprised of a bolded link](https://example.com "This is the long link's title")__ +__[This long line is comprised of a bolded link {MD050}](https://example.com "This is the long link's title")__ _**[This long line is comprised of an emphasized and bolded link](https://example.com "This is the long link's title")**_ diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index 02327e39..1d76bb9a 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -71,7 +71,13 @@ function lintTestRepo(t, globPatterns, configPath) { test("https://github.com/eslint/eslint", (t) => { const rootDir = "./test-repos/eslint-eslint"; - const globPatterns = [ join(rootDir, "docs/**/*.md") ]; + const globPatterns = [ + join(rootDir, "docs/**/*.md"), + "!" + join( + rootDir, + "docs/rules/array-callback-return.md" + ) + ]; const configPath = join(rootDir, ".markdownlint.yml"); return lintTestRepo(t, globPatterns, configPath); }); @@ -134,7 +140,17 @@ if (existsSync(dotnetDocsDir)) { const rootDir = dotnetDocsDir; const globPatterns = [ join(rootDir, "**/*.md"), - "!" + join(rootDir, "samples/**/*.md") + "!" + join(rootDir, "samples/**/*.md"), + "!" + join( + rootDir, + "docs/framework/data/adonet/dataset-datatable-dataview" + + "/security-guidance.md" + ), + "!" + join(rootDir, "docs/standard/native-interop/best-practices.md"), + "!" + join( + rootDir, + "docs/standard/serialization/binaryformatter-security-guide.md" + ) ]; const configPath = join(rootDir, ".markdownlint.json"); return lintTestRepo(t, globPatterns, configPath); diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index b3956673..85f17c40 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -492,8 +492,9 @@ test.cb("styleAll", (t) => { "MD042": [ 81 ], "MD045": [ 85 ], "MD046": [ 49, 73, 77 ], - "MD047": [ 88 ], - "MD048": [ 77 ] + "MD047": [ 92 ], + "MD048": [ 77 ], + "MD050": [ 90 ] } }; // @ts-ignore @@ -535,8 +536,9 @@ test.cb("styleRelaxed", (t) => { "MD042": [ 81 ], "MD045": [ 85 ], "MD046": [ 49, 73, 77 ], - "MD047": [ 88 ], - "MD048": [ 77 ] + "MD047": [ 92 ], + "MD048": [ 77 ], + "MD050": [ 90 ] } }; // @ts-ignore @@ -837,7 +839,7 @@ test.cb("customFileSystemAsync", (t) => { }); }); test.cb("readme", (t) => { - t.plan(115); + t.plan(117); const tagToRules = {}; rules.forEach(function forRule(rule) { rule.tags.forEach(function forTag(tag) { @@ -913,7 +915,7 @@ test.cb("readme", (t) => { }); test.cb("rules", (t) => { - t.plan(336); + t.plan(344); fs.readFile("doc/Rules.md", "utf8", (err, contents) => { t.falsy(err); @@ -1566,7 +1568,7 @@ test.cb("configBadFilePromise", (t) => { }); test("allBuiltInRulesHaveValidUrl", (t) => { - t.plan(132); + t.plan(135); rules.forEach(function forRule(rule) { t.truthy(rule.information); t.true(Object.getPrototypeOf(rule.information) === URL.prototype); diff --git a/test/mixed-emphasis-markers.md b/test/mixed-emphasis-markers.md index 63131d67..1db1d2d3 100644 --- a/test/mixed-emphasis-markers.md +++ b/test/mixed-emphasis-markers.md @@ -8,10 +8,10 @@ This paragraph *nests both _kinds_ of emphasis* marker. This paragraph *nests both __kinds__ of emphasis* marker. -This paragraph **nests both __kinds__ of emphasis** marker. +This paragraph **nests both __kinds__ of emphasis** marker. {MD050} This paragraph _nests both *kinds* of emphasis_ marker. -This paragraph _nests both **kinds** of emphasis_ marker. +This paragraph _nests both **kinds** of emphasis_ marker. {MD050} -This paragraph __nests both **kinds** of emphasis__ marker. +This paragraph __nests both **kinds** of emphasis__ marker. {MD050} diff --git a/test/spaces_inside_emphasis_markers.md b/test/spaces_inside_emphasis_markers.md index 6735a2c3..faa6d68f 100644 --- a/test/spaces_inside_emphasis_markers.md +++ b/test/spaces_inside_emphasis_markers.md @@ -1,5 +1,7 @@ # Heading + + Line with *Normal emphasis* Line with **Normal strong** diff --git a/test/strong_style_asterisk.json b/test/strong_style_asterisk.json new file mode 100644 index 00000000..4a45038c --- /dev/null +++ b/test/strong_style_asterisk.json @@ -0,0 +1,6 @@ +{ + "default": true, + "MD050": { + "style": "asterisk" + } +} diff --git a/test/strong_style_asterisk.md b/test/strong_style_asterisk.md new file mode 100644 index 00000000..4004291d --- /dev/null +++ b/test/strong_style_asterisk.md @@ -0,0 +1,5 @@ +# Strong style asterisk + +This is **fine** + +This is __not__ {MD050} diff --git a/test/strong_style_underscore.json b/test/strong_style_underscore.json new file mode 100644 index 00000000..494d8f8f --- /dev/null +++ b/test/strong_style_underscore.json @@ -0,0 +1,6 @@ +{ + "default": true, + "MD050": { + "style": "underscore" + } +} diff --git a/test/strong_style_underscore.md b/test/strong_style_underscore.md new file mode 100644 index 00000000..83800344 --- /dev/null +++ b/test/strong_style_underscore.md @@ -0,0 +1,5 @@ +# Strong style underscore + +This is __fine__ + +This is **not** {MD050} From f7dfd59a5ecffe841a7aa267762d54478052f58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Mond=C3=A9jar=20Rubio?= Date: Thu, 21 Oct 2021 13:13:42 +0200 Subject: [PATCH 04/69] Update MD033/no-inline-html to handle HTML elements in multi-line code spans (fixes #436). --- demo/markdownlint-browser.js | 14 ++++----- lib/md033.js | 20 ++++++++----- test/inline_html.md | 57 ++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index fc483429..3cf694ae 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -3158,11 +3158,10 @@ module.exports = { "use strict"; // @ts-check -var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, forEachLine = _a.forEachLine, unescapeMarkdown = _a.unescapeMarkdown; -var lineMetadata = __webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata; +var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, forEachLine = _a.forEachLine, overlapsAnyRange = _a.overlapsAnyRange, unescapeMarkdown = _a.unescapeMarkdown; +var _b = __webpack_require__(/*! ./cache */ "../lib/cache.js"), inlineCodeSpanRanges = _b.inlineCodeSpanRanges, lineMetadata = _b.lineMetadata; var htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^>]*)?)\/?>/g; var linkDestinationRe = /]\(\s*$/; -var inlineCodeRe = /^[^`]*(`+[^`]+`+[^`]+)*`+[^`]*$/; // See https://spec.commonmark.org/0.29/#autolinks var emailAddressRe = // eslint-disable-next-line max-len @@ -3175,6 +3174,7 @@ module.exports = { var allowedElements = params.config.allowed_elements; allowedElements = Array.isArray(allowedElements) ? allowedElements : []; allowedElements = allowedElements.map(function (element) { return element.toLowerCase(); }); + var exclusions = inlineCodeSpanRanges(); forEachLine(lineMetadata(), function (line, lineIndex, inCode) { var match = null; // eslint-disable-next-line no-unmodified-loop-condition @@ -3182,12 +3182,12 @@ module.exports = { var tag = match[0], content = match[1], element = match[2]; if (!allowedElements.includes(element.toLowerCase()) && !tag.endsWith("\\>") && - !emailAddressRe.test(content)) { + !emailAddressRe.test(content) && + !overlapsAnyRange(exclusions, lineIndex, match.index, match[0].length)) { var prefix = line.substring(0, match.index); - if (!linkDestinationRe.test(prefix) && !inlineCodeRe.test(prefix)) { + if (!linkDestinationRe.test(prefix)) { var unescaped = unescapeMarkdown(prefix + "<", "_"); - if (!unescaped.endsWith("_") && - ((unescaped + "`").match(/`/g).length % 2)) { + if (!unescaped.endsWith("_")) { addError(onError, lineIndex + 1, "Element: " + element, null, [match.index + 1, tag.length]); } } diff --git a/lib/md033.js b/lib/md033.js index 9c002741..73737abe 100644 --- a/lib/md033.js +++ b/lib/md033.js @@ -2,12 +2,13 @@ "use strict"; -const { addError, forEachLine, unescapeMarkdown } = require("../helpers"); -const { lineMetadata } = require("./cache"); +const { + addError, forEachLine, overlapsAnyRange, unescapeMarkdown +} = require("../helpers"); +const { inlineCodeSpanRanges, lineMetadata } = require("./cache"); const htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^>]*)?)\/?>/g; const linkDestinationRe = /]\(\s*$/; -const inlineCodeRe = /^[^`]*(`+[^`]+`+[^`]+)*`+[^`]*$/; // See https://spec.commonmark.org/0.29/#autolinks const emailAddressRe = // eslint-disable-next-line max-len @@ -21,19 +22,22 @@ module.exports = { let allowedElements = params.config.allowed_elements; allowedElements = Array.isArray(allowedElements) ? allowedElements : []; allowedElements = allowedElements.map((element) => element.toLowerCase()); + const exclusions = inlineCodeSpanRanges(); forEachLine(lineMetadata(), (line, lineIndex, inCode) => { let match = null; // eslint-disable-next-line no-unmodified-loop-condition while (!inCode && ((match = htmlElementRe.exec(line)) !== null)) { const [ tag, content, element ] = match; - if (!allowedElements.includes(element.toLowerCase()) && + if ( + !allowedElements.includes(element.toLowerCase()) && !tag.endsWith("\\>") && - !emailAddressRe.test(content)) { + !emailAddressRe.test(content) && + !overlapsAnyRange(exclusions, lineIndex, match.index, match[0].length) + ) { const prefix = line.substring(0, match.index); - if (!linkDestinationRe.test(prefix) && !inlineCodeRe.test(prefix)) { + if (!linkDestinationRe.test(prefix)) { const unescaped = unescapeMarkdown(prefix + "<", "_"); - if (!unescaped.endsWith("_") && - ((unescaped + "`").match(/`/g).length % 2)) { + if (!unescaped.endsWith("_")) { addError(onError, lineIndex + 1, "Element: " + element, null, [ match.index + 1, tag.length ]); } diff --git a/test/inline_html.md b/test/inline_html.md index e76a4245..d70267b7 100644 --- a/test/inline_html.md +++ b/test/inline_html.md @@ -22,6 +22,63 @@ Text \` text `` text \` text `` text Text \`\` text `` text Text `` text \` text `` text +## Elements in multiple line code spans + +Text `code +` + +`code +` + +`code +` text + +Text `code +code + +` + +``code ``` ```` ` +code +`` + +Text `code + +code` text + +Text `code code +code ` text + +Text `code +code code` text + +Text `code code +code code +code code` text + +Text ````code code +code code +code code```` text + +Text `code code +code ` text +text `code code +code code` text + +Text `code code +code code` text +text `code code +code ` text + +Text `code code +code ` text +text `code code +code ` text + +Text `code code +code` text text `code {MD033} +code code` text + ## Slash in element name Text **\\another\directory\\** text From aa8aa83db8d76efcbc0f0594d31c2f126892f082 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 23 Oct 2021 12:42:22 -0700 Subject: [PATCH 05/69] Update markdownlint-test-repos.js to ignore two files with newly-reported issues in dotnet-docs repo. --- test/markdownlint-test-repos.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index 1d76bb9a..a3962444 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -150,6 +150,15 @@ if (existsSync(dotnetDocsDir)) { "!" + join( rootDir, "docs/standard/serialization/binaryformatter-security-guide.md" + ), + "!" + join( + rootDir, + "docs/framework/windows-workflow-foundation/authoring-workflows-" + + "activities-and-expressions-using-imperative-code.md" + ), + "!" + join( + rootDir, + "docs/spark/how-to-guides/deploy-worker-udf-binaries.md" ) ]; const configPath = join(rootDir, ".markdownlint.json"); From 39724b991ace8bb085c0831b6410d4e3b247147c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20R=C3=A8gne?= Date: Sun, 24 Oct 2021 06:54:58 +0200 Subject: [PATCH 06/69] Add MD049/emphasis-style (fixes #150). --- README.md | 3 +- demo/markdownlint-browser.js | 31 +++ doc/Rules.md | 36 +++- lib/md049.js | 28 +++ lib/rules.js | 1 + schema/.markdownlint.jsonc | 6 + schema/.markdownlint.yaml | 5 + schema/build-config-schema.js | 14 ++ schema/markdownlint-config-schema.json | 44 +++- test/break-all-the-rules.md | 4 + test/detailed-results-MD031-MD040.json | 3 +- test/emphasis-not-heading-in-blockquote.md | 4 +- test/emphasis_instead_of_headings.md | 2 +- test/emphasis_style_asterisk.json | 6 + test/emphasis_style_asterisk.md | 5 + test/emphasis_style_underscore.json | 6 + test/emphasis_style_underscore.md | 5 + test/fix_102_extra_nodes_in_link_text.md | 6 +- test/links-with-markup.md | 2 +- test/long_lines.md | 6 +- test/markdownlint-test-repos.js | 202 +++++++++++++++++- test/markdownlint-test.js | 16 +- test/mixed-emphasis-markers.md | 10 +- test/proper-names-projects.md | 2 +- test/proper-names.md | 2 +- ...-inside-emphasis-markers-multiple-lines.md | 2 +- test/spaces_inside_emphasis_markers.md | 2 +- 27 files changed, 418 insertions(+), 35 deletions(-) create mode 100644 lib/md049.js create mode 100644 test/emphasis_style_asterisk.json create mode 100644 test/emphasis_style_asterisk.md create mode 100644 test/emphasis_style_underscore.json create mode 100644 test/emphasis_style_underscore.md diff --git a/README.md b/README.md index 63910255..9e80f2c9 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ playground for learning and exploring. * **[MD046](doc/Rules.md#md046)** *code-block-style* - Code block style * **[MD047](doc/Rules.md#md047)** *single-trailing-newline* - Files should end with a single newline character * **[MD048](doc/Rules.md#md048)** *code-fence-style* - Code fence style +* **[MD049](doc/Rules.md#md049)** *emphasis-style* - Emphasis style should be consistent * **[MD050](doc/Rules.md#md050)** *strong-style* - Strong style should be consistent @@ -126,7 +127,7 @@ rules at once. * **blockquote** - MD027, MD028 * **bullet** - MD004, MD005, MD006, MD007, MD032 * **code** - MD014, MD031, MD038, MD040, MD046, MD048 -* **emphasis** - MD036, MD037, MD050 +* **emphasis** - MD036, MD037, MD049, MD050 * **hard_tab** - MD010 * **headers** - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023, MD024, MD025, MD026, MD036, MD041, MD043 diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 3cf694ae..8409ad37 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -4039,6 +4039,36 @@ module.exports = { }; +/***/ }), + +/***/ "../lib/md049.js": +/*!***********************!*\ + !*** ../lib/md049.js ***! + \***********************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +"use strict"; +// @ts-check + +var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, emphasisOrStrongStyleFor = _a.emphasisOrStrongStyleFor, forEachInlineChild = _a.forEachInlineChild; +module.exports = { + "names": ["MD049", "emphasis-style"], + "description": "Emphasis style should be consistent", + "tags": ["emphasis"], + "function": function MD049(params, onError) { + var expectedStyle = String(params.config.style || "consistent"); + forEachInlineChild(params, "em_open", function (token) { + var lineNumber = token.lineNumber, markup = token.markup; + var markupStyle = emphasisOrStrongStyleFor(markup); + if (expectedStyle === "consistent") { + expectedStyle = markupStyle; + } + addErrorDetailIf(onError, lineNumber, expectedStyle, markupStyle); + }); + } +}; + + /***/ }), /***/ "../lib/md050.js": @@ -4128,6 +4158,7 @@ var rules = [ __webpack_require__(/*! ./md046 */ "../lib/md046.js"), __webpack_require__(/*! ./md047 */ "../lib/md047.js"), __webpack_require__(/*! ./md048 */ "../lib/md048.js"), + __webpack_require__(/*! ./md049 */ "../lib/md049.js"), __webpack_require__(/*! ./md050 */ "../lib/md050.js") ]; rules.forEach(function (rule) { diff --git a/doc/Rules.md b/doc/Rules.md index 1c9236a9..36f9da12 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -1331,20 +1331,20 @@ For more information, see . ``` Note: To use a bare URL without it being converted into a link, enclose it in -a code block, otherwise in some markdown parsers it _will_ be converted: +a code block, otherwise in some markdown parsers it *will* be converted: ```markdown `https://www.example.com` ``` -Note: The following scenario does _not_ trigger this rule to avoid conflicts +Note: The following scenario does *not* trigger this rule to avoid conflicts with `MD011`/`no-reversed-links`: ```markdown [https://www.example.com] ``` -The use of quotes around a bare link will _not_ trigger this rule, either: +The use of quotes around a bare link will *not* trigger this rule, either: ```markdown "https://www.example.com" @@ -1912,6 +1912,36 @@ can require that usage be consistent within the document. Rationale: Consistent formatting makes it easier to understand a document. + + +## MD049 - Emphasis style should be consistent + +Tags: emphasis + +Aliases: emphasis-style + +Parameters: style ("consistent", "asterisk", "underscore"; default "consistent") + +This rule is triggered when the symbols used in the document for emphasis do not +match the configured emphasis style: + +```markdown +*Text* +_Text_ +``` + +To fix this issue, use the configured emphasis style throughout the document: + +```markdown +*Text* +*Text* +``` + +The configured emphasis style can be a specific symbol to use ("asterisk", +"underscore"), or can require that usage be consistent within the document. + +Rationale: Consistent formatting makes it easier to understand a document. + ## MD050 - Strong style should be consistent diff --git a/lib/md049.js b/lib/md049.js new file mode 100644 index 00000000..0a638578 --- /dev/null +++ b/lib/md049.js @@ -0,0 +1,28 @@ +// @ts-check + +"use strict"; + +const { addErrorDetailIf, emphasisOrStrongStyleFor, forEachInlineChild } = + require("../helpers"); + +module.exports = { + "names": [ "MD049", "emphasis-style" ], + "description": "Emphasis style should be consistent", + "tags": [ "emphasis" ], + "function": function MD049(params, onError) { + let expectedStyle = String(params.config.style || "consistent"); + forEachInlineChild(params, "em_open", (token) => { + const { lineNumber, markup } = token; + const markupStyle = emphasisOrStrongStyleFor(markup); + if (expectedStyle === "consistent") { + expectedStyle = markupStyle; + } + addErrorDetailIf( + onError, + lineNumber, + expectedStyle, + markupStyle + ); + }); + } +}; diff --git a/lib/rules.js b/lib/rules.js index 4a457db8..f3f61480 100644 --- a/lib/rules.js +++ b/lib/rules.js @@ -51,6 +51,7 @@ const rules = [ require("./md046"), require("./md047"), require("./md048"), + require("./md049"), require("./md050") ]; rules.forEach((rule) => { diff --git a/schema/.markdownlint.jsonc b/schema/.markdownlint.jsonc index 59ab2239..3cea1729 100644 --- a/schema/.markdownlint.jsonc +++ b/schema/.markdownlint.jsonc @@ -250,6 +250,12 @@ "style": "consistent" }, + // MD049/emphasis-style - Emphasis style should be consistent + "MD049": { + // Emphasis style should be consistent + "style": "consistent" + }, + // MD050/strong-style - Strong style should be consistent "MD050": { // Strong style should be consistent diff --git a/schema/.markdownlint.yaml b/schema/.markdownlint.yaml index 6beaa7c5..b8960a4a 100644 --- a/schema/.markdownlint.yaml +++ b/schema/.markdownlint.yaml @@ -226,6 +226,11 @@ MD048: # Code fence style style: "consistent" +# MD049/emphasis-style - Emphasis style should be consistent +MD049: + # Emphasis style should be consistent + style: "consistent" + # MD050/strong-style - Strong style should be consistent MD050: # Strong style should be consistent diff --git a/schema/build-config-schema.js b/schema/build-config-schema.js index 17d0c01f..76cfd539 100644 --- a/schema/build-config-schema.js +++ b/schema/build-config-schema.js @@ -396,6 +396,20 @@ rules.forEach(function forRule(rule) { } }; break; + case "MD049": + scheme.properties = { + "style": { + "description": "Emphasis style should be consistent", + "type": "string", + "enum": [ + "consistent", + "asterisk", + "underscore" + ], + "default": "consistent" + } + }; + break; case "MD050": scheme.properties = { "style": { diff --git a/schema/markdownlint-config-schema.json b/schema/markdownlint-config-schema.json index 4ec02f87..e3f36409 100644 --- a/schema/markdownlint-config-schema.json +++ b/schema/markdownlint-config-schema.json @@ -1439,6 +1439,48 @@ }, "additionalProperties": false }, + "MD049": { + "description": "MD049/emphasis-style - Emphasis style should be consistent", + "type": [ + "boolean", + "object" + ], + "default": true, + "properties": { + "style": { + "description": "Emphasis style should be consistent", + "type": "string", + "enum": [ + "consistent", + "asterisk", + "underscore" + ], + "default": "consistent" + } + }, + "additionalProperties": false + }, + "emphasis-style": { + "description": "MD049/emphasis-style - Emphasis style should be consistent", + "type": [ + "boolean", + "object" + ], + "default": true, + "properties": { + "style": { + "description": "Emphasis style should be consistent", + "type": "string", + "enum": [ + "consistent", + "asterisk", + "underscore" + ], + "default": "consistent" + } + }, + "additionalProperties": false + }, "MD050": { "description": "MD050/strong-style - Strong style should be consistent", "type": [ @@ -1577,7 +1619,7 @@ "default": true }, "emphasis": { - "description": "emphasis - MD036, MD037, MD050", + "description": "emphasis - MD036, MD037, MD049, MD050", "type": "boolean", "default": true }, diff --git a/test/break-all-the-rules.md b/test/break-all-the-rules.md index a5ea2ea2..e1744f37 100644 --- a/test/break-all-the-rules.md +++ b/test/break-all-the-rules.md @@ -85,6 +85,10 @@ markdownLint {MD044} ![](image.jpg) {MD045} ## Heading 10 {MD022} +Emphasis _with_ underscore style + +Emphasis *with* different style {MD049} + Strong __with__ underscore style Strong **with** different style {MD050} diff --git a/test/detailed-results-MD031-MD040.json b/test/detailed-results-MD031-MD040.json index 7935d0c1..84228acf 100644 --- a/test/detailed-results-MD031-MD040.json +++ b/test/detailed-results-MD031-MD040.json @@ -1,4 +1,5 @@ { "default": true, - "MD041": false + "MD041": false, + "MD049": false } diff --git a/test/emphasis-not-heading-in-blockquote.md b/test/emphasis-not-heading-in-blockquote.md index 79534d6e..f9991e91 100644 --- a/test/emphasis-not-heading-in-blockquote.md +++ b/test/emphasis-not-heading-in-blockquote.md @@ -8,11 +8,11 @@ Text Text -> *Text* +> *Text* {MD049} Text -> *Text text text* +> *Text text text* {MD049} Text diff --git a/test/emphasis_instead_of_headings.md b/test/emphasis_instead_of_headings.md index e2488236..c0724a00 100644 --- a/test/emphasis_instead_of_headings.md +++ b/test/emphasis_instead_of_headings.md @@ -27,7 +27,7 @@ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -_Section 3: oh no more sections {MD036}_ +_Section 3: oh no more sections {MD036} {MD049}_ This is a normal paragraph **that just happens to have emphasized text in** diff --git a/test/emphasis_style_asterisk.json b/test/emphasis_style_asterisk.json new file mode 100644 index 00000000..cd077666 --- /dev/null +++ b/test/emphasis_style_asterisk.json @@ -0,0 +1,6 @@ +{ + "default": true, + "MD049": { + "style": "asterisk" + } +} diff --git a/test/emphasis_style_asterisk.md b/test/emphasis_style_asterisk.md new file mode 100644 index 00000000..95fe5f30 --- /dev/null +++ b/test/emphasis_style_asterisk.md @@ -0,0 +1,5 @@ +# Emphasis style asterisk + +This is *fine* + +This is _not_ {MD049} diff --git a/test/emphasis_style_underscore.json b/test/emphasis_style_underscore.json new file mode 100644 index 00000000..3d583521 --- /dev/null +++ b/test/emphasis_style_underscore.json @@ -0,0 +1,6 @@ +{ + "default": true, + "MD049": { + "style": "underscore" + } +} diff --git a/test/emphasis_style_underscore.md b/test/emphasis_style_underscore.md new file mode 100644 index 00000000..84c20592 --- /dev/null +++ b/test/emphasis_style_underscore.md @@ -0,0 +1,5 @@ +# Emphasis style underscore + +This is _fine_ + +This is *not* {MD049} diff --git a/test/fix_102_extra_nodes_in_link_text.md b/test/fix_102_extra_nodes_in_link_text.md index b3a7df06..68db9d71 100644 --- a/test/fix_102_extra_nodes_in_link_text.md +++ b/test/fix_102_extra_nodes_in_link_text.md @@ -2,9 +2,9 @@ [test _test_ test](www.test.com) [test `test` test](www.test.com) -[test *test* test](www.test.com) -[test *test* *test* test](www.test.com) -[test *test* *test* *test* test](www.test.com) +[test *test* test](www.test.com) {MD049} +[test *test* *test* test](www.test.com) {MD049} +[test *test* *test* *test* test](www.test.com) {MD049} [test **test** test](www.test.com) [test __test__ test](www.test.com) {MD050} [this should not raise](www.shouldnotraise.com) diff --git a/test/links-with-markup.md b/test/links-with-markup.md index 44d5f33e..454f3d65 100644 --- a/test/links-with-markup.md +++ b/test/links-with-markup.md @@ -10,7 +10,7 @@ [This link has `code` and right space ](link) {MD039} -[ This link has _emphasis_ and left space](link) {MD039} +[ This link has _emphasis_ and left space](link) {MD039} {MD049} [This](link) line has [multiple](link) links. diff --git a/test/long_lines.md b/test/long_lines.md index c42090eb..2ba3aa85 100644 --- a/test/long_lines.md +++ b/test/long_lines.md @@ -32,15 +32,15 @@ This long line includes a simple [reference][label] link and is long enough to v *[This long line is comprised of an emphasized link](https://example.com "This is the long link's title")* -_[This long line is comprised of an emphasized link](https://example.com "This is the long link's title")_ +_[This long line is comprised of an emphasized link {MD049}](https://example.com "This is the long link's title")_ **[This long line is comprised of a bolded link](https://example.com "This is the long link's title")** __[This long line is comprised of a bolded link {MD050}](https://example.com "This is the long link's title")__ -_**[This long line is comprised of an emphasized and bolded link](https://example.com "This is the long link's title")**_ +_**[This long line is comprised of an emphasized and bolded link {MD049}](https://example.com "This is the long link's title")**_ -**_[This long line is comprised of an emphasized and bolded link](https://example.com "This is the long link's title")_** +**_[This long line is comprised of an emphasized and bolded link {MD049}](https://example.com "This is the long link's title")_** *[](https://example.com "This long line is comprised of an emphasized link with empty text and a non-empty title")* diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index a3962444..8aa6857a 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -110,7 +110,12 @@ test("https://github.com/mochajs/mocha", (t) => { test("https://github.com/pi-hole/docs", (t) => { const rootDir = "./test-repos/pi-hole-docs"; - const globPatterns = [ join(rootDir, "**/*.md") ]; + const globPatterns = [ + join(rootDir, "**/*.md"), + "!" + join(rootDir, "docs/guides/dns/unbound.md"), + "!" + join(rootDir, "docs/index.md"), + "!" + join(rootDir, "docs/main/prerequisites.md") + ]; const configPath = join(rootDir, ".markdownlint.json"); return lintTestRepo(t, globPatterns, configPath); }); @@ -141,11 +146,188 @@ if (existsSync(dotnetDocsDir)) { const globPatterns = [ join(rootDir, "**/*.md"), "!" + join(rootDir, "samples/**/*.md"), + "!" + join(rootDir, "docs/architecture/cloud-native/candidate-apps.md"), + "!" + join( + rootDir, + "docs/architecture/containerized-lifecycle/docker-devops-workflow" + + "/docker-application-outer-loop-devops-workflow.md" + ), + "!" + join( + rootDir, + "docs/architecture/dapr-for-net-developers/getting-started.md" + ), + "!" + join( + rootDir, + "docs/architecture/grpc-for-wcf-developers/channel-credentials.md" + ), + "!" + join( + rootDir, + "docs/architecture/microservices/implement-resilient-applications" + + "/use-httpclientfactory-to-implement-resilient-http-requests.md" + ), + "!" + join( + rootDir, + "docs/architecture/microservices" + + "/multi-container-microservice-net-applications" + + "/implement-api-gateways-with-ocelot.md" + ), + "!" + join( + rootDir, + "docs/architecture/modern-web-apps-azure/architectural-principles.md" + ), + "!" + join( + rootDir, + "docs/architecture/modern-web-apps-azure" + + "/azure-hosting-recommendations-for-asp-net-web-apps.md" + ), + "!" + join( + rootDir, + "docs/architecture/modern-web-apps-azure" + + "/common-client-side-web-technologies.md" + ), + "!" + join( + rootDir, + "docs/architecture/modern-web-apps-azure" + + "/develop-asp-net-core-mvc-apps.md" + ), + "!" + join( + rootDir, + "docs/architecture/modern-web-apps-azure" + + "/development-process-for-azure.md" + ), + "!" + join(rootDir, "docs/architecture/modern-web-apps-azure/index.md"), + "!" + join(rootDir, "docs/core/additional-tools/dotnet-svcutil-guide.md"), + "!" + join(rootDir, "docs/core/dependency-loading/collect-details.md"), + "!" + join(rootDir, "docs/core/deploying/single-file.md"), + "!" + join(rootDir, "docs/core/deploying/trimming/trimming-options.md"), + "!" + join(rootDir, "docs/core/extensions/cloud-service.md"), + "!" + join(rootDir, "docs/core/extensions/console-log-formatter.md"), + "!" + join(rootDir, "docs/core/extensions/create-resource-files.md"), + "!" + join(rootDir, "docs/core/extensions/localization.md"), + "!" + join(rootDir, "docs/core/install/linux-alpine.md"), + "!" + join(rootDir, "docs/core/install/windows.md"), + "!" + join(rootDir, "docs/core/porting/third-party-deps.md"), + "!" + join(rootDir, "docs/core/project-sdk/msbuild-props-desktop.md"), + "!" + join(rootDir, "docs/core/testing/unit-testing-code-coverage.md"), + "!" + join(rootDir, "docs/core/tools/troubleshoot-usage-issues.md"), + "!" + join( + rootDir, + "docs/core/tutorials/cli-templates-create-item-template.md" + ), + "!" + join( + rootDir, + "docs/core/tutorials/cli-templates-create-project-template.md" + ), + "!" + join( + rootDir, + "docs/core/tutorials/cli-templates-create-template-pack.md" + ), + "!" + join( + rootDir, + "docs/core/tutorials/cli-templates-create-item-template.md" + ), + "!" + join( + rootDir, + "docs/core/tutorials/cli-templates-create-project-template.md" + ), + "!" + join( + rootDir, + "docs/core/tutorials/cli-templates-create-template-pack.md" + ), + "!" + join(rootDir, "docs/core/whats-new/dotnet-core-3-0.md"), + "!" + join( + rootDir, + "docs/csharp/language-reference/compiler-options/code-generation.md" + ), + "!" + join(rootDir, "docs/csharp/linq/query-expression-basics.md"), + "!" + join( + rootDir, + "docs/csharp/programming-guide/classes-and-structs" + + "/named-and-optional-arguments.md" + ), + "!" + join( + rootDir, + "docs/csharp/roslyn-sdk/tutorials" + + "/how-to-write-csharp-analyzer-code-fix.md" + ), + "!" + join(rootDir, "docs/csharp/tutorials/attributes.md"), + "!" + join(rootDir, "docs/csharp/whats-new/csharp-version-history.md"), "!" + join( rootDir, "docs/framework/data/adonet/dataset-datatable-dataview" + "/security-guidance.md" ), + "!" + join( + rootDir, + "docs/fsharp/language-reference/compiler-directives.md" + ), + "!" + join( + rootDir, + "docs/fsharp/language-reference/exception-handling" + + "/the-try-with-expression.md" + ), + "!" + join( + rootDir, + "docs/fsharp/language-reference/xml-documentation.md" + ), + "!" + join(rootDir, "docs/fsharp/style-guide/conventions.md"), + "!" + join( + rootDir, + "docs/fsharp/tutorials/asynchronous-and-concurrent-programming/async.md" + ), + "!" + join( + rootDir, + "docs/fundamentals/code-analysis/configuration-files.md" + ), + "!" + join( + rootDir, + "docs/fundamentals/code-analysis/style-rules/naming-rules.md" + ), + "!" + join( + rootDir, + "docs/machine-learning/tutorials" + + "/health-violation-classification-model-builder.md" + ), + "!" + join( + rootDir, + "docs/machine-learning/tutorials/object-detection-model-builder.md" + ), + "!" + join( + rootDir, + "docs/machine-learning/tutorials/object-detection-onnx.md" + ), + "!" + join( + rootDir, + "docs/machine-learning/tutorials/text-classification-tf.md" + ), + "!" + join( + rootDir, + "docs/standard/asynchronous-programming-patterns" + + "/event-based-asynchronous-pattern-overview.md" + ), + "!" + join( + rootDir, + "docs/standard/asynchronous-programming-patterns" + + "/implementing-the-event-based-asynchronous-pattern.md" + ), + "!" + join( + rootDir, + "docs/standard/base-types/string-comparison-net-5-plus.md" + ), + "!" + join(rootDir, "docs/standard/delegates-lambdas.md"), + "!" + join(rootDir, "docs/standard/io/isolated-storage.md"), + "!" + join( + rootDir, + "docs/standard/native-interop/tutorial-comwrappers.md" + ), + "!" + join( + rootDir, + "docs/standard/serialization/xml-schema-definition-tool-xsd-exe.md" + ), + "!" + join( + rootDir, + "docs/standard/serialization/xml-serializer-generator-tool-sgen-exe.md" + ), "!" + join(rootDir, "docs/standard/native-interop/best-practices.md"), "!" + join( rootDir, @@ -154,7 +336,7 @@ if (existsSync(dotnetDocsDir)) { "!" + join( rootDir, "docs/framework/windows-workflow-foundation/authoring-workflows-" + - "activities-and-expressions-using-imperative-code.md" + "activities-and-expressions-using-imperative-code.md" ), "!" + join( rootDir, @@ -170,7 +352,21 @@ const v8v8DevDir = "./test-repos/v8-v8-dev"; if (existsSync(v8v8DevDir)) { test("https://github.com/v8/v8.dev", (t) => { const rootDir = v8v8DevDir; - const globPatterns = [ join(rootDir, "src/**/*.md") ]; + const globPatterns = [ + join(rootDir, "src/**/*.md"), + "!" + join(rootDir, "src/blog/array-sort.md"), + "!" + join(rootDir, "src/blog/code-caching-for-devs.md"), + "!" + join(rootDir, "src/blog/fast-async.md"), + "!" + join(rootDir, "src/blog/liftoff.md"), + "!" + join(rootDir, "src/blog/pointer-compression.md"), + "!" + join(rootDir, "src/blog/react-cliff.md"), + "!" + join(rootDir, "src/blog/slack-tracking.md"), + "!" + join(rootDir, "src/blog/v8-release-74.md"), + "!" + join(rootDir, "src/features/bigint.md"), + "!" + join(rootDir, "src/features/dynamic-import.md"), + "!" + join(rootDir, "src/features/globalthis.md"), + "!" + join(rootDir, "src/features/modules.md") + ]; const configPath = join(rootDir, ".markdownlint.json"); return lintTestRepo(t, globPatterns, configPath); }); diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index 85f17c40..fd6cdf0f 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -492,9 +492,10 @@ test.cb("styleAll", (t) => { "MD042": [ 81 ], "MD045": [ 85 ], "MD046": [ 49, 73, 77 ], - "MD047": [ 92 ], + "MD047": [ 96 ], "MD048": [ 77 ], - "MD050": [ 90 ] + "MD049": [ 90 ], + "MD050": [ 94 ] } }; // @ts-ignore @@ -536,9 +537,10 @@ test.cb("styleRelaxed", (t) => { "MD042": [ 81 ], "MD045": [ 85 ], "MD046": [ 49, 73, 77 ], - "MD047": [ 92 ], + "MD047": [ 96 ], "MD048": [ 77 ], - "MD050": [ 90 ] + "MD049": [ 90 ], + "MD050": [ 94 ] } }; // @ts-ignore @@ -839,7 +841,7 @@ test.cb("customFileSystemAsync", (t) => { }); }); test.cb("readme", (t) => { - t.plan(117); + t.plan(119); const tagToRules = {}; rules.forEach(function forRule(rule) { rule.tags.forEach(function forTag(tag) { @@ -915,7 +917,7 @@ test.cb("readme", (t) => { }); test.cb("rules", (t) => { - t.plan(344); + t.plan(352); fs.readFile("doc/Rules.md", "utf8", (err, contents) => { t.falsy(err); @@ -1568,7 +1570,7 @@ test.cb("configBadFilePromise", (t) => { }); test("allBuiltInRulesHaveValidUrl", (t) => { - t.plan(135); + t.plan(138); rules.forEach(function forRule(rule) { t.truthy(rule.information); t.true(Object.getPrototypeOf(rule.information) === URL.prototype); diff --git a/test/mixed-emphasis-markers.md b/test/mixed-emphasis-markers.md index 1db1d2d3..e74a7ee0 100644 --- a/test/mixed-emphasis-markers.md +++ b/test/mixed-emphasis-markers.md @@ -1,17 +1,17 @@ # Mixed Emphasis Markers -This paragraph *uses* both _kinds_ of emphasis marker. +This paragraph *uses* both _kinds_ of emphasis marker. {MD049} -This paragraph _uses_ both *kinds* of emphasis marker. +This paragraph _uses_ both *kinds* of emphasis marker. {MD049} -This paragraph *nests both _kinds_ of emphasis* marker. +This paragraph *nests both _kinds_ of emphasis* marker. {MD049} This paragraph *nests both __kinds__ of emphasis* marker. This paragraph **nests both __kinds__ of emphasis** marker. {MD050} -This paragraph _nests both *kinds* of emphasis_ marker. +This paragraph _nests both *kinds* of emphasis_ marker. {MD049} -This paragraph _nests both **kinds** of emphasis_ marker. {MD050} +This paragraph _nests both **kinds** of emphasis_ marker. {MD049} {MD050} This paragraph __nests both **kinds** of emphasis__ marker. {MD050} diff --git a/test/proper-names-projects.md b/test/proper-names-projects.md index 0360e14c..c44c5cb0 100644 --- a/test/proper-names-projects.md +++ b/test/proper-names-projects.md @@ -40,7 +40,7 @@ Quoted "Vue" and "vue-router" Emphasized *Vue* and *vue-router* -Underscored _Vue_ and _vue-router_ +Underscored _Vue_ and _vue-router_ {MD049} Call it npm But not Npm {MD044} diff --git a/test/proper-names.md b/test/proper-names.md index 57f5a7c2..d69b6621 100644 --- a/test/proper-names.md +++ b/test/proper-names.md @@ -6,7 +6,7 @@ Quoted "Markdownlint" {MD044} Emphasized *Markdownlint* {MD044} -Emphasized _Markdownlint_ {MD044} +Emphasized _Markdownlint_ {MD044} {MD049} JavaScript is a language diff --git a/test/spaces-inside-emphasis-markers-multiple-lines.md b/test/spaces-inside-emphasis-markers-multiple-lines.md index 18e43b1b..4c0b777a 100644 --- a/test/spaces-inside-emphasis-markers-multiple-lines.md +++ b/test/spaces-inside-emphasis-markers-multiple-lines.md @@ -100,7 +100,7 @@ code Mixed `code_span` scenarios -are _also_ okay. +are _also_ okay. {MD049} Mixed `code*span` scenarios diff --git a/test/spaces_inside_emphasis_markers.md b/test/spaces_inside_emphasis_markers.md index faa6d68f..0799da86 100644 --- a/test/spaces_inside_emphasis_markers.md +++ b/test/spaces_inside_emphasis_markers.md @@ -1,6 +1,6 @@ # Heading - + Line with *Normal emphasis* From 956b55b55e6fd4edca793aa519aa92d38ab01ebf Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 23 Oct 2021 22:24:47 -0700 Subject: [PATCH 07/69] Simplify syntax for exclusion of globs/files in markdownlint-test-repos.js. --- test/markdownlint-test-repos.js | 315 ++++++++++---------------------- 1 file changed, 99 insertions(+), 216 deletions(-) diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index 8aa6857a..320f6549 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -67,16 +67,24 @@ function lintTestRepo(t, globPatterns, configPath) { }); } +/** + * Excludes a list of globs. + * + * @param {string} rootDir Root directory for globs. + * @param {...string} globs Globs to exclude. + * @returns {string[]} Array of excluded globs. + */ +function excludeGlobs(rootDir, ...globs) { + return globs.map((glob) => "!" + join(rootDir, glob)); +} + // Run markdownlint the same way the corresponding repositories do test("https://github.com/eslint/eslint", (t) => { const rootDir = "./test-repos/eslint-eslint"; const globPatterns = [ join(rootDir, "docs/**/*.md"), - "!" + join( - rootDir, - "docs/rules/array-callback-return.md" - ) + ...excludeGlobs(rootDir, "docs/rules/array-callback-return.md") ]; const configPath = join(rootDir, ".markdownlint.yml"); return lintTestRepo(t, globPatterns, configPath); @@ -88,7 +96,7 @@ test("https://github.com/mkdocs/mkdocs", (t) => { join(rootDir, "README.md"), join(rootDir, "CONTRIBUTING.md"), join(rootDir, "docs/**/*.md"), - "!" + join(rootDir, "docs/CNAME") + ...excludeGlobs(rootDir, "docs/CNAME") ]; const configPath = join(rootDir, ".markdownlintrc"); return lintTestRepo(t, globPatterns, configPath); @@ -112,9 +120,11 @@ test("https://github.com/pi-hole/docs", (t) => { const rootDir = "./test-repos/pi-hole-docs"; const globPatterns = [ join(rootDir, "**/*.md"), - "!" + join(rootDir, "docs/guides/dns/unbound.md"), - "!" + join(rootDir, "docs/index.md"), - "!" + join(rootDir, "docs/main/prerequisites.md") + ...excludeGlobs(rootDir, + "docs/guides/dns/unbound.md", + "docs/index.md", + "docs/main/prerequisites.md" + ) ]; const configPath = join(rootDir, ".markdownlint.json"); return lintTestRepo(t, globPatterns, configPath); @@ -124,7 +134,7 @@ test("https://github.com/webhintio/hint", (t) => { const rootDir = "./test-repos/webhintio-hint"; const globPatterns = [ join(rootDir, "**/*.md"), - "!" + join(rootDir, "**/CHANGELOG.md") + ...excludeGlobs(rootDir, "**/CHANGELOG.md") ]; const configPath = join(rootDir, ".markdownlintrc"); return lintTestRepo(t, globPatterns, configPath); @@ -145,202 +155,73 @@ if (existsSync(dotnetDocsDir)) { const rootDir = dotnetDocsDir; const globPatterns = [ join(rootDir, "**/*.md"), - "!" + join(rootDir, "samples/**/*.md"), - "!" + join(rootDir, "docs/architecture/cloud-native/candidate-apps.md"), - "!" + join( - rootDir, - "docs/architecture/containerized-lifecycle/docker-devops-workflow" + - "/docker-application-outer-loop-devops-workflow.md" - ), - "!" + join( - rootDir, - "docs/architecture/dapr-for-net-developers/getting-started.md" - ), - "!" + join( - rootDir, - "docs/architecture/grpc-for-wcf-developers/channel-credentials.md" - ), - "!" + join( - rootDir, - "docs/architecture/microservices/implement-resilient-applications" + - "/use-httpclientfactory-to-implement-resilient-http-requests.md" - ), - "!" + join( - rootDir, - "docs/architecture/microservices" + - "/multi-container-microservice-net-applications" + - "/implement-api-gateways-with-ocelot.md" - ), - "!" + join( - rootDir, - "docs/architecture/modern-web-apps-azure/architectural-principles.md" - ), - "!" + join( - rootDir, - "docs/architecture/modern-web-apps-azure" + - "/azure-hosting-recommendations-for-asp-net-web-apps.md" - ), - "!" + join( - rootDir, - "docs/architecture/modern-web-apps-azure" + - "/common-client-side-web-technologies.md" - ), - "!" + join( - rootDir, - "docs/architecture/modern-web-apps-azure" + - "/develop-asp-net-core-mvc-apps.md" - ), - "!" + join( - rootDir, - "docs/architecture/modern-web-apps-azure" + - "/development-process-for-azure.md" - ), - "!" + join(rootDir, "docs/architecture/modern-web-apps-azure/index.md"), - "!" + join(rootDir, "docs/core/additional-tools/dotnet-svcutil-guide.md"), - "!" + join(rootDir, "docs/core/dependency-loading/collect-details.md"), - "!" + join(rootDir, "docs/core/deploying/single-file.md"), - "!" + join(rootDir, "docs/core/deploying/trimming/trimming-options.md"), - "!" + join(rootDir, "docs/core/extensions/cloud-service.md"), - "!" + join(rootDir, "docs/core/extensions/console-log-formatter.md"), - "!" + join(rootDir, "docs/core/extensions/create-resource-files.md"), - "!" + join(rootDir, "docs/core/extensions/localization.md"), - "!" + join(rootDir, "docs/core/install/linux-alpine.md"), - "!" + join(rootDir, "docs/core/install/windows.md"), - "!" + join(rootDir, "docs/core/porting/third-party-deps.md"), - "!" + join(rootDir, "docs/core/project-sdk/msbuild-props-desktop.md"), - "!" + join(rootDir, "docs/core/testing/unit-testing-code-coverage.md"), - "!" + join(rootDir, "docs/core/tools/troubleshoot-usage-issues.md"), - "!" + join( - rootDir, - "docs/core/tutorials/cli-templates-create-item-template.md" - ), - "!" + join( - rootDir, - "docs/core/tutorials/cli-templates-create-project-template.md" - ), - "!" + join( - rootDir, - "docs/core/tutorials/cli-templates-create-template-pack.md" - ), - "!" + join( - rootDir, - "docs/core/tutorials/cli-templates-create-item-template.md" - ), - "!" + join( - rootDir, - "docs/core/tutorials/cli-templates-create-project-template.md" - ), - "!" + join( - rootDir, - "docs/core/tutorials/cli-templates-create-template-pack.md" - ), - "!" + join(rootDir, "docs/core/whats-new/dotnet-core-3-0.md"), - "!" + join( - rootDir, - "docs/csharp/language-reference/compiler-options/code-generation.md" - ), - "!" + join(rootDir, "docs/csharp/linq/query-expression-basics.md"), - "!" + join( - rootDir, - "docs/csharp/programming-guide/classes-and-structs" + - "/named-and-optional-arguments.md" - ), - "!" + join( - rootDir, - "docs/csharp/roslyn-sdk/tutorials" + - "/how-to-write-csharp-analyzer-code-fix.md" - ), - "!" + join(rootDir, "docs/csharp/tutorials/attributes.md"), - "!" + join(rootDir, "docs/csharp/whats-new/csharp-version-history.md"), - "!" + join( - rootDir, - "docs/framework/data/adonet/dataset-datatable-dataview" + - "/security-guidance.md" - ), - "!" + join( - rootDir, - "docs/fsharp/language-reference/compiler-directives.md" - ), - "!" + join( - rootDir, - "docs/fsharp/language-reference/exception-handling" + - "/the-try-with-expression.md" - ), - "!" + join( - rootDir, - "docs/fsharp/language-reference/xml-documentation.md" - ), - "!" + join(rootDir, "docs/fsharp/style-guide/conventions.md"), - "!" + join( - rootDir, - "docs/fsharp/tutorials/asynchronous-and-concurrent-programming/async.md" - ), - "!" + join( - rootDir, - "docs/fundamentals/code-analysis/configuration-files.md" - ), - "!" + join( - rootDir, - "docs/fundamentals/code-analysis/style-rules/naming-rules.md" - ), - "!" + join( - rootDir, - "docs/machine-learning/tutorials" + - "/health-violation-classification-model-builder.md" - ), - "!" + join( - rootDir, - "docs/machine-learning/tutorials/object-detection-model-builder.md" - ), - "!" + join( - rootDir, - "docs/machine-learning/tutorials/object-detection-onnx.md" - ), - "!" + join( - rootDir, - "docs/machine-learning/tutorials/text-classification-tf.md" - ), - "!" + join( - rootDir, - "docs/standard/asynchronous-programming-patterns" + - "/event-based-asynchronous-pattern-overview.md" - ), - "!" + join( - rootDir, - "docs/standard/asynchronous-programming-patterns" + - "/implementing-the-event-based-asynchronous-pattern.md" - ), - "!" + join( - rootDir, - "docs/standard/base-types/string-comparison-net-5-plus.md" - ), - "!" + join(rootDir, "docs/standard/delegates-lambdas.md"), - "!" + join(rootDir, "docs/standard/io/isolated-storage.md"), - "!" + join( - rootDir, - "docs/standard/native-interop/tutorial-comwrappers.md" - ), - "!" + join( - rootDir, - "docs/standard/serialization/xml-schema-definition-tool-xsd-exe.md" - ), - "!" + join( - rootDir, - "docs/standard/serialization/xml-serializer-generator-tool-sgen-exe.md" - ), - "!" + join(rootDir, "docs/standard/native-interop/best-practices.md"), - "!" + join( - rootDir, - "docs/standard/serialization/binaryformatter-security-guide.md" - ), - "!" + join( - rootDir, - "docs/framework/windows-workflow-foundation/authoring-workflows-" + - "activities-and-expressions-using-imperative-code.md" - ), - "!" + join( - rootDir, + ...excludeGlobs(rootDir, + "samples/**/*.md", + /* eslint-disable max-len */ + "docs/architecture/cloud-native/candidate-apps.md", + "docs/architecture/containerized-lifecycle/docker-devops-workflow/docker-application-outer-loop-devops-workflow.md", + "docs/architecture/dapr-for-net-developers/getting-started.md", + "docs/architecture/grpc-for-wcf-developers/channel-credentials.md", + "docs/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests.md", + "docs/architecture/microservices/multi-container-microservice-net-applications/implement-api-gateways-with-ocelot.md", + "docs/architecture/modern-web-apps-azure/architectural-principles.md", + "docs/architecture/modern-web-apps-azure/azure-hosting-recommendations-for-asp-net-web-apps.md", + "docs/architecture/modern-web-apps-azure/common-client-side-web-technologies.md", + "docs/architecture/modern-web-apps-azure/develop-asp-net-core-mvc-apps.md", + "docs/architecture/modern-web-apps-azure/development-process-for-azure.md", + "docs/architecture/modern-web-apps-azure/index.md", + "docs/core/additional-tools/dotnet-svcutil-guide.md", + "docs/core/dependency-loading/collect-details.md", + "docs/core/deploying/single-file.md", + "docs/core/deploying/trimming/trimming-options.md", + "docs/core/extensions/cloud-service.md", + "docs/core/extensions/console-log-formatter.md", + "docs/core/extensions/create-resource-files.md", + "docs/core/extensions/localization.md", + "docs/core/install/linux-alpine.md", + "docs/core/install/windows.md", + "docs/core/porting/third-party-deps.md", + "docs/core/project-sdk/msbuild-props-desktop.md", + "docs/core/testing/unit-testing-code-coverage.md", + "docs/core/tools/troubleshoot-usage-issues.md", + "docs/core/tutorials/cli-templates-create-item-template.md", + "docs/core/tutorials/cli-templates-create-project-template.md", + "docs/core/tutorials/cli-templates-create-template-pack.md", + "docs/core/tutorials/cli-templates-create-item-template.md", + "docs/core/tutorials/cli-templates-create-project-template.md", + "docs/core/tutorials/cli-templates-create-template-pack.md", + "docs/core/whats-new/dotnet-core-3-0.md", + "docs/csharp/language-reference/compiler-options/code-generation.md", + "docs/csharp/linq/query-expression-basics.md", + "docs/csharp/programming-guide/classes-and-structs/named-and-optional-arguments.md", + "docs/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix.md", + "docs/csharp/tutorials/attributes.md", + "docs/csharp/whats-new/csharp-version-history.md", + "docs/framework/data/adonet/dataset-datatable-dataview/security-guidance.md", + "docs/fsharp/language-reference/compiler-directives.md", + "docs/fsharp/language-reference/exception-handling/the-try-with-expression.md", + "docs/fsharp/language-reference/xml-documentation.md", + "docs/fsharp/style-guide/conventions.md", + "docs/fsharp/tutorials/asynchronous-and-concurrent-programming/async.md", + "docs/fundamentals/code-analysis/configuration-files.md", + "docs/fundamentals/code-analysis/style-rules/naming-rules.md", + "docs/machine-learning/tutorials/health-violation-classification-model-builder.md", + "docs/machine-learning/tutorials/object-detection-model-builder.md", + "docs/machine-learning/tutorials/object-detection-onnx.md", + "docs/machine-learning/tutorials/text-classification-tf.md", + "docs/standard/asynchronous-programming-patterns/event-based-asynchronous-pattern-overview.md", + "docs/standard/asynchronous-programming-patterns/implementing-the-event-based-asynchronous-pattern.md", + "docs/standard/base-types/string-comparison-net-5-plus.md", + "docs/standard/delegates-lambdas.md", + "docs/standard/io/isolated-storage.md", + "docs/standard/native-interop/tutorial-comwrappers.md", + "docs/standard/serialization/xml-schema-definition-tool-xsd-exe.md", + "docs/standard/serialization/xml-serializer-generator-tool-sgen-exe.md", + "docs/standard/native-interop/best-practices.md", + "docs/standard/serialization/binaryformatter-security-guide.md", + "docs/framework/windows-workflow-foundation/authoring-workflows-activities-and-expressions-using-imperative-code.md", "docs/spark/how-to-guides/deploy-worker-udf-binaries.md" + /* eslint-enable max-len */ ) ]; const configPath = join(rootDir, ".markdownlint.json"); @@ -354,18 +235,20 @@ if (existsSync(v8v8DevDir)) { const rootDir = v8v8DevDir; const globPatterns = [ join(rootDir, "src/**/*.md"), - "!" + join(rootDir, "src/blog/array-sort.md"), - "!" + join(rootDir, "src/blog/code-caching-for-devs.md"), - "!" + join(rootDir, "src/blog/fast-async.md"), - "!" + join(rootDir, "src/blog/liftoff.md"), - "!" + join(rootDir, "src/blog/pointer-compression.md"), - "!" + join(rootDir, "src/blog/react-cliff.md"), - "!" + join(rootDir, "src/blog/slack-tracking.md"), - "!" + join(rootDir, "src/blog/v8-release-74.md"), - "!" + join(rootDir, "src/features/bigint.md"), - "!" + join(rootDir, "src/features/dynamic-import.md"), - "!" + join(rootDir, "src/features/globalthis.md"), - "!" + join(rootDir, "src/features/modules.md") + ...excludeGlobs(rootDir, + "src/blog/array-sort.md", + "src/blog/code-caching-for-devs.md", + "src/blog/fast-async.md", + "src/blog/liftoff.md", + "src/blog/pointer-compression.md", + "src/blog/react-cliff.md", + "src/blog/slack-tracking.md", + "src/blog/v8-release-74.md", + "src/features/bigint.md", + "src/features/dynamic-import.md", + "src/features/globalthis.md", + "src/features/modules.md" + ) ]; const configPath = join(rootDir, ".markdownlint.json"); return lintTestRepo(t, globPatterns, configPath); From f111c10b18d76dbb696c5d945786398f85e194f1 Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 29 Oct 2021 21:48:24 -0700 Subject: [PATCH 08/69] Add docker-npm-install script for "npm install" within a container. --- demo/markdownlint-browser.js | 2 +- package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 8409ad37..9c6f64bd 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -4230,7 +4230,7 @@ module.exports = markdownit; /***/ ((module) => { "use strict"; -module.exports = JSON.parse('{"name":"markdownlint","version":"0.24.0","description":"A Node.js style checker and lint tool for Markdown/CommonMark files.","main":"lib/markdownlint.js","types":"lib/markdownlint.d.ts","author":"David Anson (https://dlaa.me/)","license":"MIT","homepage":"https://github.com/DavidAnson/markdownlint","repository":{"type":"git","url":"https://github.com/DavidAnson/markdownlint.git"},"bugs":"https://github.com/DavidAnson/markdownlint/issues","scripts":{"build-config":"npm run build-config-schema && npm run build-config-example","build-config-example":"node schema/build-config-example.js","build-config-schema":"node schema/build-config-schema.js","build-declaration":"tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && rimraf \'lib/{c,md,r}*.d.ts\' \'helpers/*.d.ts\'","build-demo":"cpy node_modules/markdown-it/dist/markdown-it.min.js demo && cd demo && rimraf markdownlint-browser.* && webpack --no-stats","build-example":"npm install --no-save --ignore-scripts grunt grunt-cli gulp through2","ci":"npm-run-all --continue-on-error --parallel test-cover lint declaration build-config build-demo && git diff --exit-code","clean-test-repos":"rimraf test-repos","clone-test-repos-dotnet-docs":"cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet","clone-test-repos-eslint-eslint":"cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet","clone-test-repos-mkdocs-mkdocs":"cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet","clone-test-repos-mochajs-mocha":"cd test-repos && git clone https://github.com/mochajs/mocha mochajs-mocha --depth 1 --no-tags --quiet","clone-test-repos-pi-hole-docs":"cd test-repos && git clone https://github.com/pi-hole/docs pi-hole-docs --depth 1 --no-tags --quiet","clone-test-repos-v8-v8-dev":"cd test-repos && git clone https://github.com/v8/v8.dev v8-v8-dev --depth 1 --no-tags --quiet","clone-test-repos-webhintio-hint":"cd test-repos && git clone https://github.com/webhintio/hint webhintio-hint --depth 1 --no-tags --quiet","clone-test-repos-webpack-webpack-js-org":"cd test-repos && git clone https://github.com/webpack/webpack.js.org webpack-webpack-js-org --depth 1 --no-tags --quiet","clone-test-repos":"mkdir test-repos && cd test-repos && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org","clone-test-repos-large":"npm run clone-test-repos && cd test-repos && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-v8-v8-dev","declaration":"npm run build-declaration && npm run test-declaration","example":"cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint","lint":"eslint --max-warnings 0 .","lint-test-repos":"ava --timeout=5m test/markdownlint-test-repos.js","test":"ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js","test-cover":"c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test","test-declaration":"cd example/typescript && tsc && node type-check.js","test-extra":"ava --timeout=5m test/markdownlint-test-extra.js"},"engines":{"node":">=10"},"dependencies":{"markdown-it":"12.2.0"},"devDependencies":{"ava":"~3.15.0","c8":"~7.8.0","cpy-cli":"~3.1.1","eslint":"~7.32.0","eslint-plugin-jsdoc":"~36.0.7","eslint-plugin-node":"~11.1.0","eslint-plugin-unicorn":"~35.0.0","globby":"~11.0.4","js-yaml":"~4.1.0","markdown-it-for-inline":"~0.1.1","markdown-it-sub":"~1.0.0","markdown-it-sup":"~1.0.0","markdown-it-texmath":"~0.9.1","markdownlint-rule-helpers":"~0.14.0","npm-run-all":"~4.1.5","rimraf":"~3.0.2","strip-json-comments":"~3.1.1","toml":"~3.0.0","ts-loader":"~9.2.5","tv4":"~1.3.0","typescript":"~4.3.5","webpack":"~5.51.1","webpack-cli":"~4.8.0"},"keywords":["markdown","lint","md","CommonMark","markdownlint"]}'); +module.exports = JSON.parse('{"name":"markdownlint","version":"0.24.0","description":"A Node.js style checker and lint tool for Markdown/CommonMark files.","main":"lib/markdownlint.js","types":"lib/markdownlint.d.ts","author":"David Anson (https://dlaa.me/)","license":"MIT","homepage":"https://github.com/DavidAnson/markdownlint","repository":{"type":"git","url":"https://github.com/DavidAnson/markdownlint.git"},"bugs":"https://github.com/DavidAnson/markdownlint/issues","scripts":{"build-config":"npm run build-config-schema && npm run build-config-example","build-config-example":"node schema/build-config-example.js","build-config-schema":"node schema/build-config-schema.js","build-declaration":"tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && rimraf \'lib/{c,md,r}*.d.ts\' \'helpers/*.d.ts\'","build-demo":"cpy node_modules/markdown-it/dist/markdown-it.min.js demo && cd demo && rimraf markdownlint-browser.* && webpack --no-stats","build-example":"npm install --no-save --ignore-scripts grunt grunt-cli gulp through2","ci":"npm-run-all --continue-on-error --parallel test-cover lint declaration build-config build-demo && git diff --exit-code","clean-test-repos":"rimraf test-repos","clone-test-repos-dotnet-docs":"cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet","clone-test-repos-eslint-eslint":"cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet","clone-test-repos-mkdocs-mkdocs":"cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet","clone-test-repos-mochajs-mocha":"cd test-repos && git clone https://github.com/mochajs/mocha mochajs-mocha --depth 1 --no-tags --quiet","clone-test-repos-pi-hole-docs":"cd test-repos && git clone https://github.com/pi-hole/docs pi-hole-docs --depth 1 --no-tags --quiet","clone-test-repos-v8-v8-dev":"cd test-repos && git clone https://github.com/v8/v8.dev v8-v8-dev --depth 1 --no-tags --quiet","clone-test-repos-webhintio-hint":"cd test-repos && git clone https://github.com/webhintio/hint webhintio-hint --depth 1 --no-tags --quiet","clone-test-repos-webpack-webpack-js-org":"cd test-repos && git clone https://github.com/webpack/webpack.js.org webpack-webpack-js-org --depth 1 --no-tags --quiet","clone-test-repos":"mkdir test-repos && cd test-repos && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org","clone-test-repos-large":"npm run clone-test-repos && cd test-repos && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-v8-v8-dev","declaration":"npm run build-declaration && npm run test-declaration","example":"cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint","docker-npm-install":"docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm install","lint":"eslint --max-warnings 0 .","lint-test-repos":"ava --timeout=5m test/markdownlint-test-repos.js","test":"ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js","test-cover":"c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test","test-declaration":"cd example/typescript && tsc && node type-check.js","test-extra":"ava --timeout=5m test/markdownlint-test-extra.js"},"engines":{"node":">=10"},"dependencies":{"markdown-it":"12.2.0"},"devDependencies":{"ava":"~3.15.0","c8":"~7.8.0","cpy-cli":"~3.1.1","eslint":"~7.32.0","eslint-plugin-jsdoc":"~36.0.7","eslint-plugin-node":"~11.1.0","eslint-plugin-unicorn":"~35.0.0","globby":"~11.0.4","js-yaml":"~4.1.0","markdown-it-for-inline":"~0.1.1","markdown-it-sub":"~1.0.0","markdown-it-sup":"~1.0.0","markdown-it-texmath":"~0.9.1","markdownlint-rule-helpers":"~0.14.0","npm-run-all":"~4.1.5","rimraf":"~3.0.2","strip-json-comments":"~3.1.1","toml":"~3.0.0","ts-loader":"~9.2.5","tv4":"~1.3.0","typescript":"~4.3.5","webpack":"~5.51.1","webpack-cli":"~4.8.0"},"keywords":["markdown","lint","md","CommonMark","markdownlint"]}'); /***/ }) diff --git a/package.json b/package.json index 838a578c..a1a8c804 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "clone-test-repos-large": "npm run clone-test-repos && cd test-repos && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-v8-v8-dev", "declaration": "npm run build-declaration && npm run test-declaration", "example": "cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint", + "docker-npm-install": "docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm install", "lint": "eslint --max-warnings 0 .", "lint-test-repos": "ava --timeout=5m test/markdownlint-test-repos.js", "test": "ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js", From d1215189114840d7ca031135b6583eca6a0a6dbb Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 29 Oct 2021 22:09:34 -0700 Subject: [PATCH 09/69] Add new test repo suppression for pre-release rule MD049. --- test/markdownlint-test-repos.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index 320f6549..583d4cfa 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -187,6 +187,7 @@ if (existsSync(dotnetDocsDir)) { "docs/core/tutorials/cli-templates-create-item-template.md", "docs/core/tutorials/cli-templates-create-project-template.md", "docs/core/tutorials/cli-templates-create-template-pack.md", + "docs/core/tutorials/cli-templates-create-template-package.md", "docs/core/tutorials/cli-templates-create-item-template.md", "docs/core/tutorials/cli-templates-create-project-template.md", "docs/core/tutorials/cli-templates-create-template-pack.md", From 495458c589fa3b5cc6c0699d34a181b4c878f828 Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 29 Oct 2021 22:13:33 -0700 Subject: [PATCH 10/69] Update CONTRIBUTING.md with new guidelines. --- CONTRIBUTING.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f8c38b4..d5e1700e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ Try to break the new code now, or else it will get broken later. Run tests before sending a pull request via `npm test` in the [usual manner](https://docs.npmjs.com/misc/scripts). Tests should all pass on all platforms. -The test runner is [tape](https://www.npmjs.com/package/tape) and test cases are located in `test/markdownlint-test*.js`. +The test runner is [AVA](https://github.com/avajs/ava) and test cases are located in `test/markdownlint-test*.js`. When running tests, `test/*.md` files are enumerated, linted, and fail if any violations are missing a corresponding `{MD###}` marker in the test file. For example, the line `### Heading {MD001}` is expected to trigger the rule `MD001`. For cases where the marker text can not be present on the same line, the syntax `{MD###:#}` can be used to include a line number. @@ -33,7 +33,8 @@ Lint before sending a pull request by running `npm run lint`. There should be no issues. Run a full continuous integration pass before sending a pull request via `npm run ci`. -Code coverage should remain at 100%. +Code coverage should always be 100%. +As part of a continuous integration run, generated files may get updated and fail the run - commit them to the repository and rerun continuous integration. Pull requests should contain a single commit. If necessary, squash multiple commits before creating the pull request and when making changes. @@ -43,9 +44,18 @@ Open pull requests against the `next` branch. That's where the latest changes are staged for the next release. Include the text "(fixes #??)" at the end of the commit message so the pull request will be associated with the relevant issue. End commit messages with a period (`.`). -Do not include `package-lock.json` in the pull request. Once accepted, the tag `fixed in next` will be added to the issue. When the commit is merged to the main branch during the release process, the issue will be closed automatically. (See [Closing issues using keywords](https://help.github.com/articles/closing-issues-using-keywords/) for details.) +Please refrain from using slang or meaningless placeholder words. +Sample text can be "Text" or "text text text" or the like. +Sample URLs should use [example.com](https://en.wikipedia.org/wiki/Example.com) which is safe for this purpose. +Profanity is not allowed. + +In order to maintain the permissive MIT license this project uses, all contributions must be your own and released under that license. +Code you add should be an original work and should not be copied from elsewhere. +Taking code from a different project, Stack Overflow, or the like is not allowed. +The use of tools such as GitHub Copilot that generate code from other projects is not allowed. + Thank you! From 2bde578a383624354836cf5be3274f0fb90c57f0 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 30 Oct 2021 12:53:50 -0700 Subject: [PATCH 11/69] Tweak sample text examples in CONTRIBUTING.md. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d5e1700e..bc3f886b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ When the commit is merged to the main branch during the release process, the iss (See [Closing issues using keywords](https://help.github.com/articles/closing-issues-using-keywords/) for details.) Please refrain from using slang or meaningless placeholder words. -Sample text can be "Text" or "text text text" or the like. +Sample content can be "text", "code", "heading", or the like. Sample URLs should use [example.com](https://en.wikipedia.org/wiki/Example.com) which is safe for this purpose. Profanity is not allowed. From c4a7c87d69d682520e70501da96921a99eaa4574 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 30 Oct 2021 17:32:51 -0700 Subject: [PATCH 12/69] Update package.json / engines / node to 12+ due to deprecation of Node 10. --- demo/markdownlint-browser.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 9c6f64bd..e1b6266c 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -4230,7 +4230,7 @@ module.exports = markdownit; /***/ ((module) => { "use strict"; -module.exports = JSON.parse('{"name":"markdownlint","version":"0.24.0","description":"A Node.js style checker and lint tool for Markdown/CommonMark files.","main":"lib/markdownlint.js","types":"lib/markdownlint.d.ts","author":"David Anson (https://dlaa.me/)","license":"MIT","homepage":"https://github.com/DavidAnson/markdownlint","repository":{"type":"git","url":"https://github.com/DavidAnson/markdownlint.git"},"bugs":"https://github.com/DavidAnson/markdownlint/issues","scripts":{"build-config":"npm run build-config-schema && npm run build-config-example","build-config-example":"node schema/build-config-example.js","build-config-schema":"node schema/build-config-schema.js","build-declaration":"tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && rimraf \'lib/{c,md,r}*.d.ts\' \'helpers/*.d.ts\'","build-demo":"cpy node_modules/markdown-it/dist/markdown-it.min.js demo && cd demo && rimraf markdownlint-browser.* && webpack --no-stats","build-example":"npm install --no-save --ignore-scripts grunt grunt-cli gulp through2","ci":"npm-run-all --continue-on-error --parallel test-cover lint declaration build-config build-demo && git diff --exit-code","clean-test-repos":"rimraf test-repos","clone-test-repos-dotnet-docs":"cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet","clone-test-repos-eslint-eslint":"cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet","clone-test-repos-mkdocs-mkdocs":"cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet","clone-test-repos-mochajs-mocha":"cd test-repos && git clone https://github.com/mochajs/mocha mochajs-mocha --depth 1 --no-tags --quiet","clone-test-repos-pi-hole-docs":"cd test-repos && git clone https://github.com/pi-hole/docs pi-hole-docs --depth 1 --no-tags --quiet","clone-test-repos-v8-v8-dev":"cd test-repos && git clone https://github.com/v8/v8.dev v8-v8-dev --depth 1 --no-tags --quiet","clone-test-repos-webhintio-hint":"cd test-repos && git clone https://github.com/webhintio/hint webhintio-hint --depth 1 --no-tags --quiet","clone-test-repos-webpack-webpack-js-org":"cd test-repos && git clone https://github.com/webpack/webpack.js.org webpack-webpack-js-org --depth 1 --no-tags --quiet","clone-test-repos":"mkdir test-repos && cd test-repos && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org","clone-test-repos-large":"npm run clone-test-repos && cd test-repos && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-v8-v8-dev","declaration":"npm run build-declaration && npm run test-declaration","example":"cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint","docker-npm-install":"docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm install","lint":"eslint --max-warnings 0 .","lint-test-repos":"ava --timeout=5m test/markdownlint-test-repos.js","test":"ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js","test-cover":"c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test","test-declaration":"cd example/typescript && tsc && node type-check.js","test-extra":"ava --timeout=5m test/markdownlint-test-extra.js"},"engines":{"node":">=10"},"dependencies":{"markdown-it":"12.2.0"},"devDependencies":{"ava":"~3.15.0","c8":"~7.8.0","cpy-cli":"~3.1.1","eslint":"~7.32.0","eslint-plugin-jsdoc":"~36.0.7","eslint-plugin-node":"~11.1.0","eslint-plugin-unicorn":"~35.0.0","globby":"~11.0.4","js-yaml":"~4.1.0","markdown-it-for-inline":"~0.1.1","markdown-it-sub":"~1.0.0","markdown-it-sup":"~1.0.0","markdown-it-texmath":"~0.9.1","markdownlint-rule-helpers":"~0.14.0","npm-run-all":"~4.1.5","rimraf":"~3.0.2","strip-json-comments":"~3.1.1","toml":"~3.0.0","ts-loader":"~9.2.5","tv4":"~1.3.0","typescript":"~4.3.5","webpack":"~5.51.1","webpack-cli":"~4.8.0"},"keywords":["markdown","lint","md","CommonMark","markdownlint"]}'); +module.exports = JSON.parse('{"name":"markdownlint","version":"0.24.0","description":"A Node.js style checker and lint tool for Markdown/CommonMark files.","main":"lib/markdownlint.js","types":"lib/markdownlint.d.ts","author":"David Anson (https://dlaa.me/)","license":"MIT","homepage":"https://github.com/DavidAnson/markdownlint","repository":{"type":"git","url":"https://github.com/DavidAnson/markdownlint.git"},"bugs":"https://github.com/DavidAnson/markdownlint/issues","scripts":{"build-config":"npm run build-config-schema && npm run build-config-example","build-config-example":"node schema/build-config-example.js","build-config-schema":"node schema/build-config-schema.js","build-declaration":"tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && rimraf \'lib/{c,md,r}*.d.ts\' \'helpers/*.d.ts\'","build-demo":"cpy node_modules/markdown-it/dist/markdown-it.min.js demo && cd demo && rimraf markdownlint-browser.* && webpack --no-stats","build-example":"npm install --no-save --ignore-scripts grunt grunt-cli gulp through2","ci":"npm-run-all --continue-on-error --parallel test-cover lint declaration build-config build-demo && git diff --exit-code","clean-test-repos":"rimraf test-repos","clone-test-repos-dotnet-docs":"cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet","clone-test-repos-eslint-eslint":"cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet","clone-test-repos-mkdocs-mkdocs":"cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet","clone-test-repos-mochajs-mocha":"cd test-repos && git clone https://github.com/mochajs/mocha mochajs-mocha --depth 1 --no-tags --quiet","clone-test-repos-pi-hole-docs":"cd test-repos && git clone https://github.com/pi-hole/docs pi-hole-docs --depth 1 --no-tags --quiet","clone-test-repos-v8-v8-dev":"cd test-repos && git clone https://github.com/v8/v8.dev v8-v8-dev --depth 1 --no-tags --quiet","clone-test-repos-webhintio-hint":"cd test-repos && git clone https://github.com/webhintio/hint webhintio-hint --depth 1 --no-tags --quiet","clone-test-repos-webpack-webpack-js-org":"cd test-repos && git clone https://github.com/webpack/webpack.js.org webpack-webpack-js-org --depth 1 --no-tags --quiet","clone-test-repos":"mkdir test-repos && cd test-repos && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org","clone-test-repos-large":"npm run clone-test-repos && cd test-repos && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-v8-v8-dev","declaration":"npm run build-declaration && npm run test-declaration","example":"cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint","docker-npm-install":"docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm install","lint":"eslint --max-warnings 0 .","lint-test-repos":"ava --timeout=5m test/markdownlint-test-repos.js","test":"ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js","test-cover":"c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test","test-declaration":"cd example/typescript && tsc && node type-check.js","test-extra":"ava --timeout=5m test/markdownlint-test-extra.js"},"engines":{"node":">=12"},"dependencies":{"markdown-it":"12.2.0"},"devDependencies":{"ava":"~3.15.0","c8":"~7.8.0","cpy-cli":"~3.1.1","eslint":"~7.32.0","eslint-plugin-jsdoc":"~36.0.7","eslint-plugin-node":"~11.1.0","eslint-plugin-unicorn":"~35.0.0","globby":"~11.0.4","js-yaml":"~4.1.0","markdown-it-for-inline":"~0.1.1","markdown-it-sub":"~1.0.0","markdown-it-sup":"~1.0.0","markdown-it-texmath":"~0.9.1","markdownlint-rule-helpers":"~0.14.0","npm-run-all":"~4.1.5","rimraf":"~3.0.2","strip-json-comments":"~3.1.1","toml":"~3.0.0","ts-loader":"~9.2.5","tv4":"~1.3.0","typescript":"~4.3.5","webpack":"~5.51.1","webpack-cli":"~4.8.0"},"keywords":["markdown","lint","md","CommonMark","markdownlint"]}'); /***/ }) diff --git a/package.json b/package.json index a1a8c804..ea062f76 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "test-extra": "ava --timeout=5m test/markdownlint-test-extra.js" }, "engines": { - "node": ">=10" + "node": ">=12" }, "dependencies": { "markdown-it": "12.2.0" From d032dc6b33548beddd1a609764236d1404fe7fbd Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 30 Oct 2021 17:45:33 -0700 Subject: [PATCH 13/69] Remove rimraf from build-demo script to try to avoid rare failures under GitHub Actions. --- demo/markdownlint-browser.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index e1b6266c..871b6fff 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -4230,7 +4230,7 @@ module.exports = markdownit; /***/ ((module) => { "use strict"; -module.exports = JSON.parse('{"name":"markdownlint","version":"0.24.0","description":"A Node.js style checker and lint tool for Markdown/CommonMark files.","main":"lib/markdownlint.js","types":"lib/markdownlint.d.ts","author":"David Anson (https://dlaa.me/)","license":"MIT","homepage":"https://github.com/DavidAnson/markdownlint","repository":{"type":"git","url":"https://github.com/DavidAnson/markdownlint.git"},"bugs":"https://github.com/DavidAnson/markdownlint/issues","scripts":{"build-config":"npm run build-config-schema && npm run build-config-example","build-config-example":"node schema/build-config-example.js","build-config-schema":"node schema/build-config-schema.js","build-declaration":"tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && rimraf \'lib/{c,md,r}*.d.ts\' \'helpers/*.d.ts\'","build-demo":"cpy node_modules/markdown-it/dist/markdown-it.min.js demo && cd demo && rimraf markdownlint-browser.* && webpack --no-stats","build-example":"npm install --no-save --ignore-scripts grunt grunt-cli gulp through2","ci":"npm-run-all --continue-on-error --parallel test-cover lint declaration build-config build-demo && git diff --exit-code","clean-test-repos":"rimraf test-repos","clone-test-repos-dotnet-docs":"cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet","clone-test-repos-eslint-eslint":"cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet","clone-test-repos-mkdocs-mkdocs":"cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet","clone-test-repos-mochajs-mocha":"cd test-repos && git clone https://github.com/mochajs/mocha mochajs-mocha --depth 1 --no-tags --quiet","clone-test-repos-pi-hole-docs":"cd test-repos && git clone https://github.com/pi-hole/docs pi-hole-docs --depth 1 --no-tags --quiet","clone-test-repos-v8-v8-dev":"cd test-repos && git clone https://github.com/v8/v8.dev v8-v8-dev --depth 1 --no-tags --quiet","clone-test-repos-webhintio-hint":"cd test-repos && git clone https://github.com/webhintio/hint webhintio-hint --depth 1 --no-tags --quiet","clone-test-repos-webpack-webpack-js-org":"cd test-repos && git clone https://github.com/webpack/webpack.js.org webpack-webpack-js-org --depth 1 --no-tags --quiet","clone-test-repos":"mkdir test-repos && cd test-repos && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org","clone-test-repos-large":"npm run clone-test-repos && cd test-repos && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-v8-v8-dev","declaration":"npm run build-declaration && npm run test-declaration","example":"cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint","docker-npm-install":"docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm install","lint":"eslint --max-warnings 0 .","lint-test-repos":"ava --timeout=5m test/markdownlint-test-repos.js","test":"ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js","test-cover":"c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test","test-declaration":"cd example/typescript && tsc && node type-check.js","test-extra":"ava --timeout=5m test/markdownlint-test-extra.js"},"engines":{"node":">=12"},"dependencies":{"markdown-it":"12.2.0"},"devDependencies":{"ava":"~3.15.0","c8":"~7.8.0","cpy-cli":"~3.1.1","eslint":"~7.32.0","eslint-plugin-jsdoc":"~36.0.7","eslint-plugin-node":"~11.1.0","eslint-plugin-unicorn":"~35.0.0","globby":"~11.0.4","js-yaml":"~4.1.0","markdown-it-for-inline":"~0.1.1","markdown-it-sub":"~1.0.0","markdown-it-sup":"~1.0.0","markdown-it-texmath":"~0.9.1","markdownlint-rule-helpers":"~0.14.0","npm-run-all":"~4.1.5","rimraf":"~3.0.2","strip-json-comments":"~3.1.1","toml":"~3.0.0","ts-loader":"~9.2.5","tv4":"~1.3.0","typescript":"~4.3.5","webpack":"~5.51.1","webpack-cli":"~4.8.0"},"keywords":["markdown","lint","md","CommonMark","markdownlint"]}'); +module.exports = JSON.parse('{"name":"markdownlint","version":"0.24.0","description":"A Node.js style checker and lint tool for Markdown/CommonMark files.","main":"lib/markdownlint.js","types":"lib/markdownlint.d.ts","author":"David Anson (https://dlaa.me/)","license":"MIT","homepage":"https://github.com/DavidAnson/markdownlint","repository":{"type":"git","url":"https://github.com/DavidAnson/markdownlint.git"},"bugs":"https://github.com/DavidAnson/markdownlint/issues","scripts":{"build-config":"npm run build-config-schema && npm run build-config-example","build-config-example":"node schema/build-config-example.js","build-config-schema":"node schema/build-config-schema.js","build-declaration":"tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && rimraf \'lib/{c,md,r}*.d.ts\' \'helpers/*.d.ts\'","build-demo":"cpy node_modules/markdown-it/dist/markdown-it.min.js demo && cd demo && webpack --no-stats","build-example":"npm install --no-save --ignore-scripts grunt grunt-cli gulp through2","ci":"npm-run-all --continue-on-error --parallel test-cover lint declaration build-config build-demo && git diff --exit-code","clean-test-repos":"rimraf test-repos","clone-test-repos-dotnet-docs":"cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet","clone-test-repos-eslint-eslint":"cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet","clone-test-repos-mkdocs-mkdocs":"cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet","clone-test-repos-mochajs-mocha":"cd test-repos && git clone https://github.com/mochajs/mocha mochajs-mocha --depth 1 --no-tags --quiet","clone-test-repos-pi-hole-docs":"cd test-repos && git clone https://github.com/pi-hole/docs pi-hole-docs --depth 1 --no-tags --quiet","clone-test-repos-v8-v8-dev":"cd test-repos && git clone https://github.com/v8/v8.dev v8-v8-dev --depth 1 --no-tags --quiet","clone-test-repos-webhintio-hint":"cd test-repos && git clone https://github.com/webhintio/hint webhintio-hint --depth 1 --no-tags --quiet","clone-test-repos-webpack-webpack-js-org":"cd test-repos && git clone https://github.com/webpack/webpack.js.org webpack-webpack-js-org --depth 1 --no-tags --quiet","clone-test-repos":"mkdir test-repos && cd test-repos && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org","clone-test-repos-large":"npm run clone-test-repos && cd test-repos && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-v8-v8-dev","declaration":"npm run build-declaration && npm run test-declaration","example":"cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint","docker-npm-install":"docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm install","lint":"eslint --max-warnings 0 .","lint-test-repos":"ava --timeout=5m test/markdownlint-test-repos.js","test":"ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js","test-cover":"c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test","test-declaration":"cd example/typescript && tsc && node type-check.js","test-extra":"ava --timeout=5m test/markdownlint-test-extra.js"},"engines":{"node":">=12"},"dependencies":{"markdown-it":"12.2.0"},"devDependencies":{"ava":"~3.15.0","c8":"~7.8.0","cpy-cli":"~3.1.1","eslint":"~7.32.0","eslint-plugin-jsdoc":"~36.0.7","eslint-plugin-node":"~11.1.0","eslint-plugin-unicorn":"~35.0.0","globby":"~11.0.4","js-yaml":"~4.1.0","markdown-it-for-inline":"~0.1.1","markdown-it-sub":"~1.0.0","markdown-it-sup":"~1.0.0","markdown-it-texmath":"~0.9.1","markdownlint-rule-helpers":"~0.14.0","npm-run-all":"~4.1.5","rimraf":"~3.0.2","strip-json-comments":"~3.1.1","toml":"~3.0.0","ts-loader":"~9.2.5","tv4":"~1.3.0","typescript":"~4.3.5","webpack":"~5.51.1","webpack-cli":"~4.8.0"},"keywords":["markdown","lint","md","CommonMark","markdownlint"]}'); /***/ }) diff --git a/package.json b/package.json index ea062f76..81c7bf68 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "build-config-example": "node schema/build-config-example.js", "build-config-schema": "node schema/build-config-schema.js", "build-declaration": "tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && rimraf 'lib/{c,md,r}*.d.ts' 'helpers/*.d.ts'", - "build-demo": "cpy node_modules/markdown-it/dist/markdown-it.min.js demo && cd demo && rimraf markdownlint-browser.* && webpack --no-stats", + "build-demo": "cpy node_modules/markdown-it/dist/markdown-it.min.js demo && cd demo && webpack --no-stats", "build-example": "npm install --no-save --ignore-scripts grunt grunt-cli gulp through2", "ci": "npm-run-all --continue-on-error --parallel test-cover lint declaration build-config build-demo && git diff --exit-code", "clean-test-repos": "rimraf test-repos", From 0550ba91d399a970640cc9c32650e9867cb5864c Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 30 Oct 2021 17:55:50 -0700 Subject: [PATCH 14/69] Move .eslintignore content into .eslintrc.json, sort by setting name. --- .eslintignore | 7 ---- .eslintrc.json | 95 +++++++++++++++++++++++++++----------------------- 2 files changed, 52 insertions(+), 50 deletions(-) delete mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index a41c216c..00000000 --- a/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -demo/markdown-it.min.js -demo/markdownlint-browser.js -demo/markdownlint-browser.min.js -demo/markdownlint-rule-helpers-browser.js -demo/markdownlint-rule-helpers-browser.min.js -example/typescript/type-check.js -test-repos/ diff --git a/.eslintrc.json b/.eslintrc.json index 0c799131..d8ddddc7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,27 +1,60 @@ { - "parserOptions": { - "ecmaVersion": 2019 - }, "env": { "node": true, "es6": true }, + "extends": [ + "eslint:all", + "plugin:jsdoc/recommended" + ], + "ignorePatterns": [ + "demo/markdown-it.min.js", + "demo/markdownlint-browser.js", + "demo/markdownlint-browser.min.js", + "demo/markdownlint-rule-helpers-browser.js", + "demo/markdownlint-rule-helpers-browser.min.js", + "example/typescript/type-check.js", + "test-repos/" + ], + "overrides": [ + { + "files": [ + "demo/*.js" + ], + "env": { + "browser": true + }, + "rules": { + "jsdoc/require-jsdoc": "off", + "unicorn/prefer-query-selector": "off", + "unicorn/prefer-add-event-listener": "off", + "no-console": "off", + "no-shadow": "off", + "no-var": "off" + } + }, + { + "files": [ + "example/*.js" + ], + "rules": { + "node/no-missing-require": "off", + "node/no-extraneous-require": "off", + "no-console": "off", + "no-invalid-this": "off", + "no-shadow": "off", + "object-property-newline": "off" + } + } + ], + "parserOptions": { + "ecmaVersion": 2019 + }, "plugins": [ "jsdoc", "node", "unicorn" ], - "extends": [ - "eslint:all", - "plugin:jsdoc/recommended" - ], - "settings": { - "jsdoc": { - "preferredTypes": { - "object": "Object" - } - } - }, "reportUnusedDisableDirectives": true, "rules": { "array-bracket-spacing": ["error", "always"], @@ -243,35 +276,11 @@ "unicorn/string-content": "error", "unicorn/throw-new-error": "error" }, - "overrides": [ - { - "files": [ - "demo/*.js" - ], - "env": { - "browser": true - }, - "rules": { - "jsdoc/require-jsdoc": "off", - "unicorn/prefer-query-selector": "off", - "unicorn/prefer-add-event-listener": "off", - "no-console": "off", - "no-shadow": "off", - "no-var": "off" - } - }, - { - "files": [ - "example/*.js" - ], - "rules": { - "node/no-missing-require": "off", - "node/no-extraneous-require": "off", - "no-console": "off", - "no-invalid-this": "off", - "no-shadow": "off", - "object-property-newline": "off" + "settings": { + "jsdoc": { + "preferredTypes": { + "object": "Object" } } - ] + } } From febfcd73c9d3170b2fe89d069d558ed9537ff246 Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 9 Nov 2021 22:11:35 -0800 Subject: [PATCH 15/69] Trivial simplifications to webpack.config.js. --- demo/webpack.config.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/demo/webpack.config.js b/demo/webpack.config.js index ebb8ee04..cab533ca 100644 --- a/demo/webpack.config.js +++ b/demo/webpack.config.js @@ -2,7 +2,6 @@ "use strict"; -const path = require("path"); const webpack = require("webpack"); function config(options) { @@ -23,7 +22,7 @@ function config(options) { { "loader": "ts-loader", "options": { - "configFile": path.resolve(__dirname, "tsconfig.json") + "configFile": "../demo/tsconfig.json" } } ] @@ -39,9 +38,6 @@ function config(options) { "plugins": [ new webpack.BannerPlugin({ "banner": `${name} ${version} ${homepage} @license ${license}` - }), - new webpack.DefinePlugin({ - "process.env.NODE_DEBUG": false }) ], "resolve": { From 8179adf38e9d2f670217f614f7f18361d6f57d75 Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 9 Nov 2021 22:17:21 -0800 Subject: [PATCH 16/69] Add new test repo suppressions for pre-release rule MD049. --- test/markdownlint-test-repos.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index 583d4cfa..a688b4f9 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -178,6 +178,7 @@ if (existsSync(dotnetDocsDir)) { "docs/core/extensions/console-log-formatter.md", "docs/core/extensions/create-resource-files.md", "docs/core/extensions/localization.md", + "docs/core/extensions/windows-service.md", "docs/core/install/linux-alpine.md", "docs/core/install/windows.md", "docs/core/porting/third-party-deps.md", @@ -203,6 +204,7 @@ if (existsSync(dotnetDocsDir)) { "docs/fsharp/language-reference/exception-handling/the-try-with-expression.md", "docs/fsharp/language-reference/xml-documentation.md", "docs/fsharp/style-guide/conventions.md", + "docs/fsharp/tutorials/async.md", "docs/fsharp/tutorials/asynchronous-and-concurrent-programming/async.md", "docs/fundamentals/code-analysis/configuration-files.md", "docs/fundamentals/code-analysis/style-rules/naming-rules.md", From c5345f45ebec5455344b6bd1d702dcd1528e6b21 Mon Sep 17 00:00:00 2001 From: David Anson Date: Wed, 10 Nov 2021 21:48:15 -0800 Subject: [PATCH 17/69] Replace require("package.json") with constants.js to simplify and remove that file from markdownlint-browser.js. --- demo/markdownlint-browser.js | 32 +++++++++++++++++--------------- demo/tsconfig.json | 3 +-- lib/constants.js | 6 ++++++ lib/markdownlint.js | 2 +- lib/rules.js | 4 +--- test/markdownlint-test.js | 7 +++++++ 6 files changed, 33 insertions(+), 21 deletions(-) create mode 100644 lib/constants.js diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 871b6fff..04182a1c 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -801,6 +801,21 @@ module.exports.clear = function () { }; +/***/ }), + +/***/ "../lib/constants.js": +/*!***************************!*\ + !*** ../lib/constants.js ***! + \***************************/ +/***/ ((module) => { + +"use strict"; +// @ts-check + +module.exports.homepage = "https://github.com/DavidAnson/markdownlint"; +module.exports.version = "0.24.0"; + + /***/ }), /***/ "../lib/markdownlint.js": @@ -1805,7 +1820,7 @@ function readConfigSync(file, parsers, fs) { * @returns {string} SemVer string. */ function getVersion() { - return __webpack_require__(/*! ../package.json */ "../package.json").version; + return __webpack_require__(/*! ./constants */ "../lib/constants.js").version; } // Export a/synchronous/Promise APIs markdownlint.sync = markdownlintSync; @@ -4110,9 +4125,7 @@ module.exports = { "use strict"; // @ts-check -var packageJson = __webpack_require__(/*! ../package.json */ "../package.json"); -var homepage = packageJson.homepage; -var version = packageJson.version; +var _a = __webpack_require__(/*! ./constants */ "../lib/constants.js"), homepage = _a.homepage, version = _a.version; var rules = [ __webpack_require__(/*! ./md001 */ "../lib/md001.js"), __webpack_require__(/*! ./md002 */ "../lib/md002.js"), @@ -4221,17 +4234,6 @@ module.exports = markdownit; /* (ignored) */ -/***/ }), - -/***/ "../package.json": -/*!***********************!*\ - !*** ../package.json ***! - \***********************/ -/***/ ((module) => { - -"use strict"; -module.exports = JSON.parse('{"name":"markdownlint","version":"0.24.0","description":"A Node.js style checker and lint tool for Markdown/CommonMark files.","main":"lib/markdownlint.js","types":"lib/markdownlint.d.ts","author":"David Anson (https://dlaa.me/)","license":"MIT","homepage":"https://github.com/DavidAnson/markdownlint","repository":{"type":"git","url":"https://github.com/DavidAnson/markdownlint.git"},"bugs":"https://github.com/DavidAnson/markdownlint/issues","scripts":{"build-config":"npm run build-config-schema && npm run build-config-example","build-config-example":"node schema/build-config-example.js","build-config-schema":"node schema/build-config-schema.js","build-declaration":"tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && rimraf \'lib/{c,md,r}*.d.ts\' \'helpers/*.d.ts\'","build-demo":"cpy node_modules/markdown-it/dist/markdown-it.min.js demo && cd demo && webpack --no-stats","build-example":"npm install --no-save --ignore-scripts grunt grunt-cli gulp through2","ci":"npm-run-all --continue-on-error --parallel test-cover lint declaration build-config build-demo && git diff --exit-code","clean-test-repos":"rimraf test-repos","clone-test-repos-dotnet-docs":"cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet","clone-test-repos-eslint-eslint":"cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet","clone-test-repos-mkdocs-mkdocs":"cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet","clone-test-repos-mochajs-mocha":"cd test-repos && git clone https://github.com/mochajs/mocha mochajs-mocha --depth 1 --no-tags --quiet","clone-test-repos-pi-hole-docs":"cd test-repos && git clone https://github.com/pi-hole/docs pi-hole-docs --depth 1 --no-tags --quiet","clone-test-repos-v8-v8-dev":"cd test-repos && git clone https://github.com/v8/v8.dev v8-v8-dev --depth 1 --no-tags --quiet","clone-test-repos-webhintio-hint":"cd test-repos && git clone https://github.com/webhintio/hint webhintio-hint --depth 1 --no-tags --quiet","clone-test-repos-webpack-webpack-js-org":"cd test-repos && git clone https://github.com/webpack/webpack.js.org webpack-webpack-js-org --depth 1 --no-tags --quiet","clone-test-repos":"mkdir test-repos && cd test-repos && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org","clone-test-repos-large":"npm run clone-test-repos && cd test-repos && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-v8-v8-dev","declaration":"npm run build-declaration && npm run test-declaration","example":"cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint","docker-npm-install":"docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm install","lint":"eslint --max-warnings 0 .","lint-test-repos":"ava --timeout=5m test/markdownlint-test-repos.js","test":"ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js","test-cover":"c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test","test-declaration":"cd example/typescript && tsc && node type-check.js","test-extra":"ava --timeout=5m test/markdownlint-test-extra.js"},"engines":{"node":">=12"},"dependencies":{"markdown-it":"12.2.0"},"devDependencies":{"ava":"~3.15.0","c8":"~7.8.0","cpy-cli":"~3.1.1","eslint":"~7.32.0","eslint-plugin-jsdoc":"~36.0.7","eslint-plugin-node":"~11.1.0","eslint-plugin-unicorn":"~35.0.0","globby":"~11.0.4","js-yaml":"~4.1.0","markdown-it-for-inline":"~0.1.1","markdown-it-sub":"~1.0.0","markdown-it-sup":"~1.0.0","markdown-it-texmath":"~0.9.1","markdownlint-rule-helpers":"~0.14.0","npm-run-all":"~4.1.5","rimraf":"~3.0.2","strip-json-comments":"~3.1.1","toml":"~3.0.0","ts-loader":"~9.2.5","tv4":"~1.3.0","typescript":"~4.3.5","webpack":"~5.51.1","webpack-cli":"~4.8.0"},"keywords":["markdown","lint","md","CommonMark","markdownlint"]}'); - /***/ }) /******/ }); diff --git a/demo/tsconfig.json b/demo/tsconfig.json index 3d775c70..4f79ffb5 100644 --- a/demo/tsconfig.json +++ b/demo/tsconfig.json @@ -1,8 +1,7 @@ { "compilerOptions": { "allowJs": true, - "outDir": "unused", - "resolveJsonModule": true + "outDir": "unused" }, "include": [ "../lib/*.js" diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 00000000..b64c7537 --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,6 @@ +// @ts-check + +"use strict"; + +module.exports.homepage = "https://github.com/DavidAnson/markdownlint"; +module.exports.version = "0.24.0"; diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 435cf140..82eacc75 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -1102,7 +1102,7 @@ function readConfigSync(file, parsers, fs) { * @returns {string} SemVer string. */ function getVersion() { - return require("../package.json").version; + return require("./constants").version; } // Export a/synchronous/Promise APIs diff --git a/lib/rules.js b/lib/rules.js index f3f61480..28c21214 100644 --- a/lib/rules.js +++ b/lib/rules.js @@ -2,9 +2,7 @@ "use strict"; -const packageJson = require("../package.json"); -const homepage = packageJson.homepage; -const version = packageJson.version; +const { homepage, version } = require("./constants"); const rules = [ require("./md001"), diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index fd6cdf0f..fb1d5458 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -15,6 +15,7 @@ const test = require("ava").default; const tv4 = require("tv4"); const { homepage, version } = require("../package.json"); const markdownlint = require("../lib/markdownlint"); +const constants = require("../lib/constants"); const rules = require("../lib/rules"); const customRules = require("./rules/rules.js"); const configSchema = require("../schema/markdownlint-config-schema.json"); @@ -1722,3 +1723,9 @@ test("getVersion", (t) => { const expected = version; t.is(actual, expected, "Version string not correct."); }); + +test("constants", (t) => { + t.plan(2); + t.is(constants.homepage, homepage); + t.is(constants.version, version); +}); From 6c17718242cd284519ad32f186f4f55a5df27761 Mon Sep 17 00:00:00 2001 From: David Anson Date: Wed, 10 Nov 2021 21:53:51 -0800 Subject: [PATCH 18/69] Add new test repo suppression for pre-release rule MD049. --- test/markdownlint-test-repos.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index a688b4f9..43068de4 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -175,6 +175,7 @@ if (existsSync(dotnetDocsDir)) { "docs/core/deploying/single-file.md", "docs/core/deploying/trimming/trimming-options.md", "docs/core/extensions/cloud-service.md", + "docs/core/extensions/configuration-providers.md", "docs/core/extensions/console-log-formatter.md", "docs/core/extensions/create-resource-files.md", "docs/core/extensions/localization.md", From 80863476b92643cacf15506c81a625f39e9b84b0 Mon Sep 17 00:00:00 2001 From: David Anson Date: Wed, 10 Nov 2021 22:02:51 -0800 Subject: [PATCH 19/69] Add Pi-hole documentation to Examples in README.md. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9e80f2c9..bda9a1fe 100644 --- a/README.md +++ b/README.md @@ -872,6 +872,7 @@ following projects or one of the tools in the [Related section](#Related): * [MDN Web Docs](https://developer.mozilla.org/) ([Search repository](https://github.com/mdn/content/search?q=markdownlint)) * [MkDocs](https://www.mkdocs.org/) ([Search repository](https://github.com/mkdocs/mkdocs/search?q=markdownlint)) * [Mocha](https://mochajs.org/) ([Search repository](https://github.com/mochajs/mocha/search?q=markdownlint)) +* [Pi-hole documentation](https://docs.pi-hole.net) ([Search repository](https://github.com/pi-hole/docs/search?q=markdownlint)) * [Reactable](https://glittershark.github.io/reactable/) ([Search repository](https://github.com/glittershark/reactable/search?q=markdownlint)) * [Sinon.JS](https://sinonjs.org/) ([Search repository](https://github.com/sinonjs/sinon/search?q=markdownlint)) * [TestCafe](https://devexpress.github.io/testcafe/) ([Search repository](https://github.com/DevExpress/testcafe/search?q=markdownlint)) From 090bbaa30f8bc0b3dd69b7396d0983495f5c296c Mon Sep 17 00:00:00 2001 From: David Anson Date: Wed, 10 Nov 2021 22:11:43 -0800 Subject: [PATCH 20/69] Resolve README.md-specific MD013/line-length violations. --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bda9a1fe..78d0aea5 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,10 @@ and test cases came directly from that project. ### Related * CLI - * [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli) command-line interface for Node.js ([works with pre-commit](https://github.com/igorshubovych/markdownlint-cli#use-with-pre-commit)) - * [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) command-line interface for Node.js ([works with pre-commit](https://github.com/DavidAnson/markdownlint-cli2#pre-commit)) + * [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli) + command-line interface for Node.js ([works with pre-commit](https://github.com/igorshubovych/markdownlint-cli#use-with-pre-commit)) + * [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) + command-line interface for Node.js ([works with pre-commit](https://github.com/DavidAnson/markdownlint-cli2#pre-commit)) * GitHub * [GitHub Super-Linter Action](https://github.com/github/super-linter) * [GitHub Actions problem matcher for markdownlint-cli](https://github.com/xt0rted/markdownlint-problem-matcher) @@ -165,10 +167,12 @@ appear in the final markup): * Disable all rules: `` * Enable all rules: `` -* Disable all rules for the next line only: `` +* Disable all rules for the next line only: + `` * Disable one or more rules by name: `` * Enable one or more rules by name: `` -* Disable one or more rules by name for the next line only: `` +* Disable one or more rules by name for the next line only: + `` * Capture the current rule configuration: `` * Restore the captured rule configuration: `` @@ -231,7 +235,7 @@ more rules for a file, the following more advanced syntax is supported: For example: ```markdown - + ``` or From 983e586c9c9905ba3dfa0f7d221caecc8f20282d Mon Sep 17 00:00:00 2001 From: David Anson Date: Thu, 11 Nov 2021 22:02:51 -0800 Subject: [PATCH 21/69] Remove cpy-cli and rimraf dependencies; replace with simple script helper. --- .npmignore | 1 + package.json | 7 ++----- scripts/index.js | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 scripts/index.js diff --git a/.npmignore b/.npmignore index 50c17bf6..37394db9 100644 --- a/.npmignore +++ b/.npmignore @@ -10,5 +10,6 @@ demo/* example npm-debug.log schema/*.js +scripts test test-repos diff --git a/package.json b/package.json index 81c7bf68..8343737d 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,10 @@ "build-config": "npm run build-config-schema && npm run build-config-example", "build-config-example": "node schema/build-config-example.js", "build-config-schema": "node schema/build-config-schema.js", - "build-declaration": "tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && rimraf 'lib/{c,md,r}*.d.ts' 'helpers/*.d.ts'", - "build-demo": "cpy node_modules/markdown-it/dist/markdown-it.min.js demo && cd demo && webpack --no-stats", + "build-declaration": "tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && node scripts delete 'lib/{c,md,r}*.d.ts' 'helpers/*.d.ts'", + "build-demo": "node scripts copy node_modules/markdown-it/dist/markdown-it.min.js demo/markdown-it.min.js && cd demo && webpack --no-stats", "build-example": "npm install --no-save --ignore-scripts grunt grunt-cli gulp through2", "ci": "npm-run-all --continue-on-error --parallel test-cover lint declaration build-config build-demo && git diff --exit-code", - "clean-test-repos": "rimraf test-repos", "clone-test-repos-dotnet-docs": "cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet", "clone-test-repos-eslint-eslint": "cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet", "clone-test-repos-mkdocs-mkdocs": "cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet", @@ -50,7 +49,6 @@ "devDependencies": { "ava": "~3.15.0", "c8": "~7.8.0", - "cpy-cli": "~3.1.1", "eslint": "~7.32.0", "eslint-plugin-jsdoc": "~36.0.7", "eslint-plugin-node": "~11.1.0", @@ -63,7 +61,6 @@ "markdown-it-texmath": "~0.9.1", "markdownlint-rule-helpers": "~0.14.0", "npm-run-all": "~4.1.5", - "rimraf": "~3.0.2", "strip-json-comments": "~3.1.1", "toml": "~3.0.0", "ts-loader": "~9.2.5", diff --git a/scripts/index.js b/scripts/index.js new file mode 100644 index 00000000..0f31207b --- /dev/null +++ b/scripts/index.js @@ -0,0 +1,21 @@ +// @ts-check + +"use strict"; + +const fs = require("fs"); +const globby = require("globby"); + +const [ command, ...args ] = process.argv.slice(2); + +if (command === "copy") { + const [ src, dest ] = args; + fs.copyFileSync(src, dest); +} else if (command === "delete") { + for (const arg of args) { + for (const file of globby.sync(arg)) { + fs.unlinkSync(file); + } + } +} else { + throw new Error(`Unsupported command: ${command}`); +} From 573ebe7462d2948b010210daef0ea3850de4a003 Mon Sep 17 00:00:00 2001 From: David Anson Date: Thu, 11 Nov 2021 22:37:16 -0800 Subject: [PATCH 22/69] Move two instances of deprecatedRuleNames into constants.js. --- demo/markdownlint-browser.js | 3 ++- lib/constants.js | 1 + lib/markdownlint.js | 3 +-- test/markdownlint-test.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 04182a1c..5844f078 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -812,6 +812,7 @@ module.exports.clear = function () { "use strict"; // @ts-check +module.exports.deprecatedRuleNames = ["MD002", "MD006"]; module.exports.homepage = "https://github.com/DavidAnson/markdownlint"; module.exports.version = "0.24.0"; @@ -846,6 +847,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from) { var path = __webpack_require__(/*! path */ "?b85c"); var promisify = __webpack_require__(/*! util */ "?96a2").promisify; var markdownIt = __webpack_require__(/*! markdown-it */ "markdown-it"); +var deprecatedRuleNames = __webpack_require__(/*! ./constants */ "../lib/constants.js").deprecatedRuleNames; var rules = __webpack_require__(/*! ./rules */ "../lib/rules.js"); var helpers = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"); var cache = __webpack_require__(/*! ./cache */ "../lib/cache.js"); @@ -853,7 +855,6 @@ var cache = __webpack_require__(/*! ./cache */ "../lib/cache.js"); // eslint-disable-next-line camelcase, max-len, no-inline-comments, no-undef var dynamicRequire = (typeof require === "undefined") ? __webpack_require__("../lib sync recursive") : /* c8 ignore next */ require; // Capture native require implementation for dynamic loading of modules -var deprecatedRuleNames = ["MD002", "MD006"]; /** * Validate the list of rules for structure and reuse. * diff --git a/lib/constants.js b/lib/constants.js index b64c7537..e9f5a5ee 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -2,5 +2,6 @@ "use strict"; +module.exports.deprecatedRuleNames = [ "MD002", "MD006" ]; module.exports.homepage = "https://github.com/DavidAnson/markdownlint"; module.exports.version = "0.24.0"; diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 82eacc75..039e3a7a 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -5,6 +5,7 @@ const path = require("path"); const { promisify } = require("util"); const markdownIt = require("markdown-it"); +const { deprecatedRuleNames } = require("./constants"); const rules = require("./rules"); const helpers = require("../helpers"); const cache = require("./cache"); @@ -14,8 +15,6 @@ const cache = require("./cache"); const dynamicRequire = (typeof __non_webpack_require__ === "undefined") ? require : /* c8 ignore next */ __non_webpack_require__; // Capture native require implementation for dynamic loading of modules -const deprecatedRuleNames = [ "MD002", "MD006" ]; - /** * Validate the list of rules for structure and reuse. * diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index fb1d5458..66ec05c4 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -25,7 +25,7 @@ const pluginTexMathOptions = { "renderToString": () => "" } }; -const deprecatedRuleNames = new Set([ "MD002", "MD006" ]); +const deprecatedRuleNames = new Set(constants.deprecatedRuleNames); const configSchemaStrict = { ...configSchema, "additionalProperties": false From eaa8d0f15e6e7f07d99b8ec8028a75422b830f0d Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 12 Nov 2021 20:43:35 -0800 Subject: [PATCH 23/69] Split "ci" script into two consecutive parallel groups to separate "declaration" and "build-demo" scripts which conflict when generated .d.ts files from the former are used by the latter; approximately balance the two groups based on CPU time of tasks. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8343737d..1efb2a45 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "build-declaration": "tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && node scripts delete 'lib/{c,md,r}*.d.ts' 'helpers/*.d.ts'", "build-demo": "node scripts copy node_modules/markdown-it/dist/markdown-it.min.js demo/markdown-it.min.js && cd demo && webpack --no-stats", "build-example": "npm install --no-save --ignore-scripts grunt grunt-cli gulp through2", - "ci": "npm-run-all --continue-on-error --parallel test-cover lint declaration build-config build-demo && git diff --exit-code", + "ci": "npm-run-all --continue-on-error --parallel declaration lint --parallel build-config build-demo test-cover && git diff --exit-code", "clone-test-repos-dotnet-docs": "cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet", "clone-test-repos-eslint-eslint": "cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet", "clone-test-repos-mkdocs-mkdocs": "cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet", From 8e22c009fdfff3985bef409ccbc26494eac829d6 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 13 Nov 2021 12:40:51 -0800 Subject: [PATCH 24/69] Add transpileOnly=true to ts-loader/webpack to reduce build-demo script time by ~40%. --- demo/markdownlint-browser.js | 90 ++++++++++++++++++------------------ demo/webpack.config.js | 3 +- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 5844f078..8b8a983c 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -568,8 +568,8 @@ module.exports.rangeFromRegExp = function rangeFromRegExp(line, regexp) { var match = line.match(regexp); if (match) { var column = match.index + 1; - var length_1 = match[0].length; - range = [column, length_1]; + var length = match[0].length; + range = [column, length]; } return range; }; @@ -1398,7 +1398,7 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul // Call (possibly external) rule function if (handleRuleFailures) { try { - rule["function"](params, onError); + rule.function(params, onError); } catch (error) { onError({ @@ -1408,7 +1408,7 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul } } else { - rule["function"](params, onError); + rule.function(params, onError); } // Record any errors (significant performance benefit from length check) if (errors.length > 0) { @@ -1760,9 +1760,9 @@ function readConfig(file, parsers, fs, callback) { return callback(new Error(message)); } // Extend configuration - var configExtends = config["extends"]; + var configExtends = config.extends; if (configExtends) { - delete config["extends"]; + delete config.extends; return resolveConfigExtends(file, configExtends, fs, function (_, resolvedExtends) { return readConfig(resolvedExtends, parsers, fs, function (errr, extendsConfig) { if (errr) { return callback(errr); @@ -1807,9 +1807,9 @@ function readConfigSync(file, parsers, fs) { throw new Error(message); } // Extend configuration - var configExtends = config["extends"]; + var configExtends = config.extends; if (configExtends) { - delete config["extends"]; + delete config.extends; var resolvedExtends = resolveConfigExtendsSync(file, configExtends, fs); return __assign(__assign({}, readConfigSync(resolvedExtends, parsers, fs)), config); } @@ -1998,8 +1998,8 @@ module.exports = { var match = item.line.match(listItemMarkerRe); if (match) { var column = match.index + 1; - var length_1 = match[0].length; - range = [column, length_1]; + var length = match[0].length; + range = [column, length]; fixInfo = { "editColumn": match[1].length + 1, "deleteCount": 1, @@ -2262,11 +2262,11 @@ module.exports = { var match = null; while ((match = tabRe.exec(line)) !== null) { var column = match.index + 1; - var length_1 = match[0].length; - addError(onError, lineIndex + 1, "Column: " + column, null, [column, length_1], { + var length = match[0].length; + addError(onError, lineIndex + 1, "Column: " + column, null, [column, length], { "editColumn": column, - "deleteCount": length_1, - "insertText": "".padEnd(length_1 * spaceMultiplier) + "deleteCount": length, + "insertText": "".padEnd(length * spaceMultiplier) }); } } @@ -2301,13 +2301,13 @@ module.exports = { while ((match = reversedLinkRe.exec(line)) !== null) { var reversedLink = match[0], preChar = match[1], linkText = match[2], linkDestination = match[3]; var index = match.index + preChar.length; - var length_1 = match[0].length - preChar.length; + var length = match[0].length - preChar.length; if (!linkText.endsWith("\\") && !linkDestination.endsWith("\\") && - !overlapsAnyRange(exclusions, lineIndex, index, length_1)) { - addError(onError, lineIndex + 1, reversedLink.slice(preChar.length), null, [index + 1, length_1], { + !overlapsAnyRange(exclusions, lineIndex, index, length)) { + addError(onError, lineIndex + 1, reversedLink.slice(preChar.length), null, [index + 1, length], { "editColumn": index + 1, - "deleteCount": length_1, + "deleteCount": length, "insertText": "[" + linkText + "](" + linkDestination + ")" }); } @@ -2472,8 +2472,8 @@ module.exports = { var match = dollarCommandRe.exec(line); if (match) { var column = match[1].length + 1; - var length_1 = match[2].length; - dollarInstances.push([i, lineTrim, column, length_1]); + var length = match[2].length; + dollarInstances.push([i, lineTrim, column, length]); } else { allDollars = false; @@ -2640,7 +2640,7 @@ module.exports = { var left = leftSpaceLength > 1; var right = rightSpaceLength > 1; if (left || right) { - var length_1 = line.length; + var length = line.length; var leftHashLength = leftHash.length; var rightHashLength = rightHash.length; var range = left ? @@ -2649,12 +2649,12 @@ module.exports = { leftHashLength + leftSpaceLength + 1 ] : [ - length_1 - trailSpaceLength - rightHashLength - rightSpaceLength, + length - trailSpaceLength - rightHashLength - rightSpaceLength, rightSpaceLength + rightHashLength + 1 ]; addErrorContext(onError, lineNumber, line.trim(), left, right, range, { "editColumn": 1, - "deleteCount": length_1, + "deleteCount": length, "insertText": leftHash + " " + content + " " + rightHash }); } @@ -2860,10 +2860,10 @@ module.exports = { if (match && !endOfLineHtmlEntityRe.test(trimmedLine)) { var fullMatch = match[0]; var column = match.index + 1; - var length_1 = fullMatch.length; - addError(onError, lineNumber, "Punctuation: '" + fullMatch + "'", null, [column, length_1], { + var length = fullMatch.length; + addError(onError, lineNumber, "Punctuation: '" + fullMatch + "'", null, [column, length], { "editColumn": column, - "deleteCount": length_1 + "deleteCount": length }); } }); @@ -3421,7 +3421,7 @@ module.exports = { var contextEnd = matchIndex + contextLength; var context = line.substring(contextStart, contextEnd); var column = contextStart + 1; - var length_1 = contextEnd - contextStart; + var length = contextEnd - contextStart; var leftMarker = line.substring(contextStart, emphasisIndex); var rightMarker = match ? (match[2] || match[3]) : ""; var fixedText = "" + leftMarker + content.trim() + rightMarker; @@ -3431,10 +3431,10 @@ module.exports = { context, leftSpace, rightSpace, - [column, length_1], + [column, length], { "editColumn": column, - "deleteCount": length_1, + "deleteCount": length, "insertText": fixedText } ]; @@ -3641,14 +3641,14 @@ module.exports = { var match = line.slice(lineIndex).match(spaceInLinkRe); if (match) { var column = match.index + lineIndex + 1; - var length_1 = match[0].length; - range = [column, length_1]; + var length = match[0].length; + range = [column, length]; fixInfo = { "editColumn": column + 1, - "deleteCount": length_1 - 2, + "deleteCount": length - 2, "insertText": linkText.trim() }; - lineIndex = column + length_1 - 1; + lineIndex = column + length - 1; } addErrorContext(onError, lineNumber, "[" + linkText + "]", left, right, range, fixInfo); } @@ -3900,10 +3900,10 @@ module.exports = { if (!includeCodeBlocks) { exclusions.push.apply(exclusions, inlineCodeSpanRanges()); } - var _loop_1 = function (name_1) { - var escapedName = escapeForRegExp(name_1); - var startNamePattern = /^\W/.test(name_1) ? "" : "\\b_*"; - var endNamePattern = /\W$/.test(name_1) ? "" : "_*\\b"; + var _loop_1 = function (name) { + var escapedName = escapeForRegExp(name); + var startNamePattern = /^\W/.test(name) ? "" : "\\b_*"; + var endNamePattern = /\W$/.test(name) ? "" : "_*\\b"; var namePattern = "(" + startNamePattern + ")(" + escapedName + ")" + endNamePattern; var nameRe = new RegExp(namePattern, "gi"); forEachLine(lineMetadata(), function (line, lineIndex, inCode, onFence) { @@ -3912,22 +3912,22 @@ module.exports = { while ((match = nameRe.exec(line)) !== null) { var leftMatch = match[1], nameMatch = match[2]; var index = match.index + leftMatch.length; - var length_1 = nameMatch.length; - if (!overlapsAnyRange(exclusions, lineIndex, index, length_1)) { - addErrorDetailIf(onError, lineIndex + 1, name_1, nameMatch, null, null, [index + 1, length_1], { + var length = nameMatch.length; + if (!overlapsAnyRange(exclusions, lineIndex, index, length)) { + addErrorDetailIf(onError, lineIndex + 1, name, nameMatch, null, null, [index + 1, length], { "editColumn": index + 1, - "deleteCount": length_1, - "insertText": name_1 + "deleteCount": length, + "insertText": name }); } - exclusions.push([lineIndex, index, length_1]); + exclusions.push([lineIndex, index, length]); } } }); }; for (var _i = 0, names_1 = names; _i < names_1.length; _i++) { - var name_1 = names_1[_i]; - _loop_1(name_1); + var name = names_1[_i]; + _loop_1(name); } } }; diff --git a/demo/webpack.config.js b/demo/webpack.config.js index cab533ca..3de0a334 100644 --- a/demo/webpack.config.js +++ b/demo/webpack.config.js @@ -22,7 +22,8 @@ function config(options) { { "loader": "ts-loader", "options": { - "configFile": "../demo/tsconfig.json" + "configFile": "../demo/tsconfig.json", + "transpileOnly": true } } ] From 20365f8cb8d0bdd5f9ba88c012d4631696dd2802 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 13 Nov 2021 20:42:36 -0800 Subject: [PATCH 25/69] Restructure "ci" script for slightly better performance. --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1efb2a45..5c78fb14 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "build-declaration": "tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && node scripts delete 'lib/{c,md,r}*.d.ts' 'helpers/*.d.ts'", "build-demo": "node scripts copy node_modules/markdown-it/dist/markdown-it.min.js demo/markdown-it.min.js && cd demo && webpack --no-stats", "build-example": "npm install --no-save --ignore-scripts grunt grunt-cli gulp through2", - "ci": "npm-run-all --continue-on-error --parallel declaration lint --parallel build-config build-demo test-cover && git diff --exit-code", + "ci": "npm-run-all --continue-on-error --parallel build-config lint serial-declaration-demo test-cover && git diff --exit-code", "clone-test-repos-dotnet-docs": "cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet", "clone-test-repos-eslint-eslint": "cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet", "clone-test-repos-mkdocs-mkdocs": "cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet", @@ -35,6 +35,7 @@ "docker-npm-install": "docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm install", "lint": "eslint --max-warnings 0 .", "lint-test-repos": "ava --timeout=5m test/markdownlint-test-repos.js", + "serial-declaration-demo": "npm run declaration && npm run build-demo", "test": "ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js", "test-cover": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test", "test-declaration": "cd example/typescript && tsc && node type-check.js", From 610b17e1a1b6912a893ae2a49422b5836b1c39c6 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 13 Nov 2021 21:17:43 -0800 Subject: [PATCH 26/69] Run sub-tasks of "serial-declaration-demo" script in parallel for ~20% script time reduction. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c78fb14..1e0d329e 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "docker-npm-install": "docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm install", "lint": "eslint --max-warnings 0 .", "lint-test-repos": "ava --timeout=5m test/markdownlint-test-repos.js", - "serial-declaration-demo": "npm run declaration && npm run build-demo", + "serial-declaration-demo": "npm run build-declaration && npm-run-all --continue-on-error --parallel build-demo test-declaration", "test": "ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js", "test-cover": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test", "test-declaration": "cd example/typescript && tsc && node type-check.js", From 9c60343692d8f8f8421fb8d832f4ae473f6c2d03 Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 19 Nov 2021 21:53:33 -0800 Subject: [PATCH 27/69] Add new rules MD049/MD050 to detailed-results-* test files. --- test/detailed-results-MD041-MD050.md | 14 +++++ test/detailed-results-MD041-MD050.md.fixed | 14 +++++ .../detailed-results-MD041-MD050.results.json | 56 ++++++++++++++++++- 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/test/detailed-results-MD041-MD050.md b/test/detailed-results-MD041-MD050.md index 2b2550eb..6066a9b5 100644 --- a/test/detailed-results-MD041-MD050.md +++ b/test/detailed-results-MD041-MD050.md @@ -28,4 +28,18 @@ Fenced code Fenced code ~~~ +Mixed *emphasis* on _this_ line + +Mixed __strong emphasis__ on **this** line + +Inconsistent +emphasis _text +spanning_ many +lines + +Inconsistent +strong **emphasis +spanning** many +lines + Missing newline character \ No newline at end of file diff --git a/test/detailed-results-MD041-MD050.md.fixed b/test/detailed-results-MD041-MD050.md.fixed index cf4b4f70..f9af863a 100644 --- a/test/detailed-results-MD041-MD050.md.fixed +++ b/test/detailed-results-MD041-MD050.md.fixed @@ -28,4 +28,18 @@ Fenced code Fenced code ~~~ +Mixed *emphasis* on _this_ line + +Mixed __strong emphasis__ on **this** line + +Inconsistent +emphasis _text +spanning_ many +lines + +Inconsistent +strong **emphasis +spanning** many +lines + Missing newline character diff --git a/test/detailed-results-MD041-MD050.results.json b/test/detailed-results-MD041-MD050.results.json index 70fa4e3a..346e2eb5 100644 --- a/test/detailed-results-MD041-MD050.results.json +++ b/test/detailed-results-MD041-MD050.results.json @@ -78,7 +78,7 @@ "fixInfo": null }, { - "lineNumber": 31, + "lineNumber": 45, "ruleNames": [ "MD043", "required-headings", @@ -178,7 +178,7 @@ "fixInfo": null }, { - "lineNumber": 31, + "lineNumber": 45, "ruleNames": [ "MD047", "single-trailing-newline" @@ -208,5 +208,57 @@ "errorContext": null, "errorRange": null, "fixInfo": null + }, + { + "errorContext": null, + "errorDetail": "Expected: asterisk; Actual: underscore", + "errorRange": null, + "fixInfo": null, + "lineNumber": 31, + "ruleDescription": "Emphasis style should be consistent", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md049", + "ruleNames": [ + "MD049", + "emphasis-style" + ] + }, + { + "errorContext": null, + "errorDetail": "Expected: asterisk; Actual: underscore", + "errorRange": null, + "fixInfo": null, + "lineNumber": 36, + "ruleDescription": "Emphasis style should be consistent", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md049", + "ruleNames": [ + "MD049", + "emphasis-style" + ] + }, + { + "errorContext": null, + "errorDetail": "Expected: underscore; Actual: asterisk", + "errorRange": null, + "fixInfo": null, + "lineNumber": 33, + "ruleDescription": "Strong style should be consistent", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md050", + "ruleNames": [ + "MD050", + "strong-style" + ] + }, + { + "errorContext": null, + "errorDetail": "Expected: underscore; Actual: asterisk", + "errorRange": null, + "fixInfo": null, + "lineNumber": 41, + "ruleDescription": "Strong style should be consistent", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md050", + "ruleNames": [ + "MD050", + "strong-style" + ] } ] \ No newline at end of file From 1e82f765961cc9af50b7db3ed8142c5bfc587700 Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 23 Nov 2021 04:40:05 +0000 Subject: [PATCH 28/69] Reimplement inlineCodeSpanRanges as codeBlockAndSpanRanges to fix an issue with unterminated code spans (and for flexibility). --- demo/markdownlint-browser.js | 85 ++++++++++++------- doc/Rules.md | 3 +- helpers/helpers.js | 64 +++++++++----- lib/cache.js | 18 ++-- lib/markdownlint.js | 4 +- lib/md011.js | 4 +- lib/md033.js | 4 +- lib/md044.js | 4 +- test/code-blocks-and-spans.md | 38 +++++++++ test/inline_html.md | 22 +++++ test/markdownlint-test-repos.js | 1 + ...eversed-link-issue-with-markdownlint-12.md | 2 +- 12 files changed, 178 insertions(+), 71 deletions(-) create mode 100644 test/code-blocks-and-spans.md diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 8b8a983c..e3fcf875 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -307,14 +307,20 @@ module.exports.getLineMetadata = function getLineMetadata(params) { }); return lineMetadata; }; -// Calls the provided function for each line (with context) -module.exports.forEachLine = function forEachLine(lineMetadata, handler) { +/** + * Calls the provided function for each line. + * + * @param {Object} lineMetadata Line metadata object. + * @param {Function} handler Function taking (line, lineIndex, inCode, onFence, + * inTable, inItem, inBreak, inMath). + * @returns {void} + */ +function forEachLine(lineMetadata, handler) { lineMetadata.forEach(function forMetadata(metadata) { - // Parameters: - // line, lineIndex, inCode, onFence, inTable, inItem, inBreak, inMath handler.apply(void 0, metadata); }); -}; +} +module.exports.forEachLine = forEachLine; // Returns (nested) lists as a flat array (in order) module.exports.flattenLists = function flattenLists(tokens) { var flattenedLists = []; @@ -403,7 +409,8 @@ module.exports.forEachHeading = function forEachHeading(params, handler) { * Calls the provided function for each inline code span's content. * * @param {string} input Markdown content. - * @param {Function} handler Callback function. + * @param {Function} handler Callback function taking (code, lineIndex, + * columnIndex, ticks). * @returns {void} */ function forEachInlineCodeSpan(input, handler) { @@ -533,19 +540,35 @@ module.exports.addErrorContext = function addErrorContext(onError, lineNumber, c addError(onError, lineNumber, null, context, range, fixInfo); }; /** - * Returns an array of code span ranges. + * Returns an array of code block and span content ranges. * - * @param {string[]} lines Lines to scan for code span ranges. - * @returns {number[][]} Array of ranges (line, index, length). + * @param {Object} params RuleParams instance. + * @param {Object} lineMetadata Line metadata object. + * @returns {number[][]} Array of ranges (lineIndex, columnIndex, length). */ -module.exports.inlineCodeSpanRanges = function (lines) { +module.exports.codeBlockAndSpanRanges = function (params, lineMetadata) { var exclusions = []; - forEachInlineCodeSpan(lines.join("\n"), function (code, lineIndex, columnIndex) { - var codeLines = code.split(newLineRe); - // eslint-disable-next-line unicorn/no-for-loop - for (var i = 0; i < codeLines.length; i++) { - exclusions.push([lineIndex + i, columnIndex, codeLines[i].length]); - columnIndex = 0; + // Add code block ranges (excludes fences) + forEachLine(lineMetadata, function (line, lineIndex, inCode, onFence) { + if (inCode && !onFence) { + exclusions.push(lineIndex, 0, line.length); + } + }); + // Add code span ranges (excludes ticks) + filterTokens(params, "inline", function (token) { + if (token.children.some(function (child) { return child.type === "code_inline"; })) { + var tokenLines = params.lines.slice(token.map[0], token.map[1]); + forEachInlineCodeSpan(tokenLines.join("\n"), function (code, lineIndex, columnIndex) { + var codeLines = code.split(newLineRe); + for (var _i = 0, _a = codeLines.entries(); _i < _a.length; _i++) { + var _b = _a[_i], i = _b[0], line = _b[1]; + exclusions.push([ + token.lineNumber - 1 + lineIndex + i, + i ? 0 : columnIndex, + line.length + ]); + } + }); } }); return exclusions; @@ -773,6 +796,13 @@ module.exports.applyFixes = function applyFixes(input, errors) { "use strict"; // @ts-check +var codeBlockAndSpanRanges = null; +module.exports.codeBlockAndSpanRanges = function (value) { + if (value) { + codeBlockAndSpanRanges = value; + } + return codeBlockAndSpanRanges; +}; var flattenedLists = null; module.exports.flattenedLists = function (value) { if (value) { @@ -780,13 +810,6 @@ module.exports.flattenedLists = function (value) { } return flattenedLists; }; -var inlineCodeSpanRanges = null; -module.exports.inlineCodeSpanRanges = function (value) { - if (value) { - inlineCodeSpanRanges = value; - } - return inlineCodeSpanRanges; -}; var lineMetadata = null; module.exports.lineMetadata = function (value) { if (value) { @@ -795,8 +818,8 @@ module.exports.lineMetadata = function (value) { return lineMetadata; }; module.exports.clear = function () { + codeBlockAndSpanRanges = null; flattenedLists = null; - inlineCodeSpanRanges = null; lineMetadata = null; }; @@ -1305,7 +1328,7 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul }; cache.lineMetadata(helpers.getLineMetadata(params)); cache.flattenedLists(helpers.flattenLists(params.tokens)); - cache.inlineCodeSpanRanges(helpers.inlineCodeSpanRanges(params.lines)); + cache.codeBlockAndSpanRanges(helpers.codeBlockAndSpanRanges(params, cache.lineMetadata())); // Function to run for each rule var result = (resultVersion === 0) ? {} : []; // eslint-disable-next-line jsdoc/require-jsdoc @@ -2287,14 +2310,14 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, forEachLine = _a.forEachLine, overlapsAnyRange = _a.overlapsAnyRange; -var _b = __webpack_require__(/*! ./cache */ "../lib/cache.js"), inlineCodeSpanRanges = _b.inlineCodeSpanRanges, lineMetadata = _b.lineMetadata; +var _b = __webpack_require__(/*! ./cache */ "../lib/cache.js"), codeBlockAndSpanRanges = _b.codeBlockAndSpanRanges, lineMetadata = _b.lineMetadata; var reversedLinkRe = /(^|[^\\])\(([^)]+)\)\[([^\]^][^\]]*)](?!\()/g; module.exports = { "names": ["MD011", "no-reversed-links"], "description": "Reversed link syntax", "tags": ["links"], "function": function MD011(params, onError) { - var exclusions = inlineCodeSpanRanges(); + var exclusions = codeBlockAndSpanRanges(); forEachLine(lineMetadata(), function (line, lineIndex, inCode, onFence) { if (!inCode && !onFence) { var match = null; @@ -3175,7 +3198,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, forEachLine = _a.forEachLine, overlapsAnyRange = _a.overlapsAnyRange, unescapeMarkdown = _a.unescapeMarkdown; -var _b = __webpack_require__(/*! ./cache */ "../lib/cache.js"), inlineCodeSpanRanges = _b.inlineCodeSpanRanges, lineMetadata = _b.lineMetadata; +var _b = __webpack_require__(/*! ./cache */ "../lib/cache.js"), codeBlockAndSpanRanges = _b.codeBlockAndSpanRanges, lineMetadata = _b.lineMetadata; var htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^>]*)?)\/?>/g; var linkDestinationRe = /]\(\s*$/; // See https://spec.commonmark.org/0.29/#autolinks @@ -3190,7 +3213,7 @@ module.exports = { var allowedElements = params.config.allowed_elements; allowedElements = Array.isArray(allowedElements) ? allowedElements : []; allowedElements = allowedElements.map(function (element) { return element.toLowerCase(); }); - var exclusions = inlineCodeSpanRanges(); + var exclusions = codeBlockAndSpanRanges(); forEachLine(lineMetadata(), function (line, lineIndex, inCode) { var match = null; // eslint-disable-next-line no-unmodified-loop-condition @@ -3868,7 +3891,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, bareUrlRe = _a.bareUrlRe, escapeForRegExp = _a.escapeForRegExp, forEachLine = _a.forEachLine, overlapsAnyRange = _a.overlapsAnyRange, linkRe = _a.linkRe, linkReferenceRe = _a.linkReferenceRe; -var _b = __webpack_require__(/*! ./cache */ "../lib/cache.js"), inlineCodeSpanRanges = _b.inlineCodeSpanRanges, lineMetadata = _b.lineMetadata; +var _b = __webpack_require__(/*! ./cache */ "../lib/cache.js"), codeBlockAndSpanRanges = _b.codeBlockAndSpanRanges, lineMetadata = _b.lineMetadata; module.exports = { "names": ["MD044", "proper-names"], "description": "Proper names should have the correct capitalization", @@ -3898,7 +3921,7 @@ module.exports = { } }); if (!includeCodeBlocks) { - exclusions.push.apply(exclusions, inlineCodeSpanRanges()); + exclusions.push.apply(exclusions, codeBlockAndSpanRanges()); } var _loop_1 = function (name) { var escapedName = escapeForRegExp(name); diff --git a/doc/Rules.md b/doc/Rules.md index 36f9da12..ed0a93fa 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -1762,7 +1762,8 @@ the proper capitalization, specify the desired letter case in the `names` array: ] ``` -Set the `code_blocks` parameter to `false` to disable this rule for code blocks. +Set the `code_blocks` parameter to `false` to disable this rule for code blocks +and spans. Rationale: Incorrect capitalization of proper names is usually a mistake. diff --git a/helpers/helpers.js b/helpers/helpers.js index 302bb921..500483ee 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -309,14 +309,20 @@ module.exports.getLineMetadata = function getLineMetadata(params) { return lineMetadata; }; -// Calls the provided function for each line (with context) -module.exports.forEachLine = function forEachLine(lineMetadata, handler) { +/** + * Calls the provided function for each line. + * + * @param {Object} lineMetadata Line metadata object. + * @param {Function} handler Function taking (line, lineIndex, inCode, onFence, + * inTable, inItem, inBreak, inMath). + * @returns {void} + */ +function forEachLine(lineMetadata, handler) { lineMetadata.forEach(function forMetadata(metadata) { - // Parameters: - // line, lineIndex, inCode, onFence, inTable, inItem, inBreak, inMath handler(...metadata); }); -}; +} +module.exports.forEachLine = forEachLine; // Returns (nested) lists as a flat array (in order) module.exports.flattenLists = function flattenLists(tokens) { @@ -402,7 +408,8 @@ module.exports.forEachHeading = function forEachHeading(params, handler) { * Calls the provided function for each inline code span's content. * * @param {string} input Markdown content. - * @param {Function} handler Callback function. + * @param {Function} handler Callback function taking (code, lineIndex, + * columnIndex, ticks). * @returns {void} */ function forEachInlineCodeSpan(input, handler) { @@ -537,26 +544,39 @@ module.exports.addErrorContext = function addErrorContext( }; /** - * Returns an array of code span ranges. + * Returns an array of code block and span content ranges. * - * @param {string[]} lines Lines to scan for code span ranges. - * @returns {number[][]} Array of ranges (line, index, length). + * @param {Object} params RuleParams instance. + * @param {Object} lineMetadata Line metadata object. + * @returns {number[][]} Array of ranges (lineIndex, columnIndex, length). */ -module.exports.inlineCodeSpanRanges = (lines) => { +module.exports.codeBlockAndSpanRanges = (params, lineMetadata) => { const exclusions = []; - forEachInlineCodeSpan( - lines.join("\n"), - (code, lineIndex, columnIndex) => { - const codeLines = code.split(newLineRe); - // eslint-disable-next-line unicorn/no-for-loop - for (let i = 0; i < codeLines.length; i++) { - exclusions.push( - [ lineIndex + i, columnIndex, codeLines[i].length ] - ); - columnIndex = 0; - } + // Add code block ranges (excludes fences) + forEachLine(lineMetadata, (line, lineIndex, inCode, onFence) => { + if (inCode && !onFence) { + exclusions.push(lineIndex, 0, line.length); } - ); + }); + // Add code span ranges (excludes ticks) + filterTokens(params, "inline", (token) => { + if (token.children.some((child) => child.type === "code_inline")) { + const tokenLines = params.lines.slice(token.map[0], token.map[1]); + forEachInlineCodeSpan( + tokenLines.join("\n"), + (code, lineIndex, columnIndex) => { + const codeLines = code.split(newLineRe); + for (const [ i, line ] of codeLines.entries()) { + exclusions.push([ + token.lineNumber - 1 + lineIndex + i, + i ? 0 : columnIndex, + line.length + ]); + } + } + ); + } + }); return exclusions; }; diff --git a/lib/cache.js b/lib/cache.js index a2ad39af..4d7fa662 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -2,6 +2,14 @@ "use strict"; +let codeBlockAndSpanRanges = null; +module.exports.codeBlockAndSpanRanges = (value) => { + if (value) { + codeBlockAndSpanRanges = value; + } + return codeBlockAndSpanRanges; +}; + let flattenedLists = null; module.exports.flattenedLists = (value) => { if (value) { @@ -10,14 +18,6 @@ module.exports.flattenedLists = (value) => { return flattenedLists; }; -let inlineCodeSpanRanges = null; -module.exports.inlineCodeSpanRanges = (value) => { - if (value) { - inlineCodeSpanRanges = value; - } - return inlineCodeSpanRanges; -}; - let lineMetadata = null; module.exports.lineMetadata = (value) => { if (value) { @@ -27,7 +27,7 @@ module.exports.lineMetadata = (value) => { }; module.exports.clear = () => { + codeBlockAndSpanRanges = null; flattenedLists = null; - inlineCodeSpanRanges = null; lineMetadata = null; }; diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 039e3a7a..fe653438 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -515,7 +515,9 @@ function lintContent( }; cache.lineMetadata(helpers.getLineMetadata(params)); cache.flattenedLists(helpers.flattenLists(params.tokens)); - cache.inlineCodeSpanRanges(helpers.inlineCodeSpanRanges(params.lines)); + cache.codeBlockAndSpanRanges( + helpers.codeBlockAndSpanRanges(params, cache.lineMetadata()) + ); // Function to run for each rule const result = (resultVersion === 0) ? {} : []; // eslint-disable-next-line jsdoc/require-jsdoc diff --git a/lib/md011.js b/lib/md011.js index 0c906dc0..11672e5d 100644 --- a/lib/md011.js +++ b/lib/md011.js @@ -3,7 +3,7 @@ "use strict"; const { addError, forEachLine, overlapsAnyRange } = require("../helpers"); -const { inlineCodeSpanRanges, lineMetadata } = require("./cache"); +const { codeBlockAndSpanRanges, lineMetadata } = require("./cache"); const reversedLinkRe = /(^|[^\\])\(([^)]+)\)\[([^\]^][^\]]*)](?!\()/g; @@ -13,7 +13,7 @@ module.exports = { "description": "Reversed link syntax", "tags": [ "links" ], "function": function MD011(params, onError) { - const exclusions = inlineCodeSpanRanges(); + const exclusions = codeBlockAndSpanRanges(); forEachLine(lineMetadata(), (line, lineIndex, inCode, onFence) => { if (!inCode && !onFence) { let match = null; diff --git a/lib/md033.js b/lib/md033.js index 73737abe..c4a6fe98 100644 --- a/lib/md033.js +++ b/lib/md033.js @@ -5,7 +5,7 @@ const { addError, forEachLine, overlapsAnyRange, unescapeMarkdown } = require("../helpers"); -const { inlineCodeSpanRanges, lineMetadata } = require("./cache"); +const { codeBlockAndSpanRanges, lineMetadata } = require("./cache"); const htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^>]*)?)\/?>/g; const linkDestinationRe = /]\(\s*$/; @@ -22,7 +22,7 @@ module.exports = { let allowedElements = params.config.allowed_elements; allowedElements = Array.isArray(allowedElements) ? allowedElements : []; allowedElements = allowedElements.map((element) => element.toLowerCase()); - const exclusions = inlineCodeSpanRanges(); + const exclusions = codeBlockAndSpanRanges(); forEachLine(lineMetadata(), (line, lineIndex, inCode) => { let match = null; // eslint-disable-next-line no-unmodified-loop-condition diff --git a/lib/md044.js b/lib/md044.js index 2d8da0e4..d8bc16fd 100644 --- a/lib/md044.js +++ b/lib/md044.js @@ -4,7 +4,7 @@ const { addErrorDetailIf, bareUrlRe, escapeForRegExp, forEachLine, overlapsAnyRange, linkRe, linkReferenceRe } = require("../helpers"); -const { inlineCodeSpanRanges, lineMetadata } = require("./cache"); +const { codeBlockAndSpanRanges, lineMetadata } = require("./cache"); module.exports = { "names": [ "MD044", "proper-names" ], @@ -36,7 +36,7 @@ module.exports = { } }); if (!includeCodeBlocks) { - exclusions.push(...inlineCodeSpanRanges()); + exclusions.push(...codeBlockAndSpanRanges()); } for (const name of names) { const escapedName = escapeForRegExp(name); diff --git a/test/code-blocks-and-spans.md b/test/code-blocks-and-spans.md new file mode 100644 index 00000000..54a243a9 --- /dev/null +++ b/test/code-blocks-and-spans.md @@ -0,0 +1,38 @@ +# Code Blocks and Spans {MD044} + +Text CODE text {MD044} + +Text `CODE` text + +```lang +CODE + +CODE +``` + +`CODE` text `CODE` + + CODE + + CODE + +Text `CODE +CODE` text +text text +text `CODE +CODE CODE +CODE` text + +Text `CODE {MD044} + +Text `CODE {MD044} + + diff --git a/test/inline_html.md b/test/inline_html.md index d70267b7..995113c1 100644 --- a/test/inline_html.md +++ b/test/inline_html.md @@ -96,3 +96,25 @@ Text **\\another\directory\\** text Google {MD033} Google {MD033} + +## Unterminated code span followed by element in code span + +Text text `text text + +Text `` text + +Text +text `text +text + +Text `code code` text + +```lang +code {MD046:112} + + +``` + +Text `code code` text + +Text text {MD033} diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index 43068de4..dbe5c96b 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -174,6 +174,7 @@ if (existsSync(dotnetDocsDir)) { "docs/core/dependency-loading/collect-details.md", "docs/core/deploying/single-file.md", "docs/core/deploying/trimming/trimming-options.md", + "docs/core/diagnostics/dotnet-dump.md", "docs/core/extensions/cloud-service.md", "docs/core/extensions/configuration-providers.md", "docs/core/extensions/console-log-formatter.md", diff --git a/test/reversed-link-issue-with-markdownlint-12.md b/test/reversed-link-issue-with-markdownlint-12.md index 2383d467..be398a2b 100644 --- a/test/reversed-link-issue-with-markdownlint-12.md +++ b/test/reversed-link-issue-with-markdownlint-12.md @@ -2,7 +2,7 @@ |Pattern|Description| |-------------|-----------------| -|`(?:\["'\](?<1>\[^"'\]*)["']|(?<1>\S+))`|...| +|`(?:\["'\](?<1>\[^"'\]*)["']|(?<1>\S+))`|{MD011}| |Pattern|Description| |-------------|-----------------| From 11806dc5cbd6034d7a21c501dcd12664e42d3a45 Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 26 Nov 2021 04:26:15 +0000 Subject: [PATCH 29/69] Tokens inside tables that lack a map should get it from the surrounding table row (which is more scoped than the table body) (fixes #463). --- demo/markdownlint-browser.js | 22 +++++--------- lib/markdownlint.js | 27 +++++------------ test/markdownlint-test-custom-rules.js | 2 +- test/markdownlint-test.js | 34 +++++++++++++++++++++ test/table-content-with-issues.md | 13 ++++++++ test/token-map-spans.md | 42 ++++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 34 deletions(-) create mode 100644 test/table-content-with-issues.md create mode 100644 test/token-map-spans.md diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index e3fcf875..91363bb5 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -1037,23 +1037,17 @@ function removeFrontMatter(content, frontMatter) { * @returns {void} */ function annotateTokens(tokens, lines) { - var tableMap = null; + var trMap = null; tokens.forEach(function forToken(token) { - // Handle missing maps for table head/body - if ((token.type === "thead_open") || - (token.type === "tbody_open")) { - tableMap = __spreadArray([], token.map); + // Provide missing maps for table content + if (token.type === "tr_open") { + trMap = token.map; } - else if ((token.type === "tr_close") && - tableMap) { - tableMap[0]++; + else if (token.type === "tr_close") { + trMap = null; } - else if ((token.type === "thead_close") || - (token.type === "tbody_close")) { - tableMap = null; - } - if (tableMap && !token.map) { - token.map = __spreadArray([], tableMap); + if (!token.map && trMap) { + token.map = __spreadArray([], trMap); } // Update token metadata if (token.map) { diff --git a/lib/markdownlint.js b/lib/markdownlint.js index fe653438..11a18c7b 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -181,27 +181,16 @@ function removeFrontMatter(content, frontMatter) { * @returns {void} */ function annotateTokens(tokens, lines) { - let tableMap = null; + let trMap = null; tokens.forEach(function forToken(token) { - // Handle missing maps for table head/body - if ( - (token.type === "thead_open") || - (token.type === "tbody_open") - ) { - tableMap = [ ...token.map ]; - } else if ( - (token.type === "tr_close") && - tableMap - ) { - tableMap[0]++; - } else if ( - (token.type === "thead_close") || - (token.type === "tbody_close") - ) { - tableMap = null; + // Provide missing maps for table content + if (token.type === "tr_open") { + trMap = token.map; + } else if (token.type === "tr_close") { + trMap = null; } - if (tableMap && !token.map) { - token.map = [ ...tableMap ]; + if (!token.map && trMap) { + token.map = [ ...trMap ]; } // Update token metadata if (token.map) { diff --git a/test/markdownlint-test-custom-rules.js b/test/markdownlint-test-custom-rules.js index 5975a64e..b7ef83c7 100644 --- a/test/markdownlint-test-custom-rules.js +++ b/test/markdownlint-test-custom-rules.js @@ -1002,7 +1002,7 @@ test("customRulesOnErrorInvalidHandled", (t) => { "function": function onErrorInvalid(params, onError) { onError({ "lineNumber": 13, - "details": "N/A" + "detail": "N/A" }); } } diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index 66ec05c4..bf29b4b0 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -1717,6 +1717,40 @@ test.cb("texmath test files with texmath plugin", (t) => { }); }); +test("token-map-spans", (t) => { + t.plan(38); + const options = { + "customRules": [ + { + "names": [ "token-map-spans" ], + "description": "token-map-spans", + "tags": [ "tms" ], + "function": function tokenMapSpans(params) { + const tokenLines = []; + let lastLineNumber = -1; + const inlines = params.tokens.filter((c) => c.type === "inline"); + for (const token of inlines) { + t.truthy(token.map); + for (let i = token.map[0]; i < token.map[1]; i++) { + if (tokenLines.includes(i)) { + t.true( + lastLineNumber === token.lineNumber, + `Line ${i + 1} is part of token maps from multiple lines.` + ); + } else { + tokenLines.push(i); + } + lastLineNumber = token.lineNumber; + } + } + } + } + ], + "files": [ "./test/token-map-spans.md" ] + }; + markdownlint.sync(options); +}); + test("getVersion", (t) => { t.plan(1); const actual = markdownlint.getVersion(); diff --git a/test/table-content-with-issues.md b/test/table-content-with-issues.md new file mode 100644 index 00000000..c1fbf0af --- /dev/null +++ b/test/table-content-with-issues.md @@ -0,0 +1,13 @@ +# Table Content With Issues + +| Content | Issue | +|------------------------------|---------| +| Text | N/A | +| (link)[https://example.com] | {MD011} | +|
| {MD033} | +| https://example.com | {MD034} | +| * emphasis* | {MD037} | +| __strong __ | {MD037} | +| ` code` | {MD038} | +| [link ](https://example.com) | {MD039} | +| [link]() | {MD042} | diff --git a/test/token-map-spans.md b/test/token-map-spans.md new file mode 100644 index 00000000..f54fac0e --- /dev/null +++ b/test/token-map-spans.md @@ -0,0 +1,42 @@ +# Token Map Spans + +Text *emphasis* text __strong__ text `code` text [link](https://example.com). + +Paragraph with *emphasis +spanning lines* and __strong +spanning lines__ and `code +spanning lines` and [link +spanning lines](https://example.com). + +> Blockquote +> [link](https://example.com) +> > Nested +> > blockquote +> > [link](https://example.com) + +Heading +------- + +```lang +Fenced +code +``` + + Indented + code + +1. List +2. List + - Sub-list + - Sub-list +3. List + +| Table | Column 1 | Column 2 | Column 3 | Column 4 | +|-------|------------|------------|----------|----------------------------| +| Text | *emphasis* | __strong__ | `code` | [link](https://example.com) | +| Text | *emphasis* | __strong__ | `code` | [link](https://example.com) | + + From a508824b0f43c0ca25131fa295fdd527c4f1bb7e Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 26 Nov 2021 05:37:04 +0000 Subject: [PATCH 30/69] Refactor helpers.emphasisMarkersInContent slightly to avoid duplicate/unnecessary work. --- demo/markdownlint-browser.js | 34 +++++++++++++++++----------------- helpers/helpers.js | 32 ++++++++++++++++---------------- lib/md037.js | 2 +- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 91363bb5..81406185 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -613,6 +613,18 @@ module.exports.frontMatterHasTitle = function emphasisMarkersInContent(params) { var lines = params.lines; var byLine = new Array(lines.length); + // Search links + lines.forEach(function (tokenLine, tokenLineIndex) { + var inLine = []; + var linkMatch = null; + while ((linkMatch = linkRe.exec(tokenLine))) { + var markerMatch = null; + while ((markerMatch = emphasisMarkersRe.exec(linkMatch[0]))) { + inLine.push(linkMatch.index + markerMatch.index); + } + } + byLine[tokenLineIndex] = inLine; + }); // Search code spans filterTokens(params, "inline", function (token) { var children = token.children, lineNumber = token.lineNumber, map = token.map; @@ -621,30 +633,18 @@ function emphasisMarkersInContent(params) { forEachInlineCodeSpan(tokenLines.join("\n"), function (code, lineIndex, column, tickCount) { var codeLines = code.split(newLineRe); codeLines.forEach(function (codeLine, codeLineIndex) { + var byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex; + var inLine = byLine[byLineIndex]; + var codeLineOffset = codeLineIndex ? 0 : column - 1 + tickCount; var match = null; while ((match = emphasisMarkersRe.exec(codeLine))) { - var byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex; - var inLine = byLine[byLineIndex] || []; - var codeLineOffset = codeLineIndex ? 0 : column - 1 + tickCount; inLine.push(codeLineOffset + match.index); - byLine[byLineIndex] = inLine; } + byLine[byLineIndex] = inLine; }); }); } }); - // Search links - lines.forEach(function (tokenLine, tokenLineIndex) { - var linkMatch = null; - while ((linkMatch = linkRe.exec(tokenLine))) { - var markerMatch = null; - while ((markerMatch = emphasisMarkersRe.exec(linkMatch[0]))) { - var inLine = byLine[tokenLineIndex] || []; - inLine.push(linkMatch.index + markerMatch.index); - byLine[tokenLineIndex] = inLine; - } - } - }); return byLine; } module.exports.emphasisMarkersInContent = emphasisMarkersInContent; @@ -3486,7 +3486,7 @@ module.exports = { var match = null; // Match all emphasis-looking runs in the line... while ((match = emphasisRe.exec(line))) { - var ignoreMarkersForLine = ignoreMarkersByLine[lineIndex] || []; + var ignoreMarkersForLine = ignoreMarkersByLine[lineIndex]; var matchIndex = match.index + match[1].length; if (ignoreMarkersForLine.includes(matchIndex)) { // Ignore emphasis markers inside code spans and links diff --git a/helpers/helpers.js b/helpers/helpers.js index 500483ee..6e2dd1f4 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -632,6 +632,18 @@ module.exports.frontMatterHasTitle = function emphasisMarkersInContent(params) { const { lines } = params; const byLine = new Array(lines.length); + // Search links + lines.forEach((tokenLine, tokenLineIndex) => { + const inLine = []; + let linkMatch = null; + while ((linkMatch = linkRe.exec(tokenLine))) { + let markerMatch = null; + while ((markerMatch = emphasisMarkersRe.exec(linkMatch[0]))) { + inLine.push(linkMatch.index + markerMatch.index); + } + } + byLine[tokenLineIndex] = inLine; + }); // Search code spans filterTokens(params, "inline", (token) => { const { children, lineNumber, map } = token; @@ -642,31 +654,19 @@ function emphasisMarkersInContent(params) { (code, lineIndex, column, tickCount) => { const codeLines = code.split(newLineRe); codeLines.forEach((codeLine, codeLineIndex) => { + const byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex; + const inLine = byLine[byLineIndex]; + const codeLineOffset = codeLineIndex ? 0 : column - 1 + tickCount; let match = null; while ((match = emphasisMarkersRe.exec(codeLine))) { - const byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex; - const inLine = byLine[byLineIndex] || []; - const codeLineOffset = codeLineIndex ? 0 : column - 1 + tickCount; inLine.push(codeLineOffset + match.index); - byLine[byLineIndex] = inLine; } + byLine[byLineIndex] = inLine; }); } ); } }); - // Search links - lines.forEach((tokenLine, tokenLineIndex) => { - let linkMatch = null; - while ((linkMatch = linkRe.exec(tokenLine))) { - let markerMatch = null; - while ((markerMatch = emphasisMarkersRe.exec(linkMatch[0]))) { - const inLine = byLine[tokenLineIndex] || []; - inLine.push(linkMatch.index + markerMatch.index); - byLine[tokenLineIndex] = inLine; - } - } - }); return byLine; } module.exports.emphasisMarkersInContent = emphasisMarkersInContent; diff --git a/lib/md037.js b/lib/md037.js index 14c5c032..1d46163b 100644 --- a/lib/md037.js +++ b/lib/md037.js @@ -105,7 +105,7 @@ module.exports = { let match = null; // Match all emphasis-looking runs in the line... while ((match = emphasisRe.exec(line))) { - const ignoreMarkersForLine = ignoreMarkersByLine[lineIndex] || []; + const ignoreMarkersForLine = ignoreMarkersByLine[lineIndex]; const matchIndex = match.index + match[1].length; if (ignoreMarkersForLine.includes(matchIndex)) { // Ignore emphasis markers inside code spans and links From 291597edb924c0f6a0d722d6877915b9f6dd3013 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sun, 28 Nov 2021 23:18:57 -0800 Subject: [PATCH 31/69] Update rules MD049/emphasis-style and MD050/strong-style to include range and fixInfo when reporting issues (i.e., to be automatically fixable). --- demo/markdownlint-browser.js | 85 +++++++++++++++++-- doc/Rules.md | 4 + helpers/helpers.js | 55 ++++++++++++ lib/md049.js | 35 ++++++-- lib/md050.js | 35 ++++++-- test/break-all-the-rules.md | 2 +- test/detailed-results-MD041-MD050.md | 4 +- test/detailed-results-MD041-MD050.md.fixed | 4 +- .../detailed-results-MD041-MD050.results.json | 62 +++++++++++++- test/fix_102_extra_nodes_in_link_text.md | 10 ++- test/links-with-markup.md | 2 +- test/mixed-emphasis-markers.md | 2 + test/proper-names.md | 4 +- 13 files changed, 263 insertions(+), 41 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 81406185..2296d47f 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -783,6 +783,57 @@ module.exports.applyFixes = function applyFixes(input, errors) { // Return corrected input return lines.filter(function (line) { return line !== null; }).join(lineEnding); }; +/** + * Gets the range and fixInfo values for reporting an error if the expected + * text is found on the specified line. + * + * @param {string[]} lines Lines of Markdown content. + * @param {number} lineIndex Line index to check. + * @param {string} search Text to search for. + * @param {string} replace Text to replace with. + * @returns {Object} Range and fixInfo wrapper. + */ +function getRangeAndFixInfoIfFound(lines, lineIndex, search, replace) { + var range = null; + var fixInfo = null; + var searchIndex = lines[lineIndex].indexOf(search); + if (searchIndex !== -1) { + var column = searchIndex + 1; + var length = search.length; + range = [column, length]; + fixInfo = { + "editColumn": column, + "deleteCount": length, + "insertText": replace + }; + } + return { + range: range, + fixInfo: fixInfo + }; +} +module.exports.getRangeAndFixInfoIfFound = getRangeAndFixInfoIfFound; +/** + * Gets the next (subsequent) child token if it is of the expected type. + * + * @param {Object} parentToken Parent token. + * @param {Object} childToken Child token basis. + * @param {string} nextType Token type of next token. + * @param {string} nextNextType Token type of next-next token. + * @returns {Object} Next token. + */ +function getNextChildToken(parentToken, childToken, nextType, nextNextType) { + var children = parentToken.children; + var index = children.indexOf(childToken); + if ((index !== -1) && + (children.length > index + 2) && + (children[index + 1].type === nextType) && + (children[index + 2].type === nextNextType)) { + return children[index + 1]; + } + return null; +} +module.exports.getNextChildToken = getNextChildToken; /***/ }), @@ -4083,20 +4134,31 @@ module.exports = { "use strict"; // @ts-check -var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, emphasisOrStrongStyleFor = _a.emphasisOrStrongStyleFor, forEachInlineChild = _a.forEachInlineChild; +var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, emphasisOrStrongStyleFor = _a.emphasisOrStrongStyleFor, forEachInlineChild = _a.forEachInlineChild, getNextChildToken = _a.getNextChildToken, getRangeAndFixInfoIfFound = _a.getRangeAndFixInfoIfFound; module.exports = { "names": ["MD049", "emphasis-style"], "description": "Emphasis style should be consistent", "tags": ["emphasis"], "function": function MD049(params, onError) { var expectedStyle = String(params.config.style || "consistent"); - forEachInlineChild(params, "em_open", function (token) { + forEachInlineChild(params, "em_open", function (token, parent) { var lineNumber = token.lineNumber, markup = token.markup; var markupStyle = emphasisOrStrongStyleFor(markup); if (expectedStyle === "consistent") { expectedStyle = markupStyle; } - addErrorDetailIf(onError, lineNumber, expectedStyle, markupStyle); + if (expectedStyle !== markupStyle) { + var rangeAndFixInfo = {}; + var contentToken = getNextChildToken(parent, token, "text", "em_close"); + if (contentToken) { + var content = contentToken.content; + var actual = "" + markup + content + markup; + var expectedMarkup = (expectedStyle === "asterisk") ? "*" : "_"; + var expected = "" + expectedMarkup + content + expectedMarkup; + rangeAndFixInfo = getRangeAndFixInfoIfFound(params.lines, lineNumber - 1, actual, expected); + } + addError(onError, lineNumber, "Expected: " + expectedStyle + "; Actual: " + markupStyle, null, rangeAndFixInfo.range, rangeAndFixInfo.fixInfo); + } }); } }; @@ -4113,20 +4175,31 @@ module.exports = { "use strict"; // @ts-check -var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, emphasisOrStrongStyleFor = _a.emphasisOrStrongStyleFor, forEachInlineChild = _a.forEachInlineChild; +var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, emphasisOrStrongStyleFor = _a.emphasisOrStrongStyleFor, forEachInlineChild = _a.forEachInlineChild, getNextChildToken = _a.getNextChildToken, getRangeAndFixInfoIfFound = _a.getRangeAndFixInfoIfFound; module.exports = { "names": ["MD050", "strong-style"], "description": "Strong style should be consistent", "tags": ["emphasis"], "function": function MD050(params, onError) { var expectedStyle = String(params.config.style || "consistent"); - forEachInlineChild(params, "strong_open", function (token) { + forEachInlineChild(params, "strong_open", function (token, parent) { var lineNumber = token.lineNumber, markup = token.markup; var markupStyle = emphasisOrStrongStyleFor(markup); if (expectedStyle === "consistent") { expectedStyle = markupStyle; } - addErrorDetailIf(onError, lineNumber, expectedStyle, markupStyle); + if (expectedStyle !== markupStyle) { + var rangeAndFixInfo = {}; + var contentToken = getNextChildToken(parent, token, "text", "strong_close"); + if (contentToken) { + var content = contentToken.content; + var actual = "" + markup + content + markup; + var expectedMarkup = (expectedStyle === "asterisk") ? "**" : "__"; + var expected = "" + expectedMarkup + content + expectedMarkup; + rangeAndFixInfo = getRangeAndFixInfoIfFound(params.lines, lineNumber - 1, actual, expected); + } + addError(onError, lineNumber, "Expected: " + expectedStyle + "; Actual: " + markupStyle, null, rangeAndFixInfo.range, rangeAndFixInfo.fixInfo); + } }); } }; diff --git a/doc/Rules.md b/doc/Rules.md index ed0a93fa..bb3d6844 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -1923,6 +1923,8 @@ Aliases: emphasis-style Parameters: style ("consistent", "asterisk", "underscore"; default "consistent") +Fixable: Most violations can be fixed by tooling + This rule is triggered when the symbols used in the document for emphasis do not match the configured emphasis style: @@ -1953,6 +1955,8 @@ Aliases: strong-style Parameters: style ("consistent", "asterisk", "underscore"; default "consistent") +Fixable: Most violations can be fixed by tooling + This rule is triggered when the symbols used in the document for strong do not match the configured strong style: diff --git a/helpers/helpers.js b/helpers/helpers.js index 6e2dd1f4..c4927512 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -813,3 +813,58 @@ module.exports.applyFixes = function applyFixes(input, errors) { // Return corrected input return lines.filter((line) => line !== null).join(lineEnding); }; + +/** + * Gets the range and fixInfo values for reporting an error if the expected + * text is found on the specified line. + * + * @param {string[]} lines Lines of Markdown content. + * @param {number} lineIndex Line index to check. + * @param {string} search Text to search for. + * @param {string} replace Text to replace with. + * @returns {Object} Range and fixInfo wrapper. + */ +function getRangeAndFixInfoIfFound(lines, lineIndex, search, replace) { + let range = null; + let fixInfo = null; + const searchIndex = lines[lineIndex].indexOf(search); + if (searchIndex !== -1) { + const column = searchIndex + 1; + const length = search.length; + range = [ column, length ]; + fixInfo = { + "editColumn": column, + "deleteCount": length, + "insertText": replace + }; + } + return { + range, + fixInfo + }; +} +module.exports.getRangeAndFixInfoIfFound = getRangeAndFixInfoIfFound; + +/** + * Gets the next (subsequent) child token if it is of the expected type. + * + * @param {Object} parentToken Parent token. + * @param {Object} childToken Child token basis. + * @param {string} nextType Token type of next token. + * @param {string} nextNextType Token type of next-next token. + * @returns {Object} Next token. + */ +function getNextChildToken(parentToken, childToken, nextType, nextNextType) { + const { children } = parentToken; + const index = children.indexOf(childToken); + if ( + (index !== -1) && + (children.length > index + 2) && + (children[index + 1].type === nextType) && + (children[index + 2].type === nextNextType) + ) { + return children[index + 1]; + } + return null; +} +module.exports.getNextChildToken = getNextChildToken; diff --git a/lib/md049.js b/lib/md049.js index 0a638578..4d36df55 100644 --- a/lib/md049.js +++ b/lib/md049.js @@ -2,8 +2,8 @@ "use strict"; -const { addErrorDetailIf, emphasisOrStrongStyleFor, forEachInlineChild } = - require("../helpers"); +const { addError, emphasisOrStrongStyleFor, forEachInlineChild, + getNextChildToken, getRangeAndFixInfoIfFound } = require("../helpers"); module.exports = { "names": [ "MD049", "emphasis-style" ], @@ -11,18 +11,35 @@ module.exports = { "tags": [ "emphasis" ], "function": function MD049(params, onError) { let expectedStyle = String(params.config.style || "consistent"); - forEachInlineChild(params, "em_open", (token) => { + forEachInlineChild(params, "em_open", (token, parent) => { const { lineNumber, markup } = token; const markupStyle = emphasisOrStrongStyleFor(markup); if (expectedStyle === "consistent") { expectedStyle = markupStyle; } - addErrorDetailIf( - onError, - lineNumber, - expectedStyle, - markupStyle - ); + if (expectedStyle !== markupStyle) { + let rangeAndFixInfo = {}; + const contentToken = getNextChildToken( + parent, token, "text", "em_close" + ); + if (contentToken) { + const { content } = contentToken; + const actual = `${markup}${content}${markup}`; + const expectedMarkup = (expectedStyle === "asterisk") ? "*" : "_"; + const expected = `${expectedMarkup}${content}${expectedMarkup}`; + rangeAndFixInfo = getRangeAndFixInfoIfFound( + params.lines, lineNumber - 1, actual, expected + ); + } + addError( + onError, + lineNumber, + `Expected: ${expectedStyle}; Actual: ${markupStyle}`, + null, + rangeAndFixInfo.range, + rangeAndFixInfo.fixInfo + ); + } }); } }; diff --git a/lib/md050.js b/lib/md050.js index 79ae8a18..2ca9260e 100644 --- a/lib/md050.js +++ b/lib/md050.js @@ -2,8 +2,8 @@ "use strict"; -const { addErrorDetailIf, emphasisOrStrongStyleFor, forEachInlineChild } = - require("../helpers"); +const { addError, emphasisOrStrongStyleFor, forEachInlineChild, + getNextChildToken, getRangeAndFixInfoIfFound } = require("../helpers"); module.exports = { "names": [ "MD050", "strong-style" ], @@ -11,18 +11,35 @@ module.exports = { "tags": [ "emphasis" ], "function": function MD050(params, onError) { let expectedStyle = String(params.config.style || "consistent"); - forEachInlineChild(params, "strong_open", (token) => { + forEachInlineChild(params, "strong_open", (token, parent) => { const { lineNumber, markup } = token; const markupStyle = emphasisOrStrongStyleFor(markup); if (expectedStyle === "consistent") { expectedStyle = markupStyle; } - addErrorDetailIf( - onError, - lineNumber, - expectedStyle, - markupStyle - ); + if (expectedStyle !== markupStyle) { + let rangeAndFixInfo = {}; + const contentToken = getNextChildToken( + parent, token, "text", "strong_close" + ); + if (contentToken) { + const { content } = contentToken; + const actual = `${markup}${content}${markup}`; + const expectedMarkup = (expectedStyle === "asterisk") ? "**" : "__"; + const expected = `${expectedMarkup}${content}${expectedMarkup}`; + rangeAndFixInfo = getRangeAndFixInfoIfFound( + params.lines, lineNumber - 1, actual, expected + ); + } + addError( + onError, + lineNumber, + `Expected: ${expectedStyle}; Actual: ${markupStyle}`, + null, + rangeAndFixInfo.range, + rangeAndFixInfo.fixInfo + ); + } }); } }; diff --git a/test/break-all-the-rules.md b/test/break-all-the-rules.md index e1744f37..140d87cd 100644 --- a/test/break-all-the-rules.md +++ b/test/break-all-the-rules.md @@ -64,7 +64,7 @@ https://example.com/page {MD034} _Section {MD036} Heading_ -Emphasis *with * space {MD037} +Emphasis _with _ space {MD037} Code `with ` space {MD038} diff --git a/test/detailed-results-MD041-MD050.md b/test/detailed-results-MD041-MD050.md index 6066a9b5..d4b6fd0b 100644 --- a/test/detailed-results-MD041-MD050.md +++ b/test/detailed-results-MD041-MD050.md @@ -28,9 +28,9 @@ Fenced code Fenced code ~~~ -Mixed *emphasis* on _this_ line +Mixed *emphasis* on _this_ line *with* multiple _issues_ -Mixed __strong emphasis__ on **this** line +Mixed __strong emphasis__ on **this** line __with__ multiple **issues** Inconsistent emphasis _text diff --git a/test/detailed-results-MD041-MD050.md.fixed b/test/detailed-results-MD041-MD050.md.fixed index f9af863a..a26adc9b 100644 --- a/test/detailed-results-MD041-MD050.md.fixed +++ b/test/detailed-results-MD041-MD050.md.fixed @@ -28,9 +28,9 @@ Fenced code Fenced code ~~~ -Mixed *emphasis* on _this_ line +Mixed *emphasis* on *this* line *with* multiple *issues* -Mixed __strong emphasis__ on **this** line +Mixed __strong emphasis__ on __this__ line __with__ multiple __issues__ Inconsistent emphasis _text diff --git a/test/detailed-results-MD041-MD050.results.json b/test/detailed-results-MD041-MD050.results.json index 346e2eb5..d9b7b38b 100644 --- a/test/detailed-results-MD041-MD050.results.json +++ b/test/detailed-results-MD041-MD050.results.json @@ -212,8 +212,35 @@ { "errorContext": null, "errorDetail": "Expected: asterisk; Actual: underscore", - "errorRange": null, - "fixInfo": null, + "errorRange": [ + 21, + 6 + ], + "fixInfo": { + "deleteCount": 6, + "editColumn": 21, + "insertText": "*this*" + }, + "lineNumber": 31, + "ruleDescription": "Emphasis style should be consistent", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md049", + "ruleNames": [ + "MD049", + "emphasis-style" + ] + }, + { + "errorContext": null, + "errorDetail": "Expected: asterisk; Actual: underscore", + "errorRange": [ + 49, + 8 + ], + "fixInfo": { + "deleteCount": 8, + "editColumn": 49, + "insertText": "*issues*" + }, "lineNumber": 31, "ruleDescription": "Emphasis style should be consistent", "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md049", @@ -238,8 +265,35 @@ { "errorContext": null, "errorDetail": "Expected: underscore; Actual: asterisk", - "errorRange": null, - "fixInfo": null, + "errorRange": [ + 30, + 8 + ], + "fixInfo": { + "deleteCount": 8, + "editColumn": 30, + "insertText": "__this__" + }, + "lineNumber": 33, + "ruleDescription": "Strong style should be consistent", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md050", + "ruleNames": [ + "MD050", + "strong-style" + ] + }, + { + "errorContext": null, + "errorDetail": "Expected: underscore; Actual: asterisk", + "errorRange": [ + 62, + 10 + ], + "fixInfo": { + "deleteCount": 10, + "editColumn": 62, + "insertText": "__issues__" + }, "lineNumber": 33, "ruleDescription": "Strong style should be consistent", "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md050", diff --git a/test/fix_102_extra_nodes_in_link_text.md b/test/fix_102_extra_nodes_in_link_text.md index 68db9d71..133386d8 100644 --- a/test/fix_102_extra_nodes_in_link_text.md +++ b/test/fix_102_extra_nodes_in_link_text.md @@ -2,9 +2,11 @@ [test _test_ test](www.test.com) [test `test` test](www.test.com) -[test *test* test](www.test.com) {MD049} -[test *test* *test* test](www.test.com) {MD049} -[test *test* *test* *test* test](www.test.com) {MD049} +[test *test* test](www.test.com) +[test *test* *test* test](www.test.com) +[test *test* *test* *test* test](www.test.com) [test **test** test](www.test.com) -[test __test__ test](www.test.com) {MD050} +[test __test__ test](www.test.com) [this should not raise](www.shouldnotraise.com) + + diff --git a/test/links-with-markup.md b/test/links-with-markup.md index 454f3d65..9934bda2 100644 --- a/test/links-with-markup.md +++ b/test/links-with-markup.md @@ -10,7 +10,7 @@ [This link has `code` and right space ](link) {MD039} -[ This link has _emphasis_ and left space](link) {MD039} {MD049} +[ This link has *emphasis* and left space](link) {MD039} [This](link) line has [multiple](link) links. diff --git a/test/mixed-emphasis-markers.md b/test/mixed-emphasis-markers.md index e74a7ee0..92c22393 100644 --- a/test/mixed-emphasis-markers.md +++ b/test/mixed-emphasis-markers.md @@ -15,3 +15,5 @@ This paragraph _nests both *kinds* of emphasis_ marker. {MD049} This paragraph _nests both **kinds** of emphasis_ marker. {MD049} {MD050} This paragraph __nests both **kinds** of emphasis__ marker. {MD050} + + diff --git a/test/proper-names.md b/test/proper-names.md index d69b6621..c9e9bc6d 100644 --- a/test/proper-names.md +++ b/test/proper-names.md @@ -6,8 +6,6 @@ Quoted "Markdownlint" {MD044} Emphasized *Markdownlint* {MD044} -Emphasized _Markdownlint_ {MD044} {MD049} - JavaScript is a language JavaScript is not Java @@ -52,7 +50,7 @@ HTML javascript {MD033} {MD044} node.js is runtime {MD044} ```js -javascript is code {MD044} {MD046:54} +javascript is code {MD044} {MD046:52} node.js is runtime {MD044} ``` From 925f9cd16887f02cbe72c2607962e1840e80acd8 Mon Sep 17 00:00:00 2001 From: David Anson Date: Mon, 29 Nov 2021 22:21:23 -0800 Subject: [PATCH 32/69] Simplify exclusions for external repository tests, make exclusions more selective. --- test/markdownlint-test-repos.js | 143 +++++++------------------------- 1 file changed, 30 insertions(+), 113 deletions(-) diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index dbe5c96b..f0040c74 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -40,9 +40,10 @@ function yamlParse(yaml) { * @param {Object} t Test instance. * @param {string[]} globPatterns Array of files to in/exclude. * @param {string} configPath Path to config file. + * @param {RegExp[]} [ignoreRes] Array of RegExp violations to ignore. * @returns {Promise} Test result. */ -function lintTestRepo(t, globPatterns, configPath) { +function lintTestRepo(t, globPatterns, configPath, ignoreRes) { t.plan(1); return Promise.all([ globby(globPatterns), @@ -57,7 +58,10 @@ function lintTestRepo(t, globPatterns, configPath) { // eslint-disable-next-line no-console console.log(`${t.title}: Linting ${files.length} files...`); return markdownlintPromise(options).then((results) => { - const resultsString = results.toString(); + let resultsString = results.toString(); + for (const ignoreRe of (ignoreRes || [])) { + resultsString = resultsString.replace(ignoreRe, ""); + } if (resultsString.length > 0) { // eslint-disable-next-line no-console console.log(resultsString); @@ -82,12 +86,11 @@ function excludeGlobs(rootDir, ...globs) { test("https://github.com/eslint/eslint", (t) => { const rootDir = "./test-repos/eslint-eslint"; - const globPatterns = [ - join(rootDir, "docs/**/*.md"), - ...excludeGlobs(rootDir, "docs/rules/array-callback-return.md") - ]; + const globPatterns = [ join(rootDir, "docs/**/*.md") ]; const configPath = join(rootDir, ".markdownlint.yml"); - return lintTestRepo(t, globPatterns, configPath); + const ignoreRes = + [ /^[^:]+\/array-callback-return\.md: \d+: MD050\/.*$\r?\n?/gm ]; + return lintTestRepo(t, globPatterns, configPath, ignoreRes); }); test("https://github.com/mkdocs/mkdocs", (t) => { @@ -95,8 +98,13 @@ test("https://github.com/mkdocs/mkdocs", (t) => { const globPatterns = [ join(rootDir, "README.md"), join(rootDir, "CONTRIBUTING.md"), - join(rootDir, "docs/**/*.md"), - ...excludeGlobs(rootDir, "docs/CNAME") + join(rootDir, "docs"), + ...excludeGlobs( + rootDir, + "docs/CNAME", + "docs/**/*.css", + "docs/**/*.png" + ) ]; const configPath = join(rootDir, ".markdownlintrc"); return lintTestRepo(t, globPatterns, configPath); @@ -118,16 +126,11 @@ test("https://github.com/mochajs/mocha", (t) => { test("https://github.com/pi-hole/docs", (t) => { const rootDir = "./test-repos/pi-hole-docs"; - const globPatterns = [ - join(rootDir, "**/*.md"), - ...excludeGlobs(rootDir, - "docs/guides/dns/unbound.md", - "docs/index.md", - "docs/main/prerequisites.md" - ) - ]; + const globPatterns = [ join(rootDir, "**/*.md") ]; const configPath = join(rootDir, ".markdownlint.json"); - return lintTestRepo(t, globPatterns, configPath); + const ignoreRes = + [ /^[^:]+\/(unbound|index|prerequisites)\.md: \d+: MD049\/.*$\r?\n?/gm ]; + return lintTestRepo(t, globPatterns, configPath, ignoreRes); }); test("https://github.com/webhintio/hint", (t) => { @@ -153,84 +156,13 @@ const dotnetDocsDir = "./test-repos/dotnet-docs"; if (existsSync(dotnetDocsDir)) { test("https://github.com/dotnet/docs", (t) => { const rootDir = dotnetDocsDir; - const globPatterns = [ - join(rootDir, "**/*.md"), - ...excludeGlobs(rootDir, - "samples/**/*.md", - /* eslint-disable max-len */ - "docs/architecture/cloud-native/candidate-apps.md", - "docs/architecture/containerized-lifecycle/docker-devops-workflow/docker-application-outer-loop-devops-workflow.md", - "docs/architecture/dapr-for-net-developers/getting-started.md", - "docs/architecture/grpc-for-wcf-developers/channel-credentials.md", - "docs/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests.md", - "docs/architecture/microservices/multi-container-microservice-net-applications/implement-api-gateways-with-ocelot.md", - "docs/architecture/modern-web-apps-azure/architectural-principles.md", - "docs/architecture/modern-web-apps-azure/azure-hosting-recommendations-for-asp-net-web-apps.md", - "docs/architecture/modern-web-apps-azure/common-client-side-web-technologies.md", - "docs/architecture/modern-web-apps-azure/develop-asp-net-core-mvc-apps.md", - "docs/architecture/modern-web-apps-azure/development-process-for-azure.md", - "docs/architecture/modern-web-apps-azure/index.md", - "docs/core/additional-tools/dotnet-svcutil-guide.md", - "docs/core/dependency-loading/collect-details.md", - "docs/core/deploying/single-file.md", - "docs/core/deploying/trimming/trimming-options.md", - "docs/core/diagnostics/dotnet-dump.md", - "docs/core/extensions/cloud-service.md", - "docs/core/extensions/configuration-providers.md", - "docs/core/extensions/console-log-formatter.md", - "docs/core/extensions/create-resource-files.md", - "docs/core/extensions/localization.md", - "docs/core/extensions/windows-service.md", - "docs/core/install/linux-alpine.md", - "docs/core/install/windows.md", - "docs/core/porting/third-party-deps.md", - "docs/core/project-sdk/msbuild-props-desktop.md", - "docs/core/testing/unit-testing-code-coverage.md", - "docs/core/tools/troubleshoot-usage-issues.md", - "docs/core/tutorials/cli-templates-create-item-template.md", - "docs/core/tutorials/cli-templates-create-project-template.md", - "docs/core/tutorials/cli-templates-create-template-pack.md", - "docs/core/tutorials/cli-templates-create-template-package.md", - "docs/core/tutorials/cli-templates-create-item-template.md", - "docs/core/tutorials/cli-templates-create-project-template.md", - "docs/core/tutorials/cli-templates-create-template-pack.md", - "docs/core/whats-new/dotnet-core-3-0.md", - "docs/csharp/language-reference/compiler-options/code-generation.md", - "docs/csharp/linq/query-expression-basics.md", - "docs/csharp/programming-guide/classes-and-structs/named-and-optional-arguments.md", - "docs/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix.md", - "docs/csharp/tutorials/attributes.md", - "docs/csharp/whats-new/csharp-version-history.md", - "docs/framework/data/adonet/dataset-datatable-dataview/security-guidance.md", - "docs/fsharp/language-reference/compiler-directives.md", - "docs/fsharp/language-reference/exception-handling/the-try-with-expression.md", - "docs/fsharp/language-reference/xml-documentation.md", - "docs/fsharp/style-guide/conventions.md", - "docs/fsharp/tutorials/async.md", - "docs/fsharp/tutorials/asynchronous-and-concurrent-programming/async.md", - "docs/fundamentals/code-analysis/configuration-files.md", - "docs/fundamentals/code-analysis/style-rules/naming-rules.md", - "docs/machine-learning/tutorials/health-violation-classification-model-builder.md", - "docs/machine-learning/tutorials/object-detection-model-builder.md", - "docs/machine-learning/tutorials/object-detection-onnx.md", - "docs/machine-learning/tutorials/text-classification-tf.md", - "docs/standard/asynchronous-programming-patterns/event-based-asynchronous-pattern-overview.md", - "docs/standard/asynchronous-programming-patterns/implementing-the-event-based-asynchronous-pattern.md", - "docs/standard/base-types/string-comparison-net-5-plus.md", - "docs/standard/delegates-lambdas.md", - "docs/standard/io/isolated-storage.md", - "docs/standard/native-interop/tutorial-comwrappers.md", - "docs/standard/serialization/xml-schema-definition-tool-xsd-exe.md", - "docs/standard/serialization/xml-serializer-generator-tool-sgen-exe.md", - "docs/standard/native-interop/best-practices.md", - "docs/standard/serialization/binaryformatter-security-guide.md", - "docs/framework/windows-workflow-foundation/authoring-workflows-activities-and-expressions-using-imperative-code.md", - "docs/spark/how-to-guides/deploy-worker-udf-binaries.md" - /* eslint-enable max-len */ - ) - ]; + const globPatterns = [ join(rootDir, "**/*.md") ]; const configPath = join(rootDir, ".markdownlint.json"); - return lintTestRepo(t, globPatterns, configPath); + const ignoreRes = [ + /^[^:]+: \d+: (MD049|MD050)\/.*$\r?\n?/gm, + /^[^:]+\/dotnet-dump\.md: \d+: MD033\/.*$\r?\n?/gm + ]; + return lintTestRepo(t, globPatterns, configPath, ignoreRes); }); } @@ -238,24 +170,9 @@ const v8v8DevDir = "./test-repos/v8-v8-dev"; if (existsSync(v8v8DevDir)) { test("https://github.com/v8/v8.dev", (t) => { const rootDir = v8v8DevDir; - const globPatterns = [ - join(rootDir, "src/**/*.md"), - ...excludeGlobs(rootDir, - "src/blog/array-sort.md", - "src/blog/code-caching-for-devs.md", - "src/blog/fast-async.md", - "src/blog/liftoff.md", - "src/blog/pointer-compression.md", - "src/blog/react-cliff.md", - "src/blog/slack-tracking.md", - "src/blog/v8-release-74.md", - "src/features/bigint.md", - "src/features/dynamic-import.md", - "src/features/globalthis.md", - "src/features/modules.md" - ) - ]; + const globPatterns = [ join(rootDir, "src/**/*.md") ]; const configPath = join(rootDir, ".markdownlint.json"); - return lintTestRepo(t, globPatterns, configPath); + const ignoreRes = [ /^[^:]+: \d+: MD049\/.*$\r?\n?/gm ]; + return lintTestRepo(t, globPatterns, configPath, ignoreRes); }); } From 7330ea49463490115337c4365f9c6c4a215277d1 Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 30 Nov 2021 21:28:59 -0800 Subject: [PATCH 33/69] Add information about parser/index to error messages when parsing configuration file content. --- demo/markdownlint-browser.js | 3 ++- lib/markdownlint.js | 3 ++- test/markdownlint-test.js | 10 ++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 2296d47f..5fb5f0c7 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -1717,13 +1717,14 @@ function parseConfiguration(name, content, parsers) { var config = null; var message = ""; var errors = []; + var index = 0; // Try each parser (parsers || [JSON.parse]).every(function (parser) { try { config = parser(content); } catch (error) { - errors.push(error.message); + errors.push("Parser " + index++ + ": " + error.message); } return !config; }); diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 11a18c7b..5ce2b17b 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -896,12 +896,13 @@ function parseConfiguration(name, content, parsers) { let config = null; let message = ""; const errors = []; + let index = 0; // Try each parser (parsers || [ JSON.parse ]).every((parser) => { try { config = parser(content); } catch (error) { - errors.push(error.message); + errors.push(`Parser ${index++}: ${error.message}`); } return !config; }); diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index bf29b4b0..9d2784ba 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -1321,7 +1321,7 @@ test.cb("configBadHybrid", (t) => { t.true(err instanceof Error, "Error not instance of Error."); t.truthy(err.message.match( // eslint-disable-next-line max-len - /^Unable to parse '[^']*'; Unexpected token \S+ in JSON at position \d+;/ + /^Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+;/ ), "Error message unexpected."); t.true(!result, "Got result for bad child JSON."); t.end(); @@ -1390,7 +1390,8 @@ test("configBadJsonSync", (t) => { }, { "message": - /Unable to parse '[^']*'; Unexpected token \S+ in JSON at position \d+/ + // eslint-disable-next-line max-len + /Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+/ }, "Did not get correct exception for bad JSON." ); @@ -1404,7 +1405,8 @@ test("configBadChildJsonSync", (t) => { }, { "message": - /Unable to parse '[^']*'; Unexpected token \S+ in JSON at position \d+/ + // eslint-disable-next-line max-len + /Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+/ }, "Did not get correct exception for bad child JSON." ); @@ -1498,7 +1500,7 @@ test("configBadHybridSync", (t) => { }, { // eslint-disable-next-line max-len - "message": /^Unable to parse '[^']*'; Unexpected token \S+ in JSON at position \d+;/ + "message": /^Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+;/ }, "Did not get correct exception for bad content." ); From 4d57de5c06c868667c3e723f6ab03d4969a0460f Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 30 Nov 2021 21:58:54 -0800 Subject: [PATCH 34/69] Duplicate markdownlint-test.js to markdownlint-test-config.js to split out config tests. --- test/markdownlint-test-config.js | 1767 ++++++++++++++++++++++++++++++ 1 file changed, 1767 insertions(+) create mode 100644 test/markdownlint-test-config.js diff --git a/test/markdownlint-test-config.js b/test/markdownlint-test-config.js new file mode 100644 index 00000000..9d2784ba --- /dev/null +++ b/test/markdownlint-test-config.js @@ -0,0 +1,1767 @@ +// @ts-check + +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const jsYaml = require("js-yaml"); +const md = require("markdown-it")(); +const pluginInline = require("markdown-it-for-inline"); +const pluginSub = require("markdown-it-sub"); +const pluginSup = require("markdown-it-sup"); +const pluginTexMath = require("markdown-it-texmath"); +const stripJsonComments = require("strip-json-comments"); +const test = require("ava").default; +const tv4 = require("tv4"); +const { homepage, version } = require("../package.json"); +const markdownlint = require("../lib/markdownlint"); +const constants = require("../lib/constants"); +const rules = require("../lib/rules"); +const customRules = require("./rules/rules.js"); +const configSchema = require("../schema/markdownlint-config-schema.json"); + +const pluginTexMathOptions = { + "engine": { + "renderToString": () => "" + } +}; +const deprecatedRuleNames = new Set(constants.deprecatedRuleNames); +const configSchemaStrict = { + ...configSchema, + "additionalProperties": false +}; + +test.cb("simpleAsync", (t) => { + t.plan(2); + const options = { + "strings": { + "content": "# Heading" + } + }; + const expected = "content: 1: MD047/single-trailing-newline " + + "Files should end with a single newline character"; + markdownlint(options, (err, actual) => { + t.falsy(err); + t.is(actual.toString(), expected, "Unexpected results."); + t.end(); + }); +}); + +test("simpleSync", (t) => { + t.plan(1); + const options = { + "strings": { + "content": "# Heading" + } + }; + const expected = "content: 1: MD047/single-trailing-newline " + + "Files should end with a single newline character"; + const actual = markdownlint.sync(options).toString(); + t.is(actual, expected, "Unexpected results."); +}); + +test("simplePromise", (t) => { + t.plan(1); + const options = { + "strings": { + "content": "# Heading" + } + }; + const expected = "content: 1: MD047/single-trailing-newline " + + "Files should end with a single newline character"; + return markdownlint.promises.markdownlint(options).then((actual) => { + t.is(actual.toString(), expected, "Unexpected results."); + }); +}); + +test.cb("projectFilesNoInlineConfig", (t) => { + t.plan(2); + const options = { + "files": [ + "README.md", + "CONTRIBUTING.md", + "doc/CustomRules.md", + "doc/Prettier.md", + "helpers/README.md" + ], + "noInlineConfig": true, + "config": { + "line-length": { "line_length": 150 }, + "no-duplicate-heading": false + } + }; + markdownlint(options, function callback(err, actual) { + t.falsy(err); + const expected = { + "README.md": [], + "CONTRIBUTING.md": [], + "doc/CustomRules.md": [], + "doc/Prettier.md": [], + "helpers/README.md": [] + }; + t.deepEqual(actual, expected, "Issue(s) with project files."); + t.end(); + }); +}); + +test.cb("projectFilesInlineConfig", (t) => { + t.plan(2); + const options = { + "files": [ "doc/Rules.md" ], + "config": { + "no-inline-html": false + } + }; + markdownlint(options, function callback(err, actual) { + t.falsy(err); + const expected = { + "doc/Rules.md": [] + }; + t.deepEqual(actual, expected, "Issue(s) with project files."); + t.end(); + }); +}); + +test.cb("stringInputLineEndings", (t) => { + t.plan(2); + const options = { + "strings": { + "cr": "One\rTwo\r#Three\n", + "lf": "One\nTwo\n#Three\n", + "crlf": "One\r\nTwo\r\n#Three\n", + "mixed": "One\rTwo\n#Three\n" + }, + "config": { + "MD041": false + }, + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "cr": { "MD018": [ 3 ] }, + "lf": { "MD018": [ 3 ] }, + "crlf": { "MD018": [ 3 ] }, + "mixed": { "MD018": [ 3 ] } + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("inputOnlyNewline", (t) => { + t.plan(2); + const options = { + "strings": { + "cr": "\r", + "lf": "\n", + "crlf": "\r\n" + }, + "config": { + "default": false + } + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "cr": [], + "lf": [], + "crlf": [] + }; + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("defaultTrue", (t) => { + t.plan(2); + const options = { + "files": [ + "./test/atx_heading_spacing.md", + "./test/first_heading_bad_atx.md" + ], + "config": { + "default": true + }, + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/atx_heading_spacing.md": { + "MD018": [ 1 ], + "MD019": [ 3, 5 ], + "MD041": [ 1 ] + }, + "./test/first_heading_bad_atx.md": { + "MD041": [ 1 ] + } + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("defaultFalse", (t) => { + t.plan(2); + const options = { + "files": [ + "./test/atx_heading_spacing.md", + "./test/first_heading_bad_atx.md" + ], + "config": { + "default": false + }, + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/atx_heading_spacing.md": {}, + "./test/first_heading_bad_atx.md": {} + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("defaultUndefined", (t) => { + t.plan(2); + const options = { + "files": [ + "./test/atx_heading_spacing.md", + "./test/first_heading_bad_atx.md" + ], + "config": {}, + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/atx_heading_spacing.md": { + "MD018": [ 1 ], + "MD019": [ 3, 5 ], + "MD041": [ 1 ] + }, + "./test/first_heading_bad_atx.md": { + "MD041": [ 1 ] + } + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("disableRules", (t) => { + t.plan(2); + const options = { + "files": [ + "./test/atx_heading_spacing.md", + "./test/first_heading_bad_atx.md" + ], + "config": { + "MD002": false, + "default": true, + "MD019": false, + "first-line-h1": false + }, + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/atx_heading_spacing.md": { + "MD018": [ 1 ] + }, + "./test/first_heading_bad_atx.md": {} + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("enableRules", (t) => { + t.plan(2); + const options = { + "files": [ + "./test/atx_heading_spacing.md", + "./test/first_heading_bad_atx.md" + ], + "config": { + "MD002": true, + "default": false, + "no-multiple-space-atx": true + }, + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/atx_heading_spacing.md": { + "MD002": [ 3 ], + "MD019": [ 3, 5 ] + }, + "./test/first_heading_bad_atx.md": { + "MD002": [ 1 ] + } + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("enableRulesMixedCase", (t) => { + t.plan(2); + const options = { + "files": [ + "./test/atx_heading_spacing.md", + "./test/first_heading_bad_atx.md" + ], + "config": { + "Md002": true, + "DeFaUlT": false, + "nO-mUlTiPlE-sPaCe-AtX": true + }, + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/atx_heading_spacing.md": { + "MD002": [ 3 ], + "MD019": [ 3, 5 ] + }, + "./test/first_heading_bad_atx.md": { + "MD002": [ 1 ] + } + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("disableTag", (t) => { + t.plan(2); + const options = { + "files": [ + "./test/atx_heading_spacing.md", + "./test/first_heading_bad_atx.md" + ], + "config": { + "default": true, + "spaces": false + }, + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/atx_heading_spacing.md": { + "MD041": [ 1 ] + }, + "./test/first_heading_bad_atx.md": { + "MD041": [ 1 ] + } + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("enableTag", (t) => { + t.plan(2); + const options = { + "files": [ + "./test/atx_heading_spacing.md", + "./test/first_heading_bad_atx.md" + ], + "config": { + "default": false, + "spaces": true, + "notatag": true + }, + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/atx_heading_spacing.md": { + "MD018": [ 1 ], + "MD019": [ 3, 5 ] + }, + "./test/first_heading_bad_atx.md": {} + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("enableTagMixedCase", (t) => { + t.plan(2); + const options = { + "files": [ + "./test/atx_heading_spacing.md", + "./test/first_heading_bad_atx.md" + ], + "config": { + "DeFaUlT": false, + "SpAcEs": true, + "NoTaTaG": true + }, + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/atx_heading_spacing.md": { + "MD018": [ 1 ], + "MD019": [ 3, 5 ] + }, + "./test/first_heading_bad_atx.md": {} + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("styleFiles", (t) => { + t.plan(4); + fs.readdir("./style", function readdir(err, files) { + t.falsy(err); + files.forEach(function forFile(file) { + t.truthy(require(path.join("../style", file)), "Unable to load/parse."); + }); + t.end(); + }); +}); + +test.cb("styleAll", (t) => { + t.plan(2); + const options = { + "files": [ "./test/break-all-the-rules.md" ], + "config": require("../style/all.json"), + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/break-all-the-rules.md": { + "MD001": [ 3 ], + "MD003": [ 5, 31 ], + "MD004": [ 8 ], + "MD005": [ 12 ], + "MD007": [ 8, 11 ], + "MD009": [ 14 ], + "MD010": [ 14 ], + "MD011": [ 16 ], + "MD012": [ 18 ], + "MD013": [ 21 ], + "MD014": [ 23 ], + "MD018": [ 25 ], + "MD019": [ 27 ], + "MD020": [ 29 ], + "MD021": [ 31 ], + "MD022": [ 86 ], + "MD023": [ 40 ], + "MD024": [ 35 ], + "MD026": [ 40 ], + "MD027": [ 42 ], + "MD028": [ 43 ], + "MD029": [ 47 ], + "MD030": [ 8 ], + "MD031": [ 50 ], + "MD032": [ 7, 8, 51 ], + "MD033": [ 55 ], + "MD034": [ 57 ], + "MD035": [ 61 ], + "MD036": [ 65 ], + "MD037": [ 67 ], + "MD038": [ 69 ], + "MD039": [ 71 ], + "MD040": [ 73 ], + "MD041": [ 1 ], + "MD042": [ 81 ], + "MD045": [ 85 ], + "MD046": [ 49, 73, 77 ], + "MD047": [ 96 ], + "MD048": [ 77 ], + "MD049": [ 90 ], + "MD050": [ 94 ] + } + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("styleRelaxed", (t) => { + t.plan(2); + const options = { + "files": [ "./test/break-all-the-rules.md" ], + "config": require("../style/relaxed.json"), + "resultVersion": 0 + }; + markdownlint(options, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/break-all-the-rules.md": { + "MD001": [ 3 ], + "MD003": [ 5, 31 ], + "MD004": [ 8 ], + "MD005": [ 12 ], + "MD011": [ 16 ], + "MD014": [ 23 ], + "MD018": [ 25 ], + "MD019": [ 27 ], + "MD020": [ 29 ], + "MD021": [ 31 ], + "MD022": [ 86 ], + "MD023": [ 40 ], + "MD024": [ 35 ], + "MD026": [ 40 ], + "MD029": [ 47 ], + "MD031": [ 50 ], + "MD032": [ 7, 8, 51 ], + "MD035": [ 61 ], + "MD036": [ 65 ], + "MD042": [ 81 ], + "MD045": [ 85 ], + "MD046": [ 49, 73, 77 ], + "MD047": [ 96 ], + "MD048": [ 77 ], + "MD049": [ 90 ], + "MD050": [ 94 ] + } + }; + // @ts-ignore + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("nullFrontMatter", (t) => { + t.plan(2); + markdownlint({ + "strings": { + "content": "---\n\t\n---\n# Heading\n" + }, + "frontMatter": null, + "config": { + "default": false, + "MD010": true + }, + "resultVersion": 0 + }, function callback(err, result) { + t.falsy(err); + const expectedResult = { + "content": { "MD010": [ 2 ] } + }; + // @ts-ignore + t.deepEqual(result, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("customFrontMatter", (t) => { + t.plan(2); + markdownlint({ + "strings": { + "content": "\n\t\n\n# Heading\n" + }, + "frontMatter": /[^]*<\/head>/, + "config": { + "default": false, + "MD010": true + } + }, function callback(err, result) { + t.falsy(err); + const expectedResult = { + "content": [] + }; + t.deepEqual(result, expectedResult, "Did not get empty results."); + t.end(); + }); +}); + +test.cb("noInlineConfig", (t) => { + t.plan(2); + markdownlint({ + "strings": { + "content": [ + "# Heading", + "", + "\tTab", + "", + "", + "", + "\tTab", + "", + "", + "", + "\tTab\n" + ].join("\n") + }, + "noInlineConfig": true, + "resultVersion": 0 + }, function callback(err, result) { + t.falsy(err); + const expectedResult = { + "content": { + "MD010": [ 3, 7, 11 ] + } + }; + // @ts-ignore + t.deepEqual(result, expectedResult, "Undetected issues."); + t.end(); + }); +}); + +test.cb("readmeHeadings", (t) => { + t.plan(2); + markdownlint({ + "files": "README.md", + "noInlineConfig": true, + "config": { + "default": false, + "MD013": { + "line_length": 150 + }, + "MD043": { + "headings": [ + "# markdownlint", + "## Install", + "## Overview", + "### Related", + "## Demonstration", + "## Rules / Aliases", + "## Tags", + "## Configuration", + "## API", + "### Linting", + "#### options", + "##### options.customRules", + "##### options.files", + "##### options.strings", + "##### options.config", + "##### options.frontMatter", + "##### options.handleRuleFailures", + "##### options.noInlineConfig", + "##### options.resultVersion", + "##### options.markdownItPlugins", + "##### options.fs", + "#### callback", + "#### result", + "### Config", + "#### file", + "#### parsers", + "#### fs", + "#### callback", + "#### result", + "## Usage", + "## Browser", + "## Examples", + "## Contributing", + "## History" + ] + } + } + }, function callback(err, result) { + t.falsy(err); + const expected = { "README.md": [] }; + t.deepEqual(result, expected, "Unexpected issues."); + t.end(); + }); +}); + +test.cb("filesArrayNotModified", (t) => { + t.plan(2); + const files = [ + "./test/atx_heading_spacing.md", + "./test/first_heading_bad_atx.md" + ]; + const expectedFiles = [ ...files ]; + markdownlint({ "files": files }, function callback(err) { + t.falsy(err); + t.deepEqual(files, expectedFiles, "Files modified."); + t.end(); + }); +}); + +test.cb("filesArrayAsString", (t) => { + t.plan(2); + markdownlint({ + "files": "README.md", + "noInlineConfig": true, + "config": { + "MD013": { "line_length": 150 }, + "MD024": false + } + }, function callback(err, actual) { + t.falsy(err); + const expected = { "README.md": [] }; + t.deepEqual(actual, expected, "Unexpected issues."); + t.end(); + }); +}); + +test.cb("missingOptions", (t) => { + t.plan(2); + markdownlint(null, function callback(err, result) { + t.falsy(err); + t.deepEqual( + result, + {}, + "Did not get empty result for missing options." + ); + t.end(); + }); +}); + +test.cb("missingFilesAndStrings", (t) => { + t.plan(2); + markdownlint({}, function callback(err, result) { + t.falsy(err); + t.truthy(result, "Did not get result for missing files/strings."); + t.end(); + }); +}); + +test("missingCallback", (t) => { + t.plan(0); + // @ts-ignore + markdownlint(); +}); + +test.cb("badFile", (t) => { + t.plan(4); + markdownlint({ + "files": [ "./badFile" ] + }, function callback(err, result) { + t.truthy(err, "Did not get an error for bad file."); + t.true(err instanceof Error, "Error not instance of Error."); + // @ts-ignore + t.is(err.code, "ENOENT", "Error code for bad file not ENOENT."); + t.true(!result, "Got result for bad file."); + t.end(); + }); +}); + +test("badFileSync", (t) => { + t.plan(1); + t.throws( + function badFileCall() { + markdownlint.sync({ + "files": [ "./badFile" ] + }); + }, + { + "message": /ENOENT/ + }, + "Did not get correct exception for bad file." + ); +}); + +test.cb("badFilePromise", (t) => { + t.plan(3); + markdownlint.promises.markdownlint({ + "files": [ "./badFile" ] + }).then( + null, + (error) => { + t.truthy(error, "Did not get an error for bad file."); + t.true(error instanceof Error, "Error not instance of Error."); + t.is(error.code, "ENOENT", "Error code for bad file not ENOENT."); + t.end(); + } + ); +}); + +test.cb("missingStringValue", (t) => { + t.plan(2); + markdownlint({ + "strings": { + "undefined": undefined, + "null": null, + "empty": "" + } + }, function callback(err, result) { + t.falsy(err); + const expectedResult = { + "undefined": [], + "null": [], + "empty": [] + }; + t.deepEqual(result, expectedResult, "Did not get empty results."); + t.end(); + }); +}); + +test("customFileSystemSync", (t) => { + t.plan(2); + const file = "/dir/file.md"; + const fsApi = { + "readFileSync": (p) => { + t.is(p, file); + return "# Heading"; + } + }; + const result = markdownlint.sync({ + "files": file, + "fs": fsApi + }); + t.deepEqual(result[file].length, 1, "Did not report violations."); +}); + +test.cb("customFileSystemAsync", (t) => { + t.plan(3); + const file = "/dir/file.md"; + const fsApi = { + "readFile": (p, o, cb) => { + t.is(p, file); + cb(null, "# Heading"); + } + }; + markdownlint({ + "files": file, + "fs": fsApi + }, function callback(err, result) { + t.falsy(err); + t.deepEqual(result[file].length, 1, "Did not report violations."); + t.end(); + }); +}); +test.cb("readme", (t) => { + t.plan(119); + const tagToRules = {}; + rules.forEach(function forRule(rule) { + rule.tags.forEach(function forTag(tag) { + const tagRules = tagToRules[tag] || []; + tagRules.push(rule.names[0]); + tagToRules[tag] = tagRules; + }); + }); + fs.readFile("README.md", "utf8", + function readFile(err, contents) { + t.falsy(err); + const rulesLeft = [ ...rules ]; + let seenRelated = false; + let seenRules = false; + let inRules = false; + let seenTags = false; + let inTags = false; + // @ts-ignore + md.parse(contents, {}).forEach(function forToken(token) { + if ( + (token.type === "bullet_list_open") && + (token.level === 0) + ) { + if (!seenRelated) { + seenRelated = true; + } else if (!seenRules) { + seenRules = true; + inRules = true; + } else if (!seenTags) { + seenTags = true; + inTags = true; + } + } else if ( + (token.type === "bullet_list_close") && + (token.level === 0) + ) { + inRules = false; + inTags = false; + } else if (token.type === "inline") { + if (inRules) { + const rule = rulesLeft.shift(); + t.truthy(rule, + "Missing rule implementation for " + token.content + "."); + if (rule) { + const ruleName = rule.names[0]; + const ruleAliases = rule.names.slice(1); + let expected = "**[" + ruleName + "](doc/Rules.md#" + + ruleName.toLowerCase() + ")** *" + + ruleAliases.join("/") + "* - " + rule.description; + if (deprecatedRuleNames.has(ruleName)) { + expected = "~~" + expected + "~~"; + } + t.is(token.content, expected, "Rule mismatch."); + } + } else if (inTags) { + const parts = + token.content.replace(/\*\*/g, "").split(/ - |, |,\n/); + const tag = parts.shift(); + t.deepEqual(parts, tagToRules[tag] || [], + "Rule mismatch for tag " + tag + "."); + delete tagToRules[tag]; + } + } + }); + const ruleLeft = rulesLeft.shift(); + t.true(!ruleLeft, + "Missing rule documentation for " + + (ruleLeft || "[NO RULE]").toString() + "."); + const tagLeft = Object.keys(tagToRules).shift(); + t.true(!tagLeft, "Undocumented tag " + tagLeft + "."); + t.end(); + }); +}); + +test.cb("rules", (t) => { + t.plan(352); + fs.readFile("doc/Rules.md", "utf8", + (err, contents) => { + t.falsy(err); + const rulesLeft = [ ...rules ]; + let inHeading = false; + let rule = null; + let ruleHasTags = true; + let ruleHasAliases = true; + let ruleUsesParams = null; + const tagAliasParameterRe = /, |: | /; + // eslint-disable-next-line func-style + const testTagsAliasesParams = (r) => { + // eslint-disable-next-line unicorn/prefer-default-parameters + r = r || "[NO RULE]"; + t.true(ruleHasTags, + "Missing tags for rule " + r.names + "."); + t.true(ruleHasAliases, + "Missing aliases for rule " + r.names + "."); + t.true(!ruleUsesParams, + "Missing parameters for rule " + r.names + "."); + }; + // @ts-ignore + md.parse(contents, {}).forEach(function forToken(token) { + if ((token.type === "heading_open") && (token.tag === "h2")) { + inHeading = true; + } else if (token.type === "heading_close") { + inHeading = false; + } else if (token.type === "inline") { + if (inHeading) { + testTagsAliasesParams(rule); + rule = rulesLeft.shift(); + ruleHasTags = false; + ruleHasAliases = false; + t.truthy(rule, + "Missing rule implementation for " + token.content + "."); + const ruleName = rule.names[0]; + let headingContent = ruleName + " - " + rule.description; + if (deprecatedRuleNames.has(ruleName)) { + headingContent = "~~" + headingContent + "~~"; + } + t.is(token.content, + headingContent, + "Rule mismatch."); + ruleUsesParams = rule.function.toString() + .match(/params\.config\.[_a-z]*/gi); + if (ruleUsesParams) { + ruleUsesParams = ruleUsesParams.map(function forUse(use) { + return use.split(".").pop(); + }); + ruleUsesParams.sort(); + } + } else if (token.content.startsWith("Tags: ") && rule) { + t.deepEqual(token.content.split(tagAliasParameterRe).slice(1), + rule.tags, "Tag mismatch for rule " + rule.names + "."); + ruleHasTags = true; + } else if (token.content.startsWith("Aliases: ") && rule) { + t.deepEqual(token.content.split(tagAliasParameterRe).slice(1), + rule.names.slice(1), + "Alias mismatch for rule " + rule.names + "."); + ruleHasAliases = true; + } else if (token.content.startsWith("Parameters: ") && rule) { + let inDetails = false; + const parameters = token.content.split(tagAliasParameterRe) + .slice(1) + .filter(function forPart(part) { + inDetails = inDetails || (part[0] === "("); + return !inDetails; + }); + parameters.sort(); + t.deepEqual(parameters, ruleUsesParams, + "Missing parameter for rule " + rule.names); + ruleUsesParams = null; + } + } + }); + const ruleLeft = rulesLeft.shift(); + t.true(!ruleLeft, + "Missing rule documentation for " + + (ruleLeft || { "names": "[NO RULE]" }).names + "."); + if (rule) { + testTagsAliasesParams(rule); + } + t.end(); + }); +}); + +test("validateJsonUsingConfigSchemaStrict", (t) => { + const jsonFileRe = /\.json$/i; + const resultsFileRe = /\.results\.json$/i; + const jsConfigFileRe = /^jsconfig\.json$/i; + const wrongTypesFileRe = /wrong-types-in-config-file.json$/i; + const testDirectory = __dirname; + const testFiles = fs.readdirSync(testDirectory); + testFiles.filter(function filterFile(file) { + return jsonFileRe.test(file) && + !resultsFileRe.test(file) && + !jsConfigFileRe.test(file) && + !wrongTypesFileRe.test(file); + }).forEach(function forFile(file) { + const data = fs.readFileSync( + path.join(testDirectory, file), + "utf8" + ); + t.true( + // @ts-ignore + tv4.validate(JSON.parse(data), configSchemaStrict), + file + "\n" + JSON.stringify(tv4.error, null, 2)); + }); +}); + +test("validateConfigSchemaAllowsUnknownProperties", (t) => { + t.plan(4); + const testCases = [ + { + "property": true + }, + { + "property": { + "object": 1 + } + } + ]; + testCases.forEach((testCase) => { + t.true( + // @ts-ignore + tv4.validate(testCase, configSchema), + "Unknown property blocked by default: " + JSON.stringify(testCase)); + t.false( + // @ts-ignore + tv4.validate(testCase, configSchemaStrict), + "Unknown property allowed when strict: " + JSON.stringify(testCase)); + }); +}); + +test("validateConfigSchemaAppliesToUnknownProperties", (t) => { + t.plan(4); + for (const allowed of [ true, {} ]) { + t.true( + // @ts-ignore + tv4.validate({ "property": allowed }, configSchema), + `Unknown property value ${allowed} blocked`); + } + for (const blocked of [ 2, "string" ]) { + t.false( + // @ts-ignore + tv4.validate({ "property": blocked }, configSchema), + `Unknown property value ${blocked} allowed`); + } +}); + +test("validateConfigExampleJson", (t) => { + t.plan(2); + + // Validate JSONC + const fileJson = ".markdownlint.jsonc"; + const dataJson = fs.readFileSync( + path.join(__dirname, "../schema", fileJson), + "utf8" + ); + const jsonObject = JSON.parse(stripJsonComments(dataJson)); + t.true( + // @ts-ignore + tv4.validate(jsonObject, configSchemaStrict), + fileJson + "\n" + JSON.stringify(tv4.error, null, 2)); + + // Validate YAML + const fileYaml = ".markdownlint.yaml"; + const dataYaml = fs.readFileSync( + path.join(__dirname, "../schema", fileYaml), + "utf8" + ); + const yamlObject = jsYaml.load(dataYaml); + t.deepEqual(yamlObject, jsonObject, + "YAML example does not match JSON example."); +}); + +test.cb("configSingle", (t) => { + t.plan(2); + markdownlint.readConfig("./test/config/config-child.json", + function callback(err, actual) { + t.falsy(err); + const expected = require("./config/config-child.json"); + t.deepEqual(actual, expected, "Config object not correct."); + t.end(); + }); +}); + +test.cb("configAbsolute", (t) => { + t.plan(2); + markdownlint.readConfig(path.join(__dirname, "config", "config-child.json"), + function callback(err, actual) { + t.falsy(err); + const expected = require("./config/config-child.json"); + t.deepEqual(actual, expected, "Config object not correct."); + t.end(); + }); +}); + +test.cb("configMultiple", (t) => { + t.plan(2); + markdownlint.readConfig("./test/config/config-grandparent.json", + function callback(err, actual) { + t.falsy(err); + const expected = { + ...require("./config/config-child.json"), + ...require("./config/config-parent.json"), + ...require("./config/config-grandparent.json") + }; + delete expected.extends; + t.deepEqual(actual, expected, "Config object not correct."); + t.end(); + }); +}); + +test.cb("configMultipleWithRequireResolve", (t) => { + t.plan(2); + markdownlint.readConfig("./test/config/config-packageparent.json", + function callback(err, actual) { + t.falsy(err); + const expected = { + ...require("./node_modules/pseudo-package/config-frompackage.json"), + ...require("./config/config-packageparent.json") + }; + delete expected.extends; + t.deepEqual(actual, expected, "Config object not correct."); + t.end(); + }); +}); + +test.cb("configCustomFileSystem", (t) => { + t.plan(5); + const file = path.resolve("/dir/file.json"); + const extended = path.resolve("/dir/extended.json"); + const fileContent = { + "extends": extended, + "default": true, + "MD001": false + }; + const extendedContent = { + "MD001": true, + "MD002": true + }; + const fsApi = { + "access": (p, m, cb) => { + t.is(p, extended); + return (cb || m)(); + }, + "readFile": (p, o, cb) => { + switch (p) { + case file: + t.is(p, file); + return cb(null, JSON.stringify(fileContent)); + case extended: + t.is(p, extended); + return cb(null, JSON.stringify(extendedContent)); + default: + return t.fail(); + } + } + }; + markdownlint.readConfig( + file, + null, + fsApi, + function callback(err, actual) { + t.falsy(err); + const expected = { + ...extendedContent, + ...fileContent + }; + delete expected.extends; + t.deepEqual(actual, expected, "Config object not correct."); + t.end(); + }); +}); + +test.cb("configBadFile", (t) => { + t.plan(4); + markdownlint.readConfig("./test/config/config-badfile.json", + function callback(err, result) { + t.truthy(err, "Did not get an error for bad file."); + t.true(err instanceof Error, "Error not instance of Error."); + // @ts-ignore + t.is(err.code, "ENOENT", "Error code for bad file not ENOENT."); + t.true(!result, "Got result for bad file."); + t.end(); + }); +}); + +test.cb("configBadChildFile", (t) => { + t.plan(4); + markdownlint.readConfig("./test/config/config-badchildfile.json", + function callback(err, result) { + t.truthy(err, "Did not get an error for bad child file."); + t.true(err instanceof Error, "Error not instance of Error."); + // @ts-ignore + t.is(err.code, "ENOENT", + "Error code for bad child file not ENOENT."); + t.true(!result, "Got result for bad child file."); + t.end(); + }); +}); + +test.cb("configBadChildPackage", (t) => { + t.plan(4); + markdownlint.readConfig("./test/config/config-badchildpackage.json", + function callback(err, result) { + t.truthy(err, "Did not get an error for bad child package."); + t.true(err instanceof Error, "Error not instance of Error."); + // @ts-ignore + t.is(err.code, "ENOENT", + "Error code for bad child package not ENOENT."); + t.true(!result, "Got result for bad child package."); + t.end(); + }); +}); + +test.cb("configBadJson", (t) => { + t.plan(3); + markdownlint.readConfig("./test/config/config-badjson.json", + function callback(err, result) { + t.truthy(err, "Did not get an error for bad JSON."); + t.true(err instanceof Error, "Error not instance of Error."); + t.true(!result, "Got result for bad JSON."); + t.end(); + }); +}); + +test.cb("configBadChildJson", (t) => { + t.plan(3); + markdownlint.readConfig("./test/config/config-badchildjson.json", + function callback(err, result) { + t.truthy(err, "Did not get an error for bad child JSON."); + t.true(err instanceof Error, "Error not instance of Error."); + t.true(!result, "Got result for bad child JSON."); + t.end(); + }); +}); + +test.cb("configSingleYaml", (t) => { + t.plan(2); + markdownlint.readConfig( + "./test/config/config-child.yaml", + // @ts-ignore + [ require("js-yaml").load ], + function callback(err, actual) { + t.falsy(err); + const expected = require("./config/config-child.json"); + t.deepEqual(actual, expected, "Config object not correct."); + t.end(); + }); +}); + +test.cb("configMultipleYaml", (t) => { + t.plan(2); + markdownlint.readConfig( + "./test/config/config-grandparent.yaml", + // @ts-ignore + [ require("js-yaml").load ], + function callback(err, actual) { + t.falsy(err); + const expected = { + ...require("./config/config-child.json"), + ...require("./config/config-parent.json"), + ...require("./config/config-grandparent.json") + }; + delete expected.extends; + t.deepEqual(actual, expected, "Config object not correct."); + t.end(); + }); +}); + +test.cb("configMultipleHybrid", (t) => { + t.plan(2); + markdownlint.readConfig( + "./test/config/config-grandparent-hybrid.yaml", + // @ts-ignore + [ JSON.parse, require("toml").parse, require("js-yaml").load ], + function callback(err, actual) { + t.falsy(err); + const expected = { + ...require("./config/config-child.json"), + ...require("./config/config-parent.json"), + ...require("./config/config-grandparent.json") + }; + delete expected.extends; + t.like(actual, expected, "Config object not correct."); + t.end(); + }); +}); + +test.cb("configBadHybrid", (t) => { + t.plan(4); + markdownlint.readConfig( + "./test/config/config-badcontent.txt", + // @ts-ignore + [ JSON.parse, require("toml").parse, require("js-yaml").load ], + function callback(err, result) { + t.truthy(err, "Did not get an error for bad child JSON."); + t.true(err instanceof Error, "Error not instance of Error."); + t.truthy(err.message.match( + // eslint-disable-next-line max-len + /^Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+;/ + ), "Error message unexpected."); + t.true(!result, "Got result for bad child JSON."); + t.end(); + }); +}); + +test("configSingleSync", (t) => { + t.plan(1); + const actual = markdownlint.readConfigSync("./test/config/config-child.json"); + const expected = require("./config/config-child.json"); + t.deepEqual(actual, expected, "Config object not correct."); +}); + +test("configAbsoluteSync", (t) => { + t.plan(1); + const actual = markdownlint.readConfigSync( + path.join(__dirname, "config", "config-child.json")); + const expected = require("./config/config-child.json"); + t.deepEqual(actual, expected, "Config object not correct."); +}); + +test("configMultipleSync", (t) => { + t.plan(1); + const actual = + markdownlint.readConfigSync("./test/config/config-grandparent.json"); + const expected = { + ...require("./config/config-child.json"), + ...require("./config/config-parent.json"), + ...require("./config/config-grandparent.json") + }; + delete expected.extends; + t.deepEqual(actual, expected, "Config object not correct."); +}); + +test("configBadFileSync", (t) => { + t.plan(1); + t.throws( + function badFileCall() { + markdownlint.readConfigSync("./test/config/config-badfile.json"); + }, + { + "message": /ENOENT/ + }, + "Did not get correct exception for bad file." + ); +}); + +test("configBadChildFileSync", (t) => { + t.plan(1); + t.throws( + function badChildFileCall() { + markdownlint.readConfigSync("./test/config/config-badchildfile.json"); + }, + { + "message": /ENOENT/ + }, + "Did not get correct exception for bad child file." + ); +}); + +test("configBadJsonSync", (t) => { + t.plan(1); + t.throws( + function badJsonCall() { + markdownlint.readConfigSync("./test/config/config-badjson.json"); + }, + { + "message": + // eslint-disable-next-line max-len + /Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+/ + }, + "Did not get correct exception for bad JSON." + ); +}); + +test("configBadChildJsonSync", (t) => { + t.plan(1); + t.throws( + function badChildJsonCall() { + markdownlint.readConfigSync("./test/config/config-badchildjson.json"); + }, + { + "message": + // eslint-disable-next-line max-len + /Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+/ + }, + "Did not get correct exception for bad child JSON." + ); +}); + +test("configSingleYamlSync", (t) => { + t.plan(1); + const actual = markdownlint.readConfigSync( + // @ts-ignore + "./test/config/config-child.yaml", [ require("js-yaml").load ]); + const expected = require("./config/config-child.json"); + t.deepEqual(actual, expected, "Config object not correct."); +}); + +test("configMultipleYamlSync", (t) => { + t.plan(1); + const actual = markdownlint.readConfigSync( + // @ts-ignore + "./test/config/config-grandparent.yaml", [ require("js-yaml").load ]); + const expected = { + ...require("./config/config-child.json"), + ...require("./config/config-parent.json"), + ...require("./config/config-grandparent.json") + }; + delete expected.extends; + t.deepEqual(actual, expected, "Config object not correct."); +}); + +test("configMultipleHybridSync", (t) => { + t.plan(1); + const actual = markdownlint.readConfigSync( + "./test/config/config-grandparent-hybrid.yaml", + // @ts-ignore + [ JSON.parse, require("toml").parse, require("js-yaml").load ]); + const expected = { + ...require("./config/config-child.json"), + ...require("./config/config-parent.json"), + ...require("./config/config-grandparent.json") + }; + delete expected.extends; + t.like(actual, expected, "Config object not correct."); +}); + +test("configCustomFileSystemSync", (t) => { + t.plan(4); + const file = path.resolve("/dir/file.json"); + const extended = path.resolve("/dir/extended.json"); + const fileContent = { + "extends": extended, + "default": true, + "MD001": false + }; + const extendedContent = { + "MD001": true, + "MD002": true + }; + const fsApi = { + "accessSync": (p) => { + t.is(p, extended); + }, + "readFileSync": (p) => { + switch (p) { + case file: + t.is(p, file); + return JSON.stringify(fileContent); + case extended: + t.is(p, extended); + return JSON.stringify(extendedContent); + default: + return t.fail(); + } + } + }; + const actual = markdownlint.readConfigSync(file, null, fsApi); + const expected = { + ...extendedContent, + ...fileContent + }; + delete expected.extends; + t.deepEqual(actual, expected, "Config object not correct."); +}); + +test("configBadHybridSync", (t) => { + t.plan(1); + t.throws( + function badHybridCall() { + markdownlint.readConfigSync( + "./test/config/config-badcontent.txt", + // @ts-ignore + [ JSON.parse, require("toml").parse, require("js-yaml").load ]); + }, + { + // eslint-disable-next-line max-len + "message": /^Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+;/ + }, + "Did not get correct exception for bad content." + ); +}); + +test.cb("configSinglePromise", (t) => { + t.plan(1); + markdownlint.promises.readConfig("./test/config/config-child.json") + .then((actual) => { + const expected = require("./config/config-child.json"); + t.deepEqual(actual, expected, "Config object not correct."); + t.end(); + }); +}); + +test.cb("configCustomFileSystemPromise", (t) => { + t.plan(4); + const file = path.resolve("/dir/file.json"); + const extended = path.resolve("/dir/extended.json"); + const fileContent = { + "extends": extended, + "default": true, + "MD001": false + }; + const extendedContent = { + "MD001": true, + "MD002": true + }; + const fsApi = { + "access": (p, m, cb) => { + t.is(p, extended); + return (cb || m)(); + }, + "readFile": (p, o, cb) => { + switch (p) { + case file: + t.is(p, file); + return cb(null, JSON.stringify(fileContent)); + case extended: + t.is(p, extended); + return cb(null, JSON.stringify(extendedContent)); + default: + return t.fail(); + } + } + }; + markdownlint.promises.readConfig(file, null, fsApi) + .then((actual) => { + const expected = { + ...extendedContent, + ...fileContent + }; + delete expected.extends; + t.deepEqual(actual, expected, "Config object not correct."); + t.end(); + }); +}); + +test.cb("configBadFilePromise", (t) => { + t.plan(2); + markdownlint.promises.readConfig("./test/config/config-badfile.json") + .then( + null, + (error) => { + t.truthy(error, "Did not get an error for bad JSON."); + t.true(error instanceof Error, "Error not instance of Error."); + t.end(); + } + ); +}); + +test("allBuiltInRulesHaveValidUrl", (t) => { + t.plan(138); + rules.forEach(function forRule(rule) { + t.truthy(rule.information); + t.true(Object.getPrototypeOf(rule.information) === URL.prototype); + const name = rule.names[0].toLowerCase(); + t.is( + rule.information.href, + `${homepage}/blob/v${version}/doc/Rules.md#${name}` + ); + }); +}); + +test("someCustomRulesHaveValidUrl", (t) => { + t.plan(7); + customRules.all.forEach(function forRule(rule) { + t.true(!rule.information || + (Object.getPrototypeOf(rule.information) === URL.prototype)); + if (rule === customRules.anyBlockquote) { + t.is( + rule.information.href, + `${homepage}/blob/main/test/rules/any-blockquote.js` + ); + } else if (rule === customRules.lettersEX) { + t.is( + rule.information.href, + `${homepage}/blob/main/test/rules/letters-E-X.js` + ); + } + }); +}); + +test.cb("markdownItPluginsSingle", (t) => { + t.plan(2); + markdownlint({ + "strings": { + "string": "# Heading\n\nText [ link ](https://example.com)\n" + }, + "markdownItPlugins": [ + [ + pluginInline, + "trim_text_plugin", + "text", + function iterator(tokens, index) { + tokens[index].content = tokens[index].content.trim(); + } + ] + ] + }, function callback(err, actual) { + t.falsy(err); + const expected = { "string": [] }; + t.deepEqual(actual, expected, "Unexpected issues."); + t.end(); + }); +}); + +test.cb("markdownItPluginsMultiple", (t) => { + t.plan(4); + markdownlint({ + "strings": { + "string": "# Heading\n\nText H~2~0 text 29^th^ text\n" + }, + "markdownItPlugins": [ + [ pluginSub ], + [ pluginSup ], + [ pluginInline, "check_sub_plugin", "sub_open", () => t.true(true) ], + [ pluginInline, "check_sup_plugin", "sup_open", () => t.true(true) ] + ] + }, function callback(err, actual) { + t.falsy(err); + const expected = { "string": [] }; + t.deepEqual(actual, expected, "Unexpected issues."); + t.end(); + }); +}); + +test.cb("markdownItPluginsMathjax", (t) => { + t.plan(2); + markdownlint({ + "strings": { + "string": + "# Heading\n" + + "\n" + + "$1 *2* 3$\n" + + "\n" + + "$$1 *2* 3$$\n" + + "\n" + + "$$1\n" + + "+ 2\n" + + "+ 3$$\n" + }, + "markdownItPlugins": [ [ pluginTexMath, pluginTexMathOptions ] ] + }, function callback(err, actual) { + t.falsy(err); + const expected = { "string": [] }; + t.deepEqual(actual, expected, "Unexpected issues."); + t.end(); + }); +}); + +test.cb("markdownItPluginsMathjaxIssue166", (t) => { + t.plan(2); + markdownlint({ + "strings": { + "string": +`## Heading + +$$ +1 +$$$$ +2 +$$\n` + }, + "markdownItPlugins": [ [ pluginTexMath, pluginTexMathOptions ] ], + "resultVersion": 0 + }, function callback(err, actual) { + t.falsy(err); + const expected = { + "string": { + "MD041": [ 1 ] + } + }; + // @ts-ignore + t.deepEqual(actual, expected, "Unexpected issues."); + t.end(); + }); +}); + +test.cb("texmath test files with texmath plugin", (t) => { + t.plan(2); + markdownlint({ + "files": [ + "./test/texmath-content-in-lists.md", + "./test/texmath-content-violating-md037.md" + ], + "markdownItPlugins": [ [ pluginTexMath, pluginTexMathOptions ] ] + }, function callback(err, actual) { + t.falsy(err); + const expected = { + "./test/texmath-content-in-lists.md": [], + "./test/texmath-content-violating-md037.md": [] + }; + t.deepEqual(actual, expected, "Unexpected issues."); + t.end(); + }); +}); + +test("token-map-spans", (t) => { + t.plan(38); + const options = { + "customRules": [ + { + "names": [ "token-map-spans" ], + "description": "token-map-spans", + "tags": [ "tms" ], + "function": function tokenMapSpans(params) { + const tokenLines = []; + let lastLineNumber = -1; + const inlines = params.tokens.filter((c) => c.type === "inline"); + for (const token of inlines) { + t.truthy(token.map); + for (let i = token.map[0]; i < token.map[1]; i++) { + if (tokenLines.includes(i)) { + t.true( + lastLineNumber === token.lineNumber, + `Line ${i + 1} is part of token maps from multiple lines.` + ); + } else { + tokenLines.push(i); + } + lastLineNumber = token.lineNumber; + } + } + } + } + ], + "files": [ "./test/token-map-spans.md" ] + }; + markdownlint.sync(options); +}); + +test("getVersion", (t) => { + t.plan(1); + const actual = markdownlint.getVersion(); + const expected = version; + t.is(actual, expected, "Version string not correct."); +}); + +test("constants", (t) => { + t.plan(2); + t.is(constants.homepage, homepage); + t.is(constants.version, version); +}); From e3c75289bc57714dd188d0d9aabd69e0ad9e6f35 Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 30 Nov 2021 22:03:31 -0800 Subject: [PATCH 35/69] Split config tests into a dedicated test file. --- package.json | 2 +- test/markdownlint-test-config.js | 1283 ------------------------------ test/markdownlint-test.js | 478 +---------- 3 files changed, 2 insertions(+), 1761 deletions(-) diff --git a/package.json b/package.json index 1e0d329e..d5563cb1 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "lint": "eslint --max-warnings 0 .", "lint-test-repos": "ava --timeout=5m test/markdownlint-test-repos.js", "serial-declaration-demo": "npm run build-declaration && npm-run-all --continue-on-error --parallel build-demo test-declaration", - "test": "ava test/markdownlint-test.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js", + "test": "ava test/markdownlint-test.js test/markdownlint-test-config.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js", "test-cover": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test", "test-declaration": "cd example/typescript && tsc && node type-check.js", "test-extra": "ava --timeout=5m test/markdownlint-test-extra.js" diff --git a/test/markdownlint-test-config.js b/test/markdownlint-test-config.js index 9d2784ba..6c88ed5a 100644 --- a/test/markdownlint-test-config.js +++ b/test/markdownlint-test-config.js @@ -2,1098 +2,9 @@ "use strict"; -const fs = require("fs"); const path = require("path"); -const jsYaml = require("js-yaml"); -const md = require("markdown-it")(); -const pluginInline = require("markdown-it-for-inline"); -const pluginSub = require("markdown-it-sub"); -const pluginSup = require("markdown-it-sup"); -const pluginTexMath = require("markdown-it-texmath"); -const stripJsonComments = require("strip-json-comments"); const test = require("ava").default; -const tv4 = require("tv4"); -const { homepage, version } = require("../package.json"); const markdownlint = require("../lib/markdownlint"); -const constants = require("../lib/constants"); -const rules = require("../lib/rules"); -const customRules = require("./rules/rules.js"); -const configSchema = require("../schema/markdownlint-config-schema.json"); - -const pluginTexMathOptions = { - "engine": { - "renderToString": () => "" - } -}; -const deprecatedRuleNames = new Set(constants.deprecatedRuleNames); -const configSchemaStrict = { - ...configSchema, - "additionalProperties": false -}; - -test.cb("simpleAsync", (t) => { - t.plan(2); - const options = { - "strings": { - "content": "# Heading" - } - }; - const expected = "content: 1: MD047/single-trailing-newline " + - "Files should end with a single newline character"; - markdownlint(options, (err, actual) => { - t.falsy(err); - t.is(actual.toString(), expected, "Unexpected results."); - t.end(); - }); -}); - -test("simpleSync", (t) => { - t.plan(1); - const options = { - "strings": { - "content": "# Heading" - } - }; - const expected = "content: 1: MD047/single-trailing-newline " + - "Files should end with a single newline character"; - const actual = markdownlint.sync(options).toString(); - t.is(actual, expected, "Unexpected results."); -}); - -test("simplePromise", (t) => { - t.plan(1); - const options = { - "strings": { - "content": "# Heading" - } - }; - const expected = "content: 1: MD047/single-trailing-newline " + - "Files should end with a single newline character"; - return markdownlint.promises.markdownlint(options).then((actual) => { - t.is(actual.toString(), expected, "Unexpected results."); - }); -}); - -test.cb("projectFilesNoInlineConfig", (t) => { - t.plan(2); - const options = { - "files": [ - "README.md", - "CONTRIBUTING.md", - "doc/CustomRules.md", - "doc/Prettier.md", - "helpers/README.md" - ], - "noInlineConfig": true, - "config": { - "line-length": { "line_length": 150 }, - "no-duplicate-heading": false - } - }; - markdownlint(options, function callback(err, actual) { - t.falsy(err); - const expected = { - "README.md": [], - "CONTRIBUTING.md": [], - "doc/CustomRules.md": [], - "doc/Prettier.md": [], - "helpers/README.md": [] - }; - t.deepEqual(actual, expected, "Issue(s) with project files."); - t.end(); - }); -}); - -test.cb("projectFilesInlineConfig", (t) => { - t.plan(2); - const options = { - "files": [ "doc/Rules.md" ], - "config": { - "no-inline-html": false - } - }; - markdownlint(options, function callback(err, actual) { - t.falsy(err); - const expected = { - "doc/Rules.md": [] - }; - t.deepEqual(actual, expected, "Issue(s) with project files."); - t.end(); - }); -}); - -test.cb("stringInputLineEndings", (t) => { - t.plan(2); - const options = { - "strings": { - "cr": "One\rTwo\r#Three\n", - "lf": "One\nTwo\n#Three\n", - "crlf": "One\r\nTwo\r\n#Three\n", - "mixed": "One\rTwo\n#Three\n" - }, - "config": { - "MD041": false - }, - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "cr": { "MD018": [ 3 ] }, - "lf": { "MD018": [ 3 ] }, - "crlf": { "MD018": [ 3 ] }, - "mixed": { "MD018": [ 3 ] } - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("inputOnlyNewline", (t) => { - t.plan(2); - const options = { - "strings": { - "cr": "\r", - "lf": "\n", - "crlf": "\r\n" - }, - "config": { - "default": false - } - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "cr": [], - "lf": [], - "crlf": [] - }; - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("defaultTrue", (t) => { - t.plan(2); - const options = { - "files": [ - "./test/atx_heading_spacing.md", - "./test/first_heading_bad_atx.md" - ], - "config": { - "default": true - }, - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/atx_heading_spacing.md": { - "MD018": [ 1 ], - "MD019": [ 3, 5 ], - "MD041": [ 1 ] - }, - "./test/first_heading_bad_atx.md": { - "MD041": [ 1 ] - } - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("defaultFalse", (t) => { - t.plan(2); - const options = { - "files": [ - "./test/atx_heading_spacing.md", - "./test/first_heading_bad_atx.md" - ], - "config": { - "default": false - }, - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/atx_heading_spacing.md": {}, - "./test/first_heading_bad_atx.md": {} - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("defaultUndefined", (t) => { - t.plan(2); - const options = { - "files": [ - "./test/atx_heading_spacing.md", - "./test/first_heading_bad_atx.md" - ], - "config": {}, - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/atx_heading_spacing.md": { - "MD018": [ 1 ], - "MD019": [ 3, 5 ], - "MD041": [ 1 ] - }, - "./test/first_heading_bad_atx.md": { - "MD041": [ 1 ] - } - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("disableRules", (t) => { - t.plan(2); - const options = { - "files": [ - "./test/atx_heading_spacing.md", - "./test/first_heading_bad_atx.md" - ], - "config": { - "MD002": false, - "default": true, - "MD019": false, - "first-line-h1": false - }, - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/atx_heading_spacing.md": { - "MD018": [ 1 ] - }, - "./test/first_heading_bad_atx.md": {} - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("enableRules", (t) => { - t.plan(2); - const options = { - "files": [ - "./test/atx_heading_spacing.md", - "./test/first_heading_bad_atx.md" - ], - "config": { - "MD002": true, - "default": false, - "no-multiple-space-atx": true - }, - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/atx_heading_spacing.md": { - "MD002": [ 3 ], - "MD019": [ 3, 5 ] - }, - "./test/first_heading_bad_atx.md": { - "MD002": [ 1 ] - } - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("enableRulesMixedCase", (t) => { - t.plan(2); - const options = { - "files": [ - "./test/atx_heading_spacing.md", - "./test/first_heading_bad_atx.md" - ], - "config": { - "Md002": true, - "DeFaUlT": false, - "nO-mUlTiPlE-sPaCe-AtX": true - }, - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/atx_heading_spacing.md": { - "MD002": [ 3 ], - "MD019": [ 3, 5 ] - }, - "./test/first_heading_bad_atx.md": { - "MD002": [ 1 ] - } - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("disableTag", (t) => { - t.plan(2); - const options = { - "files": [ - "./test/atx_heading_spacing.md", - "./test/first_heading_bad_atx.md" - ], - "config": { - "default": true, - "spaces": false - }, - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/atx_heading_spacing.md": { - "MD041": [ 1 ] - }, - "./test/first_heading_bad_atx.md": { - "MD041": [ 1 ] - } - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("enableTag", (t) => { - t.plan(2); - const options = { - "files": [ - "./test/atx_heading_spacing.md", - "./test/first_heading_bad_atx.md" - ], - "config": { - "default": false, - "spaces": true, - "notatag": true - }, - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/atx_heading_spacing.md": { - "MD018": [ 1 ], - "MD019": [ 3, 5 ] - }, - "./test/first_heading_bad_atx.md": {} - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("enableTagMixedCase", (t) => { - t.plan(2); - const options = { - "files": [ - "./test/atx_heading_spacing.md", - "./test/first_heading_bad_atx.md" - ], - "config": { - "DeFaUlT": false, - "SpAcEs": true, - "NoTaTaG": true - }, - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/atx_heading_spacing.md": { - "MD018": [ 1 ], - "MD019": [ 3, 5 ] - }, - "./test/first_heading_bad_atx.md": {} - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("styleFiles", (t) => { - t.plan(4); - fs.readdir("./style", function readdir(err, files) { - t.falsy(err); - files.forEach(function forFile(file) { - t.truthy(require(path.join("../style", file)), "Unable to load/parse."); - }); - t.end(); - }); -}); - -test.cb("styleAll", (t) => { - t.plan(2); - const options = { - "files": [ "./test/break-all-the-rules.md" ], - "config": require("../style/all.json"), - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/break-all-the-rules.md": { - "MD001": [ 3 ], - "MD003": [ 5, 31 ], - "MD004": [ 8 ], - "MD005": [ 12 ], - "MD007": [ 8, 11 ], - "MD009": [ 14 ], - "MD010": [ 14 ], - "MD011": [ 16 ], - "MD012": [ 18 ], - "MD013": [ 21 ], - "MD014": [ 23 ], - "MD018": [ 25 ], - "MD019": [ 27 ], - "MD020": [ 29 ], - "MD021": [ 31 ], - "MD022": [ 86 ], - "MD023": [ 40 ], - "MD024": [ 35 ], - "MD026": [ 40 ], - "MD027": [ 42 ], - "MD028": [ 43 ], - "MD029": [ 47 ], - "MD030": [ 8 ], - "MD031": [ 50 ], - "MD032": [ 7, 8, 51 ], - "MD033": [ 55 ], - "MD034": [ 57 ], - "MD035": [ 61 ], - "MD036": [ 65 ], - "MD037": [ 67 ], - "MD038": [ 69 ], - "MD039": [ 71 ], - "MD040": [ 73 ], - "MD041": [ 1 ], - "MD042": [ 81 ], - "MD045": [ 85 ], - "MD046": [ 49, 73, 77 ], - "MD047": [ 96 ], - "MD048": [ 77 ], - "MD049": [ 90 ], - "MD050": [ 94 ] - } - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("styleRelaxed", (t) => { - t.plan(2); - const options = { - "files": [ "./test/break-all-the-rules.md" ], - "config": require("../style/relaxed.json"), - "resultVersion": 0 - }; - markdownlint(options, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/break-all-the-rules.md": { - "MD001": [ 3 ], - "MD003": [ 5, 31 ], - "MD004": [ 8 ], - "MD005": [ 12 ], - "MD011": [ 16 ], - "MD014": [ 23 ], - "MD018": [ 25 ], - "MD019": [ 27 ], - "MD020": [ 29 ], - "MD021": [ 31 ], - "MD022": [ 86 ], - "MD023": [ 40 ], - "MD024": [ 35 ], - "MD026": [ 40 ], - "MD029": [ 47 ], - "MD031": [ 50 ], - "MD032": [ 7, 8, 51 ], - "MD035": [ 61 ], - "MD036": [ 65 ], - "MD042": [ 81 ], - "MD045": [ 85 ], - "MD046": [ 49, 73, 77 ], - "MD047": [ 96 ], - "MD048": [ 77 ], - "MD049": [ 90 ], - "MD050": [ 94 ] - } - }; - // @ts-ignore - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("nullFrontMatter", (t) => { - t.plan(2); - markdownlint({ - "strings": { - "content": "---\n\t\n---\n# Heading\n" - }, - "frontMatter": null, - "config": { - "default": false, - "MD010": true - }, - "resultVersion": 0 - }, function callback(err, result) { - t.falsy(err); - const expectedResult = { - "content": { "MD010": [ 2 ] } - }; - // @ts-ignore - t.deepEqual(result, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("customFrontMatter", (t) => { - t.plan(2); - markdownlint({ - "strings": { - "content": "\n\t\n\n# Heading\n" - }, - "frontMatter": /[^]*<\/head>/, - "config": { - "default": false, - "MD010": true - } - }, function callback(err, result) { - t.falsy(err); - const expectedResult = { - "content": [] - }; - t.deepEqual(result, expectedResult, "Did not get empty results."); - t.end(); - }); -}); - -test.cb("noInlineConfig", (t) => { - t.plan(2); - markdownlint({ - "strings": { - "content": [ - "# Heading", - "", - "\tTab", - "", - "", - "", - "\tTab", - "", - "", - "", - "\tTab\n" - ].join("\n") - }, - "noInlineConfig": true, - "resultVersion": 0 - }, function callback(err, result) { - t.falsy(err); - const expectedResult = { - "content": { - "MD010": [ 3, 7, 11 ] - } - }; - // @ts-ignore - t.deepEqual(result, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test.cb("readmeHeadings", (t) => { - t.plan(2); - markdownlint({ - "files": "README.md", - "noInlineConfig": true, - "config": { - "default": false, - "MD013": { - "line_length": 150 - }, - "MD043": { - "headings": [ - "# markdownlint", - "## Install", - "## Overview", - "### Related", - "## Demonstration", - "## Rules / Aliases", - "## Tags", - "## Configuration", - "## API", - "### Linting", - "#### options", - "##### options.customRules", - "##### options.files", - "##### options.strings", - "##### options.config", - "##### options.frontMatter", - "##### options.handleRuleFailures", - "##### options.noInlineConfig", - "##### options.resultVersion", - "##### options.markdownItPlugins", - "##### options.fs", - "#### callback", - "#### result", - "### Config", - "#### file", - "#### parsers", - "#### fs", - "#### callback", - "#### result", - "## Usage", - "## Browser", - "## Examples", - "## Contributing", - "## History" - ] - } - } - }, function callback(err, result) { - t.falsy(err); - const expected = { "README.md": [] }; - t.deepEqual(result, expected, "Unexpected issues."); - t.end(); - }); -}); - -test.cb("filesArrayNotModified", (t) => { - t.plan(2); - const files = [ - "./test/atx_heading_spacing.md", - "./test/first_heading_bad_atx.md" - ]; - const expectedFiles = [ ...files ]; - markdownlint({ "files": files }, function callback(err) { - t.falsy(err); - t.deepEqual(files, expectedFiles, "Files modified."); - t.end(); - }); -}); - -test.cb("filesArrayAsString", (t) => { - t.plan(2); - markdownlint({ - "files": "README.md", - "noInlineConfig": true, - "config": { - "MD013": { "line_length": 150 }, - "MD024": false - } - }, function callback(err, actual) { - t.falsy(err); - const expected = { "README.md": [] }; - t.deepEqual(actual, expected, "Unexpected issues."); - t.end(); - }); -}); - -test.cb("missingOptions", (t) => { - t.plan(2); - markdownlint(null, function callback(err, result) { - t.falsy(err); - t.deepEqual( - result, - {}, - "Did not get empty result for missing options." - ); - t.end(); - }); -}); - -test.cb("missingFilesAndStrings", (t) => { - t.plan(2); - markdownlint({}, function callback(err, result) { - t.falsy(err); - t.truthy(result, "Did not get result for missing files/strings."); - t.end(); - }); -}); - -test("missingCallback", (t) => { - t.plan(0); - // @ts-ignore - markdownlint(); -}); - -test.cb("badFile", (t) => { - t.plan(4); - markdownlint({ - "files": [ "./badFile" ] - }, function callback(err, result) { - t.truthy(err, "Did not get an error for bad file."); - t.true(err instanceof Error, "Error not instance of Error."); - // @ts-ignore - t.is(err.code, "ENOENT", "Error code for bad file not ENOENT."); - t.true(!result, "Got result for bad file."); - t.end(); - }); -}); - -test("badFileSync", (t) => { - t.plan(1); - t.throws( - function badFileCall() { - markdownlint.sync({ - "files": [ "./badFile" ] - }); - }, - { - "message": /ENOENT/ - }, - "Did not get correct exception for bad file." - ); -}); - -test.cb("badFilePromise", (t) => { - t.plan(3); - markdownlint.promises.markdownlint({ - "files": [ "./badFile" ] - }).then( - null, - (error) => { - t.truthy(error, "Did not get an error for bad file."); - t.true(error instanceof Error, "Error not instance of Error."); - t.is(error.code, "ENOENT", "Error code for bad file not ENOENT."); - t.end(); - } - ); -}); - -test.cb("missingStringValue", (t) => { - t.plan(2); - markdownlint({ - "strings": { - "undefined": undefined, - "null": null, - "empty": "" - } - }, function callback(err, result) { - t.falsy(err); - const expectedResult = { - "undefined": [], - "null": [], - "empty": [] - }; - t.deepEqual(result, expectedResult, "Did not get empty results."); - t.end(); - }); -}); - -test("customFileSystemSync", (t) => { - t.plan(2); - const file = "/dir/file.md"; - const fsApi = { - "readFileSync": (p) => { - t.is(p, file); - return "# Heading"; - } - }; - const result = markdownlint.sync({ - "files": file, - "fs": fsApi - }); - t.deepEqual(result[file].length, 1, "Did not report violations."); -}); - -test.cb("customFileSystemAsync", (t) => { - t.plan(3); - const file = "/dir/file.md"; - const fsApi = { - "readFile": (p, o, cb) => { - t.is(p, file); - cb(null, "# Heading"); - } - }; - markdownlint({ - "files": file, - "fs": fsApi - }, function callback(err, result) { - t.falsy(err); - t.deepEqual(result[file].length, 1, "Did not report violations."); - t.end(); - }); -}); -test.cb("readme", (t) => { - t.plan(119); - const tagToRules = {}; - rules.forEach(function forRule(rule) { - rule.tags.forEach(function forTag(tag) { - const tagRules = tagToRules[tag] || []; - tagRules.push(rule.names[0]); - tagToRules[tag] = tagRules; - }); - }); - fs.readFile("README.md", "utf8", - function readFile(err, contents) { - t.falsy(err); - const rulesLeft = [ ...rules ]; - let seenRelated = false; - let seenRules = false; - let inRules = false; - let seenTags = false; - let inTags = false; - // @ts-ignore - md.parse(contents, {}).forEach(function forToken(token) { - if ( - (token.type === "bullet_list_open") && - (token.level === 0) - ) { - if (!seenRelated) { - seenRelated = true; - } else if (!seenRules) { - seenRules = true; - inRules = true; - } else if (!seenTags) { - seenTags = true; - inTags = true; - } - } else if ( - (token.type === "bullet_list_close") && - (token.level === 0) - ) { - inRules = false; - inTags = false; - } else if (token.type === "inline") { - if (inRules) { - const rule = rulesLeft.shift(); - t.truthy(rule, - "Missing rule implementation for " + token.content + "."); - if (rule) { - const ruleName = rule.names[0]; - const ruleAliases = rule.names.slice(1); - let expected = "**[" + ruleName + "](doc/Rules.md#" + - ruleName.toLowerCase() + ")** *" + - ruleAliases.join("/") + "* - " + rule.description; - if (deprecatedRuleNames.has(ruleName)) { - expected = "~~" + expected + "~~"; - } - t.is(token.content, expected, "Rule mismatch."); - } - } else if (inTags) { - const parts = - token.content.replace(/\*\*/g, "").split(/ - |, |,\n/); - const tag = parts.shift(); - t.deepEqual(parts, tagToRules[tag] || [], - "Rule mismatch for tag " + tag + "."); - delete tagToRules[tag]; - } - } - }); - const ruleLeft = rulesLeft.shift(); - t.true(!ruleLeft, - "Missing rule documentation for " + - (ruleLeft || "[NO RULE]").toString() + "."); - const tagLeft = Object.keys(tagToRules).shift(); - t.true(!tagLeft, "Undocumented tag " + tagLeft + "."); - t.end(); - }); -}); - -test.cb("rules", (t) => { - t.plan(352); - fs.readFile("doc/Rules.md", "utf8", - (err, contents) => { - t.falsy(err); - const rulesLeft = [ ...rules ]; - let inHeading = false; - let rule = null; - let ruleHasTags = true; - let ruleHasAliases = true; - let ruleUsesParams = null; - const tagAliasParameterRe = /, |: | /; - // eslint-disable-next-line func-style - const testTagsAliasesParams = (r) => { - // eslint-disable-next-line unicorn/prefer-default-parameters - r = r || "[NO RULE]"; - t.true(ruleHasTags, - "Missing tags for rule " + r.names + "."); - t.true(ruleHasAliases, - "Missing aliases for rule " + r.names + "."); - t.true(!ruleUsesParams, - "Missing parameters for rule " + r.names + "."); - }; - // @ts-ignore - md.parse(contents, {}).forEach(function forToken(token) { - if ((token.type === "heading_open") && (token.tag === "h2")) { - inHeading = true; - } else if (token.type === "heading_close") { - inHeading = false; - } else if (token.type === "inline") { - if (inHeading) { - testTagsAliasesParams(rule); - rule = rulesLeft.shift(); - ruleHasTags = false; - ruleHasAliases = false; - t.truthy(rule, - "Missing rule implementation for " + token.content + "."); - const ruleName = rule.names[0]; - let headingContent = ruleName + " - " + rule.description; - if (deprecatedRuleNames.has(ruleName)) { - headingContent = "~~" + headingContent + "~~"; - } - t.is(token.content, - headingContent, - "Rule mismatch."); - ruleUsesParams = rule.function.toString() - .match(/params\.config\.[_a-z]*/gi); - if (ruleUsesParams) { - ruleUsesParams = ruleUsesParams.map(function forUse(use) { - return use.split(".").pop(); - }); - ruleUsesParams.sort(); - } - } else if (token.content.startsWith("Tags: ") && rule) { - t.deepEqual(token.content.split(tagAliasParameterRe).slice(1), - rule.tags, "Tag mismatch for rule " + rule.names + "."); - ruleHasTags = true; - } else if (token.content.startsWith("Aliases: ") && rule) { - t.deepEqual(token.content.split(tagAliasParameterRe).slice(1), - rule.names.slice(1), - "Alias mismatch for rule " + rule.names + "."); - ruleHasAliases = true; - } else if (token.content.startsWith("Parameters: ") && rule) { - let inDetails = false; - const parameters = token.content.split(tagAliasParameterRe) - .slice(1) - .filter(function forPart(part) { - inDetails = inDetails || (part[0] === "("); - return !inDetails; - }); - parameters.sort(); - t.deepEqual(parameters, ruleUsesParams, - "Missing parameter for rule " + rule.names); - ruleUsesParams = null; - } - } - }); - const ruleLeft = rulesLeft.shift(); - t.true(!ruleLeft, - "Missing rule documentation for " + - (ruleLeft || { "names": "[NO RULE]" }).names + "."); - if (rule) { - testTagsAliasesParams(rule); - } - t.end(); - }); -}); - -test("validateJsonUsingConfigSchemaStrict", (t) => { - const jsonFileRe = /\.json$/i; - const resultsFileRe = /\.results\.json$/i; - const jsConfigFileRe = /^jsconfig\.json$/i; - const wrongTypesFileRe = /wrong-types-in-config-file.json$/i; - const testDirectory = __dirname; - const testFiles = fs.readdirSync(testDirectory); - testFiles.filter(function filterFile(file) { - return jsonFileRe.test(file) && - !resultsFileRe.test(file) && - !jsConfigFileRe.test(file) && - !wrongTypesFileRe.test(file); - }).forEach(function forFile(file) { - const data = fs.readFileSync( - path.join(testDirectory, file), - "utf8" - ); - t.true( - // @ts-ignore - tv4.validate(JSON.parse(data), configSchemaStrict), - file + "\n" + JSON.stringify(tv4.error, null, 2)); - }); -}); - -test("validateConfigSchemaAllowsUnknownProperties", (t) => { - t.plan(4); - const testCases = [ - { - "property": true - }, - { - "property": { - "object": 1 - } - } - ]; - testCases.forEach((testCase) => { - t.true( - // @ts-ignore - tv4.validate(testCase, configSchema), - "Unknown property blocked by default: " + JSON.stringify(testCase)); - t.false( - // @ts-ignore - tv4.validate(testCase, configSchemaStrict), - "Unknown property allowed when strict: " + JSON.stringify(testCase)); - }); -}); - -test("validateConfigSchemaAppliesToUnknownProperties", (t) => { - t.plan(4); - for (const allowed of [ true, {} ]) { - t.true( - // @ts-ignore - tv4.validate({ "property": allowed }, configSchema), - `Unknown property value ${allowed} blocked`); - } - for (const blocked of [ 2, "string" ]) { - t.false( - // @ts-ignore - tv4.validate({ "property": blocked }, configSchema), - `Unknown property value ${blocked} allowed`); - } -}); - -test("validateConfigExampleJson", (t) => { - t.plan(2); - - // Validate JSONC - const fileJson = ".markdownlint.jsonc"; - const dataJson = fs.readFileSync( - path.join(__dirname, "../schema", fileJson), - "utf8" - ); - const jsonObject = JSON.parse(stripJsonComments(dataJson)); - t.true( - // @ts-ignore - tv4.validate(jsonObject, configSchemaStrict), - fileJson + "\n" + JSON.stringify(tv4.error, null, 2)); - - // Validate YAML - const fileYaml = ".markdownlint.yaml"; - const dataYaml = fs.readFileSync( - path.join(__dirname, "../schema", fileYaml), - "utf8" - ); - const yamlObject = jsYaml.load(dataYaml); - t.deepEqual(yamlObject, jsonObject, - "YAML example does not match JSON example."); -}); test.cb("configSingle", (t) => { t.plan(2); @@ -1571,197 +482,3 @@ test.cb("configBadFilePromise", (t) => { } ); }); - -test("allBuiltInRulesHaveValidUrl", (t) => { - t.plan(138); - rules.forEach(function forRule(rule) { - t.truthy(rule.information); - t.true(Object.getPrototypeOf(rule.information) === URL.prototype); - const name = rule.names[0].toLowerCase(); - t.is( - rule.information.href, - `${homepage}/blob/v${version}/doc/Rules.md#${name}` - ); - }); -}); - -test("someCustomRulesHaveValidUrl", (t) => { - t.plan(7); - customRules.all.forEach(function forRule(rule) { - t.true(!rule.information || - (Object.getPrototypeOf(rule.information) === URL.prototype)); - if (rule === customRules.anyBlockquote) { - t.is( - rule.information.href, - `${homepage}/blob/main/test/rules/any-blockquote.js` - ); - } else if (rule === customRules.lettersEX) { - t.is( - rule.information.href, - `${homepage}/blob/main/test/rules/letters-E-X.js` - ); - } - }); -}); - -test.cb("markdownItPluginsSingle", (t) => { - t.plan(2); - markdownlint({ - "strings": { - "string": "# Heading\n\nText [ link ](https://example.com)\n" - }, - "markdownItPlugins": [ - [ - pluginInline, - "trim_text_plugin", - "text", - function iterator(tokens, index) { - tokens[index].content = tokens[index].content.trim(); - } - ] - ] - }, function callback(err, actual) { - t.falsy(err); - const expected = { "string": [] }; - t.deepEqual(actual, expected, "Unexpected issues."); - t.end(); - }); -}); - -test.cb("markdownItPluginsMultiple", (t) => { - t.plan(4); - markdownlint({ - "strings": { - "string": "# Heading\n\nText H~2~0 text 29^th^ text\n" - }, - "markdownItPlugins": [ - [ pluginSub ], - [ pluginSup ], - [ pluginInline, "check_sub_plugin", "sub_open", () => t.true(true) ], - [ pluginInline, "check_sup_plugin", "sup_open", () => t.true(true) ] - ] - }, function callback(err, actual) { - t.falsy(err); - const expected = { "string": [] }; - t.deepEqual(actual, expected, "Unexpected issues."); - t.end(); - }); -}); - -test.cb("markdownItPluginsMathjax", (t) => { - t.plan(2); - markdownlint({ - "strings": { - "string": - "# Heading\n" + - "\n" + - "$1 *2* 3$\n" + - "\n" + - "$$1 *2* 3$$\n" + - "\n" + - "$$1\n" + - "+ 2\n" + - "+ 3$$\n" - }, - "markdownItPlugins": [ [ pluginTexMath, pluginTexMathOptions ] ] - }, function callback(err, actual) { - t.falsy(err); - const expected = { "string": [] }; - t.deepEqual(actual, expected, "Unexpected issues."); - t.end(); - }); -}); - -test.cb("markdownItPluginsMathjaxIssue166", (t) => { - t.plan(2); - markdownlint({ - "strings": { - "string": -`## Heading - -$$ -1 -$$$$ -2 -$$\n` - }, - "markdownItPlugins": [ [ pluginTexMath, pluginTexMathOptions ] ], - "resultVersion": 0 - }, function callback(err, actual) { - t.falsy(err); - const expected = { - "string": { - "MD041": [ 1 ] - } - }; - // @ts-ignore - t.deepEqual(actual, expected, "Unexpected issues."); - t.end(); - }); -}); - -test.cb("texmath test files with texmath plugin", (t) => { - t.plan(2); - markdownlint({ - "files": [ - "./test/texmath-content-in-lists.md", - "./test/texmath-content-violating-md037.md" - ], - "markdownItPlugins": [ [ pluginTexMath, pluginTexMathOptions ] ] - }, function callback(err, actual) { - t.falsy(err); - const expected = { - "./test/texmath-content-in-lists.md": [], - "./test/texmath-content-violating-md037.md": [] - }; - t.deepEqual(actual, expected, "Unexpected issues."); - t.end(); - }); -}); - -test("token-map-spans", (t) => { - t.plan(38); - const options = { - "customRules": [ - { - "names": [ "token-map-spans" ], - "description": "token-map-spans", - "tags": [ "tms" ], - "function": function tokenMapSpans(params) { - const tokenLines = []; - let lastLineNumber = -1; - const inlines = params.tokens.filter((c) => c.type === "inline"); - for (const token of inlines) { - t.truthy(token.map); - for (let i = token.map[0]; i < token.map[1]; i++) { - if (tokenLines.includes(i)) { - t.true( - lastLineNumber === token.lineNumber, - `Line ${i + 1} is part of token maps from multiple lines.` - ); - } else { - tokenLines.push(i); - } - lastLineNumber = token.lineNumber; - } - } - } - } - ], - "files": [ "./test/token-map-spans.md" ] - }; - markdownlint.sync(options); -}); - -test("getVersion", (t) => { - t.plan(1); - const actual = markdownlint.getVersion(); - const expected = version; - t.is(actual, expected, "Version string not correct."); -}); - -test("constants", (t) => { - t.plan(2); - t.is(constants.homepage, homepage); - t.is(constants.version, version); -}); diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index 9d2784ba..566cf711 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -841,6 +841,7 @@ test.cb("customFileSystemAsync", (t) => { t.end(); }); }); + test.cb("readme", (t) => { t.plan(119); const tagToRules = {}; @@ -1095,483 +1096,6 @@ test("validateConfigExampleJson", (t) => { "YAML example does not match JSON example."); }); -test.cb("configSingle", (t) => { - t.plan(2); - markdownlint.readConfig("./test/config/config-child.json", - function callback(err, actual) { - t.falsy(err); - const expected = require("./config/config-child.json"); - t.deepEqual(actual, expected, "Config object not correct."); - t.end(); - }); -}); - -test.cb("configAbsolute", (t) => { - t.plan(2); - markdownlint.readConfig(path.join(__dirname, "config", "config-child.json"), - function callback(err, actual) { - t.falsy(err); - const expected = require("./config/config-child.json"); - t.deepEqual(actual, expected, "Config object not correct."); - t.end(); - }); -}); - -test.cb("configMultiple", (t) => { - t.plan(2); - markdownlint.readConfig("./test/config/config-grandparent.json", - function callback(err, actual) { - t.falsy(err); - const expected = { - ...require("./config/config-child.json"), - ...require("./config/config-parent.json"), - ...require("./config/config-grandparent.json") - }; - delete expected.extends; - t.deepEqual(actual, expected, "Config object not correct."); - t.end(); - }); -}); - -test.cb("configMultipleWithRequireResolve", (t) => { - t.plan(2); - markdownlint.readConfig("./test/config/config-packageparent.json", - function callback(err, actual) { - t.falsy(err); - const expected = { - ...require("./node_modules/pseudo-package/config-frompackage.json"), - ...require("./config/config-packageparent.json") - }; - delete expected.extends; - t.deepEqual(actual, expected, "Config object not correct."); - t.end(); - }); -}); - -test.cb("configCustomFileSystem", (t) => { - t.plan(5); - const file = path.resolve("/dir/file.json"); - const extended = path.resolve("/dir/extended.json"); - const fileContent = { - "extends": extended, - "default": true, - "MD001": false - }; - const extendedContent = { - "MD001": true, - "MD002": true - }; - const fsApi = { - "access": (p, m, cb) => { - t.is(p, extended); - return (cb || m)(); - }, - "readFile": (p, o, cb) => { - switch (p) { - case file: - t.is(p, file); - return cb(null, JSON.stringify(fileContent)); - case extended: - t.is(p, extended); - return cb(null, JSON.stringify(extendedContent)); - default: - return t.fail(); - } - } - }; - markdownlint.readConfig( - file, - null, - fsApi, - function callback(err, actual) { - t.falsy(err); - const expected = { - ...extendedContent, - ...fileContent - }; - delete expected.extends; - t.deepEqual(actual, expected, "Config object not correct."); - t.end(); - }); -}); - -test.cb("configBadFile", (t) => { - t.plan(4); - markdownlint.readConfig("./test/config/config-badfile.json", - function callback(err, result) { - t.truthy(err, "Did not get an error for bad file."); - t.true(err instanceof Error, "Error not instance of Error."); - // @ts-ignore - t.is(err.code, "ENOENT", "Error code for bad file not ENOENT."); - t.true(!result, "Got result for bad file."); - t.end(); - }); -}); - -test.cb("configBadChildFile", (t) => { - t.plan(4); - markdownlint.readConfig("./test/config/config-badchildfile.json", - function callback(err, result) { - t.truthy(err, "Did not get an error for bad child file."); - t.true(err instanceof Error, "Error not instance of Error."); - // @ts-ignore - t.is(err.code, "ENOENT", - "Error code for bad child file not ENOENT."); - t.true(!result, "Got result for bad child file."); - t.end(); - }); -}); - -test.cb("configBadChildPackage", (t) => { - t.plan(4); - markdownlint.readConfig("./test/config/config-badchildpackage.json", - function callback(err, result) { - t.truthy(err, "Did not get an error for bad child package."); - t.true(err instanceof Error, "Error not instance of Error."); - // @ts-ignore - t.is(err.code, "ENOENT", - "Error code for bad child package not ENOENT."); - t.true(!result, "Got result for bad child package."); - t.end(); - }); -}); - -test.cb("configBadJson", (t) => { - t.plan(3); - markdownlint.readConfig("./test/config/config-badjson.json", - function callback(err, result) { - t.truthy(err, "Did not get an error for bad JSON."); - t.true(err instanceof Error, "Error not instance of Error."); - t.true(!result, "Got result for bad JSON."); - t.end(); - }); -}); - -test.cb("configBadChildJson", (t) => { - t.plan(3); - markdownlint.readConfig("./test/config/config-badchildjson.json", - function callback(err, result) { - t.truthy(err, "Did not get an error for bad child JSON."); - t.true(err instanceof Error, "Error not instance of Error."); - t.true(!result, "Got result for bad child JSON."); - t.end(); - }); -}); - -test.cb("configSingleYaml", (t) => { - t.plan(2); - markdownlint.readConfig( - "./test/config/config-child.yaml", - // @ts-ignore - [ require("js-yaml").load ], - function callback(err, actual) { - t.falsy(err); - const expected = require("./config/config-child.json"); - t.deepEqual(actual, expected, "Config object not correct."); - t.end(); - }); -}); - -test.cb("configMultipleYaml", (t) => { - t.plan(2); - markdownlint.readConfig( - "./test/config/config-grandparent.yaml", - // @ts-ignore - [ require("js-yaml").load ], - function callback(err, actual) { - t.falsy(err); - const expected = { - ...require("./config/config-child.json"), - ...require("./config/config-parent.json"), - ...require("./config/config-grandparent.json") - }; - delete expected.extends; - t.deepEqual(actual, expected, "Config object not correct."); - t.end(); - }); -}); - -test.cb("configMultipleHybrid", (t) => { - t.plan(2); - markdownlint.readConfig( - "./test/config/config-grandparent-hybrid.yaml", - // @ts-ignore - [ JSON.parse, require("toml").parse, require("js-yaml").load ], - function callback(err, actual) { - t.falsy(err); - const expected = { - ...require("./config/config-child.json"), - ...require("./config/config-parent.json"), - ...require("./config/config-grandparent.json") - }; - delete expected.extends; - t.like(actual, expected, "Config object not correct."); - t.end(); - }); -}); - -test.cb("configBadHybrid", (t) => { - t.plan(4); - markdownlint.readConfig( - "./test/config/config-badcontent.txt", - // @ts-ignore - [ JSON.parse, require("toml").parse, require("js-yaml").load ], - function callback(err, result) { - t.truthy(err, "Did not get an error for bad child JSON."); - t.true(err instanceof Error, "Error not instance of Error."); - t.truthy(err.message.match( - // eslint-disable-next-line max-len - /^Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+;/ - ), "Error message unexpected."); - t.true(!result, "Got result for bad child JSON."); - t.end(); - }); -}); - -test("configSingleSync", (t) => { - t.plan(1); - const actual = markdownlint.readConfigSync("./test/config/config-child.json"); - const expected = require("./config/config-child.json"); - t.deepEqual(actual, expected, "Config object not correct."); -}); - -test("configAbsoluteSync", (t) => { - t.plan(1); - const actual = markdownlint.readConfigSync( - path.join(__dirname, "config", "config-child.json")); - const expected = require("./config/config-child.json"); - t.deepEqual(actual, expected, "Config object not correct."); -}); - -test("configMultipleSync", (t) => { - t.plan(1); - const actual = - markdownlint.readConfigSync("./test/config/config-grandparent.json"); - const expected = { - ...require("./config/config-child.json"), - ...require("./config/config-parent.json"), - ...require("./config/config-grandparent.json") - }; - delete expected.extends; - t.deepEqual(actual, expected, "Config object not correct."); -}); - -test("configBadFileSync", (t) => { - t.plan(1); - t.throws( - function badFileCall() { - markdownlint.readConfigSync("./test/config/config-badfile.json"); - }, - { - "message": /ENOENT/ - }, - "Did not get correct exception for bad file." - ); -}); - -test("configBadChildFileSync", (t) => { - t.plan(1); - t.throws( - function badChildFileCall() { - markdownlint.readConfigSync("./test/config/config-badchildfile.json"); - }, - { - "message": /ENOENT/ - }, - "Did not get correct exception for bad child file." - ); -}); - -test("configBadJsonSync", (t) => { - t.plan(1); - t.throws( - function badJsonCall() { - markdownlint.readConfigSync("./test/config/config-badjson.json"); - }, - { - "message": - // eslint-disable-next-line max-len - /Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+/ - }, - "Did not get correct exception for bad JSON." - ); -}); - -test("configBadChildJsonSync", (t) => { - t.plan(1); - t.throws( - function badChildJsonCall() { - markdownlint.readConfigSync("./test/config/config-badchildjson.json"); - }, - { - "message": - // eslint-disable-next-line max-len - /Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+/ - }, - "Did not get correct exception for bad child JSON." - ); -}); - -test("configSingleYamlSync", (t) => { - t.plan(1); - const actual = markdownlint.readConfigSync( - // @ts-ignore - "./test/config/config-child.yaml", [ require("js-yaml").load ]); - const expected = require("./config/config-child.json"); - t.deepEqual(actual, expected, "Config object not correct."); -}); - -test("configMultipleYamlSync", (t) => { - t.plan(1); - const actual = markdownlint.readConfigSync( - // @ts-ignore - "./test/config/config-grandparent.yaml", [ require("js-yaml").load ]); - const expected = { - ...require("./config/config-child.json"), - ...require("./config/config-parent.json"), - ...require("./config/config-grandparent.json") - }; - delete expected.extends; - t.deepEqual(actual, expected, "Config object not correct."); -}); - -test("configMultipleHybridSync", (t) => { - t.plan(1); - const actual = markdownlint.readConfigSync( - "./test/config/config-grandparent-hybrid.yaml", - // @ts-ignore - [ JSON.parse, require("toml").parse, require("js-yaml").load ]); - const expected = { - ...require("./config/config-child.json"), - ...require("./config/config-parent.json"), - ...require("./config/config-grandparent.json") - }; - delete expected.extends; - t.like(actual, expected, "Config object not correct."); -}); - -test("configCustomFileSystemSync", (t) => { - t.plan(4); - const file = path.resolve("/dir/file.json"); - const extended = path.resolve("/dir/extended.json"); - const fileContent = { - "extends": extended, - "default": true, - "MD001": false - }; - const extendedContent = { - "MD001": true, - "MD002": true - }; - const fsApi = { - "accessSync": (p) => { - t.is(p, extended); - }, - "readFileSync": (p) => { - switch (p) { - case file: - t.is(p, file); - return JSON.stringify(fileContent); - case extended: - t.is(p, extended); - return JSON.stringify(extendedContent); - default: - return t.fail(); - } - } - }; - const actual = markdownlint.readConfigSync(file, null, fsApi); - const expected = { - ...extendedContent, - ...fileContent - }; - delete expected.extends; - t.deepEqual(actual, expected, "Config object not correct."); -}); - -test("configBadHybridSync", (t) => { - t.plan(1); - t.throws( - function badHybridCall() { - markdownlint.readConfigSync( - "./test/config/config-badcontent.txt", - // @ts-ignore - [ JSON.parse, require("toml").parse, require("js-yaml").load ]); - }, - { - // eslint-disable-next-line max-len - "message": /^Unable to parse '[^']*'; Parser \d+: Unexpected token \S+ in JSON at position \d+;/ - }, - "Did not get correct exception for bad content." - ); -}); - -test.cb("configSinglePromise", (t) => { - t.plan(1); - markdownlint.promises.readConfig("./test/config/config-child.json") - .then((actual) => { - const expected = require("./config/config-child.json"); - t.deepEqual(actual, expected, "Config object not correct."); - t.end(); - }); -}); - -test.cb("configCustomFileSystemPromise", (t) => { - t.plan(4); - const file = path.resolve("/dir/file.json"); - const extended = path.resolve("/dir/extended.json"); - const fileContent = { - "extends": extended, - "default": true, - "MD001": false - }; - const extendedContent = { - "MD001": true, - "MD002": true - }; - const fsApi = { - "access": (p, m, cb) => { - t.is(p, extended); - return (cb || m)(); - }, - "readFile": (p, o, cb) => { - switch (p) { - case file: - t.is(p, file); - return cb(null, JSON.stringify(fileContent)); - case extended: - t.is(p, extended); - return cb(null, JSON.stringify(extendedContent)); - default: - return t.fail(); - } - } - }; - markdownlint.promises.readConfig(file, null, fsApi) - .then((actual) => { - const expected = { - ...extendedContent, - ...fileContent - }; - delete expected.extends; - t.deepEqual(actual, expected, "Config object not correct."); - t.end(); - }); -}); - -test.cb("configBadFilePromise", (t) => { - t.plan(2); - markdownlint.promises.readConfig("./test/config/config-badfile.json") - .then( - null, - (error) => { - t.truthy(error, "Did not get an error for bad JSON."); - t.true(error instanceof Error, "Error not instance of Error."); - t.end(); - } - ); -}); - test("allBuiltInRulesHaveValidUrl", (t) => { t.plan(138); rules.forEach(function forRule(rule) { From 53e5e4272eb0e9e4815c96a97bb29dc2db0d39e8 Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 30 Nov 2021 22:35:19 -0800 Subject: [PATCH 36/69] Update custom rules tests to have matching coverage for sync and async scenarios, use test.cb/t.end for all async tests. --- test/markdownlint-test-custom-rules.js | 211 +++++++++++++++++++++++-- 1 file changed, 200 insertions(+), 11 deletions(-) diff --git a/test/markdownlint-test-custom-rules.js b/test/markdownlint-test-custom-rules.js index b7ef83c7..358cfade 100644 --- a/test/markdownlint-test-custom-rules.js +++ b/test/markdownlint-test-custom-rules.js @@ -328,7 +328,7 @@ test.cb("customRulesConfig", (t) => { }); }); -test("customRulesNpmPackage", (t) => { +test.cb("customRulesNpmPackage", (t) => { t.plan(2); const options = { "customRules": [ require("./rules/npm") ], @@ -345,6 +345,7 @@ test("customRulesNpmPackage", (t) => { }; // @ts-ignore t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); }); }); @@ -395,7 +396,7 @@ test("customRulesBadProperty", (t) => { }); }); -test("customRulesUsedNameName", (t) => { +test.cb("customRulesUsedNameName", (t) => { t.plan(4); markdownlint({ "customRules": [ @@ -414,10 +415,11 @@ test("customRulesUsedNameName", (t) => { "already used as a name or tag.", "Incorrect message for duplicate name."); t.true(!result, "Got result for duplicate name."); + t.end(); }); }); -test("customRulesUsedNameTag", (t) => { +test.cb("customRulesUsedNameTag", (t) => { t.plan(4); markdownlint({ "customRules": [ @@ -435,10 +437,11 @@ test("customRulesUsedNameTag", (t) => { "Name 'HtMl' of custom rule at index 0 is already used as a name or tag.", "Incorrect message for duplicate name."); t.true(!result, "Got result for duplicate name."); + t.end(); }); }); -test("customRulesUsedTagName", (t) => { +test.cb("customRulesUsedTagName", (t) => { t.plan(4); markdownlint({ "customRules": [ @@ -463,6 +466,7 @@ test("customRulesUsedTagName", (t) => { "already used as a name.", "Incorrect message for duplicate name."); t.true(!result, "Got result for duplicate tag."); + t.end(); }); }); @@ -517,7 +521,7 @@ test("customRulesThrowForFileSync", (t) => { ); }); -test("customRulesThrowForString", (t) => { +test.cb("customRulesThrowForString", (t) => { t.plan(4); const exceptionMessage = "Test exception message"; markdownlint({ @@ -540,10 +544,69 @@ test("customRulesThrowForString", (t) => { t.is(err.message, exceptionMessage, "Incorrect message for function thrown."); t.true(!result, "Got result for function thrown."); + t.end(); }); }); -test("customRulesOnErrorNull", (t) => { +test("customRulesThrowForStringSync", (t) => { + t.plan(1); + const exceptionMessage = "Test exception message"; + t.throws( + function customRuleThrowsCall() { + markdownlint.sync({ + "customRules": [ + { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "function": function throws() { + throw new Error(exceptionMessage); + } + } + ], + "strings": { + "string": "String" + } + }); + }, + { + "message": exceptionMessage + }, + "Did not get correct exception for function thrown." + ); +}); + +test.cb("customRulesOnErrorNull", (t) => { + t.plan(4); + markdownlint({ + "customRules": [ + { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "function": function onErrorNull(params, onError) { + onError(null); + } + } + ], + "strings": { + "string": "String" + } + }, + function callback(err, result) { + t.truthy(err, "Did not get an error for function thrown."); + t.true(err instanceof Error, "Error not instance of Error."); + t.is( + err.message, + "Property 'lineNumber' of onError parameter is incorrect.", + "Did not get correct exception for null object." + ); + t.true(!result, "Got result for function thrown."); + t.end(); + }); +}); + +test("customRulesOnErrorNullSync", (t) => { t.plan(1); const options = { "customRules": [ @@ -802,7 +865,7 @@ test("customRulesOnErrorValid", (t) => { }); }); -test("customRulesOnErrorLazy", (t) => { +test.cb("customRulesOnErrorLazy", (t) => { t.plan(2); const options = { "customRules": [ @@ -840,10 +903,11 @@ test("customRulesOnErrorLazy", (t) => { ] }; t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); }); }); -test("customRulesOnErrorModified", (t) => { +test.cb("customRulesOnErrorModified", (t) => { t.plan(2); const errorObject = { "lineNumber": 1, @@ -900,6 +964,7 @@ test("customRulesOnErrorModified", (t) => { ] }; t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); }); }); @@ -940,7 +1005,41 @@ test.cb("customRulesThrowForFileHandled", (t) => { }); }); -test("customRulesThrowForStringHandled", (t) => { +test("customRulesThrowForFileHandledSync", (t) => { + t.plan(1); + const exceptionMessage = "Test exception message"; + const actualResult = markdownlint.sync({ + "customRules": [ + { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "function": function throws() { + throw new Error(exceptionMessage); + } + } + ], + "files": [ "./test/custom-rules.md" ], + "handleRuleFailures": true + }); + const expectedResult = { + "./test/custom-rules.md": [ + { + "lineNumber": 1, + "ruleNames": [ "name" ], + "ruleDescription": "description", + "ruleInformation": null, + "errorDetail": + `This rule threw an exception: ${exceptionMessage}`, + "errorContext": null, + "errorRange": null + } + ] + }; + t.deepEqual(actualResult, expectedResult, "Undetected issues."); +}); + +test.cb("customRulesThrowForStringHandled", (t) => { t.plan(2); const exceptionMessage = "Test exception message"; const informationUrl = "https://example.com/rule"; @@ -988,10 +1087,60 @@ test("customRulesThrowForStringHandled", (t) => { ] }; t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); }); }); -test("customRulesOnErrorInvalidHandled", (t) => { +test("customRulesThrowForStringHandledSync", (t) => { + t.plan(1); + const exceptionMessage = "Test exception message"; + const informationUrl = "https://example.com/rule"; + const actualResult = markdownlint.sync({ + "customRules": [ + { + "names": [ "name" ], + "description": "description", + "information": new URL(informationUrl), + "tags": [ "tag" ], + "function": function throws() { + throw new Error(exceptionMessage); + } + } + ], + "strings": { + "string": "String\n" + }, + "handleRuleFailures": true + }); + const expectedResult = { + "string": [ + { + "lineNumber": 1, + "ruleNames": [ "MD041", "first-line-heading", "first-line-h1" ], + "ruleDescription": + "First line in a file should be a top-level heading", + "ruleInformation": + `${homepage}/blob/v${version}/doc/Rules.md#md041`, + "errorDetail": null, + "errorContext": "String", + "errorRange": null + }, + { + "lineNumber": 1, + "ruleNames": [ "name" ], + "ruleDescription": "description", + "ruleInformation": informationUrl, + "errorDetail": + `This rule threw an exception: ${exceptionMessage}`, + "errorContext": null, + "errorRange": null + } + ] + }; + t.deepEqual(actualResult, expectedResult, "Undetected issues."); +}); + +test.cb("customRulesOnErrorInvalidHandled", (t) => { t.plan(2); markdownlint({ "customRules": [ @@ -1028,9 +1177,48 @@ test("customRulesOnErrorInvalidHandled", (t) => { ] }; t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); }); }); +test("customRulesOnErrorInvalidHandledSync", (t) => { + t.plan(1); + const actualResult = markdownlint.sync({ + "customRules": [ + { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "function": function onErrorInvalid(params, onError) { + onError({ + "lineNumber": 13, + "detail": "N/A" + }); + } + } + ], + "strings": { + "string": "# Heading\n" + }, + "handleRuleFailures": true + }); + const expectedResult = { + "string": [ + { + "lineNumber": 1, + "ruleNames": [ "name" ], + "ruleDescription": "description", + "ruleInformation": null, + "errorDetail": "This rule threw an exception: " + + "Property 'lineNumber' of onError parameter is incorrect.", + "errorContext": null, + "errorRange": null + } + ] + }; + t.deepEqual(actualResult, expectedResult, "Undetected issues."); +}); + test.cb("customRulesFileName", (t) => { t.plan(2); const options = { @@ -1052,7 +1240,7 @@ test.cb("customRulesFileName", (t) => { }); }); -test("customRulesStringName", (t) => { +test.cb("customRulesStringName", (t) => { t.plan(2); const options = { "customRules": [ @@ -1071,6 +1259,7 @@ test("customRulesStringName", (t) => { }; markdownlint(options, function callback(err) { t.falsy(err); + t.end(); }); }); From e531bd635981a26cdc7e2fdea7c130dbcd2a9c18 Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 3 Dec 2021 22:43:58 -0800 Subject: [PATCH 37/69] Refactor lintInput to share code between sync/async, support an async path for strings, and process files first for better concurrency. --- demo/markdownlint-browser.js | 94 +++++++++++++------------- lib/markdownlint.js | 126 +++++++++++++++-------------------- 2 files changed, 101 insertions(+), 119 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 5fb5f0c7..087bc320 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -1608,65 +1608,63 @@ function lintInput(options, synchronous, callback) { var fs = options.fs || __webpack_require__(/*! fs */ "?ec0a"); var results = newResults(ruleList); var done = false; - // Linting of strings is always synchronous - var syncItem = null; - // eslint-disable-next-line jsdoc/require-jsdoc - function syncCallback(err, result) { - if (err) { - done = true; - return callback(err); - } - results[syncItem] = result; - return null; - } - while (!done && (syncItem = stringsKeys.shift())) { - lintContent(ruleList, syncItem, strings[syncItem] || "", md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, syncCallback); - } - if (synchronous) { - // Lint files synchronously - while (!done && (syncItem = files.shift())) { - lintFile(ruleList, syncItem, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, fs, synchronous, syncCallback); - } - return done || callback(null, results); - } - // Lint files asynchronously var concurrency = 0; // eslint-disable-next-line jsdoc/require-jsdoc - function lintConcurrently() { - var asyncItem = files.shift(); - if (done) { - // Nothing to do + function lintWorker() { + var currentItem = null; + // eslint-disable-next-line jsdoc/require-jsdoc + function lintWorkerCallback(err, result) { + concurrency--; + if (err) { + done = true; + return callback(err); + } + results[currentItem] = result; + if (!synchronous) { + lintWorker(); + } + return null; } - else if (asyncItem) { + if (done) { + // Abort for error or nothing left to do + } + else if (files.length > 0) { + // Lint next file concurrency++; - lintFile(ruleList, asyncItem, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, fs, synchronous, function (err, result) { - concurrency--; - if (err) { - done = true; - return callback(err); - } - results[asyncItem] = result; - lintConcurrently(); - return null; - }); + currentItem = files.shift(); + lintFile(ruleList, currentItem, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, fs, synchronous, lintWorkerCallback); + } + else if (stringsKeys.length > 0) { + // Lint next string + concurrency++; + currentItem = stringsKeys.shift(); + lintContent(ruleList, currentItem, strings[currentItem] || "", md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, lintWorkerCallback); } else if (concurrency === 0) { + // Finish done = true; return callback(null, results); } return null; } - // Testing on a Raspberry Pi 4 Model B with an artificial 5ms file access - // delay suggests that a concurrency factor of 8 can eliminate the impact - // of that delay (i.e., total time is the same as with no delay). - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); + if (synchronous) { + while (!done) { + lintWorker(); + } + } + else { + // Testing on a Raspberry Pi 4 Model B with an artificial 5ms file access + // delay suggests that a concurrency factor of 8 can eliminate the impact + // of that delay (i.e., total time is the same as with no delay). + lintWorker(); + lintWorker(); + lintWorker(); + lintWorker(); + lintWorker(); + lintWorker(); + lintWorker(); + lintWorker(); + } return null; } /** diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 5ce2b17b..10eeb4b3 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -749,62 +749,32 @@ function lintInput(options, synchronous, callback) { const fs = options.fs || require("fs"); const results = newResults(ruleList); let done = false; - // Linting of strings is always synchronous - let syncItem = null; - // eslint-disable-next-line jsdoc/require-jsdoc - function syncCallback(err, result) { - if (err) { - done = true; - return callback(err); - } - results[syncItem] = result; - return null; - } - while (!done && (syncItem = stringsKeys.shift())) { - lintContent( - ruleList, - syncItem, - strings[syncItem] || "", - md, - config, - frontMatter, - handleRuleFailures, - noInlineConfig, - resultVersion, - syncCallback - ); - } - if (synchronous) { - // Lint files synchronously - while (!done && (syncItem = files.shift())) { - lintFile( - ruleList, - syncItem, - md, - config, - frontMatter, - handleRuleFailures, - noInlineConfig, - resultVersion, - fs, - synchronous, - syncCallback - ); - } - return done || callback(null, results); - } - // Lint files asynchronously let concurrency = 0; // eslint-disable-next-line jsdoc/require-jsdoc - function lintConcurrently() { - const asyncItem = files.shift(); + function lintWorker() { + let currentItem = null; + // eslint-disable-next-line jsdoc/require-jsdoc + function lintWorkerCallback(err, result) { + concurrency--; + if (err) { + done = true; + return callback(err); + } + results[currentItem] = result; + if (!synchronous) { + lintWorker(); + } + return null; + } if (done) { - // Nothing to do - } else if (asyncItem) { + // Abort for error or nothing left to do + } else if (files.length > 0) { + // Lint next file concurrency++; + currentItem = files.shift(); lintFile( ruleList, - asyncItem, + currentItem, md, config, frontMatter, @@ -813,34 +783,48 @@ function lintInput(options, synchronous, callback) { resultVersion, fs, synchronous, - (err, result) => { - concurrency--; - if (err) { - done = true; - return callback(err); - } - results[asyncItem] = result; - lintConcurrently(); - return null; - } + lintWorkerCallback + ); + } else if (stringsKeys.length > 0) { + // Lint next string + concurrency++; + currentItem = stringsKeys.shift(); + lintContent( + ruleList, + currentItem, + strings[currentItem] || "", + md, + config, + frontMatter, + handleRuleFailures, + noInlineConfig, + resultVersion, + lintWorkerCallback ); } else if (concurrency === 0) { + // Finish done = true; return callback(null, results); } return null; } - // Testing on a Raspberry Pi 4 Model B with an artificial 5ms file access - // delay suggests that a concurrency factor of 8 can eliminate the impact - // of that delay (i.e., total time is the same as with no delay). - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); - lintConcurrently(); + if (synchronous) { + while (!done) { + lintWorker(); + } + } else { + // Testing on a Raspberry Pi 4 Model B with an artificial 5ms file access + // delay suggests that a concurrency factor of 8 can eliminate the impact + // of that delay (i.e., total time is the same as with no delay). + lintWorker(); + lintWorker(); + lintWorker(); + lintWorker(); + lintWorker(); + lintWorker(); + lintWorker(); + lintWorker(); + } return null; } From d3c56d3ab86128073208b014219ed3a8916968cb Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 4 Dec 2021 17:02:11 -0800 Subject: [PATCH 38/69] Refactor lintContent to do less processing of errors for each rule and instead handle all errors at the end. --- demo/markdownlint-browser.js | 130 ++++++++++++++++------------------ lib/markdownlint.js | 132 ++++++++++++++++------------------- 2 files changed, 119 insertions(+), 143 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 087bc320..c2915315 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -1306,35 +1306,6 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin enabledRulesPerLineNumber: enabledRulesPerLineNumber }; } -/** - * Compare function for Array.prototype.sort for ascending order of errors. - * - * @param {LintError} a First error. - * @param {LintError} b Second error. - * @returns {number} Positive value if a>b, negative value if b array[index - 1].lineNumber); -} /** * Lints a string containing Markdown content. * @@ -1375,7 +1346,7 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul cache.flattenedLists(helpers.flattenLists(params.tokens)); cache.codeBlockAndSpanRanges(helpers.codeBlockAndSpanRanges(params, cache.lineMetadata())); // Function to run for each rule - var result = (resultVersion === 0) ? {} : []; + var results = []; // eslint-disable-next-line jsdoc/require-jsdoc function forRule(rule) { // Configure rule @@ -1463,7 +1434,7 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul "fixInfo": fixInfo ? cleanFixInfo : null }); } - // Call (possibly external) rule function + // Call (possibly external) rule function to report errors if (handleRuleFailures) { try { rule.function(params, onError); @@ -1480,46 +1451,20 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul } // Record any errors (significant performance benefit from length check) if (errors.length > 0) { - errors.sort(lineNumberComparison); var filteredErrors = errors - .filter((resultVersion === 3) ? - filterAllValues : - uniqueFilterForSortedErrors) - .filter(function removeDisabledRules(error) { - return enabledRulesPerLineNumber[error.lineNumber][ruleName]; - }) - .map(function formatResults(error) { - if (resultVersion === 0) { - return error.lineNumber; - } - var errorObject = {}; - errorObject.lineNumber = error.lineNumber; - if (resultVersion === 1) { - errorObject.ruleName = ruleNameFriendly; - errorObject.ruleAlias = rule.names[1] || rule.names[0]; - } - else { - errorObject.ruleNames = rule.names; - } - errorObject.ruleDescription = rule.description; - errorObject.ruleInformation = - rule.information ? rule.information.href : null; - errorObject.errorDetail = error.detail; - errorObject.errorContext = error.context; - errorObject.errorRange = error.range; - if (resultVersion === 3) { - errorObject.fixInfo = error.fixInfo; - } - return errorObject; - }); - if (filteredErrors.length > 0) { - if (resultVersion === 0) { - result[ruleNameFriendly] = filteredErrors; - } - else { - Array.prototype.push.apply(result, filteredErrors); - } - } + .filter(function (error) { return (enabledRulesPerLineNumber[error.lineNumber][ruleName]); }) + .map(function (error) { return ({ + "lineNumber": error.lineNumber, + "ruleName": rule.names[0], + "ruleNames": rule.names, + "ruleDescription": rule.description, + "ruleInformation": rule.information ? rule.information.href : null, + "errorDetail": error.detail, + "errorContext": error.context, + "errorRange": error.range, + "fixInfo": error.fixInfo + }); }); + Array.prototype.push.apply(results, filteredErrors); } } // Run all rules @@ -1531,7 +1476,50 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul return callback(error); } cache.clear(); - return callback(null, result); + // Sort results by rule name by line number + results.sort(function (a, b) { return (a.ruleName.localeCompare(b.ruleName) || + a.lineNumber - b.lineNumber); }); + if (resultVersion < 3) { + // Remove fixInfo and multiple errors for the same rule and line number + var noPrevious_1 = { + "ruleName": null, + "lineNumber": -1 + }; + results = results.filter(function (error, index, array) { + delete error.fixInfo; + var previous = array[index - 1] || noPrevious_1; + return ((error.ruleName !== previous.ruleName) || + (error.lineNumber !== previous.lineNumber)); + }); + } + if (resultVersion === 0) { + // Return a dictionary of rule->[line numbers] + var dictionary = {}; + for (var _i = 0, results_1 = results; _i < results_1.length; _i++) { + var error = results_1[_i]; + var ruleLines = dictionary[error.ruleName] || []; + ruleLines.push(error.lineNumber); + dictionary[error.ruleName] = ruleLines; + } + // @ts-ignore + results = dictionary; + } + else if (resultVersion === 1) { + // Use ruleAlias instead of ruleNames + for (var _b = 0, results_2 = results; _b < results_2.length; _b++) { + var error = results_2[_b]; + error.ruleAlias = error.ruleNames[1] || error.ruleName; + delete error.ruleNames; + } + } + else { + // resultVersion 2 or 3: Remove unwanted ruleName + for (var _c = 0, results_3 = results; _c < results_3.length; _c++) { + var error = results_3[_c]; + delete error.ruleName; + } + } + return callback(null, results); } /** * Lints a file containing Markdown content. diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 10eeb4b3..7c4554d1 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -416,38 +416,6 @@ function getEnabledRulesPerLineNumber( }; } -/** - * Compare function for Array.prototype.sort for ascending order of errors. - * - * @param {LintError} a First error. - * @param {LintError} b Second error. - * @returns {number} Positive value if a>b, negative value if b array[index - 1].lineNumber); -} - /** * Lints a string containing Markdown content. * @@ -508,7 +476,7 @@ function lintContent( helpers.codeBlockAndSpanRanges(params, cache.lineMetadata()) ); // Function to run for each rule - const result = (resultVersion === 0) ? {} : []; + let results = []; // eslint-disable-next-line jsdoc/require-jsdoc function forRule(rule) { // Configure rule @@ -597,7 +565,7 @@ function lintContent( "fixInfo": fixInfo ? cleanFixInfo : null }); } - // Call (possibly external) rule function + // Call (possibly external) rule function to report errors if (handleRuleFailures) { try { rule.function(params, onError); @@ -612,44 +580,22 @@ function lintContent( } // Record any errors (significant performance benefit from length check) if (errors.length > 0) { - errors.sort(lineNumberComparison); const filteredErrors = errors - .filter((resultVersion === 3) ? - filterAllValues : - uniqueFilterForSortedErrors) - .filter(function removeDisabledRules(error) { - return enabledRulesPerLineNumber[error.lineNumber][ruleName]; - }) - .map(function formatResults(error) { - if (resultVersion === 0) { - return error.lineNumber; - } - const errorObject = {}; - errorObject.lineNumber = error.lineNumber; - if (resultVersion === 1) { - errorObject.ruleName = ruleNameFriendly; - errorObject.ruleAlias = rule.names[1] || rule.names[0]; - } else { - errorObject.ruleNames = rule.names; - } - errorObject.ruleDescription = rule.description; - errorObject.ruleInformation = - rule.information ? rule.information.href : null; - errorObject.errorDetail = error.detail; - errorObject.errorContext = error.context; - errorObject.errorRange = error.range; - if (resultVersion === 3) { - errorObject.fixInfo = error.fixInfo; - } - return errorObject; - }); - if (filteredErrors.length > 0) { - if (resultVersion === 0) { - result[ruleNameFriendly] = filteredErrors; - } else { - Array.prototype.push.apply(result, filteredErrors); - } - } + .filter((error) => ( + enabledRulesPerLineNumber[error.lineNumber][ruleName] + )) + .map((error) => ({ + "lineNumber": error.lineNumber, + "ruleName": rule.names[0], + "ruleNames": rule.names, + "ruleDescription": rule.description, + "ruleInformation": rule.information ? rule.information.href : null, + "errorDetail": error.detail, + "errorContext": error.context, + "errorRange": error.range, + "fixInfo": error.fixInfo + })); + Array.prototype.push.apply(results, filteredErrors); } } // Run all rules @@ -660,7 +606,49 @@ function lintContent( return callback(error); } cache.clear(); - return callback(null, result); + // Sort results by rule name by line number + results.sort((a, b) => ( + a.ruleName.localeCompare(b.ruleName) || + a.lineNumber - b.lineNumber + )); + if (resultVersion < 3) { + // Remove fixInfo and multiple errors for the same rule and line number + const noPrevious = { + "ruleName": null, + "lineNumber": -1 + }; + results = results.filter((error, index, array) => { + delete error.fixInfo; + const previous = array[index - 1] || noPrevious; + return ( + (error.ruleName !== previous.ruleName) || + (error.lineNumber !== previous.lineNumber) + ); + }); + } + if (resultVersion === 0) { + // Return a dictionary of rule->[line numbers] + const dictionary = {}; + for (const error of results) { + const ruleLines = dictionary[error.ruleName] || []; + ruleLines.push(error.lineNumber); + dictionary[error.ruleName] = ruleLines; + } + // @ts-ignore + results = dictionary; + } else if (resultVersion === 1) { + // Use ruleAlias instead of ruleNames + for (const error of results) { + error.ruleAlias = error.ruleNames[1] || error.ruleName; + delete error.ruleNames; + } + } else { + // resultVersion 2 or 3: Remove unwanted ruleName + for (const error of results) { + delete error.ruleName; + } + } + return callback(null, results); } /** From e7662b11b5bd933bd9468bda3112a4378e1197d0 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 4 Dec 2021 22:09:20 -0800 Subject: [PATCH 39/69] Simplify lintContent by removing errors array and processing errors in onError so nothing needs to be done after invoking a rule. --- demo/markdownlint-browser.js | 39 ++++++++++++---------------------- lib/markdownlint.js | 41 ++++++++++++------------------------ 2 files changed, 28 insertions(+), 52 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index c2915315..ef973fbe 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -1350,14 +1350,12 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul // eslint-disable-next-line jsdoc/require-jsdoc function forRule(rule) { // Configure rule - var ruleNameFriendly = rule.names[0]; - var ruleName = ruleNameFriendly.toUpperCase(); + var ruleName = rule.names[0].toUpperCase(); params.config = effectiveConfig[ruleName]; // eslint-disable-next-line jsdoc/require-jsdoc function throwError(property) { throw new Error("Property '" + property + "' of onError parameter is incorrect."); } - var errors = []; // eslint-disable-next-line jsdoc/require-jsdoc function onError(errorInfo) { if (!errorInfo || @@ -1366,6 +1364,10 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul (errorInfo.lineNumber > lines.length)) { throwError("lineNumber"); } + var lineNumber = errorInfo.lineNumber + frontMatterLines.length; + if (!enabledRulesPerLineNumber[lineNumber][ruleName]) { + return; + } if (errorInfo.detail && !helpers.isString(errorInfo.detail)) { throwError("detail"); @@ -1426,11 +1428,15 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul cleanFixInfo.insertText = fixInfo.insertText; } } - errors.push({ - "lineNumber": errorInfo.lineNumber + frontMatterLines.length, - "detail": errorInfo.detail || null, - "context": errorInfo.context || null, - "range": errorInfo.range ? __spreadArray([], errorInfo.range) : null, + results.push({ + lineNumber: lineNumber, + "ruleName": rule.names[0], + "ruleNames": rule.names, + "ruleDescription": rule.description, + "ruleInformation": rule.information ? rule.information.href : null, + "errorDetail": errorInfo.detail || null, + "errorContext": errorInfo.context || null, + "errorRange": errorInfo.range ? __spreadArray([], errorInfo.range) : null, "fixInfo": fixInfo ? cleanFixInfo : null }); } @@ -1449,23 +1455,6 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul else { rule.function(params, onError); } - // Record any errors (significant performance benefit from length check) - if (errors.length > 0) { - var filteredErrors = errors - .filter(function (error) { return (enabledRulesPerLineNumber[error.lineNumber][ruleName]); }) - .map(function (error) { return ({ - "lineNumber": error.lineNumber, - "ruleName": rule.names[0], - "ruleNames": rule.names, - "ruleDescription": rule.description, - "ruleInformation": rule.information ? rule.information.href : null, - "errorDetail": error.detail, - "errorContext": error.context, - "errorRange": error.range, - "fixInfo": error.fixInfo - }); }); - Array.prototype.push.apply(results, filteredErrors); - } } // Run all rules try { diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 7c4554d1..0f841cb8 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -480,15 +480,13 @@ function lintContent( // eslint-disable-next-line jsdoc/require-jsdoc function forRule(rule) { // Configure rule - const ruleNameFriendly = rule.names[0]; - const ruleName = ruleNameFriendly.toUpperCase(); + const ruleName = rule.names[0].toUpperCase(); params.config = effectiveConfig[ruleName]; // eslint-disable-next-line jsdoc/require-jsdoc function throwError(property) { throw new Error( "Property '" + property + "' of onError parameter is incorrect."); } - const errors = []; // eslint-disable-next-line jsdoc/require-jsdoc function onError(errorInfo) { if (!errorInfo || @@ -497,6 +495,10 @@ function lintContent( (errorInfo.lineNumber > lines.length)) { throwError("lineNumber"); } + const lineNumber = errorInfo.lineNumber + frontMatterLines.length; + if (!enabledRulesPerLineNumber[lineNumber][ruleName]) { + return; + } if (errorInfo.detail && !helpers.isString(errorInfo.detail)) { throwError("detail"); @@ -557,11 +559,15 @@ function lintContent( cleanFixInfo.insertText = fixInfo.insertText; } } - errors.push({ - "lineNumber": errorInfo.lineNumber + frontMatterLines.length, - "detail": errorInfo.detail || null, - "context": errorInfo.context || null, - "range": errorInfo.range ? [ ...errorInfo.range ] : null, + results.push({ + lineNumber, + "ruleName": rule.names[0], + "ruleNames": rule.names, + "ruleDescription": rule.description, + "ruleInformation": rule.information ? rule.information.href : null, + "errorDetail": errorInfo.detail || null, + "errorContext": errorInfo.context || null, + "errorRange": errorInfo.range ? [ ...errorInfo.range ] : null, "fixInfo": fixInfo ? cleanFixInfo : null }); } @@ -578,25 +584,6 @@ function lintContent( } else { rule.function(params, onError); } - // Record any errors (significant performance benefit from length check) - if (errors.length > 0) { - const filteredErrors = errors - .filter((error) => ( - enabledRulesPerLineNumber[error.lineNumber][ruleName] - )) - .map((error) => ({ - "lineNumber": error.lineNumber, - "ruleName": rule.names[0], - "ruleNames": rule.names, - "ruleDescription": rule.description, - "ruleInformation": rule.information ? rule.information.href : null, - "errorDetail": error.detail, - "errorContext": error.context, - "errorRange": error.range, - "fixInfo": error.fixInfo - })); - Array.prototype.push.apply(results, filteredErrors); - } } // Run all rules try { From 109e0d8cb67c873e4b712b25298c68e5270b5b92 Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 10 Dec 2021 21:33:20 -0800 Subject: [PATCH 40/69] Refactor custom rule exception tests for smaller size and better coverage. --- demo/markdownlint-browser.js | 5 +- lib/markdownlint.js | 5 +- test/markdownlint-test-custom-rules.js | 298 ++++++++++--------------- 3 files changed, 129 insertions(+), 179 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index ef973fbe..ccd34c2c 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -1446,9 +1446,10 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul rule.function(params, onError); } catch (error) { + var message = (error instanceof Error) ? error.message : error; onError({ "lineNumber": 1, - "detail": "This rule threw an exception: " + error.message + "detail": "This rule threw an exception: " + message }); } } @@ -1462,7 +1463,7 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul } catch (error) { cache.clear(); - return callback(error); + return callback((error instanceof Error) ? error : new Error(error)); } cache.clear(); // Sort results by rule name by line number diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 0f841cb8..1d810303 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -576,9 +576,10 @@ function lintContent( try { rule.function(params, onError); } catch (error) { + const message = (error instanceof Error) ? error.message : error; onError({ "lineNumber": 1, - "detail": `This rule threw an exception: ${error.message}` + "detail": `This rule threw an exception: ${message}` }); } } else { @@ -590,7 +591,7 @@ function lintContent( ruleList.forEach(forRule); } catch (error) { cache.clear(); - return callback(error); + return callback((error instanceof Error) ? error : new Error(error)); } cache.clear(); // Sort results by rule name by line number diff --git a/test/markdownlint-test-custom-rules.js b/test/markdownlint-test-custom-rules.js index 358cfade..63b8f799 100644 --- a/test/markdownlint-test-custom-rules.js +++ b/test/markdownlint-test-custom-rules.js @@ -7,7 +7,6 @@ const packageJson = require("../package.json"); const markdownlint = require("../lib/markdownlint"); const customRules = require("./rules/rules.js"); const homepage = packageJson.homepage; -const version = packageJson.version; test.cb("customRulesV0", (t) => { t.plan(4); @@ -968,178 +967,6 @@ test.cb("customRulesOnErrorModified", (t) => { }); }); -test.cb("customRulesThrowForFileHandled", (t) => { - t.plan(2); - const exceptionMessage = "Test exception message"; - markdownlint({ - "customRules": [ - { - "names": [ "name" ], - "description": "description", - "tags": [ "tag" ], - "function": function throws() { - throw new Error(exceptionMessage); - } - } - ], - "files": [ "./test/custom-rules.md" ], - "handleRuleFailures": true - }, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "./test/custom-rules.md": [ - { - "lineNumber": 1, - "ruleNames": [ "name" ], - "ruleDescription": "description", - "ruleInformation": null, - "errorDetail": - `This rule threw an exception: ${exceptionMessage}`, - "errorContext": null, - "errorRange": null - } - ] - }; - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test("customRulesThrowForFileHandledSync", (t) => { - t.plan(1); - const exceptionMessage = "Test exception message"; - const actualResult = markdownlint.sync({ - "customRules": [ - { - "names": [ "name" ], - "description": "description", - "tags": [ "tag" ], - "function": function throws() { - throw new Error(exceptionMessage); - } - } - ], - "files": [ "./test/custom-rules.md" ], - "handleRuleFailures": true - }); - const expectedResult = { - "./test/custom-rules.md": [ - { - "lineNumber": 1, - "ruleNames": [ "name" ], - "ruleDescription": "description", - "ruleInformation": null, - "errorDetail": - `This rule threw an exception: ${exceptionMessage}`, - "errorContext": null, - "errorRange": null - } - ] - }; - t.deepEqual(actualResult, expectedResult, "Undetected issues."); -}); - -test.cb("customRulesThrowForStringHandled", (t) => { - t.plan(2); - const exceptionMessage = "Test exception message"; - const informationUrl = "https://example.com/rule"; - markdownlint({ - "customRules": [ - { - "names": [ "name" ], - "description": "description", - "information": new URL(informationUrl), - "tags": [ "tag" ], - "function": function throws() { - throw new Error(exceptionMessage); - } - } - ], - "strings": { - "string": "String\n" - }, - "handleRuleFailures": true - }, function callback(err, actualResult) { - t.falsy(err); - const expectedResult = { - "string": [ - { - "lineNumber": 1, - "ruleNames": [ "MD041", "first-line-heading", "first-line-h1" ], - "ruleDescription": - "First line in a file should be a top-level heading", - "ruleInformation": - `${homepage}/blob/v${version}/doc/Rules.md#md041`, - "errorDetail": null, - "errorContext": "String", - "errorRange": null - }, - { - "lineNumber": 1, - "ruleNames": [ "name" ], - "ruleDescription": "description", - "ruleInformation": informationUrl, - "errorDetail": - `This rule threw an exception: ${exceptionMessage}`, - "errorContext": null, - "errorRange": null - } - ] - }; - t.deepEqual(actualResult, expectedResult, "Undetected issues."); - t.end(); - }); -}); - -test("customRulesThrowForStringHandledSync", (t) => { - t.plan(1); - const exceptionMessage = "Test exception message"; - const informationUrl = "https://example.com/rule"; - const actualResult = markdownlint.sync({ - "customRules": [ - { - "names": [ "name" ], - "description": "description", - "information": new URL(informationUrl), - "tags": [ "tag" ], - "function": function throws() { - throw new Error(exceptionMessage); - } - } - ], - "strings": { - "string": "String\n" - }, - "handleRuleFailures": true - }); - const expectedResult = { - "string": [ - { - "lineNumber": 1, - "ruleNames": [ "MD041", "first-line-heading", "first-line-h1" ], - "ruleDescription": - "First line in a file should be a top-level heading", - "ruleInformation": - `${homepage}/blob/v${version}/doc/Rules.md#md041`, - "errorDetail": null, - "errorContext": "String", - "errorRange": null - }, - { - "lineNumber": 1, - "ruleNames": [ "name" ], - "ruleDescription": "description", - "ruleInformation": informationUrl, - "errorDetail": - `This rule threw an exception: ${exceptionMessage}`, - "errorContext": null, - "errorRange": null - } - ] - }; - t.deepEqual(actualResult, expectedResult, "Undetected issues."); -}); - test.cb("customRulesOnErrorInvalidHandled", (t) => { t.plan(2); markdownlint({ @@ -1150,8 +977,7 @@ test.cb("customRulesOnErrorInvalidHandled", (t) => { "tags": [ "tag" ], "function": function onErrorInvalid(params, onError) { onError({ - "lineNumber": 13, - "detail": "N/A" + "lineNumber": 13 }); } } @@ -1312,3 +1138,125 @@ test.cb("customRulesLintJavaScript", (t) => { t.end(); }); }); + +const errorMessage = "Custom error message."; +const stringScenarios = [ + [ + "Files", + [ "./test/custom-rules.md" ], + null + ], + [ + "Strings", + null, + { "./test/custom-rules.md": "# Heading\n" } + ] +]; + +[ + [ + "customRulesThrowString", + () => { + throw errorMessage; + } + ], + [ + "customRulesThrowError", + () => { + throw new Error(errorMessage); + } + ] +].forEach((flavor) => { + const [ name, func ] = flavor; + const customRule = [ + { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "function": func + } + ]; + const expectedResult = { + "./test/custom-rules.md": [ + { + "lineNumber": 1, + "ruleNames": [ "name" ], + "ruleDescription": "description", + "ruleInformation": null, + "errorDetail": `This rule threw an exception: ${errorMessage}`, + "errorContext": null, + "errorRange": null + } + ] + }; + stringScenarios.forEach((inputs) => { + const [ subname, files, strings ] = inputs; + + test.cb(`${name}${subname}UnhandledAsync`, (t) => { + t.plan(4); + markdownlint({ + // @ts-ignore + "customRules": customRule, + // @ts-ignore + files, + // @ts-ignore + strings + }, function callback(err, result) { + t.truthy(err, "Did not get an error for exception."); + t.true(err instanceof Error, "Error not instance of Error."); + t.is(err.message, errorMessage, "Incorrect message for exception."); + t.true(!result, "Got result for exception."); + t.end(); + }); + }); + + test.cb(`${name}${subname}HandledAsync`, (t) => { + t.plan(2); + markdownlint({ + // @ts-ignore + "customRules": customRule, + // @ts-ignore + files, + // @ts-ignore + strings, + "handleRuleFailures": true + }, function callback(err, actualResult) { + t.falsy(err); + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); + }); + + test(`${name}${subname}UnhandledSync`, (t) => { + t.plan(1); + t.throws( + () => markdownlint.sync({ + // @ts-ignore + "customRules": customRule, + // @ts-ignore + files, + // @ts-ignore + strings + }), + { + "message": errorMessage + }, + "Unexpected exception." + ); + }); + + test(`${name}${subname}HandledSync`, (t) => { + t.plan(1); + const actualResult = markdownlint.sync({ + // @ts-ignore + "customRules": customRule, + // @ts-ignore + files, + // @ts-ignore + strings, + "handleRuleFailures": true + }); + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + }); + }); +}); From 5167f0e57637f42e6928653cab95c16d7fbc6b55 Mon Sep 17 00:00:00 2001 From: Darius D Date: Sat, 11 Dec 2021 15:02:04 -0600 Subject: [PATCH 41/69] Remove Node 10.x logic from CI workflow. (#465) --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a12dd3bc..d060e1a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,8 +27,8 @@ jobs: - name: Install Dependencies run: npm install --no-package-lock - name: Run All Validations - if: ${{ matrix.node-version != '10.x' && matrix.node-version != '12.x' }} + if: ${{ matrix.node-version != '12.x' }} run: npm run ci - name: Run Tests Only - if: ${{ matrix.node-version == '10.x' || matrix.node-version == '12.x' }} + if: ${{ matrix.node-version == '12.x' }} run: npm run test From 2056d81682bca3496292feec6222da4c9236f48d Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 11 Dec 2021 21:44:25 -0800 Subject: [PATCH 42/69] Add support for asynchronous custom rules (ex: to read a file or make a network request). --- demo/markdownlint-browser.js | 159 +++++++++------ doc/CustomRules.md | 17 +- lib/markdownlint.d.ts | 4 + lib/markdownlint.js | 177 ++++++++++------ test/markdownlint-test-custom-rules.js | 271 ++++++++++++++++++++++++- 5 files changed, 499 insertions(+), 129 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index ccd34c2c..36bb9462 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -933,9 +933,10 @@ var dynamicRequire = (typeof require === "undefined") ? __webpack_require__("../ * Validate the list of rules for structure and reuse. * * @param {Rule[]} ruleList List of rules. + * @param {boolean} synchronous Whether to execute synchronously. * @returns {string} Error message if validation fails. */ -function validateRuleList(ruleList) { +function validateRuleList(ruleList, synchronous) { var result = null; if (ruleList.length === rules.length) { // No need to validate if only using built-in rules @@ -972,6 +973,15 @@ function validateRuleList(ruleList) { (Object.getPrototypeOf(rule.information) !== URL.prototype)) { result = newError("information"); } + if (!result && + (rule.asynchronous !== undefined) && + (typeof rule.asynchronous !== "boolean")) { + result = newError("asynchronous"); + } + if (!result && rule.asynchronous && synchronous) { + result = new Error("Custom rule " + rule.names.join("/") + " at index " + customIndex + + " is asynchronous and can not be used in a synchronous context."); + } if (!result) { rule.names.forEach(function forName(name) { var nameUpper = name.toUpperCase(); @@ -1441,75 +1451,106 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul }); } // Call (possibly external) rule function to report errors - if (handleRuleFailures) { - try { - rule.function(params, onError); + // eslint-disable-next-line func-style + var catchCallsOnError = function (error) { return onError({ + "lineNumber": 1, + "detail": "This rule threw an exception: " + (error.message || error) + }); }; + // eslint-disable-next-line func-style + var invokeRuleFunction = function () { return rule.function(params, onError); }; + if (rule.asynchronous) { + // Asynchronous rule, ensure it returns a Promise + var ruleFunctionPromise = Promise.resolve().then(invokeRuleFunction); + return handleRuleFailures ? + ruleFunctionPromise.catch(catchCallsOnError) : + ruleFunctionPromise; + } + // Synchronous rule + try { + invokeRuleFunction(); + } + catch (error) { + if (handleRuleFailures) { + catchCallsOnError(error); } - catch (error) { - var message = (error instanceof Error) ? error.message : error; - onError({ - "lineNumber": 1, - "detail": "This rule threw an exception: " + message - }); + else { + throw error; + } + } + return null; + } + // eslint-disable-next-line jsdoc/require-jsdoc + function formatResults() { + // Sort results by rule name by line number + results.sort(function (a, b) { return (a.ruleName.localeCompare(b.ruleName) || + a.lineNumber - b.lineNumber); }); + if (resultVersion < 3) { + // Remove fixInfo and multiple errors for the same rule and line number + var noPrevious_1 = { + "ruleName": null, + "lineNumber": -1 + }; + results = results.filter(function (error, index, array) { + delete error.fixInfo; + var previous = array[index - 1] || noPrevious_1; + return ((error.ruleName !== previous.ruleName) || + (error.lineNumber !== previous.lineNumber)); + }); + } + if (resultVersion === 0) { + // Return a dictionary of rule->[line numbers] + var dictionary = {}; + for (var _i = 0, results_1 = results; _i < results_1.length; _i++) { + var error = results_1[_i]; + var ruleLines = dictionary[error.ruleName] || []; + ruleLines.push(error.lineNumber); + dictionary[error.ruleName] = ruleLines; + } + // @ts-ignore + results = dictionary; + } + else if (resultVersion === 1) { + // Use ruleAlias instead of ruleNames + for (var _a = 0, results_2 = results; _a < results_2.length; _a++) { + var error = results_2[_a]; + error.ruleAlias = error.ruleNames[1] || error.ruleName; + delete error.ruleNames; } } else { - rule.function(params, onError); + // resultVersion 2 or 3: Remove unwanted ruleName + for (var _b = 0, results_3 = results; _b < results_3.length; _b++) { + var error = results_3[_b]; + delete error.ruleName; + } } + return results; } // Run all rules + var ruleListAsync = ruleList.filter(function (rule) { return rule.asynchronous; }); + var ruleListSync = ruleList.filter(function (rule) { return !rule.asynchronous; }); + var ruleListAsyncFirst = __spreadArray(__spreadArray([], ruleListAsync), ruleListSync); + // eslint-disable-next-line func-style + var callbackSuccess = function () { return callback(null, formatResults()); }; + // eslint-disable-next-line func-style + var callbackError = function (error) { return callback(error instanceof Error ? error : new Error(error)); }; try { - ruleList.forEach(forRule); + var ruleResults = ruleListAsyncFirst.map(forRule); + if (ruleListAsync.length > 0) { + Promise.all(ruleResults.slice(0, ruleListAsync.length)) + .then(callbackSuccess) + .catch(callbackError); + } + else { + callbackSuccess(); + } } catch (error) { + callbackError(error); + } + finally { cache.clear(); - return callback((error instanceof Error) ? error : new Error(error)); } - cache.clear(); - // Sort results by rule name by line number - results.sort(function (a, b) { return (a.ruleName.localeCompare(b.ruleName) || - a.lineNumber - b.lineNumber); }); - if (resultVersion < 3) { - // Remove fixInfo and multiple errors for the same rule and line number - var noPrevious_1 = { - "ruleName": null, - "lineNumber": -1 - }; - results = results.filter(function (error, index, array) { - delete error.fixInfo; - var previous = array[index - 1] || noPrevious_1; - return ((error.ruleName !== previous.ruleName) || - (error.lineNumber !== previous.lineNumber)); - }); - } - if (resultVersion === 0) { - // Return a dictionary of rule->[line numbers] - var dictionary = {}; - for (var _i = 0, results_1 = results; _i < results_1.length; _i++) { - var error = results_1[_i]; - var ruleLines = dictionary[error.ruleName] || []; - ruleLines.push(error.lineNumber); - dictionary[error.ruleName] = ruleLines; - } - // @ts-ignore - results = dictionary; - } - else if (resultVersion === 1) { - // Use ruleAlias instead of ruleNames - for (var _b = 0, results_2 = results; _b < results_2.length; _b++) { - var error = results_2[_b]; - error.ruleAlias = error.ruleNames[1] || error.ruleName; - delete error.ruleNames; - } - } - else { - // resultVersion 2 or 3: Remove unwanted ruleName - for (var _c = 0, results_3 = results; _c < results_3.length; _c++) { - var error = results_3[_c]; - delete error.ruleName; - } - } - return callback(null, results); } /** * Lints a file containing Markdown content. @@ -1557,7 +1598,7 @@ function lintInput(options, synchronous, callback) { callback = callback || function noop() { }; // eslint-disable-next-line unicorn/prefer-spread var ruleList = rules.concat(options.customRules || []); - var ruleErr = validateRuleList(ruleList); + var ruleErr = validateRuleList(ruleList, synchronous); if (ruleErr) { return callback(ruleErr); } diff --git a/doc/CustomRules.md b/doc/CustomRules.md index 6eb4273c..42aca6b5 100644 --- a/doc/CustomRules.md +++ b/doc/CustomRules.md @@ -41,7 +41,8 @@ A rule is implemented as an `Object` with one optional and four required propert - `description` is a required `String` value that describes the rule in output messages. - `information` is an optional (absolute) `URL` of a link to more information about the rule. - `tags` is a required `Array` of `String` values that groups related rules for easier customization. -- `function` is a required synchronous `Function` that implements the rule and is passed two parameters: +- `asynchronous` is an optional `Boolean` value that indicates whether the rule returns a `Promise` and runs asynchronously. +- `function` is a required `Function` that implements the rule and is passed two parameters: - `params` is an `Object` with properties that describe the content being analyzed: - `name` is a `String` that identifies the input file/string. - `tokens` is an `Array` of [`markdown-it` `Token` objects](https://markdown-it.github.io/markdown-it/#Token) @@ -66,15 +67,25 @@ A rule is implemented as an `Object` with one optional and four required propert The collection of helper functions shared by the built-in rules is available for use by custom rules in the [markdownlint-rule-helpers package](https://www.npmjs.com/package/markdownlint-rule-helpers). +### Asynchronous Rules + +If a rule needs to perform asynchronous operations (such as fetching a network resource), it can specify the value `true` for its `asynchronous` property. +Asynchronous rules should return a `Promise` from their `function` implementation that is resolved when the rule completes. +(The value passed to `resolve(...)` is ignored.) +Linting violations from asynchronous rules are reported via the `onError` function just like for synchronous rules. + +**Note**: Asynchronous rules cannot be referenced in a synchronous calling context (i.e., `markdownlint.sync(...)`). +Attempting to do so throws an exception. + ## Examples - [Simple rules used by the project's test cases](../test/rules) - [Code for all `markdownlint` built-in rules](../lib) - [Package configuration for publishing to npm](../test/rules/npm) - Packages should export a single rule object or an `Array` of rule objects -- [Custom rules from the Microsoft/vscode-docs-authoring repository](https://github.com/microsoft/vscode-docs-authoring/tree/master/packages/docs-linting/markdownlint-custom-rules) +- [Custom rules from the Microsoft/vscode-docs-authoring repository](https://github.com/microsoft/vscode-docs-authoring/tree/main/packages/docs-linting/markdownlint-custom-rules) - [Custom rules from the axibase/docs-util repository](https://github.com/axibase/docs-util/tree/master/linting-rules) -- [Custom rules from the webhintio/hint repository](https://github.com/webhintio/hint/blob/master/scripts/lint-markdown.js) +- [Custom rules from the webhintio/hint repository](https://github.com/webhintio/hint/blob/main/scripts/lint-markdown.js) ## References diff --git a/lib/markdownlint.d.ts b/lib/markdownlint.d.ts index c19bcfc4..d636a4d4 100644 --- a/lib/markdownlint.d.ts +++ b/lib/markdownlint.d.ts @@ -263,6 +263,10 @@ type Rule = { * Rule tag(s). */ tags: string[]; + /** + * True if asynchronous. + */ + asynchronous?: boolean; /** * Rule implementation. */ diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 1d810303..f604e40a 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -19,9 +19,10 @@ const dynamicRequire = (typeof __non_webpack_require__ === "undefined") ? requir * Validate the list of rules for structure and reuse. * * @param {Rule[]} ruleList List of rules. + * @param {boolean} synchronous Whether to execute synchronously. * @returns {string} Error message if validation fails. */ -function validateRuleList(ruleList) { +function validateRuleList(ruleList, synchronous) { let result = null; if (ruleList.length === rules.length) { // No need to validate if only using built-in rules @@ -61,6 +62,19 @@ function validateRuleList(ruleList) { ) { result = newError("information"); } + if ( + !result && + (rule.asynchronous !== undefined) && + (typeof rule.asynchronous !== "boolean") + ) { + result = newError("asynchronous"); + } + if (!result && rule.asynchronous && synchronous) { + result = new Error( + "Custom rule " + rule.names.join("/") + " at index " + customIndex + + " is asynchronous and can not be used in a synchronous context." + ); + } if (!result) { rule.names.forEach(function forName(name) { const nameUpper = name.toUpperCase(); @@ -509,12 +523,12 @@ function lintContent( } if (errorInfo.range && (!Array.isArray(errorInfo.range) || - (errorInfo.range.length !== 2) || - !helpers.isNumber(errorInfo.range[0]) || - (errorInfo.range[0] < 1) || - !helpers.isNumber(errorInfo.range[1]) || - (errorInfo.range[1] < 1) || - ((errorInfo.range[0] + errorInfo.range[1] - 1) > + (errorInfo.range.length !== 2) || + !helpers.isNumber(errorInfo.range[0]) || + (errorInfo.range[0] < 1) || + !helpers.isNumber(errorInfo.range[1]) || + (errorInfo.range[1] < 1) || + ((errorInfo.range[0] + errorInfo.range[1] - 1) > lines[errorInfo.lineNumber - 1].length))) { throwError("range"); } @@ -572,71 +586,105 @@ function lintContent( }); } // Call (possibly external) rule function to report errors - if (handleRuleFailures) { - try { - rule.function(params, onError); - } catch (error) { - const message = (error instanceof Error) ? error.message : error; - onError({ - "lineNumber": 1, - "detail": `This rule threw an exception: ${message}` - }); + // eslint-disable-next-line func-style + const catchCallsOnError = (error) => onError({ + "lineNumber": 1, + "detail": `This rule threw an exception: ${error.message || error}` + }); + // eslint-disable-next-line func-style + const invokeRuleFunction = () => rule.function(params, onError); + if (rule.asynchronous) { + // Asynchronous rule, ensure it returns a Promise + const ruleFunctionPromise = + Promise.resolve().then(invokeRuleFunction); + return handleRuleFailures ? + ruleFunctionPromise.catch(catchCallsOnError) : + ruleFunctionPromise; + } + // Synchronous rule + try { + invokeRuleFunction(); + } catch (error) { + if (handleRuleFailures) { + catchCallsOnError(error); + } else { + throw error; + } + } + return null; + } + // eslint-disable-next-line jsdoc/require-jsdoc + function formatResults() { + // Sort results by rule name by line number + results.sort((a, b) => ( + a.ruleName.localeCompare(b.ruleName) || + a.lineNumber - b.lineNumber + )); + if (resultVersion < 3) { + // Remove fixInfo and multiple errors for the same rule and line number + const noPrevious = { + "ruleName": null, + "lineNumber": -1 + }; + results = results.filter((error, index, array) => { + delete error.fixInfo; + const previous = array[index - 1] || noPrevious; + return ( + (error.ruleName !== previous.ruleName) || + (error.lineNumber !== previous.lineNumber) + ); + }); + } + if (resultVersion === 0) { + // Return a dictionary of rule->[line numbers] + const dictionary = {}; + for (const error of results) { + const ruleLines = dictionary[error.ruleName] || []; + ruleLines.push(error.lineNumber); + dictionary[error.ruleName] = ruleLines; + } + // @ts-ignore + results = dictionary; + } else if (resultVersion === 1) { + // Use ruleAlias instead of ruleNames + for (const error of results) { + error.ruleAlias = error.ruleNames[1] || error.ruleName; + delete error.ruleNames; } } else { - rule.function(params, onError); + // resultVersion 2 or 3: Remove unwanted ruleName + for (const error of results) { + delete error.ruleName; + } } + return results; } // Run all rules + const ruleListAsync = ruleList.filter((rule) => rule.asynchronous); + const ruleListSync = ruleList.filter((rule) => !rule.asynchronous); + const ruleListAsyncFirst = [ + ...ruleListAsync, + ...ruleListSync + ]; + // eslint-disable-next-line func-style + const callbackSuccess = () => callback(null, formatResults()); + // eslint-disable-next-line func-style + const callbackError = + (error) => callback(error instanceof Error ? error : new Error(error)); try { - ruleList.forEach(forRule); + const ruleResults = ruleListAsyncFirst.map(forRule); + if (ruleListAsync.length > 0) { + Promise.all(ruleResults.slice(0, ruleListAsync.length)) + .then(callbackSuccess) + .catch(callbackError); + } else { + callbackSuccess(); + } } catch (error) { + callbackError(error); + } finally { cache.clear(); - return callback((error instanceof Error) ? error : new Error(error)); } - cache.clear(); - // Sort results by rule name by line number - results.sort((a, b) => ( - a.ruleName.localeCompare(b.ruleName) || - a.lineNumber - b.lineNumber - )); - if (resultVersion < 3) { - // Remove fixInfo and multiple errors for the same rule and line number - const noPrevious = { - "ruleName": null, - "lineNumber": -1 - }; - results = results.filter((error, index, array) => { - delete error.fixInfo; - const previous = array[index - 1] || noPrevious; - return ( - (error.ruleName !== previous.ruleName) || - (error.lineNumber !== previous.lineNumber) - ); - }); - } - if (resultVersion === 0) { - // Return a dictionary of rule->[line numbers] - const dictionary = {}; - for (const error of results) { - const ruleLines = dictionary[error.ruleName] || []; - ruleLines.push(error.lineNumber); - dictionary[error.ruleName] = ruleLines; - } - // @ts-ignore - results = dictionary; - } else if (resultVersion === 1) { - // Use ruleAlias instead of ruleNames - for (const error of results) { - error.ruleAlias = error.ruleNames[1] || error.ruleName; - delete error.ruleNames; - } - } else { - // resultVersion 2 or 3: Remove unwanted ruleName - for (const error of results) { - delete error.ruleName; - } - } - return callback(null, results); } /** @@ -697,7 +745,7 @@ function lintInput(options, synchronous, callback) { callback = callback || function noop() {}; // eslint-disable-next-line unicorn/prefer-spread const ruleList = rules.concat(options.customRules || []); - const ruleErr = validateRuleList(ruleList); + const ruleErr = validateRuleList(ruleList, synchronous); if (ruleErr) { return callback(ruleErr); } @@ -1147,6 +1195,7 @@ module.exports = markdownlint; * @property {string} description Rule description. * @property {URL} [information] Link to more information. * @property {string[]} tags Rule tag(s). + * @property {boolean} [asynchronous] True if asynchronous. * @property {RuleFunction} function Rule implementation. */ diff --git a/test/markdownlint-test-custom-rules.js b/test/markdownlint-test-custom-rules.js index 63b8f799..76c1d344 100644 --- a/test/markdownlint-test-custom-rules.js +++ b/test/markdownlint-test-custom-rules.js @@ -2,11 +2,11 @@ "use strict"; +const fs = require("fs").promises; const test = require("ava").default; -const packageJson = require("../package.json"); const markdownlint = require("../lib/markdownlint"); const customRules = require("./rules/rules.js"); -const homepage = packageJson.homepage; +const { homepage, version } = require("../package.json"); test.cb("customRulesV0", (t) => { t.plan(4); @@ -349,7 +349,7 @@ test.cb("customRulesNpmPackage", (t) => { }); test("customRulesBadProperty", (t) => { - t.plan(23); + t.plan(27); [ { "propertyName": "names", @@ -364,6 +364,10 @@ test("customRulesBadProperty", (t) => { "propertyName": "information", "propertyValues": [ 10, [], "string", "https://example.com" ] }, + { + "propertyName": "asynchronous", + "propertyValues": [ null, 10, "", [] ] + }, { "propertyName": "tags", "propertyValues": @@ -1139,6 +1143,168 @@ test.cb("customRulesLintJavaScript", (t) => { }); }); +test("customRulesAsyncThrowsInSyncContext", (t) => { + t.plan(1); + const options = { + "customRules": [ + { + "names": [ "name1", "name2" ], + "description": "description", + "tags": [ "tag" ], + "asynchronous": true, + "function": () => {} + } + ], + "strings": { + "string": "Unused" + } + }; + t.throws( + () => markdownlint.sync(options), + { + "message": "Custom rule name1/name2 at index 0 is asynchronous and " + + "can not be used in a synchronous context." + }, + "Did not get correct exception for async rule in sync context." + ); +}); + +test("customRulesAsyncReadFiles", (t) => { + t.plan(3); + const options = { + "customRules": [ + { + "names": [ "name1" ], + "description": "description1", + "information": new URL("https://example.com/asyncRule1"), + "tags": [ "tag" ], + "asynchronous": true, + "function": + (params, onError) => fs.readFile(__filename, "utf8").then( + (content) => { + t.true(content.length > 0); + onError({ + "lineNumber": 1, + "detail": "detail1", + "context": "context1", + "range": [ 2, 3 ] + }); + } + ) + }, + { + "names": [ "name2" ], + "description": "description2", + "tags": [ "tag" ], + "asynchronous": true, + "function": + (params, onError) => fs.readFile(__filename, "utf8").then( + (content) => { + t.true(content.length > 0); + onError({ + "lineNumber": 1, + "detail": "detail2", + "context": "context2" + }); + } + ) + } + ], + "strings": { + "string": "# Heading" + } + }; + const expected = { + "string": [ + { + "lineNumber": 1, + "ruleNames": [ "MD047", "single-trailing-newline" ], + "ruleDescription": "Files should end with a single newline character", + "ruleInformation": `${homepage}/blob/v${version}/doc/Rules.md#md047`, + "errorDetail": null, + "errorContext": null, + "errorRange": [ 9, 1 ] + }, + { + "lineNumber": 1, + "ruleNames": [ "name1" ], + "ruleDescription": "description1", + "ruleInformation": "https://example.com/asyncRule1", + "errorDetail": "detail1", + "errorContext": "context1", + "errorRange": [ 2, 3 ] + }, + { + "lineNumber": 1, + "ruleNames": [ "name2" ], + "ruleDescription": "description2", + "ruleInformation": null, + "errorDetail": "detail2", + "errorContext": "context2", + "errorRange": null + } + ] + }; + return markdownlint.promises.markdownlint(options) + .then((actual) => t.deepEqual(actual, expected, "Unexpected issues.")); +}); + +test("customRulesAsyncIgnoresSyncReturn", (t) => { + t.plan(1); + const options = { + "customRules": [ + { + "names": [ "sync" ], + "description": "description", + "information": new URL("https://example.com/asyncRule"), + "tags": [ "tag" ], + "asynchronous": false, + "function": () => new Promise(() => { + // Never resolves + }) + }, + { + "names": [ "async" ], + "description": "description", + "information": new URL("https://example.com/asyncRule"), + "tags": [ "tag" ], + "asynchronous": true, + "function": (params, onError) => new Promise((resolve) => { + onError({ "lineNumber": 1 }); + resolve(); + }) + } + ], + "strings": { + "string": "# Heading" + } + }; + const expected = { + "string": [ + { + "lineNumber": 1, + "ruleNames": [ "async" ], + "ruleDescription": "description", + "ruleInformation": "https://example.com/asyncRule", + "errorDetail": null, + "errorContext": null, + "errorRange": null + }, + { + "lineNumber": 1, + "ruleNames": [ "MD047", "single-trailing-newline" ], + "ruleDescription": "Files should end with a single newline character", + "ruleInformation": `${homepage}/blob/v${version}/doc/Rules.md#md047`, + "errorDetail": null, + "errorContext": null, + "errorRange": [ 9, 1 ] + } + ] + }; + return markdownlint.promises.markdownlint(options) + .then((actual) => t.deepEqual(actual, expected, "Unexpected issues.")); +}); + const errorMessage = "Custom error message."; const stringScenarios = [ [ @@ -1260,3 +1426,102 @@ const stringScenarios = [ }); }); }); + +[ + [ + "customRulesAsyncExceptionString", + () => { + throw errorMessage; + } + ], + [ + "customRulesAsyncExceptionError", + () => { + throw new Error(errorMessage); + } + ], + [ + "customRulesAsyncDeferredString", + () => fs.readFile(__filename, "utf8").then( + () => { + throw errorMessage; + } + ) + ], + [ + "customRulesAsyncDeferredError", + () => fs.readFile(__filename, "utf8").then( + () => { + throw new Error(errorMessage); + } + ) + ], + [ + "customRulesAsyncRejectString", + () => Promise.reject(errorMessage) + ], + [ + "customRulesAsyncRejectError", + () => Promise.reject(new Error(errorMessage)) + ] +].forEach((flavor) => { + const [ name, func ] = flavor; + const customRule = { + "names": [ "name" ], + "description": "description", + "tags": [ "tag" ], + "asynchronous": true, + "function": func + }; + stringScenarios.forEach((inputs) => { + const [ subname, files, strings ] = inputs; + + test.cb(`${name}${subname}Unhandled`, (t) => { + t.plan(4); + markdownlint({ + // @ts-ignore + "customRules": [ customRule ], + // @ts-ignore + files, + // @ts-ignore + strings + }, function callback(err, result) { + t.truthy(err, "Did not get an error for rejection."); + t.true(err instanceof Error, "Error not instance of Error."); + t.is(err.message, errorMessage, "Incorrect message for rejection."); + t.true(!result, "Got result for rejection."); + t.end(); + }); + }); + + test.cb(`${name}${subname}Handled`, (t) => { + t.plan(2); + markdownlint({ + // @ts-ignore + "customRules": [ customRule ], + // @ts-ignore + files, + // @ts-ignore + strings, + "handleRuleFailures": true + }, function callback(err, actualResult) { + t.falsy(err); + const expectedResult = { + "./test/custom-rules.md": [ + { + "lineNumber": 1, + "ruleNames": [ "name" ], + "ruleDescription": "description", + "ruleInformation": null, + "errorDetail": `This rule threw an exception: ${errorMessage}`, + "errorContext": null, + "errorRange": null + } + ] + }; + t.deepEqual(actualResult, expectedResult, "Undetected issues."); + t.end(); + }); + }); + }); +}); From 13e375b281ecd0034da14c70ac2a7f07d2b14f9e Mon Sep 17 00:00:00 2001 From: David Anson Date: Sat, 11 Dec 2021 21:48:00 -0800 Subject: [PATCH 43/69] Disable ESLint rule func-style. --- .eslintrc.json | 2 +- demo/markdownlint-browser.js | 5 ----- lib/markdownlint.js | 4 ---- lib/md043.js | 1 - test/markdownlint-test.js | 1 - 5 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index d8ddddc7..0fe1cb83 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -62,7 +62,7 @@ "capitalized-comments": "off", "complexity": "off", "dot-location": ["error", "property"], - "func-style": ["error", "declaration"], + "func-style": "off", "function-call-argument-newline": "off", "function-paren-newline": "off", "global-require": "off", diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 36bb9462..aade2f47 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -1451,12 +1451,10 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul }); } // Call (possibly external) rule function to report errors - // eslint-disable-next-line func-style var catchCallsOnError = function (error) { return onError({ "lineNumber": 1, "detail": "This rule threw an exception: " + (error.message || error) }); }; - // eslint-disable-next-line func-style var invokeRuleFunction = function () { return rule.function(params, onError); }; if (rule.asynchronous) { // Asynchronous rule, ensure it returns a Promise @@ -1530,9 +1528,7 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul var ruleListAsync = ruleList.filter(function (rule) { return rule.asynchronous; }); var ruleListSync = ruleList.filter(function (rule) { return !rule.asynchronous; }); var ruleListAsyncFirst = __spreadArray(__spreadArray([], ruleListAsync), ruleListSync); - // eslint-disable-next-line func-style var callbackSuccess = function () { return callback(null, formatResults()); }; - // eslint-disable-next-line func-style var callbackError = function (error) { return callback(error instanceof Error ? error : new Error(error)); }; try { var ruleResults = ruleListAsyncFirst.map(forRule); @@ -3901,7 +3897,6 @@ module.exports = { var matchAny_1 = false; var hasError_1 = false; var anyHeadings_1 = false; - // eslint-disable-next-line func-style var getExpected_1 = function () { return requiredHeadings[i_1++] || "[None]"; }; forEachHeading(params, function (heading, content) { if (!hasError_1) { diff --git a/lib/markdownlint.js b/lib/markdownlint.js index f604e40a..6745fe8f 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -586,12 +586,10 @@ function lintContent( }); } // Call (possibly external) rule function to report errors - // eslint-disable-next-line func-style const catchCallsOnError = (error) => onError({ "lineNumber": 1, "detail": `This rule threw an exception: ${error.message || error}` }); - // eslint-disable-next-line func-style const invokeRuleFunction = () => rule.function(params, onError); if (rule.asynchronous) { // Asynchronous rule, ensure it returns a Promise @@ -666,9 +664,7 @@ function lintContent( ...ruleListAsync, ...ruleListSync ]; - // eslint-disable-next-line func-style const callbackSuccess = () => callback(null, formatResults()); - // eslint-disable-next-line func-style const callbackError = (error) => callback(error instanceof Error ? error : new Error(error)); try { diff --git a/lib/md043.js b/lib/md043.js index 5805305b..37b3cbd2 100644 --- a/lib/md043.js +++ b/lib/md043.js @@ -20,7 +20,6 @@ module.exports = { let matchAny = false; let hasError = false; let anyHeadings = false; - // eslint-disable-next-line func-style const getExpected = () => requiredHeadings[i++] || "[None]"; forEachHeading(params, (heading, content) => { if (!hasError) { diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index 566cf711..f49fe81f 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -930,7 +930,6 @@ test.cb("rules", (t) => { let ruleHasAliases = true; let ruleUsesParams = null; const tagAliasParameterRe = /, |: | /; - // eslint-disable-next-line func-style const testTagsAliasesParams = (r) => { // eslint-disable-next-line unicorn/prefer-default-parameters r = r || "[NO RULE]"; From 517eb4201582816e9cc77c46d944d2929bebf203 Mon Sep 17 00:00:00 2001 From: David Anson Date: Mon, 13 Dec 2021 21:49:43 -0800 Subject: [PATCH 44/69] Update MD007/ul-indent to support start_indent parameter (fixes #337, closes #397). --- demo/markdownlint-browser.js | 5 +- doc/Rules.md | 10 +++- lib/md007.js | 6 ++- schema/.markdownlint.jsonc | 4 +- schema/.markdownlint.yaml | 2 + schema/build-config-schema.js | 6 +++ schema/markdownlint-config-schema.json | 10 ++++ test/list-indentation-start-indent-indent.md | 35 ++++++++++++++ ...list-indentation-start-indent-no-indent.md | 34 ++++++++++++++ ...st-indentation-start-indented-indent.json} | 0 ...list-indentation-start-indented-indent.md} | 0 ...st-indentation-start-indented-no-indent.md | 46 +++++++++++++++++++ 12 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 test/list-indentation-start-indent-indent.md create mode 100644 test/list-indentation-start-indent-no-indent.md rename test/{list-indentation-start-indented.json => list-indentation-start-indented-indent.json} (100%) rename test/{list-indentation-start-indented.md => list-indentation-start-indented-indent.md} (100%) create mode 100644 test/list-indentation-start-indented-no-indent.md diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index aade2f47..e1f9a55d 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -2212,12 +2212,13 @@ module.exports = { "function": function MD007(params, onError) { var indent = Number(params.config.indent || 2); var startIndented = !!params.config.start_indented; + var startIndent = Number(params.config.start_indent || indent); flattenedLists().forEach(function (list) { if (list.unordered && list.parentsUnordered) { list.items.forEach(function (item) { var lineNumber = item.lineNumber, line = item.line; - var expectedNesting = list.nesting + (startIndented ? 1 : 0); - var expectedIndent = expectedNesting * indent; + var expectedIndent = (startIndented ? startIndent : 0) + + (list.nesting * indent); var actualIndent = indentFor(item); var range = null; var editColumn = 1; diff --git a/doc/Rules.md b/doc/Rules.md index bb3d6844..9b968c7c 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -292,7 +292,11 @@ Tags: bullet, ul, indentation Aliases: ul-indent -Parameters: indent, start_indented (number; default 2, boolean; default false) + + +Parameters: indent, start_indented, start_indent (number; default 2, boolean; default false, number; defaults to indent) + + Fixable: Most violations can be fixed by tooling @@ -319,7 +323,9 @@ rule). The `start_indented` parameter allows the first level of lists to be indented by the configured number of spaces rather than starting at zero (the inverse of -MD006). +MD006). The `start_indent` parameter allows the first level of lists to be indented +by a different number of spaces than the rest (ignored when `start_indented` is not +set). Rationale: Indenting by 2 spaces allows the content of a nested list to be in line with the start of the content of the parent list when a single space is diff --git a/lib/md007.js b/lib/md007.js index 0a02b618..1bb6f342 100644 --- a/lib/md007.js +++ b/lib/md007.js @@ -13,12 +13,14 @@ module.exports = { "function": function MD007(params, onError) { const indent = Number(params.config.indent || 2); const startIndented = !!params.config.start_indented; + const startIndent = Number(params.config.start_indent || indent); flattenedLists().forEach((list) => { if (list.unordered && list.parentsUnordered) { list.items.forEach((item) => { const { lineNumber, line } = item; - const expectedNesting = list.nesting + (startIndented ? 1 : 0); - const expectedIndent = expectedNesting * indent; + const expectedIndent = + (startIndented ? startIndent : 0) + + (list.nesting * indent); const actualIndent = indentFor(item); let range = null; let editColumn = 1; diff --git a/schema/.markdownlint.jsonc b/schema/.markdownlint.jsonc index 3cea1729..a7bfa5b2 100644 --- a/schema/.markdownlint.jsonc +++ b/schema/.markdownlint.jsonc @@ -39,7 +39,9 @@ // Spaces for indent "indent": 2, // Whether to indent the first level of the list - "start_indented": false + "start_indented": false, + // Spaces for first level indent (when start_indented is set) + "start_indent": 2 }, // MD009/no-trailing-spaces - Trailing spaces diff --git a/schema/.markdownlint.yaml b/schema/.markdownlint.yaml index b8960a4a..ce92ec5c 100644 --- a/schema/.markdownlint.yaml +++ b/schema/.markdownlint.yaml @@ -36,6 +36,8 @@ MD007: indent: 2 # Whether to indent the first level of the list start_indented: false + # Spaces for first level indent (when start_indented is set) + start_indent: 2 # MD009/no-trailing-spaces - Trailing spaces MD009: diff --git a/schema/build-config-schema.js b/schema/build-config-schema.js index 76cfd539..f0585e19 100644 --- a/schema/build-config-schema.js +++ b/schema/build-config-schema.js @@ -107,6 +107,12 @@ rules.forEach(function forRule(rule) { "description": "Whether to indent the first level of the list", "type": "boolean", "default": false + }, + "start_indent": { + "description": + "Spaces for first level indent (when start_indented is set)", + "type": "integer", + "default": 2 } }; break; diff --git a/schema/markdownlint-config-schema.json b/schema/markdownlint-config-schema.json index e3f36409..5c974fd7 100644 --- a/schema/markdownlint-config-schema.json +++ b/schema/markdownlint-config-schema.json @@ -238,6 +238,11 @@ "description": "Whether to indent the first level of the list", "type": "boolean", "default": false + }, + "start_indent": { + "description": "Spaces for first level indent (when start_indented is set)", + "type": "integer", + "default": 2 } }, "additionalProperties": false @@ -259,6 +264,11 @@ "description": "Whether to indent the first level of the list", "type": "boolean", "default": false + }, + "start_indent": { + "description": "Spaces for first level indent (when start_indented is set)", + "type": "integer", + "default": 2 } }, "additionalProperties": false diff --git a/test/list-indentation-start-indent-indent.md b/test/list-indentation-start-indent-indent.md new file mode 100644 index 00000000..727cc951 --- /dev/null +++ b/test/list-indentation-start-indent-indent.md @@ -0,0 +1,35 @@ +# List Indentation start_indent/indent + + * item 1 + * item 2 + * item 2.1 + * item 2.2 + * item 2.2.1 + * item 2.3 + * item 2.3.1 {MD007} + * item 3 + * item 4 {MD005} {MD007} + +Text + + * item 1 {MD007} + * item 2 {MD007} + * item 2.1 + * item 2.2 + * item 2.2.1 + +Text + + * item 1 + * item 2 + * item 2.1 {MD007} + * item 2.2 {MD007} + * item 2.2.1 {MD007} + + diff --git a/test/list-indentation-start-indent-no-indent.md b/test/list-indentation-start-indent-no-indent.md new file mode 100644 index 00000000..27ec4acd --- /dev/null +++ b/test/list-indentation-start-indent-no-indent.md @@ -0,0 +1,34 @@ +# List Indentation start_indent/no indent + + * item 1 + * item 2 + * item 2.1 + * item 2.2 + * item 2.2.1 + * item 2.3 + * item 2.3.1 {MD007} + * item 3 + * item 4 {MD005} {MD007} + +Text + + * item 1 {MD007} + * item 2 {MD007} + * item 2.1 {MD007} + * item 2.2 {MD007} + * item 2.2.1 {MD007} + +Text + + * item 1 + * item 2 + * item 2.1 {MD007} + * item 2.2 {MD007} + * item 2.2.1 {MD007} + + diff --git a/test/list-indentation-start-indented.json b/test/list-indentation-start-indented-indent.json similarity index 100% rename from test/list-indentation-start-indented.json rename to test/list-indentation-start-indented-indent.json diff --git a/test/list-indentation-start-indented.md b/test/list-indentation-start-indented-indent.md similarity index 100% rename from test/list-indentation-start-indented.md rename to test/list-indentation-start-indented-indent.md diff --git a/test/list-indentation-start-indented-no-indent.md b/test/list-indentation-start-indented-no-indent.md new file mode 100644 index 00000000..b30e1b6a --- /dev/null +++ b/test/list-indentation-start-indented-no-indent.md @@ -0,0 +1,46 @@ +# List Indentation - Start Indented/No Indent + + * item 1 + * item 2 + * item 2.1 + * item 2.2 + * item 2.2.1 + * item 2.3 + * item 3 + +## Disallowed List Indentation - Starts at Zero + +* item 1 {MD007} +* item 2 {MD007} + * item 2.1 {MD007} + * item 2.2 {MD007} + * item 2.2.1 {MD007} + * item 2.3 {MD007} +* item 3 {MD007} + +## Disallowed List Indentation - Starts at One + + * item 1 {MD007} + * item 2 {MD007} + * item 2.1 {MD007} + * item 2.2 {MD007} + * item 2.2.1 {MD007} + * item 2.3 {MD007} + * item 2.3.1 + * item 3 {MD007} + +## Disallowed List Indentation - Starts at Three + + * item 1 {MD007} + * item 2 {MD007} + * item 2.1 {MD007} + * item 2.2 {MD007} + * item 2.2.1 {MD007} + * item 2.3 {MD007} + * item 3 {MD007} + + From 8fde53cf31715183b46b63c48c376c4b00fdbd54 Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 14 Dec 2021 23:05:03 -0800 Subject: [PATCH 45/69] Update MD035/hr-style to correctly match thematic breaks in block quotes. --- demo/markdownlint-browser.js | 8 +++---- doc/Rules.md | 4 ++-- lib/md035.js | 8 +++---- test/hr-in-blockquote-dash.md | 39 ++++++++++++++++++++++++++++++++++ test/hr-in-blockquote-star.md | 39 ++++++++++++++++++++++++++++++++++ test/hr-in-blockquote-under.md | 39 ++++++++++++++++++++++++++++++++++ test/hr_style_dashes.md | 3 +-- test/hr_style_inconsistent.md | 3 +-- test/hr_style_stars.md | 3 +-- 9 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 test/hr-in-blockquote-dash.md create mode 100644 test/hr-in-blockquote-star.md create mode 100644 test/hr-in-blockquote-under.md diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index e1f9a55d..40e1396c 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -3376,12 +3376,12 @@ module.exports = { "tags": ["hr"], "function": function MD035(params, onError) { var style = String(params.config.style || "consistent"); - filterTokens(params, "hr", function forToken(token) { - var lineTrim = token.line.trim(); + filterTokens(params, "hr", function (token) { + var lineNumber = token.lineNumber, markup = token.markup; if (style === "consistent") { - style = lineTrim; + style = markup; } - addErrorDetailIf(onError, token.lineNumber, style, lineTrim); + addErrorDetailIf(onError, lineNumber, style, markup); }); } }; diff --git a/doc/Rules.md b/doc/Rules.md index 9b968c7c..13471b78 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -1368,8 +1368,8 @@ Tags: hr Aliases: hr-style -Parameters: style ("consistent", "---", "***", or other string specifying the -horizontal rule; default "consistent") +Parameters: style ("consistent", "---", "***", "___", or other string specifying +the horizontal rule; default "consistent") This rule is triggered when inconsistent styles of horizontal rules are used in the document: diff --git a/lib/md035.js b/lib/md035.js index a2149474..80425867 100644 --- a/lib/md035.js +++ b/lib/md035.js @@ -10,12 +10,12 @@ module.exports = { "tags": [ "hr" ], "function": function MD035(params, onError) { let style = String(params.config.style || "consistent"); - filterTokens(params, "hr", function forToken(token) { - const lineTrim = token.line.trim(); + filterTokens(params, "hr", (token) => { + const { lineNumber, markup } = token; if (style === "consistent") { - style = lineTrim; + style = markup; } - addErrorDetailIf(onError, token.lineNumber, style, lineTrim); + addErrorDetailIf(onError, lineNumber, style, markup); }); } }; diff --git a/test/hr-in-blockquote-dash.md b/test/hr-in-blockquote-dash.md new file mode 100644 index 00000000..689563bb --- /dev/null +++ b/test/hr-in-blockquote-dash.md @@ -0,0 +1,39 @@ +# HR in Blockquote, Dash + +--- + +*** + +___ + +{MD035:5} {MD035:7} + +> Text +> +> --- +> +> *** +> +> ___ +> +> Text + +{MD035:15} {MD035:17} + +- - - + +> Text +> +> > Text +> > +> > --- +> > +> > *** +> > +> > ___ +> > +> > Text +> +> Text + +{MD035:31} {MD035:33} diff --git a/test/hr-in-blockquote-star.md b/test/hr-in-blockquote-star.md new file mode 100644 index 00000000..ad24cc0e --- /dev/null +++ b/test/hr-in-blockquote-star.md @@ -0,0 +1,39 @@ +# HR in Blockquote, Star + +*** + +___ + +--- + +{MD035:5} {MD035:7} + +> Text +> +> --- +> +> *** +> +> ___ +> +> Text + +{MD035:13} {MD035:17} + +* * * + +> Text +> +> > Text +> > +> > --- +> > +> > *** +> > +> > ___ +> > +> > Text +> +> Text + +{MD035:29} {MD035:33} diff --git a/test/hr-in-blockquote-under.md b/test/hr-in-blockquote-under.md new file mode 100644 index 00000000..6829fc30 --- /dev/null +++ b/test/hr-in-blockquote-under.md @@ -0,0 +1,39 @@ +# HR in Blockquote, Under + +___ + +--- + +*** + +{MD035:5} {MD035:7} + +> Text +> +> --- +> +> *** +> +> ___ +> +> Text + +{MD035:13} {MD035:15} + +_ _ _ + +> Text +> +> > Text +> > +> > --- +> > +> > *** +> > +> > ___ +> > +> > Text +> +> Text + +{MD035:29} {MD035:31} diff --git a/test/hr_style_dashes.md b/test/hr_style_dashes.md index 3d90b12b..59e17f8b 100644 --- a/test/hr_style_dashes.md +++ b/test/hr_style_dashes.md @@ -20,5 +20,4 @@ _____ *** -{MD035:3} {MD035:5} {MD035:7} {MD035:11} {MD035:13} {MD035:15} {MD035:17} -{MD035:19} {MD035:21} +{MD035:3} {MD035:5} {MD035:7} {MD035:13} {MD035:15} {MD035:17} {MD035:19} {MD035:21} diff --git a/test/hr_style_inconsistent.md b/test/hr_style_inconsistent.md index 20d4c745..0da18c58 100644 --- a/test/hr_style_inconsistent.md +++ b/test/hr_style_inconsistent.md @@ -20,5 +20,4 @@ _____ *** -{MD035:5} {MD035:7} {MD035:9} {MD035:11} {MD035:13} {MD035:15} {MD035:17} -{MD035:19} +{MD035:7} {MD035:9} {MD035:11} {MD035:13} {MD035:15} {MD035:17} {MD035:19} diff --git a/test/hr_style_stars.md b/test/hr_style_stars.md index b096afb0..f9e7839c 100644 --- a/test/hr_style_stars.md +++ b/test/hr_style_stars.md @@ -20,5 +20,4 @@ _____ *** -{MD035:5} {MD035:7} {MD035:9} {MD035:11} {MD035:13} {MD035:15} {MD035:17} -{MD035:19} +{MD035:7} {MD035:9} {MD035:11} {MD035:13} {MD035:15} {MD035:17} {MD035:19} From 4ed6af67acb0de4e9e6a8ec3c146602a0e60ff8e Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 14 Dec 2021 23:08:48 -0800 Subject: [PATCH 46/69] Placeholder to note that parent commit fixes #430. From ecf42ad7f51e6bf7a90f444f60196b253ef1332d Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 17 Dec 2021 01:56:42 +0000 Subject: [PATCH 47/69] Fix test break introduced by test-only dependency markdown-it-texmath changing the tag for token type "math_block" from "math" to "$$" from version 0.9.6 to 0.9.7. --- demo/markdownlint-browser.js | 2 +- helpers/helpers.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 40e1396c..46d33528 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -266,7 +266,7 @@ module.exports.filterTokens = filterTokens; * @returns {boolean} True iff token is a math block. */ function isMathBlock(token) { - return ((token.tag === "math") && + return (((token.tag === "$$") || (token.tag === "math")) && token.type.startsWith("math_block") && !token.type.endsWith("_end")); } diff --git a/helpers/helpers.js b/helpers/helpers.js index c4927512..bf48e4c2 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -263,7 +263,7 @@ module.exports.filterTokens = filterTokens; */ function isMathBlock(token) { return ( - (token.tag === "math") && + ((token.tag === "$$") || (token.tag === "math")) && token.type.startsWith("math_block") && !token.type.endsWith("_end") ); From 442dcfe5b804fc13d57428ecb59b3405e10041f4 Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 17 Dec 2021 02:15:13 +0000 Subject: [PATCH 48/69] Duplicate test markdownlint-test-extra.js to markdownlint-test-extra-type.js and markdownlint-test-extra-parse.js for splitting. --- test/markdownlint-test-extra-parse.js | 41 +++++++++++++++++++++++++++ test/markdownlint-test-extra-type.js | 41 +++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 test/markdownlint-test-extra-parse.js create mode 100644 test/markdownlint-test-extra-type.js diff --git a/test/markdownlint-test-extra-parse.js b/test/markdownlint-test-extra-parse.js new file mode 100644 index 00000000..d6e643df --- /dev/null +++ b/test/markdownlint-test-extra-parse.js @@ -0,0 +1,41 @@ +// @ts-check + +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const globby = require("globby"); +const test = require("ava").default; +const markdownlint = require("../lib/markdownlint"); + +// Simulates typing each test file to validate handling of partial input +const files = fs.readdirSync("./test"); +files.filter((file) => /\.md$/.test(file)).forEach((file) => { + const strings = {}; + let content = fs.readFileSync(path.join("./test", file), "utf8"); + while (content) { + strings[content.length.toString()] = content; + content = content.slice(0, -1); + } + test(`type ${file}`, (t) => { + t.plan(1); + markdownlint.sync({ + // @ts-ignore + strings, + "resultVersion": 0 + }); + t.pass(); + }); +}); + +// Parses all Markdown files in all package dependencies +test.cb("parseAllFiles", (t) => { + t.plan(1); + const options = { + "files": globby.sync("**/*.{md,markdown}") + }; + markdownlint(options, (err) => { + t.falsy(err); + t.end(); + }); +}); diff --git a/test/markdownlint-test-extra-type.js b/test/markdownlint-test-extra-type.js new file mode 100644 index 00000000..d6e643df --- /dev/null +++ b/test/markdownlint-test-extra-type.js @@ -0,0 +1,41 @@ +// @ts-check + +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const globby = require("globby"); +const test = require("ava").default; +const markdownlint = require("../lib/markdownlint"); + +// Simulates typing each test file to validate handling of partial input +const files = fs.readdirSync("./test"); +files.filter((file) => /\.md$/.test(file)).forEach((file) => { + const strings = {}; + let content = fs.readFileSync(path.join("./test", file), "utf8"); + while (content) { + strings[content.length.toString()] = content; + content = content.slice(0, -1); + } + test(`type ${file}`, (t) => { + t.plan(1); + markdownlint.sync({ + // @ts-ignore + strings, + "resultVersion": 0 + }); + t.pass(); + }); +}); + +// Parses all Markdown files in all package dependencies +test.cb("parseAllFiles", (t) => { + t.plan(1); + const options = { + "files": globby.sync("**/*.{md,markdown}") + }; + markdownlint(options, (err) => { + t.falsy(err); + t.end(); + }); +}); From 0d9dfe71205bfbcdfc6174ebfbbe2a5276533524 Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 17 Dec 2021 02:20:55 +0000 Subject: [PATCH 49/69] Split markdownlint-test-extra-* tests into separate files for (slightly) better concurrency. --- package.json | 2 +- test/markdownlint-test-extra-parse.js | 22 -------------- test/markdownlint-test-extra-type.js | 13 --------- test/markdownlint-test-extra.js | 41 --------------------------- 4 files changed, 1 insertion(+), 77 deletions(-) delete mode 100644 test/markdownlint-test-extra.js diff --git a/package.json b/package.json index d5563cb1..1e658657 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "test": "ava test/markdownlint-test.js test/markdownlint-test-config.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js", "test-cover": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test", "test-declaration": "cd example/typescript && tsc && node type-check.js", - "test-extra": "ava --timeout=5m test/markdownlint-test-extra.js" + "test-extra": "ava --timeout=5m test/markdownlint-test-extra-parse.js test/markdownlint-test-extra-type.js" }, "engines": { "node": ">=12" diff --git a/test/markdownlint-test-extra-parse.js b/test/markdownlint-test-extra-parse.js index d6e643df..9f88e10f 100644 --- a/test/markdownlint-test-extra-parse.js +++ b/test/markdownlint-test-extra-parse.js @@ -2,32 +2,10 @@ "use strict"; -const fs = require("fs"); -const path = require("path"); const globby = require("globby"); const test = require("ava").default; const markdownlint = require("../lib/markdownlint"); -// Simulates typing each test file to validate handling of partial input -const files = fs.readdirSync("./test"); -files.filter((file) => /\.md$/.test(file)).forEach((file) => { - const strings = {}; - let content = fs.readFileSync(path.join("./test", file), "utf8"); - while (content) { - strings[content.length.toString()] = content; - content = content.slice(0, -1); - } - test(`type ${file}`, (t) => { - t.plan(1); - markdownlint.sync({ - // @ts-ignore - strings, - "resultVersion": 0 - }); - t.pass(); - }); -}); - // Parses all Markdown files in all package dependencies test.cb("parseAllFiles", (t) => { t.plan(1); diff --git a/test/markdownlint-test-extra-type.js b/test/markdownlint-test-extra-type.js index d6e643df..827957aa 100644 --- a/test/markdownlint-test-extra-type.js +++ b/test/markdownlint-test-extra-type.js @@ -4,7 +4,6 @@ const fs = require("fs"); const path = require("path"); -const globby = require("globby"); const test = require("ava").default; const markdownlint = require("../lib/markdownlint"); @@ -27,15 +26,3 @@ files.filter((file) => /\.md$/.test(file)).forEach((file) => { t.pass(); }); }); - -// Parses all Markdown files in all package dependencies -test.cb("parseAllFiles", (t) => { - t.plan(1); - const options = { - "files": globby.sync("**/*.{md,markdown}") - }; - markdownlint(options, (err) => { - t.falsy(err); - t.end(); - }); -}); diff --git a/test/markdownlint-test-extra.js b/test/markdownlint-test-extra.js deleted file mode 100644 index d6e643df..00000000 --- a/test/markdownlint-test-extra.js +++ /dev/null @@ -1,41 +0,0 @@ -// @ts-check - -"use strict"; - -const fs = require("fs"); -const path = require("path"); -const globby = require("globby"); -const test = require("ava").default; -const markdownlint = require("../lib/markdownlint"); - -// Simulates typing each test file to validate handling of partial input -const files = fs.readdirSync("./test"); -files.filter((file) => /\.md$/.test(file)).forEach((file) => { - const strings = {}; - let content = fs.readFileSync(path.join("./test", file), "utf8"); - while (content) { - strings[content.length.toString()] = content; - content = content.slice(0, -1); - } - test(`type ${file}`, (t) => { - t.plan(1); - markdownlint.sync({ - // @ts-ignore - strings, - "resultVersion": 0 - }); - t.pass(); - }); -}); - -// Parses all Markdown files in all package dependencies -test.cb("parseAllFiles", (t) => { - t.plan(1); - const options = { - "files": globby.sync("**/*.{md,markdown}") - }; - markdownlint(options, (err) => { - t.falsy(err); - t.end(); - }); -}); From d57b4770edff5ddda5d2ef193a0ad0ae499604c7 Mon Sep 17 00:00:00 2001 From: David Anson Date: Fri, 17 Dec 2021 17:24:00 -0800 Subject: [PATCH 50/69] Update MD010/no-hard-tabs to allow tabs in code spans when the code_blocks parameter is set to false (for consistency) (fixes #454). --- demo/markdownlint-browser.js | 26 ++++++++++++++---------- doc/Rules.md | 12 ++++++----- helpers/helpers.js | 2 +- lib/md010.js | 37 +++++++++++++++++++--------------- test/code-with-tabs-allowed.md | 33 ++++++++++++++++++++++++++++++ test/code-with-tabs-blocked.md | 33 ++++++++++++++++++++++++++++++ 6 files changed, 110 insertions(+), 33 deletions(-) create mode 100644 test/code-with-tabs-allowed.md create mode 100644 test/code-with-tabs-blocked.md diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 46d33528..821348bf 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -551,7 +551,7 @@ module.exports.codeBlockAndSpanRanges = function (params, lineMetadata) { // Add code block ranges (excludes fences) forEachLine(lineMetadata, function (line, lineIndex, inCode, onFence) { if (inCode && !onFence) { - exclusions.push(lineIndex, 0, line.length); + exclusions.push([lineIndex, 0, line.length]); } }); // Add code span ranges (excludes ticks) @@ -2326,8 +2326,8 @@ module.exports = { "use strict"; // @ts-check -var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, forEachLine = _a.forEachLine; -var lineMetadata = __webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata; +var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, forEachLine = _a.forEachLine, overlapsAnyRange = _a.overlapsAnyRange; +var _b = __webpack_require__(/*! ./cache */ "../lib/cache.js"), codeBlockAndSpanRanges = _b.codeBlockAndSpanRanges, lineMetadata = _b.lineMetadata; var tabRe = /\t+/g; module.exports = { "names": ["MD010", "no-hard-tabs"], @@ -2335,22 +2335,26 @@ module.exports = { "tags": ["whitespace", "hard_tab"], "function": function MD010(params, onError) { var codeBlocks = params.config.code_blocks; - var includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks; + var includeCode = (codeBlocks === undefined) ? true : !!codeBlocks; var spacesPerTab = params.config.spaces_per_tab; var spaceMultiplier = (spacesPerTab === undefined) ? 1 : Math.max(0, Number(spacesPerTab)); + var exclusions = includeCode ? [] : codeBlockAndSpanRanges(); forEachLine(lineMetadata(), function (line, lineIndex, inCode) { - if (!inCode || includeCodeBlocks) { + if (includeCode || !inCode) { var match = null; while ((match = tabRe.exec(line)) !== null) { - var column = match.index + 1; + var index = match.index; + var column = index + 1; var length = match[0].length; - addError(onError, lineIndex + 1, "Column: " + column, null, [column, length], { - "editColumn": column, - "deleteCount": length, - "insertText": "".padEnd(length * spaceMultiplier) - }); + if (!overlapsAnyRange(exclusions, lineIndex, index, length)) { + addError(onError, lineIndex + 1, "Column: " + column, null, [column, length], { + "editColumn": column, + "deleteCount": length, + "insertText": "".padEnd(length * spaceMultiplier) + }); + } } } }); diff --git a/doc/Rules.md b/doc/Rules.md index 13471b78..7c3bd922 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -424,12 +424,14 @@ Some text * Spaces used to indent the list item instead ``` -You have the option to exclude this rule for code blocks. To do so, set the -`code_blocks` parameter to `false`. Code blocks are included by default since -handling of tabs by tools is often inconsistent (ex: using 4 vs. 8 spaces). +You have the option to exclude this rule for code blocks and spans. To do so, +set the `code_blocks` parameter to `false`. Code blocks and spans are included +by default since handling of tabs by Markdown tools can be inconsistent (e.g., +using 4 vs. 8 spaces). -If you would like the fixer to change tabs to x spaces, then configure the `spaces_per_tab` -parameter to the number x. The default value would be 1. +By default, violations of this rule are fixed by replacing the tab with 1 space +character. To use a different number of spaces, set the `spaces_per_tab` +parameter to the desired value. Rationale: Hard tabs are often rendered inconsistently by different editors and can be harder to work with than spaces. diff --git a/helpers/helpers.js b/helpers/helpers.js index bf48e4c2..c8c4fea7 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -555,7 +555,7 @@ module.exports.codeBlockAndSpanRanges = (params, lineMetadata) => { // Add code block ranges (excludes fences) forEachLine(lineMetadata, (line, lineIndex, inCode, onFence) => { if (inCode && !onFence) { - exclusions.push(lineIndex, 0, line.length); + exclusions.push([ lineIndex, 0, line.length ]); } }); // Add code span ranges (excludes ticks) diff --git a/lib/md010.js b/lib/md010.js index de20f6cc..2a359313 100644 --- a/lib/md010.js +++ b/lib/md010.js @@ -2,8 +2,8 @@ "use strict"; -const { addError, forEachLine } = require("../helpers"); -const { lineMetadata } = require("./cache"); +const { addError, forEachLine, overlapsAnyRange } = require("../helpers"); +const { codeBlockAndSpanRanges, lineMetadata } = require("./cache"); const tabRe = /\t+/g; @@ -13,28 +13,33 @@ module.exports = { "tags": [ "whitespace", "hard_tab" ], "function": function MD010(params, onError) { const codeBlocks = params.config.code_blocks; - const includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks; + const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks; const spacesPerTab = params.config.spaces_per_tab; const spaceMultiplier = (spacesPerTab === undefined) ? 1 : Math.max(0, Number(spacesPerTab)); + const exclusions = includeCode ? [] : codeBlockAndSpanRanges(); forEachLine(lineMetadata(), (line, lineIndex, inCode) => { - if (!inCode || includeCodeBlocks) { + if (includeCode || !inCode) { let match = null; while ((match = tabRe.exec(line)) !== null) { - const column = match.index + 1; + const { index } = match; + const column = index + 1; const length = match[0].length; - addError( - onError, - lineIndex + 1, - "Column: " + column, - null, - [ column, length ], - { - "editColumn": column, - "deleteCount": length, - "insertText": "".padEnd(length * spaceMultiplier) - }); + if (!overlapsAnyRange(exclusions, lineIndex, index, length)) { + addError( + onError, + lineIndex + 1, + "Column: " + column, + null, + [ column, length ], + { + "editColumn": column, + "deleteCount": length, + "insertText": "".padEnd(length * spaceMultiplier) + } + ); + } } } }); diff --git a/test/code-with-tabs-allowed.md b/test/code-with-tabs-allowed.md new file mode 100644 index 00000000..b86f0a36 --- /dev/null +++ b/test/code-with-tabs-allowed.md @@ -0,0 +1,33 @@ +# Code With Tabs Allowed + +Text text {MD010} + +Text `code code` text + +Text ` code` text + +Text `code ` text + +Text `code code +code code +code code` text + + console.log(" "); + +```js +console.log(" "); +``` + +```j s {MD010} +console.log(" "); +``` + + console.log(""); + + diff --git a/test/code-with-tabs-blocked.md b/test/code-with-tabs-blocked.md new file mode 100644 index 00000000..10464557 --- /dev/null +++ b/test/code-with-tabs-blocked.md @@ -0,0 +1,33 @@ +# Code With Tabs Blocked + +Text text {MD010} + +Text `code code` text {MD010} + +Text ` code` text {MD010} + +Text `code ` text {MD010} + +Text `code code +code code {MD010} +code code` text + + console.log(" "); // {MD010} + +```js +console.log(" "); // {MD010} +``` + +```j s {MD010} +console.log(" "); // {MD010} +``` + + console.log(""); // {MD010} + + From 4ed314d1e7a6a2e3922517753a1b25f31f7af152 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sun, 19 Dec 2021 03:41:05 +0000 Subject: [PATCH 51/69] Add npm scripts upgrade and docker-npm-run-upgrade. --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1e658657..f87ac849 100644 --- a/package.json +++ b/package.json @@ -33,13 +33,15 @@ "declaration": "npm run build-declaration && npm run test-declaration", "example": "cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint", "docker-npm-install": "docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm install", + "docker-npm-run-upgrade": "docker run --rm --tty --name npm-run-upgrade --volume $PWD:/home/workdir --workdir /home/workdir --user node node:16 npm run upgrade", "lint": "eslint --max-warnings 0 .", "lint-test-repos": "ava --timeout=5m test/markdownlint-test-repos.js", "serial-declaration-demo": "npm run build-declaration && npm-run-all --continue-on-error --parallel build-demo test-declaration", "test": "ava test/markdownlint-test.js test/markdownlint-test-config.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js", "test-cover": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 npm test", "test-declaration": "cd example/typescript && tsc && node type-check.js", - "test-extra": "ava --timeout=5m test/markdownlint-test-extra-parse.js test/markdownlint-test-extra-type.js" + "test-extra": "ava --timeout=5m test/markdownlint-test-extra-parse.js test/markdownlint-test-extra-type.js", + "upgrade": "npx npm-check-updates --upgrade" }, "engines": { "node": ">=12" From a656762710d5a6680c56349f9c08eabba43bc97c Mon Sep 17 00:00:00 2001 From: David Anson Date: Sun, 19 Dec 2021 03:45:00 +0000 Subject: [PATCH 52/69] Update dependency: markdown-it to 12.3.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f87ac849..3114cd89 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "node": ">=12" }, "dependencies": { - "markdown-it": "12.2.0" + "markdown-it": "12.3.0" }, "devDependencies": { "ava": "~3.15.0", From 64fcee7e491795ec8e4e9b652d51e88de56a5b41 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sun, 19 Dec 2021 03:55:10 +0000 Subject: [PATCH 53/69] Update dependencies: c8 to 7.10.0, markdown-it-texmath to 0.9.7, markdownlint-rule-helpers to 0.15.0, ts-loader to 9.2.6, typescript to 4.5.4, webpack to 5.65.0, webpack-cli to 4.9.1. --- demo/markdownlint-browser.js | 112 ++++++++++++++++++----------------- package.json | 14 ++--- 2 files changed, 65 insertions(+), 61 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 821348bf..70e340c5 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -913,15 +913,19 @@ var __assign = (this && this.__assign) || function () { }; return __assign.apply(this, arguments); }; -var __spreadArray = (this && this.__spreadArray) || function (to, from) { - for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) - to[j] = from[i]; - return to; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); }; var path = __webpack_require__(/*! path */ "?b85c"); -var promisify = __webpack_require__(/*! util */ "?96a2").promisify; +var promisify = (__webpack_require__(/*! util */ "?96a2").promisify); var markdownIt = __webpack_require__(/*! markdown-it */ "markdown-it"); -var deprecatedRuleNames = __webpack_require__(/*! ./constants */ "../lib/constants.js").deprecatedRuleNames; +var deprecatedRuleNames = (__webpack_require__(/*! ./constants */ "../lib/constants.js").deprecatedRuleNames); var rules = __webpack_require__(/*! ./rules */ "../lib/rules.js"); var helpers = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"); var cache = __webpack_require__(/*! ./cache */ "../lib/cache.js"); @@ -1108,7 +1112,7 @@ function annotateTokens(tokens, lines) { trMap = null; } if (!token.map && trMap) { - token.map = __spreadArray([], trMap); + token.map = __spreadArray([], trMap, true); } // Update token metadata if (token.map) { @@ -1446,14 +1450,14 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul "ruleInformation": rule.information ? rule.information.href : null, "errorDetail": errorInfo.detail || null, "errorContext": errorInfo.context || null, - "errorRange": errorInfo.range ? __spreadArray([], errorInfo.range) : null, + "errorRange": errorInfo.range ? __spreadArray([], errorInfo.range, true) : null, "fixInfo": fixInfo ? cleanFixInfo : null }); } // Call (possibly external) rule function to report errors var catchCallsOnError = function (error) { return onError({ "lineNumber": 1, - "detail": "This rule threw an exception: " + (error.message || error) + "detail": "This rule threw an exception: ".concat(error.message || error) }); }; var invokeRuleFunction = function () { return rule.function(params, onError); }; if (rule.asynchronous) { @@ -1527,7 +1531,7 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul // Run all rules var ruleListAsync = ruleList.filter(function (rule) { return rule.asynchronous; }); var ruleListSync = ruleList.filter(function (rule) { return !rule.asynchronous; }); - var ruleListAsyncFirst = __spreadArray(__spreadArray([], ruleListAsync), ruleListSync); + var ruleListAsyncFirst = __spreadArray(__spreadArray([], ruleListAsync, true), ruleListSync, true); var callbackSuccess = function () { return callback(null, formatResults()); }; var callbackError = function (error) { return callback(error instanceof Error ? error : new Error(error)); }; try { @@ -1600,7 +1604,7 @@ function lintInput(options, synchronous, callback) { } var files = []; if (Array.isArray(options.files)) { - files = __spreadArray([], options.files); + files = __spreadArray([], options.files, true); } else if (options.files) { files = [String(options.files)]; @@ -1737,13 +1741,13 @@ function parseConfiguration(name, content, parsers) { config = parser(content); } catch (error) { - errors.push("Parser " + index++ + ": " + error.message); + errors.push("Parser ".concat(index++, ": ").concat(error.message)); } return !config; }); // Message if unable to parse if (!config) { - errors.unshift("Unable to parse '" + name + "'"); + errors.unshift("Unable to parse '".concat(name, "'")); message = errors.join("; "); } return { @@ -1903,7 +1907,7 @@ function readConfigSync(file, parsers, fs) { * @returns {string} SemVer string. */ function getVersion() { - return __webpack_require__(/*! ./constants */ "../lib/constants.js").version; + return (__webpack_require__(/*! ./constants */ "../lib/constants.js").version); } // Export a/synchronous/Promise APIs markdownlint.sync = markdownlintSync; @@ -1957,7 +1961,7 @@ module.exports = { "use strict"; // @ts-check -var addErrorDetailIf = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js").addErrorDetailIf; +var addErrorDetailIf = (__webpack_require__(/*! ../helpers */ "../helpers/helpers.js").addErrorDetailIf); module.exports = { "names": ["MD002", "first-heading-h1", "first-header-h1"], "description": "First heading should be a top-level heading", @@ -2035,7 +2039,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, listItemMarkerRe = _a.listItemMarkerRe, unorderedListStyleFor = _a.unorderedListStyleFor; -var flattenedLists = __webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists; +var flattenedLists = (__webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists); var expectedStyleToMarker = { "dash": "-", "plus": "+", @@ -2108,7 +2112,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, addErrorDetailIf = _a.addErrorDetailIf, indentFor = _a.indentFor, listItemMarkerRe = _a.listItemMarkerRe, orderedListItemMarkerRe = _a.orderedListItemMarkerRe, rangeFromRegExp = _a.rangeFromRegExp; -var flattenedLists = __webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists; +var flattenedLists = (__webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists); module.exports = { "names": ["MD005", "list-indent"], "description": "Inconsistent indentation for list items at the same level", @@ -2138,8 +2142,8 @@ module.exports = { } else { var detail = endMatching ? - "Expected: (" + expectedEnd + "); Actual: (" + actualEnd + ")" : - "Expected: " + expectedIndent + "; Actual: " + actualIndent; + "Expected: (".concat(expectedEnd, "); Actual: (").concat(actualEnd, ")") : + "Expected: ".concat(expectedIndent, "; Actual: ").concat(actualIndent); var expected = endMatching ? expectedEnd - markerLength : expectedIndent; @@ -2172,7 +2176,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, listItemMarkerRe = _a.listItemMarkerRe, rangeFromRegExp = _a.rangeFromRegExp; -var flattenedLists = __webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists; +var flattenedLists = (__webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists); module.exports = { "names": ["MD006", "ul-start-left"], "description": "Consider starting bulleted lists at the beginning of the line", @@ -2204,7 +2208,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, indentFor = _a.indentFor, listItemMarkerRe = _a.listItemMarkerRe; -var flattenedLists = __webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists; +var flattenedLists = (__webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists); module.exports = { "names": ["MD007", "ul-indent"], "description": "Unordered list indentation", @@ -2251,7 +2255,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, filterTokens = _a.filterTokens, forEachInlineCodeSpan = _a.forEachInlineCodeSpan, forEachLine = _a.forEachLine, includesSorted = _a.includesSorted, newLineRe = _a.newLineRe, numericSortAscending = _a.numericSortAscending; -var lineMetadata = __webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata; +var lineMetadata = (__webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata); module.exports = { "names": ["MD009", "no-trailing-spaces"], "description": "Trailing spaces", @@ -2395,7 +2399,7 @@ module.exports = { addError(onError, lineIndex + 1, reversedLink.slice(preChar.length), null, [index + 1, length], { "editColumn": index + 1, "deleteCount": length, - "insertText": "[" + linkText + "](" + linkDestination + ")" + "insertText": "[".concat(linkText, "](").concat(linkDestination, ")") }); } } @@ -2417,7 +2421,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, forEachLine = _a.forEachLine; -var lineMetadata = __webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata; +var lineMetadata = (__webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata); module.exports = { "names": ["MD012", "no-multiple-blanks"], "description": "Multiple consecutive blank lines", @@ -2449,7 +2453,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, filterTokens = _a.filterTokens, forEachHeading = _a.forEachHeading, forEachLine = _a.forEachLine, includesSorted = _a.includesSorted; -var lineMetadata = __webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata; +var lineMetadata = (__webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata); var longLineRePrefix = "^.{"; var longLineRePostfixRelaxed = "}.*\\s.*$"; var longLineRePostfixStrict = "}.+$"; @@ -2594,7 +2598,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorContext = _a.addErrorContext, forEachLine = _a.forEachLine; -var lineMetadata = __webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata; +var lineMetadata = (__webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata); module.exports = { "names": ["MD018", "no-missing-space-atx"], "description": "No space after hash on atx style heading", @@ -2662,7 +2666,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorContext = _a.addErrorContext, forEachLine = _a.forEachLine; -var lineMetadata = __webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata; +var lineMetadata = (__webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata); module.exports = { "names": ["MD020", "no-missing-space-closed-atx"], "description": "No space inside hashes on closed atx style heading", @@ -2677,7 +2681,7 @@ module.exports = { var rightHashLength = rightHash.length; var left = !leftSpaceLength; var right = !rightSpaceLength || rightEscape; - var rightEscapeReplacement = rightEscape ? rightEscape + " " : ""; + var rightEscapeReplacement = rightEscape ? "".concat(rightEscape, " ") : ""; if (left || right) { var range = left ? [ @@ -2691,7 +2695,7 @@ module.exports = { addErrorContext(onError, lineIndex + 1, line.trim(), left, right, range, { "editColumn": 1, "deleteCount": line.length, - "insertText": leftHash + " " + content + " " + rightEscapeReplacement + rightHash + "insertText": "".concat(leftHash, " ").concat(content, " ").concat(rightEscapeReplacement).concat(rightHash) }); } } @@ -2742,7 +2746,7 @@ module.exports = { addErrorContext(onError, lineNumber, line.trim(), left, right, range, { "editColumn": 1, "deleteCount": length, - "insertText": leftHash + " " + content + " " + rightHash + "insertText": "".concat(leftHash, " ").concat(content, " ").concat(rightHash) }); } } @@ -2948,7 +2952,7 @@ module.exports = { var fullMatch = match[0]; var column = match.index + 1; var length = fullMatch.length; - addError(onError, lineNumber, "Punctuation: '" + fullMatch + "'", null, [column, length], { + addError(onError, lineNumber, "Punctuation: '".concat(fullMatch, "'"), null, [column, length], { "editColumn": column, "deleteCount": length }); @@ -3024,7 +3028,7 @@ module.exports = { "use strict"; // @ts-check -var addError = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js").addError; +var addError = (__webpack_require__(/*! ../helpers */ "../helpers/helpers.js").addError); module.exports = { "names": ["MD028", "no-blanks-blockquote"], "description": "Blank line inside blockquote", @@ -3060,7 +3064,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, listItemMarkerRe = _a.listItemMarkerRe, orderedListItemMarkerRe = _a.orderedListItemMarkerRe, rangeFromRegExp = _a.rangeFromRegExp; -var flattenedLists = __webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists; +var flattenedLists = (__webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists); var listStyleExamples = { "one": "1/1/1", "ordered": "1/2/3", @@ -3129,8 +3133,8 @@ module.exports = { "use strict"; // @ts-check -var addErrorDetailIf = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js").addErrorDetailIf; -var flattenedLists = __webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists; +var addErrorDetailIf = (__webpack_require__(/*! ../helpers */ "../helpers/helpers.js").addErrorDetailIf); +var flattenedLists = (__webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists); module.exports = { "names": ["MD030", "list-marker-space"], "description": "Spaces after list markers", @@ -3179,7 +3183,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorContext = _a.addErrorContext, forEachLine = _a.forEachLine, isBlankLine = _a.isBlankLine; -var lineMetadata = __webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata; +var lineMetadata = (__webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata); var codeFencePrefixRe = /^(.*?)\s*[`~]/; module.exports = { "names": ["MD031", "blanks-around-fences"], @@ -3198,7 +3202,7 @@ module.exports = { var _a = line.match(codeFencePrefixRe) || [], prefix = _a[1]; var fixInfo = (prefix === undefined) ? null : { "lineNumber": i + (onTopFence ? 1 : 2), - "insertText": prefix + "\n" + "insertText": "".concat(prefix, "\n") }; addErrorContext(onError, i + 1, lines[i].trim(), null, null, null, fixInfo); } @@ -3219,7 +3223,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorContext = _a.addErrorContext, isBlankLine = _a.isBlankLine; -var flattenedLists = __webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists; +var flattenedLists = (__webpack_require__(/*! ./cache */ "../lib/cache.js").flattenedLists); var quotePrefixRe = /^[>\s]*/; module.exports = { "names": ["MD032", "blanks-around-lists"], @@ -3233,7 +3237,7 @@ module.exports = { var line = lines[firstIndex]; var quotePrefix = line.match(quotePrefixRe)[0].trimEnd(); addErrorContext(onError, firstIndex + 1, line.trim(), null, null, null, { - "insertText": quotePrefix + "\n" + "insertText": "".concat(quotePrefix, "\n") }); } var lastIndex = list.lastLineIndex - 1; @@ -3242,7 +3246,7 @@ module.exports = { var quotePrefix = line.match(quotePrefixRe)[0].trimEnd(); addErrorContext(onError, lastIndex + 1, line.trim(), null, null, null, { "lineNumber": lastIndex + 2, - "insertText": quotePrefix + "\n" + "insertText": "".concat(quotePrefix, "\n") }); } }); @@ -3350,7 +3354,7 @@ module.exports = { var fixInfo = range ? { "editColumn": range[0], "deleteCount": range[1], - "insertText": "<" + bareUrl + ">" + "insertText": "<".concat(bareUrl, ">") } : null; addErrorContext(onError, lineNumber, bareUrl, null, null, range, fixInfo); } @@ -3468,7 +3472,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorContext = _a.addErrorContext, emphasisMarkersInContent = _a.emphasisMarkersInContent, forEachLine = _a.forEachLine, isBlankLine = _a.isBlankLine; -var lineMetadata = __webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata; +var lineMetadata = (__webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata); var emphasisRe = /(^|[^\\]|\\\\)(?:(\*\*?\*?)|(__?_?))/g; var asteriskListItemMarkerRe = /^([\s>]*)\*(\s+)/; var leftSpaceRe = /^\s+/; @@ -3511,7 +3515,7 @@ module.exports = { var length = contextEnd - contextStart; var leftMarker = line.substring(contextStart, emphasisIndex); var rightMarker = match ? (match[2] || match[3]) : ""; - var fixedText = "" + leftMarker + content.trim() + rightMarker; + var fixedText = "".concat(leftMarker).concat(content.trim()).concat(rightMarker); return [ onError, lineIndex + 1, @@ -3737,7 +3741,7 @@ module.exports = { }; lineIndex = column + length - 1; } - addErrorContext(onError, lineNumber, "[" + linkText + "]", left, right, range, fixInfo); + addErrorContext(onError, lineNumber, "[".concat(linkText, "]"), left, right, range, fixInfo); } } else if ((type === "softbreak") || (type === "hardbreak")) { @@ -3800,7 +3804,7 @@ module.exports = { var tag = "h" + level; var foundFrontMatterTitle = frontMatterHasTitle(params.frontMatterLines, params.config.front_matter_title); if (!foundFrontMatterTitle) { - var htmlHeadingRe_1 = new RegExp("^]", "i"); + var htmlHeadingRe_1 = new RegExp("^]"), "i"); params.tokens.every(function (token) { var isError = false; if (token.type === "html_block") { @@ -3990,7 +3994,7 @@ module.exports = { var escapedName = escapeForRegExp(name); var startNamePattern = /^\W/.test(name) ? "" : "\\b_*"; var endNamePattern = /\W$/.test(name) ? "" : "_*\\b"; - var namePattern = "(" + startNamePattern + ")(" + escapedName + ")" + endNamePattern; + var namePattern = "(".concat(startNamePattern, ")(").concat(escapedName, ")").concat(endNamePattern); var nameRe = new RegExp(namePattern, "gi"); forEachLine(lineMetadata(), function (line, lineIndex, inCode, onFence) { if (includeCodeBlocks || (!inCode && !onFence)) { @@ -4056,7 +4060,7 @@ module.exports = { "use strict"; // @ts-check -var addErrorDetailIf = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js").addErrorDetailIf; +var addErrorDetailIf = (__webpack_require__(/*! ../helpers */ "../helpers/helpers.js").addErrorDetailIf); var tokenTypeToStyle = { "fence": "fenced", "code_block": "indented" @@ -4170,12 +4174,12 @@ module.exports = { var contentToken = getNextChildToken(parent, token, "text", "em_close"); if (contentToken) { var content = contentToken.content; - var actual = "" + markup + content + markup; + var actual = "".concat(markup).concat(content).concat(markup); var expectedMarkup = (expectedStyle === "asterisk") ? "*" : "_"; - var expected = "" + expectedMarkup + content + expectedMarkup; + var expected = "".concat(expectedMarkup).concat(content).concat(expectedMarkup); rangeAndFixInfo = getRangeAndFixInfoIfFound(params.lines, lineNumber - 1, actual, expected); } - addError(onError, lineNumber, "Expected: " + expectedStyle + "; Actual: " + markupStyle, null, rangeAndFixInfo.range, rangeAndFixInfo.fixInfo); + addError(onError, lineNumber, "Expected: ".concat(expectedStyle, "; Actual: ").concat(markupStyle), null, rangeAndFixInfo.range, rangeAndFixInfo.fixInfo); } }); } @@ -4211,12 +4215,12 @@ module.exports = { var contentToken = getNextChildToken(parent, token, "text", "strong_close"); if (contentToken) { var content = contentToken.content; - var actual = "" + markup + content + markup; + var actual = "".concat(markup).concat(content).concat(markup); var expectedMarkup = (expectedStyle === "asterisk") ? "**" : "__"; - var expected = "" + expectedMarkup + content + expectedMarkup; + var expected = "".concat(expectedMarkup).concat(content).concat(expectedMarkup); rangeAndFixInfo = getRangeAndFixInfoIfFound(params.lines, lineNumber - 1, actual, expected); } - addError(onError, lineNumber, "Expected: " + expectedStyle + "; Actual: " + markupStyle, null, rangeAndFixInfo.range, rangeAndFixInfo.fixInfo); + addError(onError, lineNumber, "Expected: ".concat(expectedStyle, "; Actual: ").concat(markupStyle), null, rangeAndFixInfo.range, rangeAndFixInfo.fixInfo); } }); } @@ -4287,7 +4291,7 @@ rules.forEach(function (rule) { var name = rule.names[0].toLowerCase(); // eslint-disable-next-line dot-notation rule["information"] = - new URL(homepage + "/blob/v" + version + "/doc/Rules.md#" + name); + new URL("".concat(homepage, "/blob/v").concat(version, "/doc/Rules.md#").concat(name)); }); module.exports = rules; diff --git a/package.json b/package.json index 3114cd89..0e987366 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ }, "devDependencies": { "ava": "~3.15.0", - "c8": "~7.8.0", + "c8": "~7.10.0", "eslint": "~7.32.0", "eslint-plugin-jsdoc": "~36.0.7", "eslint-plugin-node": "~11.1.0", @@ -61,16 +61,16 @@ "markdown-it-for-inline": "~0.1.1", "markdown-it-sub": "~1.0.0", "markdown-it-sup": "~1.0.0", - "markdown-it-texmath": "~0.9.1", - "markdownlint-rule-helpers": "~0.14.0", + "markdown-it-texmath": "~0.9.7", + "markdownlint-rule-helpers": "~0.15.0", "npm-run-all": "~4.1.5", "strip-json-comments": "~3.1.1", "toml": "~3.0.0", - "ts-loader": "~9.2.5", + "ts-loader": "~9.2.6", "tv4": "~1.3.0", - "typescript": "~4.3.5", - "webpack": "~5.51.1", - "webpack-cli": "~4.8.0" + "typescript": "~4.5.4", + "webpack": "~5.65.0", + "webpack-cli": "~4.9.1" }, "keywords": [ "markdown", From 1b23976aa2bcd699b80505075ebf7cd04e9905b8 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sun, 19 Dec 2021 04:04:53 +0000 Subject: [PATCH 54/69] Update dependencies: eslint-plugin-jsdoc to 37.2.8, eslint-plugin-unicorn to 39.0.0. --- .eslintrc.json | 7 +++++++ package.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 0fe1cb83..d76f8131 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -207,11 +207,14 @@ "unicorn/no-array-method-this-argument": "error", "unicorn/no-array-push-push": "error", "unicorn/no-array-reduce": "error", + "unicorn/no-await-expression-member": "error", "unicorn/no-console-spaces": "error", "unicorn/no-document-cookie": "error", + "unicorn/no-empty-file": "error", "unicorn/no-for-loop": "error", "unicorn/no-hex-escape": "error", "unicorn/no-instanceof-array": "error", + "unicorn/no-invalid-remove-event-listener": "error", "unicorn/no-keyword-prefix": "off", "unicorn/no-lonely-if": "error", "unicorn/no-nested-ternary": "error", @@ -225,6 +228,7 @@ "unicorn/no-unreadable-array-destructuring": "error", "unicorn/no-unsafe-regex": "off", "unicorn/no-unused-properties": "error", + "unicorn/no-useless-fallback-in-spread": "error", "unicorn/no-useless-length-check": "error", "unicorn/no-useless-spread": "error", "unicorn/no-useless-undefined": "error", @@ -238,12 +242,14 @@ "unicorn/prefer-array-index-of": "error", "unicorn/prefer-array-some": "error", "unicorn/prefer-at": "off", + "unicorn/prefer-code-point": "error", "unicorn/prefer-date-now": "error", "unicorn/prefer-default-parameters": "error", "unicorn/prefer-dom-node-append": "error", "unicorn/prefer-dom-node-dataset": "error", "unicorn/prefer-dom-node-remove": "error", "unicorn/prefer-dom-node-text-content": "error", + "unicorn/prefer-export-from": "error", "unicorn/prefer-includes": "error", "unicorn/prefer-keyboard-event-key": "error", "unicorn/prefer-math-trunc": "error", @@ -274,6 +280,7 @@ "unicorn/require-number-to-fixed-digits-argument": "error", "unicorn/require-post-message-target-origin": "error", "unicorn/string-content": "error", + "unicorn/template-indent": "error", "unicorn/throw-new-error": "error" }, "settings": { diff --git a/package.json b/package.json index 0e987366..7b8e894e 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,9 @@ "ava": "~3.15.0", "c8": "~7.10.0", "eslint": "~7.32.0", - "eslint-plugin-jsdoc": "~36.0.7", + "eslint-plugin-jsdoc": "~37.2.8", "eslint-plugin-node": "~11.1.0", - "eslint-plugin-unicorn": "~35.0.0", + "eslint-plugin-unicorn": "~39.0.0", "globby": "~11.0.4", "js-yaml": "~4.1.0", "markdown-it-for-inline": "~0.1.1", From 6dea67825aac29bc6e43bd932d641e2bfe7b38ff Mon Sep 17 00:00:00 2001 From: David Anson Date: Mon, 20 Dec 2021 04:18:45 +0000 Subject: [PATCH 55/69] Update definition of helpers.isBlankLine to treat unterminated start/end comments as potentially blank lines (fixes #431). --- demo/markdownlint-browser.js | 12 +++++++--- helpers/helpers.js | 14 ++++++++--- test/lists-with-commented-items.md | 38 ++++++++++++++++++++++++++++++ test/markdownlint-test-helpers.js | 14 +++++++---- 4 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 test/lists-with-commented-items.md diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 70e340c5..dd5c37e5 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -77,12 +77,18 @@ module.exports.isObject = function isObject(obj) { return (obj !== null) && (typeof obj === "object") && !Array.isArray(obj); }; // Returns true iff the input line is blank (no content) -// Example: Contains nothing, whitespace, or comments -var blankLineRe = />|(?:)/g; +// Example: Contains nothing, whitespace, or comment (unclosed start/end okay) module.exports.isBlankLine = function isBlankLine(line) { // Call to String.replace follows best practices and is not a security check // False-positive for js/incomplete-multi-character-sanitization - return !line || !line.trim() || !line.replace(blankLineRe, "").trim(); + return (!line || + !line.trim() || + !line + .replace(//g, "") + .replace(//g, "") + .replace(/>/g, "") + .trim()); }; /** * Compare function for Array.prototype.sort for ascending order of numbers. diff --git a/helpers/helpers.js b/helpers/helpers.js index c8c4fea7..6a865ac4 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -63,12 +63,20 @@ module.exports.isObject = function isObject(obj) { }; // Returns true iff the input line is blank (no content) -// Example: Contains nothing, whitespace, or comments -const blankLineRe = />|(?:)/g; +// Example: Contains nothing, whitespace, or comment (unclosed start/end okay) module.exports.isBlankLine = function isBlankLine(line) { // Call to String.replace follows best practices and is not a security check // False-positive for js/incomplete-multi-character-sanitization - return !line || !line.trim() || !line.replace(blankLineRe, "").trim(); + return ( + !line || + !line.trim() || + !line + .replace(//g, "") + .replace(//g, "") + .replace(/>/g, "") + .trim() + ); }; /** diff --git a/test/lists-with-commented-items.md b/test/lists-with-commented-items.md new file mode 100644 index 00000000..6fa15c3b --- /dev/null +++ b/test/lists-with-commented-items.md @@ -0,0 +1,38 @@ +# Lists with Commented Items + +Text + +- item +- item + +- item +- item + +Text + +- item +- item + +- item +- item + +Text + +- item + +- item + +Text + +- item +- item + + +- item +- item + +Text diff --git a/test/markdownlint-test-helpers.js b/test/markdownlint-test-helpers.js index 5e05f3ce..ea752ba0 100644 --- a/test/markdownlint-test-helpers.js +++ b/test/markdownlint-test-helpers.js @@ -226,7 +226,7 @@ bar` }); test("isBlankLine", (t) => { - t.plan(25); + t.plan(29); const blankLines = [ null, "", @@ -244,7 +244,11 @@ test("isBlankLine", (t) => { "> ", "> > > \t", "> ", - ">>" + ">>", + " ", + "-->" ]; blankLines.forEach((line) => t.true(helpers.isBlankLine(line), line || "")); const nonBlankLines = [ @@ -253,9 +257,9 @@ test("isBlankLine", (t) => { ".", "> .", " text", - "", - "" + "text ", + "text text" ]; nonBlankLines.forEach((line) => t.true(!helpers.isBlankLine(line), line)); }); From 3e8d3320f754d0136993f5ccc53894185edc3524 Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 21 Dec 2021 05:31:26 +0000 Subject: [PATCH 56/69] Add test for outdated ignore expressions to markdownlint-test-repos. --- test/markdownlint-test-repos.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index f0040c74..bf96e7e8 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -60,7 +60,11 @@ function lintTestRepo(t, globPatterns, configPath, ignoreRes) { return markdownlintPromise(options).then((results) => { let resultsString = results.toString(); for (const ignoreRe of (ignoreRes || [])) { + const lengthBefore = resultsString.length; resultsString = resultsString.replace(ignoreRe, ""); + if (resultsString.length === lengthBefore) { + t.fail(`Unnecessary ignore: ${ignoreRe}`); + } } if (resultsString.length > 0) { // eslint-disable-next-line no-console @@ -158,10 +162,7 @@ if (existsSync(dotnetDocsDir)) { const rootDir = dotnetDocsDir; const globPatterns = [ join(rootDir, "**/*.md") ]; const configPath = join(rootDir, ".markdownlint.json"); - const ignoreRes = [ - /^[^:]+: \d+: (MD049|MD050)\/.*$\r?\n?/gm, - /^[^:]+\/dotnet-dump\.md: \d+: MD033\/.*$\r?\n?/gm - ]; + const ignoreRes = [ /^[^:]+: \d+: (MD049|MD050)\/.*$\r?\n?/gm ]; return lintTestRepo(t, globPatterns, configPath, ignoreRes); }); } From 7cf9c2d6bee40e87aa5ad615f8b1fd75f33e4b36 Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 21 Dec 2021 22:21:28 +0000 Subject: [PATCH 57/69] Update MD037/no-space-in-emphasis to ignore embedded underscore emphasis markers (fixes #444, fixes #408, fixes #354, fixes #324). --- demo/markdownlint-browser.js | 6 ++++-- lib/md037.js | 6 ++++-- test/spaces_inside_emphasis_markers.md | 10 ++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index dd5c37e5..eb3b1a24 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -3480,6 +3480,7 @@ module.exports = { var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorContext = _a.addErrorContext, emphasisMarkersInContent = _a.emphasisMarkersInContent, forEachLine = _a.forEachLine, isBlankLine = _a.isBlankLine; var lineMetadata = (__webpack_require__(/*! ./cache */ "../lib/cache.js").lineMetadata); var emphasisRe = /(^|[^\\]|\\\\)(?:(\*\*?\*?)|(__?_?))/g; +var embeddedUnderscoreRe = /([A-Za-z0-9])_([A-Za-z0-9])/g; var asteriskListItemMarkerRe = /^([\s>]*)\*(\s+)/; var leftSpaceRe = /^\s+/; var rightSpaceRe = /\s+$/; @@ -3559,13 +3560,14 @@ module.exports = { // Emphasis has no meaning here return; } + var patchedLine = line.replace(embeddedUnderscoreRe, "$1 $2"); if (onItemStart) { // Trim overlapping '*' list item marker - line = line.replace(asteriskListItemMarkerRe, "$1 $2"); + patchedLine = patchedLine.replace(asteriskListItemMarkerRe, "$1 $2"); } var match = null; // Match all emphasis-looking runs in the line... - while ((match = emphasisRe.exec(line))) { + while ((match = emphasisRe.exec(patchedLine))) { var ignoreMarkersForLine = ignoreMarkersByLine[lineIndex]; var matchIndex = match.index + match[1].length; if (ignoreMarkersForLine.includes(matchIndex)) { diff --git a/lib/md037.js b/lib/md037.js index 1d46163b..2466038f 100644 --- a/lib/md037.js +++ b/lib/md037.js @@ -7,6 +7,7 @@ const { addErrorContext, emphasisMarkersInContent, forEachLine, isBlankLine } = const { lineMetadata } = require("./cache"); const emphasisRe = /(^|[^\\]|\\\\)(?:(\*\*?\*?)|(__?_?))/g; +const embeddedUnderscoreRe = /([A-Za-z0-9])_([A-Za-z0-9])/g; const asteriskListItemMarkerRe = /^([\s>]*)\*(\s+)/; const leftSpaceRe = /^\s+/; const rightSpaceRe = /\s+$/; @@ -98,13 +99,14 @@ module.exports = { // Emphasis has no meaning here return; } + let patchedLine = line.replace(embeddedUnderscoreRe, "$1 $2"); if (onItemStart) { // Trim overlapping '*' list item marker - line = line.replace(asteriskListItemMarkerRe, "$1 $2"); + patchedLine = patchedLine.replace(asteriskListItemMarkerRe, "$1 $2"); } let match = null; // Match all emphasis-looking runs in the line... - while ((match = emphasisRe.exec(line))) { + while ((match = emphasisRe.exec(patchedLine))) { const ignoreMarkersForLine = ignoreMarkersByLine[lineIndex]; const matchIndex = match.index + match[1].length; if (ignoreMarkersForLine.includes(matchIndex)) { diff --git a/test/spaces_inside_emphasis_markers.md b/test/spaces_inside_emphasis_markers.md index 0799da86..0e716a8e 100644 --- a/test/spaces_inside_emphasis_markers.md +++ b/test/spaces_inside_emphasis_markers.md @@ -347,3 +347,13 @@ text [reference*link] star * star text ```yaml /* autogenerated */ # YAML... ``` + +new_value from *old_value* and *older_value*. + +:ballot_box_with_check: _Emoji syntax_ + +some_snake_case_function() is _called_ + +_~/.ssh/id_rsa_ and _emphasis_ + +Partial *em*phasis of a *wo*rd. From ff8f4ea9fc7640eead190277a0f729f863879a5b Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 21 Dec 2021 21:31:47 -0800 Subject: [PATCH 58/69] Reduce execution time by ~50% by updating getEnabledRulesPerLineNumber to make enabledRules immutable and copy only when changed (also, simplify handleInlineConfig slightly). --- demo/markdownlint-browser.js | 27 ++++++++++++++------------- lib/markdownlint.js | 35 ++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index eb3b1a24..2b3925f2 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -1239,8 +1239,7 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin var enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length); // Helper functions // eslint-disable-next-line jsdoc/require-jsdoc - function handleInlineConfig(perLine, forEachMatch, forEachLine) { - var input = perLine ? lines : [lines.join("\n")]; + function handleInlineConfig(input, forEachMatch, forEachLine) { input.forEach(function (line, lineIndex) { if (!noInlineConfig) { var match = null; @@ -1269,6 +1268,7 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin } // eslint-disable-next-line jsdoc/require-jsdoc function applyEnableDisable(action, parameter, state) { + state = __assign({}, state); var enabled = (action.startsWith("ENABLE")); var items = parameter ? parameter.trim().toUpperCase().split(/\s+/) : @@ -1278,38 +1278,39 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin state[ruleName] = enabled; }); }); + return state; } // eslint-disable-next-line jsdoc/require-jsdoc function enableDisableFile(action, parameter) { if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) { - applyEnableDisable(action, parameter, enabledRules); + enabledRules = applyEnableDisable(action, parameter, enabledRules); } } // eslint-disable-next-line jsdoc/require-jsdoc function captureRestoreEnableDisable(action, parameter) { if (action === "CAPTURE") { - capturedRules = __assign({}, enabledRules); + capturedRules = enabledRules; } else if (action === "RESTORE") { - enabledRules = __assign({}, capturedRules); + enabledRules = capturedRules; } else if ((action === "ENABLE") || (action === "DISABLE")) { - enabledRules = __assign({}, enabledRules); - applyEnableDisable(action, parameter, enabledRules); + enabledRules = applyEnableDisable(action, parameter, enabledRules); } } // eslint-disable-next-line jsdoc/require-jsdoc function updateLineState() { - enabledRulesPerLineNumber.push(__assign({}, enabledRules)); + enabledRulesPerLineNumber.push(enabledRules); } // eslint-disable-next-line jsdoc/require-jsdoc function disableNextLine(action, parameter, lineNumber) { if (action === "DISABLE-NEXT-LINE") { - applyEnableDisable(action, parameter, enabledRulesPerLineNumber[lineNumber + 1] || {}); + enabledRulesPerLineNumber[lineNumber + 1] = + applyEnableDisable(action, parameter, enabledRulesPerLineNumber[lineNumber + 1] || {}); } } // Handle inline comments - handleInlineConfig(false, configureFile); + handleInlineConfig([lines.join("\n")], configureFile); var effectiveConfig = getEffectiveConfig(ruleList, config, aliasToRuleNames); ruleList.forEach(function (rule) { var ruleName = rule.names[0].toUpperCase(); @@ -1317,9 +1318,9 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin enabledRules[ruleName] = !!effectiveConfig[ruleName]; }); capturedRules = enabledRules; - handleInlineConfig(true, enableDisableFile); - handleInlineConfig(true, captureRestoreEnableDisable, updateLineState); - handleInlineConfig(true, disableNextLine); + handleInlineConfig(lines, enableDisableFile); + handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState); + handleInlineConfig(lines, disableNextLine); // Return results return { effectiveConfig: effectiveConfig, diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 6745fe8f..8cb90033 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -337,8 +337,7 @@ function getEnabledRulesPerLineNumber( const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length); // Helper functions // eslint-disable-next-line jsdoc/require-jsdoc - function handleInlineConfig(perLine, forEachMatch, forEachLine) { - const input = perLine ? lines : [ lines.join("\n") ]; + function handleInlineConfig(input, forEachMatch, forEachLine) { input.forEach((line, lineIndex) => { if (!noInlineConfig) { let match = null; @@ -369,6 +368,7 @@ function getEnabledRulesPerLineNumber( } // eslint-disable-next-line jsdoc/require-jsdoc function applyEnableDisable(action, parameter, state) { + state = { ...state }; const enabled = (action.startsWith("ENABLE")); const items = parameter ? parameter.trim().toUpperCase().split(/\s+/) : @@ -378,40 +378,41 @@ function getEnabledRulesPerLineNumber( state[ruleName] = enabled; }); }); + return state; } // eslint-disable-next-line jsdoc/require-jsdoc function enableDisableFile(action, parameter) { if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) { - applyEnableDisable(action, parameter, enabledRules); + enabledRules = applyEnableDisable(action, parameter, enabledRules); } } // eslint-disable-next-line jsdoc/require-jsdoc function captureRestoreEnableDisable(action, parameter) { if (action === "CAPTURE") { - capturedRules = { ...enabledRules }; + capturedRules = enabledRules; } else if (action === "RESTORE") { - enabledRules = { ...capturedRules }; + enabledRules = capturedRules; } else if ((action === "ENABLE") || (action === "DISABLE")) { - enabledRules = { ...enabledRules }; - applyEnableDisable(action, parameter, enabledRules); + enabledRules = applyEnableDisable(action, parameter, enabledRules); } } // eslint-disable-next-line jsdoc/require-jsdoc function updateLineState() { - enabledRulesPerLineNumber.push({ ...enabledRules }); + enabledRulesPerLineNumber.push(enabledRules); } // eslint-disable-next-line jsdoc/require-jsdoc function disableNextLine(action, parameter, lineNumber) { if (action === "DISABLE-NEXT-LINE") { - applyEnableDisable( - action, - parameter, - enabledRulesPerLineNumber[lineNumber + 1] || {} - ); + enabledRulesPerLineNumber[lineNumber + 1] = + applyEnableDisable( + action, + parameter, + enabledRulesPerLineNumber[lineNumber + 1] || {} + ); } } // Handle inline comments - handleInlineConfig(false, configureFile); + handleInlineConfig([ lines.join("\n") ], configureFile); const effectiveConfig = getEffectiveConfig( ruleList, config, aliasToRuleNames); ruleList.forEach((rule) => { @@ -420,9 +421,9 @@ function getEnabledRulesPerLineNumber( enabledRules[ruleName] = !!effectiveConfig[ruleName]; }); capturedRules = enabledRules; - handleInlineConfig(true, enableDisableFile); - handleInlineConfig(true, captureRestoreEnableDisable, updateLineState); - handleInlineConfig(true, disableNextLine); + handleInlineConfig(lines, enableDisableFile); + handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState); + handleInlineConfig(lines, disableNextLine); // Return results return { effectiveConfig, From 064a1e33e15b0260c7a30987d7e40088bf40d58d Mon Sep 17 00:00:00 2001 From: David Anson Date: Tue, 21 Dec 2021 21:38:29 -0800 Subject: [PATCH 59/69] Update Node version for TestRepos workflow from 12 to 16. --- .github/workflows/test-repos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-repos.yml b/.github/workflows/test-repos.yml index 7ff15af6..b36a0e58 100644 --- a/.github/workflows/test-repos.yml +++ b/.github/workflows/test-repos.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node-version: [12.x] + node-version: [16.x] steps: - uses: actions/checkout@v2 From 7a76f1d22d653d958869d4727479c942f564ea28 Mon Sep 17 00:00:00 2001 From: David Anson Date: Wed, 22 Dec 2021 14:52:43 -0800 Subject: [PATCH 60/69] Update MD039/no-space-in-links to fix reference-style links, be slightly more permissive matching link content. --- demo/markdownlint-browser.js | 2 +- lib/md039.js | 3 +- test/detailed-results-links.md | 14 +++ test/detailed-results-links.md.fixed | 14 +++ test/detailed-results-links.results.json | 120 +++++++++++++++++++++++ test/spaces_inside_link_text.md | 12 +++ 6 files changed, 163 insertions(+), 2 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 2b3925f2..185f94ef 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -3712,7 +3712,7 @@ module.exports = { // @ts-check var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorContext = _a.addErrorContext, filterTokens = _a.filterTokens; -var spaceInLinkRe = /\[(?:\s+(?:[^\]]*?)\s*|(?:[^\]]*?)\s+)](?=\(\S*\))/; +var spaceInLinkRe = /\[(?:\s+(?:[^\]]*?)\s*|(?:[^\]]*?)\s+)](?=((?:\([^)]*\))|(?:\[[^\]]*\])))/; module.exports = { "names": ["MD039", "no-space-in-links"], "description": "Spaces inside link text", diff --git a/lib/md039.js b/lib/md039.js index c65a9cbe..8397cfc5 100644 --- a/lib/md039.js +++ b/lib/md039.js @@ -4,7 +4,8 @@ const { addErrorContext, filterTokens } = require("../helpers"); -const spaceInLinkRe = /\[(?:\s+(?:[^\]]*?)\s*|(?:[^\]]*?)\s+)](?=\(\S*\))/; +const spaceInLinkRe = + /\[(?:\s+(?:[^\]]*?)\s*|(?:[^\]]*?)\s+)](?=((?:\([^)]*\))|(?:\[[^\]]*\])))/; module.exports = { "names": [ "MD039", "no-space-in-links" ], diff --git a/test/detailed-results-links.md b/test/detailed-results-links.md index 3548c6c5..c9d90dc3 100644 --- a/test/detailed-results-links.md +++ b/test/detailed-results-links.md @@ -28,3 +28,17 @@ Text more \* text https://example.com/same more \[ te Text https://example.com/first more text https://example.com/second still more text https://example.com/third done (Incorrect link syntax)[https://www.example.com/] + +Text [link ](https://example.com/) text. + +Text [ link](https://example.com/) text. + +Text [ link ](https://example.com/) text. + +Text [link ][reference] text. + +Text [ link][reference] text. + +Text [ link ][reference] text. + +[reference]: https://example.com/ diff --git a/test/detailed-results-links.md.fixed b/test/detailed-results-links.md.fixed index 90f735cf..ab3cf6e0 100644 --- a/test/detailed-results-links.md.fixed +++ b/test/detailed-results-links.md.fixed @@ -28,3 +28,17 @@ Text more \* text https://example.com/same more \[ te Text more text still more text done [Incorrect link syntax](https://www.example.com/) + +Text [link](https://example.com/) text. + +Text [link](https://example.com/) text. + +Text [link](https://example.com/) text. + +Text [link][reference] text. + +Text [link][reference] text. + +Text [link][reference] text. + +[reference]: https://example.com/ diff --git a/test/detailed-results-links.results.json b/test/detailed-results-links.results.json index 9d6c721a..8dc8bae9 100644 --- a/test/detailed-results-links.results.json +++ b/test/detailed-results-links.results.json @@ -271,5 +271,125 @@ "deleteCount": 25, "insertText": "" } + }, + { + "lineNumber": 32, + "ruleNames": [ + "MD039", + "no-space-in-links" + ], + "ruleDescription": "Spaces inside link text", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039", + "errorDetail": null, + "errorContext": "[link ]", + "errorRange": [ + 6, + 7 + ], + "fixInfo": { + "editColumn": 7, + "deleteCount": 5, + "insertText": "link" + } + }, + { + "lineNumber": 34, + "ruleNames": [ + "MD039", + "no-space-in-links" + ], + "ruleDescription": "Spaces inside link text", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039", + "errorDetail": null, + "errorContext": "[ link]", + "errorRange": [ + 6, + 7 + ], + "fixInfo": { + "editColumn": 7, + "deleteCount": 5, + "insertText": "link" + } + }, + { + "lineNumber": 36, + "ruleNames": [ + "MD039", + "no-space-in-links" + ], + "ruleDescription": "Spaces inside link text", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039", + "errorDetail": null, + "errorContext": "[ link ]", + "errorRange": [ + 6, + 8 + ], + "fixInfo": { + "editColumn": 7, + "deleteCount": 6, + "insertText": "link" + } + }, + { + "lineNumber": 38, + "ruleNames": [ + "MD039", + "no-space-in-links" + ], + "ruleDescription": "Spaces inside link text", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039", + "errorDetail": null, + "errorContext": "[link ]", + "errorRange": [ + 6, + 7 + ], + "fixInfo": { + "editColumn": 7, + "deleteCount": 5, + "insertText": "link" + } + }, + { + "lineNumber": 40, + "ruleNames": [ + "MD039", + "no-space-in-links" + ], + "ruleDescription": "Spaces inside link text", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039", + "errorDetail": null, + "errorContext": "[ link]", + "errorRange": [ + 6, + 7 + ], + "fixInfo": { + "editColumn": 7, + "deleteCount": 5, + "insertText": "link" + } + }, + { + "lineNumber": 42, + "ruleNames": [ + "MD039", + "no-space-in-links" + ], + "ruleDescription": "Spaces inside link text", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md039", + "errorDetail": null, + "errorContext": "[ link ]", + "errorRange": [ + 6, + 8 + ], + "fixInfo": { + "editColumn": 7, + "deleteCount": 6, + "insertText": "link" + } } ] \ No newline at end of file diff --git a/test/spaces_inside_link_text.md b/test/spaces_inside_link_text.md index 8e6baa15..84176ff4 100644 --- a/test/spaces_inside_link_text.md +++ b/test/spaces_inside_link_text.md @@ -50,3 +50,15 @@ Wrapped [ link with leading space ](https://example.com) {MD039} Non-wrapped [ link with leading space](https://example.com) {MD039} + +[][ref] + +[link][ref] + +[link ][ref] {MD039} + +[ link][ref] {MD039} + +[ link ][ref] {MD039} + +[ref]: https://example.com From 5253669495e7e57aebdccd8822c8c2e311c56f1f Mon Sep 17 00:00:00 2001 From: Adithya Balaji Date: Sun, 19 Dec 2021 00:41:08 -0500 Subject: [PATCH 61/69] Fix array indexing for markdownlint-disable-next-line when front matter is present. --- demo/markdownlint-browser.js | 5 +++-- lib/markdownlint.js | 5 +++-- test/front-matter-with-disable-next-line.md | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 test/front-matter-with-disable-next-line.md diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 185f94ef..70104fb3 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -1305,8 +1305,9 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin // eslint-disable-next-line jsdoc/require-jsdoc function disableNextLine(action, parameter, lineNumber) { if (action === "DISABLE-NEXT-LINE") { - enabledRulesPerLineNumber[lineNumber + 1] = - applyEnableDisable(action, parameter, enabledRulesPerLineNumber[lineNumber + 1] || {}); + var nextLineNumber = frontMatterLines.length + lineNumber + 1; + enabledRulesPerLineNumber[nextLineNumber] = + applyEnableDisable(action, parameter, enabledRulesPerLineNumber[nextLineNumber] || {}); } } // Handle inline comments diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 8cb90033..46396162 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -403,11 +403,12 @@ function getEnabledRulesPerLineNumber( // eslint-disable-next-line jsdoc/require-jsdoc function disableNextLine(action, parameter, lineNumber) { if (action === "DISABLE-NEXT-LINE") { - enabledRulesPerLineNumber[lineNumber + 1] = + const nextLineNumber = frontMatterLines.length + lineNumber + 1; + enabledRulesPerLineNumber[nextLineNumber] = applyEnableDisable( action, parameter, - enabledRulesPerLineNumber[lineNumber + 1] || {} + enabledRulesPerLineNumber[nextLineNumber] || {} ); } } diff --git a/test/front-matter-with-disable-next-line.md b/test/front-matter-with-disable-next-line.md new file mode 100644 index 00000000..74953fdd --- /dev/null +++ b/test/front-matter-with-disable-next-line.md @@ -0,0 +1,20 @@ +--- +front: matter +--- + +# Front Matter with Disable-Next-Line + + +
+ +
{MD033} + + +
+ +
{MD033} +
{MD033} + +
+
{MD033} +
{MD033} From 5f0040679d2f18afa977b64f16b0a50302eede48 Mon Sep 17 00:00:00 2001 From: David Anson Date: Thu, 23 Dec 2021 04:34:25 +0000 Subject: [PATCH 62/69] Deep freeze name/tokens/lines/frontMatterLines properties of params object before passing to (custom) rules for shared access. --- demo/markdownlint-browser.js | 40 ++++++++++++++++++++++++------- helpers/helpers.js | 27 +++++++++++++++++---- lib/markdownlint.js | 13 ++++++---- test/markdownlint-test-helpers.js | 34 ++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 16 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 70104fb3..1fa5fede 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -276,6 +276,7 @@ function isMathBlock(token) { token.type.startsWith("math_block") && !token.type.endsWith("_end")); } +module.exports.isMathBlock = isMathBlock; // Get line metadata array module.exports.getLineMetadata = function getLineMetadata(params) { var lineMetadata = params.lines.map(function (line, index) { return [line, index, false, 0, false, false, false, false]; }); @@ -336,10 +337,6 @@ module.exports.flattenLists = function flattenLists(tokens) { var nestingStack = []; var lastWithMap = { "map": [0, 1] }; tokens.forEach(function (token) { - if (isMathBlock(token) && token.map[1]) { - // markdown-it-texmath plugin does not account for math_block_end - token.map[1]++; - } if ((token.type === "bullet_list_open") || (token.type === "ordered_list_open")) { // Save current context and start a new one @@ -840,6 +837,28 @@ function getNextChildToken(parentToken, childToken, nextType, nextNextType) { return null; } module.exports.getNextChildToken = getNextChildToken; +/** + * Calls Object.freeze() on an object and its children. + * + * @param {Object} obj Object to deep freeze. + * @returns {Object} Object passed to the function. + */ +function deepFreeze(obj) { + var pending = [obj]; + var current = null; + while ((current = pending.shift())) { + Object.freeze(current); + for (var _i = 0, _a = Object.getOwnPropertyNames(current); _i < _a.length; _i++) { + var name = _a[_i]; + var value = current[name]; + if (value && (typeof value === "object")) { + pending.push(value); + } + } + } + return obj; +} +module.exports.deepFreeze = deepFreeze; /***/ }), @@ -1120,6 +1139,11 @@ function annotateTokens(tokens, lines) { if (!token.map && trMap) { token.map = __spreadArray([], trMap, true); } + // Adjust maps for math blocks + if (helpers.isMathBlock(token) && token.map[1]) { + // markdown-it-texmath plugin does not account for math_block_end + token.map[1]++; + } // Update token metadata if (token.map) { token.line = lines[token.map[0]]; @@ -1359,10 +1383,10 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul var _a = getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlineConfig, config, aliasToRuleNames), effectiveConfig = _a.effectiveConfig, enabledRulesPerLineNumber = _a.enabledRulesPerLineNumber; // Create parameters for rules var params = { - name: name, - tokens: tokens, - lines: lines, - frontMatterLines: frontMatterLines + "name": helpers.deepFreeze(name), + "tokens": helpers.deepFreeze(tokens), + "lines": helpers.deepFreeze(lines), + "frontMatterLines": helpers.deepFreeze(frontMatterLines) }; cache.lineMetadata(helpers.getLineMetadata(params)); cache.flattenedLists(helpers.flattenLists(params.tokens)); diff --git a/helpers/helpers.js b/helpers/helpers.js index 6a865ac4..106a7af2 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -276,6 +276,7 @@ function isMathBlock(token) { !token.type.endsWith("_end") ); } +module.exports.isMathBlock = isMathBlock; // Get line metadata array module.exports.getLineMetadata = function getLineMetadata(params) { @@ -341,10 +342,6 @@ module.exports.flattenLists = function flattenLists(tokens) { const nestingStack = []; let lastWithMap = { "map": [ 0, 1 ] }; tokens.forEach((token) => { - if (isMathBlock(token) && token.map[1]) { - // markdown-it-texmath plugin does not account for math_block_end - token.map[1]++; - } if ((token.type === "bullet_list_open") || (token.type === "ordered_list_open")) { // Save current context and start a new one @@ -876,3 +873,25 @@ function getNextChildToken(parentToken, childToken, nextType, nextNextType) { return null; } module.exports.getNextChildToken = getNextChildToken; + +/** + * Calls Object.freeze() on an object and its children. + * + * @param {Object} obj Object to deep freeze. + * @returns {Object} Object passed to the function. + */ +function deepFreeze(obj) { + const pending = [ obj ]; + let current = null; + while ((current = pending.shift())) { + Object.freeze(current); + for (const name of Object.getOwnPropertyNames(current)) { + const value = current[name]; + if (value && (typeof value === "object")) { + pending.push(value); + } + } + } + return obj; +} +module.exports.deepFreeze = deepFreeze; diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 46396162..eba87c0e 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -206,6 +206,11 @@ function annotateTokens(tokens, lines) { if (!token.map && trMap) { token.map = [ ...trMap ]; } + // Adjust maps for math blocks + if (helpers.isMathBlock(token) && token.map[1]) { + // markdown-it-texmath plugin does not account for math_block_end + token.map[1]++; + } // Update token metadata if (token.map) { token.line = lines[token.map[0]]; @@ -481,10 +486,10 @@ function lintContent( ); // Create parameters for rules const params = { - name, - tokens, - lines, - frontMatterLines + "name": helpers.deepFreeze(name), + "tokens": helpers.deepFreeze(tokens), + "lines": helpers.deepFreeze(lines), + "frontMatterLines": helpers.deepFreeze(frontMatterLines) }; cache.lineMetadata(helpers.getLineMetadata(params)); cache.flattenedLists(helpers.flattenLists(params.tokens)); diff --git a/test/markdownlint-test-helpers.js b/test/markdownlint-test-helpers.js index ea752ba0..92cffec7 100644 --- a/test/markdownlint-test-helpers.js +++ b/test/markdownlint-test-helpers.js @@ -938,3 +938,37 @@ test("applyFixes", (t) => { t.is(actual, expected, "Incorrect fix applied."); }); }); + +test("deepFreeze", (t) => { + t.plan(6); + const obj = { + "prop": true, + "func": () => true, + "sub": { + "prop": [ 1 ], + "sub": { + "prop": "one" + } + } + }; + t.is(helpers.deepFreeze(obj), obj, "Did not return object."); + [ + () => { + obj.prop = false; + }, + () => { + obj.func = () => false; + }, + () => { + obj.sub.prop = []; + }, + () => { + obj.sub.prop[0] = 0; + }, + () => { + obj.sub.sub.prop = "zero"; + } + ].forEach((scenario) => { + t.throws(scenario, null, "Assigned to frozen object."); + }); +}); From 9ec14f13a15007512a21695ac86ce1b39aa73d27 Mon Sep 17 00:00:00 2001 From: David Anson Date: Thu, 23 Dec 2021 20:52:17 +0000 Subject: [PATCH 63/69] Include custom rule markdownlint-rule-github-internal-links when validating project Markdown files. --- README.md | 2 +- package.json | 1 + test/markdownlint-test.js | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 78d0aea5..94cc9b58 100644 --- a/README.md +++ b/README.md @@ -865,7 +865,7 @@ const results = window.markdownlint.sync(options).toString(); ## Examples For ideas how to integrate `markdownlint` into your workflow, refer to the -following projects or one of the tools in the [Related section](#Related): +following projects or one of the tools in the [Related section](#related): * [.NET Documentation](https://docs.microsoft.com/en-us/dotnet/) ([Search repository](https://github.com/dotnet/docs/search?q=markdownlint)) * [ally.js](https://allyjs.io/) ([Search repository](https://github.com/medialize/ally.js/search?q=markdownlint)) diff --git a/package.json b/package.json index 7b8e894e..8a9fbf42 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "markdown-it-sub": "~1.0.0", "markdown-it-sup": "~1.0.0", "markdown-it-texmath": "~0.9.7", + "markdownlint-rule-github-internal-links": "~0.1.0", "markdownlint-rule-helpers": "~0.15.0", "npm-run-all": "~4.1.5", "strip-json-comments": "~3.1.1", diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index f49fe81f..21bc0182 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -84,11 +84,12 @@ test.cb("projectFilesNoInlineConfig", (t) => { "doc/Prettier.md", "helpers/README.md" ], - "noInlineConfig": true, "config": { "line-length": { "line_length": 150 }, "no-duplicate-heading": false - } + }, + "customRules": [ require("markdownlint-rule-github-internal-links") ], + "noInlineConfig": true }; markdownlint(options, function callback(err, actual) { t.falsy(err); From fd24b9552be8ca6eb543cf3f74fdccb44c9959cb Mon Sep 17 00:00:00 2001 From: David Anson Date: Mon, 27 Dec 2021 03:41:43 +0000 Subject: [PATCH 64/69] Remove require("os") from helpers to reduce dependencies for browser scenarios. --- demo/markdownlint-browser.js | 19 +++++-------------- demo/webpack.config.js | 1 - helpers/helpers.js | 8 ++++---- test/markdownlint-test-helpers.js | 12 +++++++++++- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 1fa5fede..403f721b 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -25,12 +25,11 @@ module.exports = webpackEmptyContext; /*!*****************************!*\ !*** ../helpers/helpers.js ***! \*****************************/ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { +/***/ ((module) => { "use strict"; // @ts-check -var os = __webpack_require__(/*! os */ "?591e"); // Regular expression for matching common newline characters // See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js var newLineRe = /\r\n?|\n/g; @@ -655,9 +654,10 @@ module.exports.emphasisMarkersInContent = emphasisMarkersInContent; * Gets the most common line ending, falling back to the platform default. * * @param {string} input Markdown content to analyze. + * @param {string} [platform] Platform identifier (process.platform). * @returns {string} Preferred line ending. */ -function getPreferredLineEnding(input) { +function getPreferredLineEnding(input, platform) { var cr = 0; var lf = 0; var crlf = 0; @@ -678,7 +678,8 @@ function getPreferredLineEnding(input) { }); var preferredLineEnding = null; if (!cr && !lf && !crlf) { - preferredLineEnding = os.EOL; + preferredLineEnding = + ((platform || process.platform) === "win32") ? "\r\n" : "\n"; } else if ((lf >= crlf) && (lf >= cr)) { preferredLineEnding = "\n"; @@ -4343,16 +4344,6 @@ module.exports = markdownit; /***/ }), -/***/ "?591e": -/*!********************!*\ - !*** os (ignored) ***! - \********************/ -/***/ (() => { - -/* (ignored) */ - -/***/ }), - /***/ "?ec0a": /*!********************!*\ !*** fs (ignored) ***! diff --git a/demo/webpack.config.js b/demo/webpack.config.js index 3de0a334..73a2b173 100644 --- a/demo/webpack.config.js +++ b/demo/webpack.config.js @@ -44,7 +44,6 @@ function config(options) { "resolve": { "fallback": { "fs": false, - "os": false, "path": false, "util": false } diff --git a/helpers/helpers.js b/helpers/helpers.js index 106a7af2..9db231f8 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -2,8 +2,6 @@ "use strict"; -const os = require("os"); - // Regular expression for matching common newline characters // See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js const newLineRe = /\r\n?|\n/g; @@ -680,9 +678,10 @@ module.exports.emphasisMarkersInContent = emphasisMarkersInContent; * Gets the most common line ending, falling back to the platform default. * * @param {string} input Markdown content to analyze. + * @param {string} [platform] Platform identifier (process.platform). * @returns {string} Preferred line ending. */ -function getPreferredLineEnding(input) { +function getPreferredLineEnding(input, platform) { let cr = 0; let lf = 0; let crlf = 0; @@ -703,7 +702,8 @@ function getPreferredLineEnding(input) { }); let preferredLineEnding = null; if (!cr && !lf && !crlf) { - preferredLineEnding = os.EOL; + preferredLineEnding = + ((platform || process.platform) === "win32") ? "\r\n" : "\n"; } else if ((lf >= crlf) && (lf >= cr)) { preferredLineEnding = "\n"; } else if (crlf >= cr) { diff --git a/test/markdownlint-test-helpers.js b/test/markdownlint-test-helpers.js index 92cffec7..05d4cf3a 100644 --- a/test/markdownlint-test-helpers.js +++ b/test/markdownlint-test-helpers.js @@ -387,7 +387,7 @@ test("forEachInlineCodeSpan", (t) => { }); test("getPreferredLineEnding", (t) => { - t.plan(17); + t.plan(19); const testCases = [ [ "", os.EOL ], [ "\r", "\r" ], @@ -412,6 +412,16 @@ test("getPreferredLineEnding", (t) => { const actual = helpers.getPreferredLineEnding(input); t.is(actual, expected, "Incorrect line ending returned."); }); + t.is( + helpers.getPreferredLineEnding("", "linux"), + "\n", + "Incorrect line ending for linux" + ); + t.is( + helpers.getPreferredLineEnding("", "win32"), + "\r\n", + "Incorrect line ending for win32" + ); }); test("applyFix", (t) => { From 528758e96245260444828c02f72ace348b5bde28 Mon Sep 17 00:00:00 2001 From: David Anson Date: Mon, 27 Dec 2021 04:37:51 +0000 Subject: [PATCH 65/69] Update dependencies: eslint to 8.5.0, eslint-plugin-jsdoc to 37.4.0. --- .eslintrc.json | 2 +- package.json | 4 ++-- test/rules/lint-javascript.js | 28 ++++++++++++++++------------ 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index d76f8131..12df41a7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -105,7 +105,7 @@ "jsdoc/check-access": "error", "jsdoc/check-alignment": "error", - "jsdoc/check-examples": "error", + "jsdoc/check-examples": "off", "jsdoc/check-indentation": "error", "jsdoc/check-line-alignment": "error", "jsdoc/check-param-names": "error", diff --git a/package.json b/package.json index 8a9fbf42..b151e132 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,8 @@ "devDependencies": { "ava": "~3.15.0", "c8": "~7.10.0", - "eslint": "~7.32.0", - "eslint-plugin-jsdoc": "~37.2.8", + "eslint": "~8.5.0", + "eslint-plugin-jsdoc": "~37.4.0", "eslint-plugin-node": "~11.1.0", "eslint-plugin-unicorn": "~39.0.0", "globby": "~11.0.4", diff --git a/test/rules/lint-javascript.js b/test/rules/lint-javascript.js index 441e1a6d..0c8f4d1f 100644 --- a/test/rules/lint-javascript.js +++ b/test/rules/lint-javascript.js @@ -4,7 +4,7 @@ const { filterTokens } = require("markdownlint-rule-helpers"); const eslint = require("eslint"); -const cliEngine = new eslint.CLIEngine({}); +const eslintInstance = new eslint.ESLint(); const linter = new eslint.Linter(); const languageJavaScript = /js|javascript/i; @@ -28,23 +28,27 @@ module.exports = { "names": [ "lint-javascript" ], "description": "Rule that lints JavaScript code", "tags": [ "test", "lint", "javascript" ], + "asynchronous": true, "function": (params, onError) => { filterTokens(params, "fence", (fence) => { if (languageJavaScript.test(fence.info)) { - let config = cliEngine.getConfigForFile(params.name); - config = cleanJsdocRulesFromEslintConfig(config); - const results = linter.verify(fence.content, config); - results.forEach((result) => { - const lineNumber = fence.lineNumber + result.line; - onError({ - "lineNumber": lineNumber, - "detail": result.message, - "context": params.lines[lineNumber - 1] + return eslintInstance.calculateConfigForFile(params.name) + .then((config) => { + config = cleanJsdocRulesFromEslintConfig(config); + const results = linter.verify(fence.content, config); + results.forEach((result) => { + const lineNumber = fence.lineNumber + result.line; + onError({ + "lineNumber": lineNumber, + "detail": result.message, + "context": params.lines[lineNumber - 1] + }); + }); }); - }); } + return Promise.resolve(); }); - // Unused: + // Unsupported: // filterTokens("code_block"), language unknown // filterTokens("code_inline"), too brief } From 05b9e6e43c8924beb932bebb8d93d657dd49a306 Mon Sep 17 00:00:00 2001 From: David Anson Date: Mon, 27 Dec 2021 21:59:56 +0000 Subject: [PATCH 66/69] Update dependency: strip-json-comments to 4.0.0. --- .eslintrc.json | 2 +- package.json | 2 +- test/markdownlint-test-repos.js | 27 +++++---------------------- test/markdownlint-test.js | 7 +++---- 4 files changed, 10 insertions(+), 28 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 12df41a7..765f6bfe 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -48,7 +48,7 @@ } ], "parserOptions": { - "ecmaVersion": 2019 + "ecmaVersion": 2020 }, "plugins": [ "jsdoc", diff --git a/package.json b/package.json index b151e132..210c5e6a 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "markdownlint-rule-github-internal-links": "~0.1.0", "markdownlint-rule-helpers": "~0.15.0", "npm-run-all": "~4.1.5", - "strip-json-comments": "~3.1.1", + "strip-json-comments": "~4.0.0", "toml": "~3.0.0", "ts-loader": "~9.2.6", "tv4": "~1.3.0", diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index bf96e7e8..1363792b 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -8,32 +8,11 @@ const { join } = require("path"); const { promisify } = require("util"); const globby = require("globby"); const jsYaml = require("js-yaml"); -const stripJsonComments = require("strip-json-comments"); const test = require("ava").default; const markdownlint = require("../lib/markdownlint"); const markdownlintPromise = promisify(markdownlint); const readConfigPromise = promisify(markdownlint.readConfig); -/** - * Parses JSONC text. - * - * @param {string} json JSON to parse. - * @returns {Object} Object representation. - */ -function jsoncParse(json) { - return JSON.parse(stripJsonComments(json)); -} - -/** - * Parses YAML text. - * - * @param {string} yaml YAML to parse. - * @returns {Object} Object representation. - */ -function yamlParse(yaml) { - return jsYaml.load(yaml); -} - /** * Lints a test repository. * @@ -43,8 +22,12 @@ function yamlParse(yaml) { * @param {RegExp[]} [ignoreRes] Array of RegExp violations to ignore. * @returns {Promise} Test result. */ -function lintTestRepo(t, globPatterns, configPath, ignoreRes) { +async function lintTestRepo(t, globPatterns, configPath, ignoreRes) { t.plan(1); + // eslint-disable-next-line node/no-unsupported-features/es-syntax + const { "default": stripJsonComments } = await import("strip-json-comments"); + const jsoncParse = (json) => JSON.parse(stripJsonComments(json)); + const yamlParse = (yaml) => jsYaml.load(yaml); return Promise.all([ globby(globPatterns), // @ts-ignore diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index 21bc0182..45227323 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -10,7 +10,6 @@ const pluginInline = require("markdown-it-for-inline"); const pluginSub = require("markdown-it-sub"); const pluginSup = require("markdown-it-sup"); const pluginTexMath = require("markdown-it-texmath"); -const stripJsonComments = require("strip-json-comments"); const test = require("ava").default; const tv4 = require("tv4"); const { homepage, version } = require("../package.json"); @@ -1070,9 +1069,10 @@ test("validateConfigSchemaAppliesToUnknownProperties", (t) => { } }); -test("validateConfigExampleJson", (t) => { +test("validateConfigExampleJson", async(t) => { t.plan(2); - + // eslint-disable-next-line node/no-unsupported-features/es-syntax + const { "default": stripJsonComments } = await import("strip-json-comments"); // Validate JSONC const fileJson = ".markdownlint.jsonc"; const dataJson = fs.readFileSync( @@ -1084,7 +1084,6 @@ test("validateConfigExampleJson", (t) => { // @ts-ignore tv4.validate(jsonObject, configSchemaStrict), fileJson + "\n" + JSON.stringify(tv4.error, null, 2)); - // Validate YAML const fileYaml = ".markdownlint.yaml"; const dataYaml = fs.readFileSync( From 11e9a205315f0c7ebaff1db6db4760c757f99c19 Mon Sep 17 00:00:00 2001 From: David Anson Date: Mon, 27 Dec 2021 22:40:44 +0000 Subject: [PATCH 67/69] Update dependency: globby to 12.0.2. --- package.json | 2 +- scripts/index.js | 33 ++++++++++++++++----------- test/markdownlint-test-extra-parse.js | 15 +++++------- test/markdownlint-test-repos.js | 3 ++- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 210c5e6a..b5c36045 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "eslint-plugin-jsdoc": "~37.4.0", "eslint-plugin-node": "~11.1.0", "eslint-plugin-unicorn": "~39.0.0", - "globby": "~11.0.4", + "globby": "~12.0.2", "js-yaml": "~4.1.0", "markdown-it-for-inline": "~0.1.1", "markdown-it-sub": "~1.0.0", diff --git a/scripts/index.js b/scripts/index.js index 0f31207b..de3f9141 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -2,20 +2,27 @@ "use strict"; -const fs = require("fs"); -const globby = require("globby"); +const fs = require("fs").promises; const [ command, ...args ] = process.argv.slice(2); -if (command === "copy") { - const [ src, dest ] = args; - fs.copyFileSync(src, dest); -} else if (command === "delete") { - for (const arg of args) { - for (const file of globby.sync(arg)) { - fs.unlinkSync(file); - } +// eslint-disable-next-line unicorn/prefer-top-level-await +(async() => { + if (command === "copy") { + const [ src, dest ] = args; + await fs.copyFile(src, dest); + } else if (command === "delete") { + // eslint-disable-next-line node/no-unsupported-features/es-syntax + const { globby } = await import("globby"); + await Promise.all( + args.flatMap( + (glob) => globby(glob) + .then( + (files) => files.map((file) => fs.unlink(file)) + ) + ) + ); + } else { + throw new Error(`Unsupported command: ${command}`); } -} else { - throw new Error(`Unsupported command: ${command}`); -} +})(); diff --git a/test/markdownlint-test-extra-parse.js b/test/markdownlint-test-extra-parse.js index 9f88e10f..7c96500d 100644 --- a/test/markdownlint-test-extra-parse.js +++ b/test/markdownlint-test-extra-parse.js @@ -2,18 +2,15 @@ "use strict"; -const globby = require("globby"); const test = require("ava").default; const markdownlint = require("../lib/markdownlint"); // Parses all Markdown files in all package dependencies -test.cb("parseAllFiles", (t) => { +test("parseAllFiles", async(t) => { t.plan(1); - const options = { - "files": globby.sync("**/*.{md,markdown}") - }; - markdownlint(options, (err) => { - t.falsy(err); - t.end(); - }); + // eslint-disable-next-line node/no-unsupported-features/es-syntax + const { globby } = await import("globby"); + const files = await globby("**/*.{md,markdown}"); + await markdownlint.promises.markdownlint({ files }); + t.pass(); }); diff --git a/test/markdownlint-test-repos.js b/test/markdownlint-test-repos.js index 1363792b..aefd9889 100644 --- a/test/markdownlint-test-repos.js +++ b/test/markdownlint-test-repos.js @@ -6,7 +6,6 @@ const { existsSync } = require("fs"); // eslint-disable-next-line unicorn/import-style const { join } = require("path"); const { promisify } = require("util"); -const globby = require("globby"); const jsYaml = require("js-yaml"); const test = require("ava").default; const markdownlint = require("../lib/markdownlint"); @@ -25,6 +24,8 @@ const readConfigPromise = promisify(markdownlint.readConfig); async function lintTestRepo(t, globPatterns, configPath, ignoreRes) { t.plan(1); // eslint-disable-next-line node/no-unsupported-features/es-syntax + const { globby } = await import("globby"); + // eslint-disable-next-line node/no-unsupported-features/es-syntax const { "default": stripJsonComments } = await import("strip-json-comments"); const jsoncParse = (json) => JSON.parse(stripJsonComments(json)); const yamlParse = (yaml) => jsYaml.load(yaml); From e298e3daa4208c7f78a1e30f69a97ed049a95a85 Mon Sep 17 00:00:00 2001 From: David Anson Date: Mon, 27 Dec 2021 18:25:18 -0800 Subject: [PATCH 68/69] Include async/await function in custom rules test for asynchronous mode. --- test/markdownlint-test-custom-rules.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/test/markdownlint-test-custom-rules.js b/test/markdownlint-test-custom-rules.js index 76c1d344..59e68a18 100644 --- a/test/markdownlint-test-custom-rules.js +++ b/test/markdownlint-test-custom-rules.js @@ -1198,16 +1198,15 @@ test("customRulesAsyncReadFiles", (t) => { "tags": [ "tag" ], "asynchronous": true, "function": - (params, onError) => fs.readFile(__filename, "utf8").then( - (content) => { - t.true(content.length > 0); - onError({ - "lineNumber": 1, - "detail": "detail2", - "context": "context2" - }); - } - ) + async(params, onError) => { + const content = await fs.readFile(__filename, "utf8"); + t.true(content.length > 0); + onError({ + "lineNumber": 1, + "detail": "detail2", + "context": "context2" + }); + } } ], "strings": { From 4ff4cbcc41d4c0be0237666e530bc848fc57ef87 Mon Sep 17 00:00:00 2001 From: David Anson Date: Mon, 27 Dec 2021 18:43:25 -0800 Subject: [PATCH 69/69] Update to version 0.25.0. --- README.md | 3 +++ demo/markdownlint-browser.js | 4 ++-- helpers/package.json | 2 +- lib/constants.js | 2 +- package.json | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 94cc9b58..ceae9997 100644 --- a/README.md +++ b/README.md @@ -975,6 +975,9 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for more information. * 0.24.0 - Remove support for end-of-life Node version 10, add support for custom file system module, improve MD010/MD011/MD037/MD043/MD044, improve TypeScript declaration file and JSON schema, update dependencies. +* 0.25.0 - Add MD049/MD050 for consistent emphasis/strong style (both auto-fixable), improve + MD007/MD010/MD032/MD033/MD035/MD037/MD039, support asynchronous custom rules, + improve performance, improve CI process, reduce dependencies, update dependencies. [npm-image]: https://img.shields.io/npm/v/markdownlint.svg [npm-url]: https://www.npmjs.com/package/markdownlint diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 403f721b..896a5608 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -1,4 +1,4 @@ -/*! markdownlint 0.24.0 https://github.com/DavidAnson/markdownlint @license MIT */ +/*! markdownlint 0.25.0 https://github.com/DavidAnson/markdownlint @license MIT */ var markdownlint; /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ @@ -914,7 +914,7 @@ module.exports.clear = function () { module.exports.deprecatedRuleNames = ["MD002", "MD006"]; module.exports.homepage = "https://github.com/DavidAnson/markdownlint"; -module.exports.version = "0.24.0"; +module.exports.version = "0.25.0"; /***/ }), diff --git a/helpers/package.json b/helpers/package.json index 5b25454e..22e67a6a 100644 --- a/helpers/package.json +++ b/helpers/package.json @@ -1,6 +1,6 @@ { "name": "markdownlint-rule-helpers", - "version": "0.15.0", + "version": "0.16.0", "description": "A collection of markdownlint helper functions for custom rules", "main": "helpers.js", "author": "David Anson (https://dlaa.me/)", diff --git a/lib/constants.js b/lib/constants.js index e9f5a5ee..4b24b2a2 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -4,4 +4,4 @@ module.exports.deprecatedRuleNames = [ "MD002", "MD006" ]; module.exports.homepage = "https://github.com/DavidAnson/markdownlint"; -module.exports.version = "0.24.0"; +module.exports.version = "0.25.0"; diff --git a/package.json b/package.json index b5c36045..a88e4545 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "markdownlint", - "version": "0.24.0", + "version": "0.25.0", "description": "A Node.js style checker and lint tool for Markdown/CommonMark files.", "main": "lib/markdownlint.js", "types": "lib/markdownlint.d.ts",