Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: use blocksCache instead of single blocks instance (fixes #181) #183

Merged
merged 6 commits into from
Apr 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/typescript/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# React Example
# TypeScript Example
JounQin marked this conversation as resolved.
Show resolved Hide resolved

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.

Expand Down
18 changes: 13 additions & 5 deletions lib/processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ const SUPPORTS_AUTOFIX = true;
const markdown = unified().use(remarkParse);

/**
* @type {Block[]}
* @type {Map<string, Block[]>}
*/
let blocks = [];
const blocksCache = new Map();

/**
* Performs a depth-first traversal of the Markdown AST.
Expand Down Expand Up @@ -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
Expand All @@ -250,7 +254,6 @@ function preprocess(text) {
*/
let htmlComments = [];

blocks = [];
traverse(ast, {
"*"() {
htmlComments = [];
Expand Down Expand Up @@ -370,9 +373,14 @@ function excludeUnsatisfiableRules(message) {
* Transforms generated messages for output.
* @param {Array<Message[]>} 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]);

Expand Down
54 changes: 48 additions & 6 deletions tests/lib/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 = [
"<!-- test.md -->",
"",
"````markdown",
"<!-- test.md/0_0.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", () => {
Expand Down Expand Up @@ -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", () => {
Expand Down