From b447c809bdde803710135090b876e134939de85c Mon Sep 17 00:00:00 2001 From: David Anson Date: Thu, 28 Apr 2022 21:09:06 -0700 Subject: [PATCH] Update MD010/no-hard-tabs to add ignore_code_languages parameter (fixes #383). --- demo/markdownlint-browser.js | 12 +++++- doc/Rules.md | 8 +++- lib/md010.js | 15 ++++++- schema/.markdownlint.jsonc | 2 + schema/.markdownlint.yaml | 2 + schema/build-config-schema.js | 8 ++++ schema/markdownlint-config-schema.json | 8 ++++ test/code-block-with-language-allowed.json | 10 +++++ test/code-block-with-language-allowed.md | 50 ++++++++++++++++++++++ 9 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 test/code-block-with-language-allowed.json create mode 100644 test/code-block-with-language-allowed.md diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 9f75fecd..7798ed6b 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -2550,7 +2550,7 @@ module.exports = { "use strict"; // @ts-check -var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, forEachLine = _a.forEachLine, overlapsAnyRange = _a.overlapsAnyRange; +var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, filterTokens = _a.filterTokens, 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 = { @@ -2560,11 +2560,21 @@ module.exports = { "function": function MD010(params, onError) { var codeBlocks = params.config.code_blocks; var includeCode = (codeBlocks === undefined) ? true : !!codeBlocks; + var ignoreCodeLanguages = new Set((params.config.ignore_code_languages || []) + .map(function (language) { return language.toLowerCase(); })); var spacesPerTab = params.config.spaces_per_tab; var spaceMultiplier = (spacesPerTab === undefined) ? 1 : Math.max(0, Number(spacesPerTab)); var exclusions = includeCode ? [] : codeBlockAndSpanRanges(); + filterTokens(params, "fence", function (token) { + var language = token.info.trim().toLowerCase(); + if (ignoreCodeLanguages.has(language)) { + for (var i = token.map[0] + 1; i < token.map[1] - 1; i++) { + exclusions.push([i, 0, params.lines[i].length]); + } + } + }); forEachLine(lineMetadata(), function (line, lineIndex, inCode) { if (includeCode || !inCode) { var match = null; diff --git a/doc/Rules.md b/doc/Rules.md index 5f6f3429..75571ec3 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -396,7 +396,7 @@ Tags: whitespace, hard_tab Aliases: no-hard-tabs -Parameters: code_blocks, spaces_per_tab (boolean; default true, number; default 1) +Parameters: code_blocks, ignore_code_languages, spaces_per_tab (boolean; default true, array of string; default empty, number; default 1) Fixable: Most violations can be fixed by tooling @@ -429,6 +429,12 @@ 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). +When code blocks are scanned (e.g., by default or if `code_blocks` is `true`), +the `ignore_code_languages` parameter can be set to a list of languages that +should be ignored (i.e., hard tabs will be allowed, though not required). This +makes it easier for documents to include code for languages that require hard +tabs. + 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. diff --git a/lib/md010.js b/lib/md010.js index 2a359313..26f82764 100644 --- a/lib/md010.js +++ b/lib/md010.js @@ -2,7 +2,8 @@ "use strict"; -const { addError, forEachLine, overlapsAnyRange } = require("../helpers"); +const { addError, filterTokens, forEachLine, overlapsAnyRange } = + require("../helpers"); const { codeBlockAndSpanRanges, lineMetadata } = require("./cache"); const tabRe = /\t+/g; @@ -14,11 +15,23 @@ module.exports = { "function": function MD010(params, onError) { const codeBlocks = params.config.code_blocks; const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks; + const ignoreCodeLanguages = new Set( + (params.config.ignore_code_languages || []) + .map((language) => language.toLowerCase()) + ); const spacesPerTab = params.config.spaces_per_tab; const spaceMultiplier = (spacesPerTab === undefined) ? 1 : Math.max(0, Number(spacesPerTab)); const exclusions = includeCode ? [] : codeBlockAndSpanRanges(); + filterTokens(params, "fence", (token) => { + const language = token.info.trim().toLowerCase(); + if (ignoreCodeLanguages.has(language)) { + for (let i = token.map[0] + 1; i < token.map[1] - 1; i++) { + exclusions.push([ i, 0, params.lines[i].length ]); + } + } + }); forEachLine(lineMetadata(), (line, lineIndex, inCode) => { if (includeCode || !inCode) { let match = null; diff --git a/schema/.markdownlint.jsonc b/schema/.markdownlint.jsonc index c0021a4b..c91ec09b 100644 --- a/schema/.markdownlint.jsonc +++ b/schema/.markdownlint.jsonc @@ -58,6 +58,8 @@ "MD010": { // Include code blocks "code_blocks": true, + // Fenced code languages to ignore + "ignore_code_languages": [], // Number of spaces for each hard tab "spaces_per_tab": 1 }, diff --git a/schema/.markdownlint.yaml b/schema/.markdownlint.yaml index 07d6a838..7d1b2ce4 100644 --- a/schema/.markdownlint.yaml +++ b/schema/.markdownlint.yaml @@ -52,6 +52,8 @@ MD009: MD010: # Include code blocks code_blocks: true + # Fenced code languages to ignore + ignore_code_languages: [] # Number of spaces for each hard tab spaces_per_tab: 1 diff --git a/schema/build-config-schema.js b/schema/build-config-schema.js index 510980aa..fea4ab4c 100644 --- a/schema/build-config-schema.js +++ b/schema/build-config-schema.js @@ -147,6 +147,14 @@ rules.forEach(function forRule(rule) { "type": "boolean", "default": true }, + "ignore_code_languages": { + "description": "Fenced code languages to ignore", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, "spaces_per_tab": { "description": "Number of spaces for each hard tab", "type": "integer", diff --git a/schema/markdownlint-config-schema.json b/schema/markdownlint-config-schema.json index f9a21756..bd48a2fc 100644 --- a/schema/markdownlint-config-schema.json +++ b/schema/markdownlint-config-schema.json @@ -201,6 +201,14 @@ "type": "boolean", "default": true }, + "ignore_code_languages": { + "description": "Fenced code languages to ignore", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, "spaces_per_tab": { "description": "Number of spaces for each hard tab", "type": "integer", diff --git a/test/code-block-with-language-allowed.json b/test/code-block-with-language-allowed.json new file mode 100644 index 00000000..78813ce9 --- /dev/null +++ b/test/code-block-with-language-allowed.json @@ -0,0 +1,10 @@ +{ + "default": true, + "MD010": { + "ignore_code_languages": [ + "js", + "text" + ] + }, + "MD046": false +} diff --git a/test/code-block-with-language-allowed.md b/test/code-block-with-language-allowed.md new file mode 100644 index 00000000..52bb950e --- /dev/null +++ b/test/code-block-with-language-allowed.md @@ -0,0 +1,50 @@ +# Heading + +```js +if (true) { + console.log("true"); + if (false) { + console.log("false"); + } +} +``` + +```js +if (true) { + console.log("true"); + if (false) { + console.log("false"); + } +} +``` + + if (true) { + console.log("true"); + if (false) { + console.log("false"); + } + } + +```text + hello + world +} +``` + + if (true) { // {MD010} + console.log("true"); // {MD010} + if (false) { // {MD010} + console.log("false"); // {MD010} + } // {MD010} + } // {MD010} + +Line with hard tab. {MD010} + +```javascript +if (true) { + console.log("true"); // {MD010} + if (false) { // {MD010} + console.log("false"); // {MD010} + } // {MD010} +} +```