From d23d5f739943d136669aac945ef25528f31cd7db Mon Sep 17 00:00:00 2001 From: JounQin Date: Mon, 5 Apr 2021 10:50:44 +0800 Subject: [PATCH] Fix: use blocksCache instead of single blocks instance (fixes #181) (#183) * Fix: use blocksCache instead of single blocks instance pass through lint messages from other plugins * refactor: use Map and delete map after postprocess remove unnecessary mdx example * Chore: add test case for blocksCache * Revert: downgrade remark-parse and unified * chore: remove unnecessary fallback check * refactor: simplify return statement --- examples/typescript/README.md | 2 +- lib/processor.js | 18 ++++++++---- tests/lib/plugin.js | 54 +++++++++++++++++++++++++++++++---- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/examples/typescript/README.md b/examples/typescript/README.md index 031b152..36296c3 100644 --- a/examples/typescript/README.md +++ b/examples/typescript/README.md @@ -1,4 +1,4 @@ -# React Example +# TypeScript Example The `@typescript-eslint` parser and the `recommended` config's rules will work in `ts` code blocks. However, [type-aware rules](https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/TYPED_LINTING.md) will not work because the code blocks are not part of a compilable `tsconfig.json` project. diff --git a/lib/processor.js b/lib/processor.js index 0808594..94cf816 100644 --- a/lib/processor.js +++ b/lib/processor.js @@ -36,9 +36,9 @@ const SUPPORTS_AUTOFIX = true; const markdown = unified().use(remarkParse); /** - * @type {Block[]} + * @type {Map} */ -let blocks = []; +const blocksCache = new Map(); /** * Performs a depth-first traversal of the Markdown AST. @@ -235,10 +235,14 @@ function getBlockRangeMap(text, node, comments) { /** * Extracts lintable code blocks from Markdown text. * @param {string} text The text of the file. + * @param {string} filename The filename of the file * @returns {Array<{ filename: string, text: string }>} Source code blocks to lint. */ -function preprocess(text) { +function preprocess(text, filename) { const ast = markdown.parse(text); + const blocks = []; + + blocksCache.set(filename, blocks); /** * During the depth-first traversal, keep track of any sequences of HTML @@ -250,7 +254,6 @@ function preprocess(text) { */ let htmlComments = []; - blocks = []; traverse(ast, { "*"() { htmlComments = []; @@ -370,9 +373,14 @@ function excludeUnsatisfiableRules(message) { * Transforms generated messages for output. * @param {Array} messages An array containing one array of messages * for each code block returned from `preprocess`. + * @param {string} filename The filename of the file * @returns {Message[]} A flattened array of messages with mapped locations. */ -function postprocess(messages) { +function postprocess(messages, filename) { + const blocks = blocksCache.get(filename); + + blocksCache.delete(filename); + return [].concat(...messages.map((group, i) => { const adjust = adjustBlock(blocks[i]); diff --git a/tests/lib/plugin.js b/tests/lib/plugin.js index 8f0d002..837137d 100644 --- a/tests/lib/plugin.js +++ b/tests/lib/plugin.js @@ -11,20 +11,23 @@ const CLIEngine = require("eslint").CLIEngine; const path = require("path"); const plugin = require("../.."); +/** + * @typedef {import('eslint/lib/cli-engine/cli-engine').CLIEngineOptions} CLIEngineOptions + */ + /** * Helper function which creates CLIEngine instance with enabled/disabled autofix feature. * @param {string} fixtureConfigName ESLint JSON config fixture filename. - * @param {boolean} [isAutofixEnabled=false] Whether to enable autofix feature. + * @param {CLIEngineOptions} [options={}] Whether to enable autofix feature. * @returns {CLIEngine} CLIEngine instance to execute in tests. */ -function initCLI(fixtureConfigName, isAutofixEnabled) { - const fix = isAutofixEnabled || false; +function initCLI(fixtureConfigName, options = {}) { const cli = new CLIEngine({ cwd: path.resolve(__dirname, "../fixtures/"), - fix, ignore: false, useEslintrc: false, - configFile: path.resolve(__dirname, "../fixtures/", fixtureConfigName) + configFile: path.resolve(__dirname, "../fixtures/", fixtureConfigName), + ...options }); cli.addPlugin("markdown", plugin); @@ -242,6 +245,45 @@ describe("plugin", () => { assert.strictEqual(report.results[0].messages[4].column, 2); }); + // https://github.com/eslint/eslint-plugin-markdown/issues/181 + it("should work when called on nested code blocks in the same file", () => { + + /* + * As of this writing, the nested code block, though it uses the same + * Markdown processor, must use a different extension or ESLint will not + * re-apply the processor on the nested code block. To work around that, + * a file named `test.md` contains a nested `markdown` code block in + * this test. + * + * https://github.com/eslint/eslint/pull/14227/files#r602802758 + */ + const code = [ + "", + "", + "````markdown", + "", + "", + "This test only repros if the MD files have a different number of lines before code blocks.", + "", + "```js", + "// test.md/0_0.markdown/0_0.js", + "console.log('single quotes')", + "```", + "````" + ].join("\n"); + const recursiveCli = initCLI("eslintrc.json", { + extensions: [".js", ".markdown", ".md"] + }); + const report = recursiveCli.executeOnText(code, "test.md"); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual(report.results[0].messages[0].message, "Unexpected console statement."); + assert.strictEqual(report.results[0].messages[0].line, 10); + assert.strictEqual(report.results[0].messages[1].message, "Strings must use doublequote."); + assert.strictEqual(report.results[0].messages[1].line, 10); + }); + describe("configuration comments", () => { it("apply only to the code block immediately following", () => { @@ -282,7 +324,7 @@ describe("plugin", () => { describe("should fix code", () => { before(() => { - cli = initCLI("eslintrc.json", true); + cli = initCLI("eslintrc.json", { fix: true }); }); it("in the simplest case", () => {