From de328c86bc844e9a44e2501074dc6f682b505777 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 8 Apr 2025 20:33:49 +0900 Subject: [PATCH 1/8] feat: add support for language plugins --- lib/internal/disabled-area.js | 98 +++++++++++---- lib/internal/get-all-directive-comments.js | 131 +++++++++++++++++++++ lib/internal/utils.js | 43 +++++-- lib/rules/disable-enable-pair.js | 5 +- lib/rules/no-aggregating-enable.js | 5 +- lib/rules/no-duplicate-disable.js | 5 +- lib/rules/no-restricted-disable.js | 5 +- lib/rules/no-unlimited-disable.js | 17 ++- lib/rules/no-unused-enable.js | 5 +- lib/rules/no-use.js | 16 ++- lib/rules/require-description.js | 15 ++- package.json | 10 +- 12 files changed, 274 insertions(+), 81 deletions(-) create mode 100644 lib/internal/get-all-directive-comments.js diff --git a/lib/internal/disabled-area.js b/lib/internal/disabled-area.js index bad87a8..d4d3324 100644 --- a/lib/internal/disabled-area.js +++ b/lib/internal/disabled-area.js @@ -8,25 +8,7 @@ const utils = require("./utils") const DELIMITER = /[\s,]+/gu const pool = new WeakMap() -module.exports = class DisabledArea { - /** - * Get singleton instance for the given source code. - * - * @param {eslint.SourceCode} sourceCode - The source code to get. - * @returns {DisabledArea} The singleton object for the source code. - */ - static get(sourceCode) { - let retv = pool.get(sourceCode.ast) - - if (retv == null) { - retv = new DisabledArea() - retv._scan(sourceCode) - pool.set(sourceCode.ast, retv) - } - - return retv - } - +class DisabledArea { /** * Constructor. */ @@ -45,7 +27,7 @@ module.exports = class DisabledArea { * @param {string[]|null} ruleIds - The ruleId names to disable. * @param {string} kind - The kind of disable-comments. * @returns {void} - * @private + * @protected */ _disable(comment, location, ruleIds, kind) { if (ruleIds) { @@ -85,7 +67,7 @@ module.exports = class DisabledArea { * @param {string[]|null} ruleIds - The ruleId names to enable. * @param {string} kind - The kind of disable-comments. * @returns {void} - * @private + * @protected */ _enable(comment, location, ruleIds, kind) { const relatedDisableDirectives = new Set() @@ -159,13 +141,60 @@ module.exports = class DisabledArea { return null } +} + +class DisabledAreaForLanguagePlugin extends DisabledArea { + /** + * Scan the source code and setup disabled area list. + * + * @param {import('@eslint/core').TextSourceCode} sourceCode - The source code to scan. + * @returns {void} + */ + _scan(sourceCode) { + const disableDirectives = sourceCode.getDisableDirectives() + for (const directive of disableDirectives.directives) { + if ( + directive.type !== "disable" && + directive.type !== "enable" && + directive.type !== "disable-line" && + directive.type !== "disable-next-line" + ) { + continue + } + const ruleIds = directive.value + ? directive.value.split(DELIMITER) + : null + + const loc = sourceCode.getLoc(directive.node) + if (directive.type === "disable") { + this._disable(directive.node, loc.start, ruleIds, "block") + } else if (directive.type === "enable") { + this._enable(directive.node, loc.start, ruleIds, "block") + } else if (directive.type === "disable-line") { + const line = loc.start.line + const start = { line, column: 0 } + const end = { line: line + 1, column: -1 } + + this._disable(directive.node, start, ruleIds, "line") + this._enable(directive.node, end, ruleIds, "line") + } else if (directive.type === "disable-next-line") { + const line = loc.start.line + const start = { line: line + 1, column: 0 } + const end = { line: line + 2, column: -1 } + + this._disable(directive.node, start, ruleIds, "line") + this._enable(directive.node, end, ruleIds, "line") + } + } + } +} +class DisabledAreaForLegacy extends DisabledArea { /** * Scan the source code and setup disabled area list. * * @param {eslint.SourceCode} sourceCode - The source code to scan. * @returns {void} - * @private */ _scan(sourceCode) { for (const comment of sourceCode.getAllComments()) { @@ -209,3 +238,28 @@ module.exports = class DisabledArea { } } } + +module.exports = { + /** + * Get singleton instance for the given rule context. + * + * @param {import("@eslint/core").RuleContext} context - The rule context code to get. + * @returns {DisabledArea} The singleton object for the rule context. + */ + getDisabledArea(context) { + const sourceCode = context.sourceCode || context.getSourceCode() + let retv = pool.get(sourceCode.ast) + + if (retv == null) { + if (typeof sourceCode.getDisableDirectives === "function") { + retv = new DisabledAreaForLanguagePlugin() + } else { + retv = new DisabledAreaForLegacy() + } + retv._scan(sourceCode) + pool.set(sourceCode.ast, retv) + } + + return retv + }, +} diff --git a/lib/internal/get-all-directive-comments.js b/lib/internal/get-all-directive-comments.js new file mode 100644 index 0000000..1362331 --- /dev/null +++ b/lib/internal/get-all-directive-comments.js @@ -0,0 +1,131 @@ +"use strict" + +const utils = require("./utils") + +/** + * @typedef {object} DirectiveComment + * @property {string} kind The kind of directive comment. + * @property {string} [value] The directive value if it is `eslint-` comment. + * @property {string} description The description of the directive comment. + * @property {object} node The node of the directive comment. + * @property {import("@eslint/core").SourceRange} range The range of the directive comment. + * @property {import("@eslint/core").SourceLocation} loc The location of the directive comment. + */ + +const pool = new WeakMap() + +/** + * @param {import('eslint').SourceCode} sourceCode - The source code to scan. + * @returns {DirectiveComment[]} The directive comments. + */ +function getAllDirectiveCommentsFromAllComments(sourceCode) { + /** @type {DirectiveComment[]} */ + const result = [] + for (const comment of sourceCode.getAllComments()) { + const directiveComment = utils.parseDirectiveComment(comment) + if (directiveComment != null) { + result.push({ + kind: directiveComment.kind, + value: directiveComment.value, + description: directiveComment.description, + node: comment, + range: comment.range, + loc: comment.loc, + }) + } + } + return result +} + +/** + * @param {import('@eslint/core').TextSourceCode} sourceCode - The source code to scan. + * @returns {DirectiveComment[]} The directive comments. + */ +function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { + /** @type {DirectiveComment[]} */ + const result = [] + const disableDirectives = sourceCode.getDisableDirectives() + for (const directive of disableDirectives.directives) { + result.push({ + kind: `eslint-${directive.type}`, + value: directive.value, + description: directive.justification, + node: directive.node, + range: sourceCode.getRange(directive.node), + get loc() { + return sourceCode.getLoc(directive.node) + }, + }) + } + for (const node of sourceCode.getInlineConfigNodes()) { + const range = sourceCode.getRange(node) + // The node has intersection of the directive comment. + // So, we need to skip it. + if ( + result.some( + (comment) => + comment.range[0] > range[1] && range[0] < comment.range[1] + ) + ) { + continue + } + const nodeText = sourceCode.text.slice(range[0], range[1]) + // Extract comment content from the comment text. + // The comment format was based on the language comment definition in vscode-eslint. + // See https://github.com/microsoft/vscode-eslint/blob/c0e753713ea9935667e849d91e549adbff213e7e/server/src/languageDefaults.ts#L14 + const commentValue = + nodeText.startsWith("/*") && nodeText.startsWith("*/") + ? nodeText.slice(2, -2) + : nodeText.startsWith("//") + ? nodeText.slice(2) + : nodeText.startsWith("") + ? nodeText.slice(4, -3) + : nodeText.startsWith("###") && nodeText.endsWith("###") + ? nodeText.slice(1) + : nodeText.startsWith("#") + ? nodeText.slice(1) + : nodeText + const directiveComment = utils.parseDirectiveText(commentValue) + if (directiveComment != null) { + result.push({ + kind: directiveComment.kind, + value: directiveComment.value, + description: directiveComment.description, + node, + range, + get loc() { + return sourceCode.getLoc(node) + }, + }) + } + } + return result +} + +module.exports = { + /** + * Get all directive comments for the given rule context. + * + * @param {import("@eslint/core").RuleContext} context - The rule context to get. + * @returns {DirectiveComment[]} The all directive comments object for the rule context. + */ + getAllDirectiveComments(context) { + const sourceCode = context.sourceCode || context.getSourceCode() + let result = pool.get(sourceCode.ast) + + if (result == null) { + if ( + typeof sourceCode.getInlineConfigNodes === "function" && + typeof sourceCode.getDisableDirectives === "function" + ) { + result = + getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) + } else { + result = getAllDirectiveCommentsFromAllComments(sourceCode) + } + pool.set(sourceCode.ast, result) + } + + return result + }, +} diff --git a/lib/internal/utils.js b/lib/internal/utils.js index 931d483..0cd38fd 100644 --- a/lib/internal/utils.js +++ b/lib/internal/utils.js @@ -107,14 +107,12 @@ module.exports = { * @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment. */ parseDirectiveComment(comment) { - const { text, description } = divideDirectiveComment(comment.value) - const match = DIRECTIVE_PATTERN.exec(text) - - if (!match) { + const parsed = parseDirectiveText(comment.value) + if (!parsed) { return null } - const directiveText = match[1] - const lineCommentSupported = LINE_COMMENT_PATTERN.test(directiveText) + + const lineCommentSupported = LINE_COMMENT_PATTERN.test(parsed.kind) if (comment.type === "Line" && !lineCommentSupported) { return null @@ -128,14 +126,33 @@ module.exports = { return null } - const directiveValue = text.slice(match.index + directiveText.length) - - return { - kind: directiveText, - value: directiveValue.trim(), - description, - } + return parsed }, + parseDirectiveText, +} + +/** + * Parse the given text as a directive comment. + * + * @param {string} textToParse - The text to parse. + * @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment. + */ +function parseDirectiveText(textToParse) { + const { text, description } = divideDirectiveComment(textToParse) + const match = DIRECTIVE_PATTERN.exec(text) + + if (!match) { + return null + } + const directiveText = match[1] + + const directiveValue = text.slice(match.index + directiveText.length) + + return { + kind: directiveText, + value: directiveValue.trim(), + description, + } } /** diff --git a/lib/rules/disable-enable-pair.js b/lib/rules/disable-enable-pair.js index bb28be2..bd2b5c3 100644 --- a/lib/rules/disable-enable-pair.js +++ b/lib/rules/disable-enable-pair.js @@ -4,7 +4,7 @@ */ "use strict" -const DisabledArea = require("../internal/disabled-area") +const { getDisabledArea } = require("../internal/disabled-area") const utils = require("../internal/utils") module.exports = { @@ -39,8 +39,7 @@ module.exports = { create(context) { const allowWholeFile = context.options[0] && context.options[0].allowWholeFile - const sourceCode = context.getSourceCode() - const disabledArea = DisabledArea.get(sourceCode) + const disabledArea = getDisabledArea(context) return { Program(node) { diff --git a/lib/rules/no-aggregating-enable.js b/lib/rules/no-aggregating-enable.js index 6f34616..a27a023 100644 --- a/lib/rules/no-aggregating-enable.js +++ b/lib/rules/no-aggregating-enable.js @@ -4,7 +4,7 @@ */ "use strict" -const DisabledArea = require("../internal/disabled-area") +const { getDisabledArea } = require("../internal/disabled-area") const utils = require("../internal/utils") module.exports = { @@ -26,8 +26,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode() - const disabledArea = DisabledArea.get(sourceCode) + const disabledArea = getDisabledArea(context) return { Program() { diff --git a/lib/rules/no-duplicate-disable.js b/lib/rules/no-duplicate-disable.js index f5e7846..7ff15c1 100644 --- a/lib/rules/no-duplicate-disable.js +++ b/lib/rules/no-duplicate-disable.js @@ -4,7 +4,7 @@ */ "use strict" -const DisabledArea = require("../internal/disabled-area") +const { getDisabledArea } = require("../internal/disabled-area") const utils = require("../internal/utils") module.exports = { @@ -25,8 +25,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode() - const disabledArea = DisabledArea.get(sourceCode) + const disabledArea = getDisabledArea(context) return { Program() { diff --git a/lib/rules/no-restricted-disable.js b/lib/rules/no-restricted-disable.js index a620d50..c1cbfb9 100644 --- a/lib/rules/no-restricted-disable.js +++ b/lib/rules/no-restricted-disable.js @@ -5,7 +5,7 @@ "use strict" const ignore = require("ignore") -const DisabledArea = require("../internal/disabled-area") +const { getDisabledArea } = require("../internal/disabled-area") const utils = require("../internal/utils") module.exports = { @@ -30,8 +30,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode() - const disabledArea = DisabledArea.get(sourceCode) + const disabledArea = getDisabledArea(context) if (context.options.length === 0) { return {} diff --git a/lib/rules/no-unlimited-disable.js b/lib/rules/no-unlimited-disable.js index 942c6d3..9bf72c9 100644 --- a/lib/rules/no-unlimited-disable.js +++ b/lib/rules/no-unlimited-disable.js @@ -4,6 +4,9 @@ */ "use strict" +const { + getAllDirectiveComments, +} = require("../internal/get-all-directive-comments") const utils = require("../internal/utils") module.exports = { @@ -25,17 +28,11 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode() - return { Program() { - for (const comment of sourceCode.getAllComments()) { - const directiveComment = - utils.parseDirectiveComment(comment) - if (directiveComment == null) { - continue - } - + for (const directiveComment of getAllDirectiveComments( + context + )) { const kind = directiveComment.kind if ( kind !== "eslint-disable" && @@ -46,7 +43,7 @@ module.exports = { } if (!directiveComment.value) { context.report({ - loc: utils.toForceLocation(comment.loc), + loc: utils.toForceLocation(directiveComment.loc), messageId: "unexpected", data: { kind: directiveComment.kind }, }) diff --git a/lib/rules/no-unused-enable.js b/lib/rules/no-unused-enable.js index 39feec1..640c180 100644 --- a/lib/rules/no-unused-enable.js +++ b/lib/rules/no-unused-enable.js @@ -4,7 +4,7 @@ */ "use strict" -const DisabledArea = require("../internal/disabled-area") +const { getDisabledArea } = require("../internal/disabled-area") const utils = require("../internal/utils") module.exports = { @@ -26,8 +26,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode() - const disabledArea = DisabledArea.get(sourceCode) + const disabledArea = getDisabledArea(context) return { Program() { diff --git a/lib/rules/no-use.js b/lib/rules/no-use.js index 99f841f..cb95c26 100644 --- a/lib/rules/no-use.js +++ b/lib/rules/no-use.js @@ -4,6 +4,9 @@ */ "use strict" +const { + getAllDirectiveComments, +} = require("../internal/get-all-directive-comments") const utils = require("../internal/utils") module.exports = { @@ -48,23 +51,18 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode() const allowed = new Set( (context.options[0] && context.options[0].allow) || [] ) return { Program() { - for (const comment of sourceCode.getAllComments()) { - const directiveComment = - utils.parseDirectiveComment(comment) - if (directiveComment == null) { - continue - } - + for (const directiveComment of getAllDirectiveComments( + context + )) { if (!allowed.has(directiveComment.kind)) { context.report({ - loc: utils.toForceLocation(comment.loc), + loc: utils.toForceLocation(directiveComment.loc), messageId: "disallow", }) } diff --git a/lib/rules/require-description.js b/lib/rules/require-description.js index b751aef..b572ad9 100644 --- a/lib/rules/require-description.js +++ b/lib/rules/require-description.js @@ -4,6 +4,9 @@ */ "use strict" +const { + getAllDirectiveComments, +} = require("../internal/get-all-directive-comments") const utils = require("../internal/utils") module.exports = { @@ -50,25 +53,21 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode() const ignores = new Set( (context.options[0] && context.options[0].ignore) || [] ) return { Program() { - for (const comment of sourceCode.getAllComments()) { - const directiveComment = - utils.parseDirectiveComment(comment) - if (directiveComment == null) { - continue - } + for (const directiveComment of getAllDirectiveComments( + context + )) { if (ignores.has(directiveComment.kind)) { continue } if (!directiveComment.description) { context.report({ - loc: utils.toForceLocation(comment.loc), + loc: utils.toForceLocation(directiveComment.loc), messageId: "missingDescription", }) } diff --git a/package.json b/package.json index d9d91a6..c59ad0d 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,9 @@ "configs.js", "lib" ], - "exports": { - "./configs": "./configs.js", - ".": "./index.js" + "exports": { + "./configs": "./configs.js", + ".": "./index.js" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" @@ -26,6 +26,7 @@ "@babel/core": "^7.22.9", "@babel/eslint-parser": "^7.22.9", "@eslint-community/eslint-plugin-mysticatea": "^15.5.1", + "@eslint/core": "^0.13.0", "@types/node": "^14.18.54", "@vuepress/plugin-pwa": "^1.9.9", "cross-spawn": "^7.0.3", @@ -49,7 +50,8 @@ "docs:build": "vitepress build docs", "docs:watch": "vitepress dev docs", "lint": "eslint lib scripts tests", - "test": "nyc mocha \"tests/lib/**/*.js\" --reporter dot --timeout 8000", + "test": "nyc npm run debug", + "debug": "mocha \"tests/lib/**/*.js\" --reporter dot --timeout 8000", "coverage": "nyc report --reporter lcov && opener coverage/lcov-report/index.html", "watch": "npm run -s test -- --watch --growl" }, From 95bd95daec49e2211a4a4e127ef4cc859bb593e9 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 8 Apr 2025 21:11:59 +0900 Subject: [PATCH 2/8] fix --- lib/internal/get-all-directive-comments.js | 43 +++++++++++++--------- lib/internal/utils.js | 2 +- tests/lib/illegal-eslint-disable-line.js | 4 +- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/lib/internal/get-all-directive-comments.js b/lib/internal/get-all-directive-comments.js index 1362331..8645a88 100644 --- a/lib/internal/get-all-directive-comments.js +++ b/lib/internal/get-all-directive-comments.js @@ -64,29 +64,21 @@ function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { if ( result.some( (comment) => - comment.range[0] > range[1] && range[0] < comment.range[1] + comment.range[0] <= range[1] && range[0] <= comment.range[1] ) ) { continue } const nodeText = sourceCode.text.slice(range[0], range[1]) - // Extract comment content from the comment text. - // The comment format was based on the language comment definition in vscode-eslint. - // See https://github.com/microsoft/vscode-eslint/blob/c0e753713ea9935667e849d91e549adbff213e7e/server/src/languageDefaults.ts#L14 - const commentValue = - nodeText.startsWith("/*") && nodeText.startsWith("*/") - ? nodeText.slice(2, -2) - : nodeText.startsWith("//") - ? nodeText.slice(2) - : nodeText.startsWith("") - ? nodeText.slice(4, -3) - : nodeText.startsWith("###") && nodeText.endsWith("###") - ? nodeText.slice(1) - : nodeText.startsWith("#") - ? nodeText.slice(1) - : nodeText + const commentValue = extractCommentContent(nodeText) const directiveComment = utils.parseDirectiveText(commentValue) - if (directiveComment != null) { + if ( + directiveComment != null && + directiveComment.kind !== "eslint-disable" && + directiveComment.kind !== "eslint-disable-line" && + directiveComment.kind !== "eslint-disable-next-line" && + directiveComment.kind !== "eslint-enable" + ) { result.push({ kind: directiveComment.kind, value: directiveComment.value, @@ -102,6 +94,23 @@ function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { return result } +function extractCommentContent(text) { + // Extract comment content from the comment text. + // The comment format was based on the language comment definition in vscode-eslint. + // See https://github.com/microsoft/vscode-eslint/blob/c0e753713ea9935667e849d91e549adbff213e7e/server/src/languageDefaults.ts#L14 + return text.startsWith("/*") && text.endsWith("*/") + ? text.slice(2, -2) + : text.startsWith("//") + ? text.slice(2) + : text.startsWith("") + ? text.slice(4, -3) + : text.startsWith("###") && text.endsWith("###") + ? text.slice(1) + : text.startsWith("#") + ? text.slice(1) + : text +} + module.exports = { /** * Get all directive comments for the given rule context. diff --git a/lib/internal/utils.js b/lib/internal/utils.js index 0cd38fd..75a0254 100644 --- a/lib/internal/utils.js +++ b/lib/internal/utils.js @@ -119,7 +119,7 @@ module.exports = { } if ( - lineCommentSupported && + parsed.kind === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line ) { // disable-line comment should not span multiple lines. diff --git a/tests/lib/illegal-eslint-disable-line.js b/tests/lib/illegal-eslint-disable-line.js index b05a6dd..495d68c 100644 --- a/tests/lib/illegal-eslint-disable-line.js +++ b/tests/lib/illegal-eslint-disable-line.js @@ -77,8 +77,6 @@ describe("multi-line eslint-disable-line comments", () => { for (const code of [ `/* eslint @eslint-community/eslint-comments/no-use:[error, {allow: ['eslint']}] */ /* eslint-disable-line -*/ -/* eslint-disable-next-line */`, `/* eslint @eslint-community/eslint-comments/no-duplicate-disable:error */ /*eslint-disable no-undef*/ @@ -92,7 +90,7 @@ no-undef*/ const normalMessages = messages.filter( (message) => message.ruleId != null ) - assert.strictEqual(normalMessages.length, 0) + assert.deepStrictEqual(normalMessages, []) }) ) } From 28cc29141af52bbf4145cb096676c322c04f6bd4 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 8 Apr 2025 21:54:37 +0900 Subject: [PATCH 3/8] add test and fix bugs --- lib/internal/utils.js | 29 +++++++++++++--- lib/rules/disable-enable-pair.js | 48 +++++++++++++------------- lib/rules/no-aggregating-enable.js | 25 ++++++-------- lib/rules/no-duplicate-disable.js | 17 ++++----- lib/rules/no-restricted-disable.js | 32 ++++++++--------- lib/rules/no-unlimited-disable.js | 39 +++++++++------------ lib/rules/no-unused-enable.js | 17 ++++----- lib/rules/no-use.js | 21 +++++------ lib/rules/require-description.js | 27 ++++++--------- lib/utils/patch.js | 2 +- package.json | 1 + tests/lib/rules/disable-enable-pair.js | 38 ++++++++++++++++++++ 12 files changed, 164 insertions(+), 132 deletions(-) diff --git a/lib/internal/utils.js b/lib/internal/utils.js index 75a0254..961c225 100644 --- a/lib/internal/utils.js +++ b/lib/internal/utils.js @@ -31,13 +31,15 @@ module.exports = { /** * Calculate the location of the given rule in the given comment token. * + * @param {Partial} context - The rule context code. * @param {Token} comment - The comment token to calculate. * @param {string|null} ruleId - The rule name to calculate. * @returns {object} The location of the given information. */ - toRuleIdLocation(comment, ruleId) { + toRuleIdLocation(context, comment, ruleId) { + const commentLoc = getLoc(context, comment) if (ruleId == null) { - return module.exports.toForceLocation(comment.loc) + return module.exports.toForceLocation(commentLoc) } const lines = comment.value.match(LINE_PATTERN) @@ -49,7 +51,7 @@ module.exports = { { const m = ruleIdPattern.exec(lines[0]) if (m != null) { - const start = comment.loc.start + const start = commentLoc.start return { start: { line: start.line, @@ -71,7 +73,7 @@ module.exports = { for (let i = 1; i < lines.length; ++i) { const m = ruleIdPattern.exec(lines[i]) if (m != null) { - const start = comment.loc.start + const start = commentLoc.start return { start: { line: start.line + i, @@ -86,7 +88,7 @@ module.exports = { } /*istanbul ignore next : foolproof */ - return comment.loc + return commentLoc }, /** @@ -129,6 +131,8 @@ module.exports = { return parsed }, parseDirectiveText, + + getLoc, } /** @@ -168,3 +172,18 @@ function divideDirectiveComment(value) { description: divided.length > 1 ? divided[1].trim() : null, } } + +/** + * Get source code location from the given node. + * + * @param {Partial} context - The rule context code. + * @param {unknown} nodeOrToken - The node or token to get. + * @returns {object} The source code location. + */ +function getLoc(context, nodeOrToken) { + const sourceCode = context.sourceCode || context.getSourceCode?.() + if (typeof sourceCode?.getLoc === "function") { + return sourceCode.getLoc(nodeOrToken) + } + return nodeOrToken.loc +} diff --git a/lib/rules/disable-enable-pair.js b/lib/rules/disable-enable-pair.js index bd2b5c3..663b672 100644 --- a/lib/rules/disable-enable-pair.js +++ b/lib/rules/disable-enable-pair.js @@ -41,32 +41,32 @@ module.exports = { context.options[0] && context.options[0].allowWholeFile const disabledArea = getDisabledArea(context) - return { - Program(node) { - if (allowWholeFile && node.body.length === 0) { - return - } + /** @type {import('@eslint/core').TextSourceCode} */ + const sourceCode = context.sourceCode || context.getSourceCode() - for (const area of disabledArea.areas) { - if (area.end != null) { - continue - } - if ( - allowWholeFile && - utils.lte(area.start, node.loc.start) - ) { - continue - } + const firstToken = sourceCode.ast?.tokens?.[0] - context.report({ - loc: utils.toRuleIdLocation(area.comment, area.ruleId), - messageId: area.ruleId - ? "missingRulePair" - : "missingPair", - data: area, - }) - } - }, + if (allowWholeFile && !firstToken) { + return {} + } + + for (const area of disabledArea.areas) { + if (area.end != null) { + continue + } + if ( + allowWholeFile && + utils.lte(area.start, utils.getLoc(context, firstToken).start) + ) { + continue + } + + context.report({ + loc: utils.toRuleIdLocation(context, area.comment, area.ruleId), + messageId: area.ruleId ? "missingRulePair" : "missingPair", + data: area, + }) } + return {} }, } diff --git a/lib/rules/no-aggregating-enable.js b/lib/rules/no-aggregating-enable.js index a27a023..1cd4274 100644 --- a/lib/rules/no-aggregating-enable.js +++ b/lib/rules/no-aggregating-enable.js @@ -28,21 +28,18 @@ module.exports = { create(context) { const disabledArea = getDisabledArea(context) - return { - Program() { - for (const entry of disabledArea.numberOfRelatedDisableDirectives) { - const comment = entry[0] - const count = entry[1] + for (const entry of disabledArea.numberOfRelatedDisableDirectives) { + const comment = entry[0] + const count = entry[1] - if (count >= 2) { - context.report({ - loc: utils.toForceLocation(comment.loc), - messageId: "aggregatingEnable", - data: { count }, - }) - } - } - }, + if (count >= 2) { + context.report({ + loc: utils.toForceLocation(comment.loc), + messageId: "aggregatingEnable", + data: { count }, + }) + } } + return {} }, } diff --git a/lib/rules/no-duplicate-disable.js b/lib/rules/no-duplicate-disable.js index 7ff15c1..78a7aaa 100644 --- a/lib/rules/no-duplicate-disable.js +++ b/lib/rules/no-duplicate-disable.js @@ -27,16 +27,13 @@ module.exports = { create(context) { const disabledArea = getDisabledArea(context) - return { - Program() { - for (const item of disabledArea.duplicateDisableDirectives) { - context.report({ - loc: utils.toRuleIdLocation(item.comment, item.ruleId), - messageId: item.ruleId ? "duplicateRule" : "duplicate", - data: item, - }) - } - }, + for (const item of disabledArea.duplicateDisableDirectives) { + context.report({ + loc: utils.toRuleIdLocation(context, item.comment, item.ruleId), + messageId: item.ruleId ? "duplicateRule" : "duplicate", + data: item, + }) } + return {} }, } diff --git a/lib/rules/no-restricted-disable.js b/lib/rules/no-restricted-disable.js index c1cbfb9..b96ab9d 100644 --- a/lib/rules/no-restricted-disable.js +++ b/lib/rules/no-restricted-disable.js @@ -41,23 +41,21 @@ module.exports = { ig.add(pattern) } - return { - Program() { - for (const area of disabledArea.areas) { - if (area.ruleId == null || ig.ignores(area.ruleId)) { - context.report({ - loc: utils.toRuleIdLocation( - area.comment, - area.ruleId - ), - messageId: "disallow", - data: { - ruleId: area.ruleId || String(context.options), - }, - }) - } - } - }, + for (const area of disabledArea.areas) { + if (area.ruleId == null || ig.ignores(area.ruleId)) { + context.report({ + loc: utils.toRuleIdLocation( + context, + area.comment, + area.ruleId + ), + messageId: "disallow", + data: { + ruleId: area.ruleId || String(context.options), + }, + }) + } } + return {} }, } diff --git a/lib/rules/no-unlimited-disable.js b/lib/rules/no-unlimited-disable.js index 9bf72c9..87ae0f0 100644 --- a/lib/rules/no-unlimited-disable.js +++ b/lib/rules/no-unlimited-disable.js @@ -28,28 +28,23 @@ module.exports = { }, create(context) { - return { - Program() { - for (const directiveComment of getAllDirectiveComments( - context - )) { - const kind = directiveComment.kind - if ( - kind !== "eslint-disable" && - kind !== "eslint-disable-line" && - kind !== "eslint-disable-next-line" - ) { - continue - } - if (!directiveComment.value) { - context.report({ - loc: utils.toForceLocation(directiveComment.loc), - messageId: "unexpected", - data: { kind: directiveComment.kind }, - }) - } - } - }, + for (const directiveComment of getAllDirectiveComments(context)) { + const kind = directiveComment.kind + if ( + kind !== "eslint-disable" && + kind !== "eslint-disable-line" && + kind !== "eslint-disable-next-line" + ) { + continue + } + if (!directiveComment.value) { + context.report({ + loc: utils.toForceLocation(directiveComment.loc), + messageId: "unexpected", + data: { kind: directiveComment.kind }, + }) + } } + return {} }, } diff --git a/lib/rules/no-unused-enable.js b/lib/rules/no-unused-enable.js index 640c180..c4e4fba 100644 --- a/lib/rules/no-unused-enable.js +++ b/lib/rules/no-unused-enable.js @@ -28,16 +28,13 @@ module.exports = { create(context) { const disabledArea = getDisabledArea(context) - return { - Program() { - for (const item of disabledArea.unusedEnableDirectives) { - context.report({ - loc: utils.toRuleIdLocation(item.comment, item.ruleId), - messageId: item.ruleId ? "unusedRule" : "unused", - data: item, - }) - } - }, + for (const item of disabledArea.unusedEnableDirectives) { + context.report({ + loc: utils.toRuleIdLocation(context, item.comment, item.ruleId), + messageId: item.ruleId ? "unusedRule" : "unused", + data: item, + }) } + return {} }, } diff --git a/lib/rules/no-use.js b/lib/rules/no-use.js index cb95c26..5b1afc0 100644 --- a/lib/rules/no-use.js +++ b/lib/rules/no-use.js @@ -55,19 +55,14 @@ module.exports = { (context.options[0] && context.options[0].allow) || [] ) - return { - Program() { - for (const directiveComment of getAllDirectiveComments( - context - )) { - if (!allowed.has(directiveComment.kind)) { - context.report({ - loc: utils.toForceLocation(directiveComment.loc), - messageId: "disallow", - }) - } - } - }, + for (const directiveComment of getAllDirectiveComments(context)) { + if (!allowed.has(directiveComment.kind)) { + context.report({ + loc: utils.toForceLocation(directiveComment.loc), + messageId: "disallow", + }) + } } + return {} }, } diff --git a/lib/rules/require-description.js b/lib/rules/require-description.js index b572ad9..149840f 100644 --- a/lib/rules/require-description.js +++ b/lib/rules/require-description.js @@ -57,22 +57,17 @@ module.exports = { (context.options[0] && context.options[0].ignore) || [] ) - return { - Program() { - for (const directiveComment of getAllDirectiveComments( - context - )) { - if (ignores.has(directiveComment.kind)) { - continue - } - if (!directiveComment.description) { - context.report({ - loc: utils.toForceLocation(directiveComment.loc), - messageId: "missingDescription", - }) - } - } - }, + for (const directiveComment of getAllDirectiveComments(context)) { + if (ignores.has(directiveComment.kind)) { + continue + } + if (!directiveComment.description) { + context.report({ + loc: utils.toForceLocation(directiveComment.loc), + messageId: "missingDescription", + }) + } } + return {} }, } diff --git a/lib/utils/patch.js b/lib/utils/patch.js index 8acbff9..404b44c 100644 --- a/lib/utils/patch.js +++ b/lib/utils/patch.js @@ -90,7 +90,7 @@ function createNoUnusedDisableError(ruleId, severity, message, comment) { if (comment != null) { if (targetRuleId) { - const loc = toRuleIdLocation(comment, targetRuleId) + const loc = toRuleIdLocation({}, comment, targetRuleId) clone.line = loc.start.line clone.column = loc.start.column + 1 clone.endLine = loc.end.line diff --git a/package.json b/package.json index c59ad0d..e0e4d78 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@babel/eslint-parser": "^7.22.9", "@eslint-community/eslint-plugin-mysticatea": "^15.5.1", "@eslint/core": "^0.13.0", + "@eslint/css": "^0.6.0", "@types/node": "^14.18.54", "@vuepress/plugin-pwa": "^1.9.9", "cross-spawn": "^7.0.3", diff --git a/tests/lib/rules/disable-enable-pair.js b/tests/lib/rules/disable-enable-pair.js index a79f162..b1e3197 100644 --- a/tests/lib/rules/disable-enable-pair.js +++ b/tests/lib/rules/disable-enable-pair.js @@ -92,6 +92,22 @@ var foo = 1 `, ] : []), + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: ` +/*eslint-disable no-undef*/ +/*eslint-enable no-undef*/ +a {} +`, + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + }, + ] + : []), ], invalid: [ { @@ -232,5 +248,27 @@ console.log(); }, ] : []), + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: "/* eslint-disable no-unused-vars */ a {}", + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + errors: [ + { + message: + "Requires 'eslint-enable' directive for 'no-unused-vars'.", + line: 1, + column: 19, + endLine: 1, + endColumn: 33, + }, + ], + }, + ] + : []), ], }) From 95bb51b6564a4e4ca55ade4853cad95267289b3e Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 8 Apr 2025 22:01:04 +0900 Subject: [PATCH 4/8] add tests and fix bugs --- lib/internal/utils.js | 5 ++-- lib/rules/disable-enable-pair.js | 3 +- lib/rules/no-aggregating-enable.js | 2 +- tests/lib/rules/no-aggregating-enable.js | 35 ++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/lib/internal/utils.js b/lib/internal/utils.js index 961c225..a85e00c 100644 --- a/lib/internal/utils.js +++ b/lib/internal/utils.js @@ -181,8 +181,9 @@ function divideDirectiveComment(value) { * @returns {object} The source code location. */ function getLoc(context, nodeOrToken) { - const sourceCode = context.sourceCode || context.getSourceCode?.() - if (typeof sourceCode?.getLoc === "function") { + const sourceCode = + context.sourceCode || (context.getSourceCode && context.getSourceCode()) + if (sourceCode && typeof sourceCode.getLoc === "function") { return sourceCode.getLoc(nodeOrToken) } return nodeOrToken.loc diff --git a/lib/rules/disable-enable-pair.js b/lib/rules/disable-enable-pair.js index 663b672..ce0330e 100644 --- a/lib/rules/disable-enable-pair.js +++ b/lib/rules/disable-enable-pair.js @@ -44,7 +44,8 @@ module.exports = { /** @type {import('@eslint/core').TextSourceCode} */ const sourceCode = context.sourceCode || context.getSourceCode() - const firstToken = sourceCode.ast?.tokens?.[0] + const firstToken = + sourceCode.ast && sourceCode.ast.tokens && sourceCode.ast.tokens[0] if (allowWholeFile && !firstToken) { return {} diff --git a/lib/rules/no-aggregating-enable.js b/lib/rules/no-aggregating-enable.js index 1cd4274..d7c1f94 100644 --- a/lib/rules/no-aggregating-enable.js +++ b/lib/rules/no-aggregating-enable.js @@ -34,7 +34,7 @@ module.exports = { if (count >= 2) { context.report({ - loc: utils.toForceLocation(comment.loc), + loc: utils.toForceLocation(utils.getLoc(context, comment)), messageId: "aggregatingEnable", data: { count }, }) diff --git a/tests/lib/rules/no-aggregating-enable.js b/tests/lib/rules/no-aggregating-enable.js index c9cde5a..979ddce 100644 --- a/tests/lib/rules/no-aggregating-enable.js +++ b/tests/lib/rules/no-aggregating-enable.js @@ -32,6 +32,22 @@ tester.run("no-aggregating-enable", rule, { /*eslint-enable no-redeclare*/ /*eslint-enable no-shadow*/ `, + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: ` + /*eslint-disable no-redeclare, no-shadow*/ + /*eslint-enable no-redeclare*/ + /*eslint-enable no-shadow*/ + a {}`, + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + }, + ] + : []), ], invalid: [ { @@ -80,5 +96,24 @@ tester.run("no-aggregating-enable", rule, { }, ] : []), + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: ` + /*eslint-disable no-redeclare*/ + /*eslint-disable no-shadow*/ + /*eslint-enable*/ + a {}`, + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + errors: [ + "This `eslint-enable` comment affects 2 `eslint-disable` comments. An `eslint-enable` comment should be for an `eslint-disable` comment.", + ], + }, + ] + : []), ], }) From 31506f2d292a3cc89c20ec47cece3836a979effb Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 8 Apr 2025 22:09:19 +0900 Subject: [PATCH 5/8] add tests --- tests/lib/rules/no-duplicate-disable.js | 42 ++++++++++++++++++++++++ tests/lib/rules/no-restricted-disable.js | 27 +++++++++++++++ tests/lib/rules/no-unlimited-disable.js | 27 +++++++++++++++ tests/lib/rules/no-use.js | 29 +++++++++++++++- tests/lib/rules/require-description.js | 28 ++++++++++++++++ 5 files changed, 152 insertions(+), 1 deletion(-) diff --git a/tests/lib/rules/no-duplicate-disable.js b/tests/lib/rules/no-duplicate-disable.js index cb278c7..3ba9652 100644 --- a/tests/lib/rules/no-duplicate-disable.js +++ b/tests/lib/rules/no-duplicate-disable.js @@ -29,6 +29,23 @@ tester.run("no-duplicate-disable", rule, { /*eslint-disable-next-line semi*/ /*eslint-disable eqeqeq*/ `, + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: ` +/*eslint-disable no-undef*/ +/*eslint-disable-line no-unused-vars*/ +/*eslint-disable-next-line semi*/ +/*eslint-disable eqeqeq*/ +a {}`, + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + }, + ] + : []), ], invalid: [ { @@ -142,5 +159,30 @@ tester.run("no-duplicate-disable", rule, { }, ] : []), + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: ` +/* eslint-disable-next-line no-undef */ +/* eslint-disable-line no-undef */ +a {}`, + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + errors: [ + { + message: + "'no-undef' rule has been disabled already.", + line: 3, + column: 24, + endLine: 3, + endColumn: 32, + }, + ], + }, + ] + : []), ], }) diff --git a/tests/lib/rules/no-restricted-disable.js b/tests/lib/rules/no-restricted-disable.js index 569fed7..e515838 100644 --- a/tests/lib/rules/no-restricted-disable.js +++ b/tests/lib/rules/no-restricted-disable.js @@ -48,6 +48,19 @@ tester.run("no-restricted-disable", rule, { code: "/*eslint-disable eqeqeq*/", options: ["*", "!eqeqeq"], }, + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: "/*eslint-disable eqeqeq*/ a {}", + options: ["*", "!eqeqeq"], + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + }, + ] + : []), ], invalid: [ { @@ -179,5 +192,19 @@ tester.run("no-restricted-disable", rule, { }, ] : []), + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: "/*eslint-disable eqeqeq*/ a {}", + options: ["eqeqeq"], + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + errors: ["Disabling 'eqeqeq' is not allowed."], + }, + ] + : []), ], }) diff --git a/tests/lib/rules/no-unlimited-disable.js b/tests/lib/rules/no-unlimited-disable.js index 24aad0b..34f7427 100644 --- a/tests/lib/rules/no-unlimited-disable.js +++ b/tests/lib/rules/no-unlimited-disable.js @@ -19,6 +19,18 @@ tester.run("no-unlimited-disable", rule, { "/*eslint-disable-next-line eqeqeq*/", "var foo;\n//eslint-disable-line eqeqeq", "var foo;\n/*eslint-disable-line eqeqeq*/", + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: "/*eslint-disable-line eqeqeq*/ a {}", + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + }, + ] + : []), ], invalid: [ { @@ -104,5 +116,20 @@ tester.run("no-unlimited-disable", rule, { }, ] : []), + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: "/* eslint-disable */ a {}", + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + errors: [ + "Unexpected unlimited 'eslint-disable' comment. Specify some rule names to disable.", + ], + }, + ] + : []), ], }) diff --git a/tests/lib/rules/no-use.js b/tests/lib/rules/no-use.js index bcd14f8..861f372 100644 --- a/tests/lib/rules/no-use.js +++ b/tests/lib/rules/no-use.js @@ -4,7 +4,8 @@ */ "use strict" -const { RuleTester } = require("eslint") +const semver = require("semver") +const { Linter, RuleTester } = require("eslint") const rule = require("../../../lib/rules/no-use") const tester = new RuleTester() @@ -62,6 +63,19 @@ tester.run("no-use", rule, { code: "/* globals */", options: [{ allow: ["globals"] }], }, + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: "/* eslint-disable */ a {}", + options: [{ allow: ["eslint-disable"] }], + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + }, + ] + : []), ], invalid: [ { @@ -108,5 +122,18 @@ tester.run("no-use", rule, { code: "/* globals */", errors: ["Unexpected ESLint directive comment."], }, + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: "/* eslint-disable */ a {}", + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + errors: ["Unexpected ESLint directive comment."], + }, + ] + : []), ], }) diff --git a/tests/lib/rules/require-description.js b/tests/lib/rules/require-description.js index f5f7325..56b6f08 100644 --- a/tests/lib/rules/require-description.js +++ b/tests/lib/rules/require-description.js @@ -74,6 +74,19 @@ tester.run("require-description", rule, { code: "/* globals */", options: [{ ignore: ["globals"] }], }, + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: "/* eslint-disable */ a {}", + options: [{ ignore: ["eslint-disable"] }], + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + }, + ] + : []), ], invalid: [ { @@ -209,5 +222,20 @@ tester.run("require-description", rule, { "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", ], }, + // Language plugin + ...(semver.satisfies(Linter.version, ">=9.6.0") + ? [ + { + code: "/* eslint-disable */ a {}", + plugins: { + css: require("@eslint/css").default, + }, + language: "css/css", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + ] + : []), ], }) From 4fd4745e0543af107a4a5b7064cb2f103f1952e0 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Wed, 9 Apr 2025 09:38:09 +0900 Subject: [PATCH 6/8] refactor --- lib/internal/disabled-area.js | 29 ++++++------ lib/internal/get-all-directive-comments.js | 54 +++++++++++----------- lib/internal/utils.js | 7 ++- 3 files changed, 46 insertions(+), 44 deletions(-) diff --git a/lib/internal/disabled-area.js b/lib/internal/disabled-area.js index d4d3324..f7dca6a 100644 --- a/lib/internal/disabled-area.js +++ b/lib/internal/disabled-area.js @@ -154,10 +154,12 @@ class DisabledAreaForLanguagePlugin extends DisabledArea { const disableDirectives = sourceCode.getDisableDirectives() for (const directive of disableDirectives.directives) { if ( - directive.type !== "disable" && - directive.type !== "enable" && - directive.type !== "disable-line" && - directive.type !== "disable-next-line" + ![ + "disable", + "enable", + "disable-line", + "disable-next-line", + ].includes(directive.type) ) { continue } @@ -205,10 +207,12 @@ class DisabledAreaForLegacy extends DisabledArea { const kind = directiveComment.kind if ( - kind !== "eslint-disable" && - kind !== "eslint-enable" && - kind !== "eslint-disable-line" && - kind !== "eslint-disable-next-line" + ![ + "eslint-disable", + "eslint-enable", + "eslint-disable-line", + "eslint-disable-next-line", + ].includes(kind) ) { continue } @@ -251,11 +255,10 @@ module.exports = { let retv = pool.get(sourceCode.ast) if (retv == null) { - if (typeof sourceCode.getDisableDirectives === "function") { - retv = new DisabledAreaForLanguagePlugin() - } else { - retv = new DisabledAreaForLegacy() - } + retv = + typeof sourceCode.getDisableDirectives === "function" + ? new DisabledAreaForLanguagePlugin() + : new DisabledAreaForLegacy() retv._scan(sourceCode) pool.set(sourceCode.ast, retv) } diff --git a/lib/internal/get-all-directive-comments.js b/lib/internal/get-all-directive-comments.js index 8645a88..419bbae 100644 --- a/lib/internal/get-all-directive-comments.js +++ b/lib/internal/get-all-directive-comments.js @@ -19,22 +19,24 @@ const pool = new WeakMap() * @returns {DirectiveComment[]} The directive comments. */ function getAllDirectiveCommentsFromAllComments(sourceCode) { - /** @type {DirectiveComment[]} */ - const result = [] - for (const comment of sourceCode.getAllComments()) { - const directiveComment = utils.parseDirectiveComment(comment) - if (directiveComment != null) { - result.push({ - kind: directiveComment.kind, - value: directiveComment.value, - description: directiveComment.description, - node: comment, - range: comment.range, - loc: comment.loc, - }) - } - } - return result + return sourceCode + .getAllComments() + .map((comment) => ({ + comment, + directiveComment: utils.parseDirectiveComment(comment), + })) + .filter(({ directiveComment }) => Boolean(directiveComment)) + .map( + ({ comment, directiveComment }) => + /** @type {DirectiveComment} */ ({ + kind: directiveComment.kind, + value: directiveComment.value, + description: directiveComment.description, + node: comment, + range: comment.range, + loc: comment.loc, + }) + ) } /** @@ -74,10 +76,12 @@ function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { const directiveComment = utils.parseDirectiveText(commentValue) if ( directiveComment != null && - directiveComment.kind !== "eslint-disable" && - directiveComment.kind !== "eslint-disable-line" && - directiveComment.kind !== "eslint-disable-next-line" && - directiveComment.kind !== "eslint-enable" + ![ + "eslint-disable", + "eslint-disable-line", + "eslint-disable-next-line", + "eslint-enable", + ].includes(directiveComment.kind) ) { result.push({ kind: directiveComment.kind, @@ -123,15 +127,11 @@ module.exports = { let result = pool.get(sourceCode.ast) if (result == null) { - if ( + result = typeof sourceCode.getInlineConfigNodes === "function" && typeof sourceCode.getDisableDirectives === "function" - ) { - result = - getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) - } else { - result = getAllDirectiveCommentsFromAllComments(sourceCode) - } + ? getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) + : getAllDirectiveCommentsFromAllComments(sourceCode) pool.set(sourceCode.ast, result) } diff --git a/lib/internal/utils.js b/lib/internal/utils.js index a85e00c..bf88709 100644 --- a/lib/internal/utils.js +++ b/lib/internal/utils.js @@ -183,8 +183,7 @@ function divideDirectiveComment(value) { function getLoc(context, nodeOrToken) { const sourceCode = context.sourceCode || (context.getSourceCode && context.getSourceCode()) - if (sourceCode && typeof sourceCode.getLoc === "function") { - return sourceCode.getLoc(nodeOrToken) - } - return nodeOrToken.loc + return sourceCode && typeof sourceCode.getLoc === "function" + ? sourceCode.getLoc(nodeOrToken) + : nodeOrToken.loc } From 2e9a12b62ad2af021ada6be8104e757823714984 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Wed, 9 Apr 2025 10:58:42 +0900 Subject: [PATCH 7/8] fix typo --- lib/internal/get-all-directive-comments.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/get-all-directive-comments.js b/lib/internal/get-all-directive-comments.js index 419bbae..4054a47 100644 --- a/lib/internal/get-all-directive-comments.js +++ b/lib/internal/get-all-directive-comments.js @@ -109,7 +109,7 @@ function extractCommentContent(text) { : text.startsWith("") ? text.slice(4, -3) : text.startsWith("###") && text.endsWith("###") - ? text.slice(1) + ? text.slice(3, -3) : text.startsWith("#") ? text.slice(1) : text From 8a9ad958dea6a5196c4f0d234e3fe54e90ab1286 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Wed, 9 Apr 2025 18:37:31 +0900 Subject: [PATCH 8/8] refactor --- lib/internal/get-all-directive-comments.js | 112 ++++++++++++--------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/lib/internal/get-all-directive-comments.js b/lib/internal/get-all-directive-comments.js index 4054a47..f471c0a 100644 --- a/lib/internal/get-all-directive-comments.js +++ b/lib/internal/get-all-directive-comments.js @@ -44,58 +44,72 @@ function getAllDirectiveCommentsFromAllComments(sourceCode) { * @returns {DirectiveComment[]} The directive comments. */ function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { - /** @type {DirectiveComment[]} */ - const result = [] - const disableDirectives = sourceCode.getDisableDirectives() - for (const directive of disableDirectives.directives) { - result.push({ - kind: `eslint-${directive.type}`, - value: directive.value, - description: directive.justification, - node: directive.node, - range: sourceCode.getRange(directive.node), - get loc() { - return sourceCode.getLoc(directive.node) - }, - }) - } - for (const node of sourceCode.getInlineConfigNodes()) { - const range = sourceCode.getRange(node) - // The node has intersection of the directive comment. - // So, we need to skip it. - if ( - result.some( - (comment) => - comment.range[0] <= range[1] && range[0] <= comment.range[1] - ) - ) { - continue - } - const nodeText = sourceCode.text.slice(range[0], range[1]) - const commentValue = extractCommentContent(nodeText) - const directiveComment = utils.parseDirectiveText(commentValue) - if ( - directiveComment != null && - ![ - "eslint-disable", - "eslint-disable-line", - "eslint-disable-next-line", - "eslint-enable", - ].includes(directiveComment.kind) - ) { - result.push({ - kind: directiveComment.kind, - value: directiveComment.value, - description: directiveComment.description, - node, - range, + const result = sourceCode.getDisableDirectives().directives.map( + (directive) => + /** @type {DirectiveComment} */ ({ + kind: `eslint-${directive.type}`, + value: directive.value, + description: directive.justification, + node: directive.node, + range: sourceCode.getRange(directive.node), get loc() { - return sourceCode.getLoc(node) + return sourceCode.getLoc(directive.node) }, }) - } - } - return result + ) + + return result.concat( + sourceCode + .getInlineConfigNodes() + .map((node) => ({ + node, + range: sourceCode.getRange(node), + })) + .filter( + ({ range }) => + // The node has intersection of the directive comment. + // So, we need to skip it. + !result.some( + (comment) => + comment.range[0] <= range[1] && + range[0] <= comment.range[1] + ) + ) + .map(({ node, range }) => { + const nodeText = sourceCode.text.slice(range[0], range[1]) + const commentValue = extractCommentContent(nodeText) + const directiveComment = utils.parseDirectiveText(commentValue) + + return { + directiveComment, + node, + range, + } + }) + .filter( + ({ directiveComment }) => + directiveComment != null && + ![ + "eslint-disable", + "eslint-disable-line", + "eslint-disable-next-line", + "eslint-enable", + ].includes(directiveComment.kind) + ) + .map( + ({ directiveComment, node, range }) => + /** @type {DirectiveComment} */ ({ + kind: directiveComment.kind, + value: directiveComment.value, + description: directiveComment.description, + node, + range, + get loc() { + return sourceCode.getLoc(node) + }, + }) + ) + ) } function extractCommentContent(text) {