diff --git a/package.json b/package.json index d7e94dd73b..c054526dea 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "@eslint-community/eslint-utils": "^4.9.0", - "@eslint/plugin-kit": "^0.4.0", "change-case": "^5.4.4", "ci-info": "^4.3.1", "clean-regexp": "^1.0.0", diff --git a/rules/expiring-todo-comments.js b/rules/expiring-todo-comments.js index 5fe07c21f2..5f903242fc 100644 --- a/rules/expiring-todo-comments.js +++ b/rules/expiring-todo-comments.js @@ -2,7 +2,10 @@ import path from 'node:path'; import {isRegExp} from 'node:util/types'; import semver from 'semver'; import * as ci from 'ci-info'; -import getBuiltinRule from './utils/get-builtin-rule.js'; +import { + isEslintDisableOrEnableDirective, + getBuiltinRule, +} from './utils/index.js'; import {readPackageJson} from './shared/package-json.js'; const baseRule = getBuiltinRule('no-warning-comments'); @@ -285,7 +288,7 @@ const create = context => { const {sourceCode} = context; const comments = sourceCode.getAllComments(); const unusedComments = comments - .filter(token => token.type !== 'Shebang') + .filter(comment => comment.type !== 'Shebang' && !isEslintDisableOrEnableDirective(context, comment)) // Block comments come as one. // Split for situations like this: // /* diff --git a/rules/no-abusive-eslint-disable.js b/rules/no-abusive-eslint-disable.js index d665ed4f58..b3bf860e98 100644 --- a/rules/no-abusive-eslint-disable.js +++ b/rules/no-abusive-eslint-disable.js @@ -1,45 +1,31 @@ -import {ConfigCommentParser} from '@eslint/plugin-kit'; +import { + getEslintDisableDirectives, +} from './utils/index.js'; const MESSAGE_ID = 'no-abusive-eslint-disable'; const messages = { [MESSAGE_ID]: 'Specify the rules you want to disable.', }; -// https://github.com/eslint/eslint/blob/ecd0ede7fd2ccbb4c0daf0e4732e97ea0f49db1b/lib/linter/linter.js#L509-L512 -const eslintDisableDirectives = new Set([ - 'eslint-disable', - 'eslint-disable-line', - 'eslint-disable-next-line', -]); - -let commentParser; /** @param {import('eslint').Rule.RuleContext} context */ const create = context => { - context.on('Program', function * (node) { - for (const comment of node.comments) { - commentParser ??= new ConfigCommentParser(); - const result = commentParser.parseDirective(comment.value); - - if (!( - // It's a eslint-disable comment - eslintDisableDirectives.has(result?.label) - // But it did not specify any rules - && !result?.value - )) { - return; + context.on('Program', function * () { + for (const directive of getEslintDisableDirectives(context)) { + if (directive.value) { + continue; } - const {sourceCode} = context; + const {start, end} = context.sourceCode.getLoc(directive.node); yield { // Can't set it at the given location as the warning // will be ignored due to the disable comment loc: { start: { - ...sourceCode.getLoc(comment).start, + ...start, column: -1, }, - end: sourceCode.getLoc(comment).end, + end, }, messageId: MESSAGE_ID, }; diff --git a/rules/utils/eslint-directive.js b/rules/utils/eslint-directive.js new file mode 100644 index 0000000000..29e81364d4 --- /dev/null +++ b/rules/utils/eslint-directive.js @@ -0,0 +1,21 @@ +// https://github.com/eslint/eslint/blob/df5566f826d9f5740546e473aa6876b1f7d2f12c/lib/languages/js/source-code/source-code.js#L914-L917 +const ESLINT_DISABLE_DIRECTIVE_TYPES = new Set([ + 'disable', + 'disable-next-line', + 'disable-line', +]); + +function getEslintDisableDirectives(context) { + const {directives} = context.sourceCode.getDisableDirectives(); + return directives.filter(({type}) => ESLINT_DISABLE_DIRECTIVE_TYPES.has(type)); +} + +function isEslintDisableOrEnableDirective(context, comment) { + const {directives} = context.sourceCode.getDisableDirectives(); + return directives.some(directive => directive.node === comment); +} + +export { + getEslintDisableDirectives, + isEslintDisableOrEnableDirective, +}; diff --git a/rules/utils/index.js b/rules/utils/index.js index 424a06a2f1..7792657351 100644 --- a/rules/utils/index.js +++ b/rules/utils/index.js @@ -49,6 +49,10 @@ export {default as isShorthandImportLocal} from './is-shorthand-import-local.js' export {default as isShorthandPropertyValue} from './is-shorthand-property-value.js'; export {default as isValueNotUsable} from './is-value-not-usable.js'; export {default as needsSemicolon} from './needs-semicolon.js'; +export { + getEslintDisableDirectives, + isEslintDisableOrEnableDirective, +} from './eslint-directive.js'; export {checkVueTemplate} from './rule.js'; export {default as shouldAddParenthesesToAwaitExpressionArgument} from './should-add-parentheses-to-await-expression-argument.js'; export {default as shouldAddParenthesesToCallExpressionCallee} from './should-add-parentheses-to-call-expression-callee.js'; @@ -64,3 +68,4 @@ export {default as getAncestor} from './get-ancestor.js'; export {getPreviousNode, getNextNode} from './get-sibling-node.js'; export * from './string-cases.js'; export * from './numeric.js'; +export {default as getBuiltinRule} from './get-builtin-rule.js'; diff --git a/test/expiring-todo-comments.js b/test/expiring-todo-comments.js index 57728e3cb6..9c81b9124e 100644 --- a/test/expiring-todo-comments.js +++ b/test/expiring-todo-comments.js @@ -114,6 +114,18 @@ test({ code: '// TODO [2001-01-01]: quite old', options: [{date: '2000-01-01'}], }, + { + code: `// eslint-disable-next-line rule-to-test/expiring-todo-comments + // TODO without a date`, + options: [{allowWarningComments: false}], + }, + { + code: `/* eslint-disable rule-to-test/expiring-todo-comments */ + // TODO without a date + // fixme [2000-01-01]: too old' + /* eslint-enable rule-to-test/expiring-todo-comments */`, + options: [{allowWarningComments: false}], + }, ], invalid: [ { diff --git a/test/no-abusive-eslint-disable.js b/test/no-abusive-eslint-disable.js index aba5ace11f..e5a8e1fc9c 100644 --- a/test/no-abusive-eslint-disable.js +++ b/test/no-abusive-eslint-disable.js @@ -96,5 +96,11 @@ test.snapshot({ // eslint-disable-next-line -- reason eval(); `, + outdent` + // eslint-disable-next-line no-eval + eval(); + // eslint-disable-next-line + eval(); + `, ], }); diff --git a/test/snapshots/no-abusive-eslint-disable.js.md b/test/snapshots/no-abusive-eslint-disable.js.md index 830a7f6f2b..6805e488ac 100644 --- a/test/snapshots/no-abusive-eslint-disable.js.md +++ b/test/snapshots/no-abusive-eslint-disable.js.md @@ -129,3 +129,25 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Specify the rules you want to disable.␊ 2 | eval();␊ ` + +## invalid(8): // eslint-disable-next-line no-eval eval(); // eslint-disable-next-line eval(); + +> Input + + `␊ + 1 | // eslint-disable-next-line no-eval␊ + 2 | eval();␊ + 3 | // eslint-disable-next-line␊ + 4 | eval();␊ + ` + +> Error 1/1 + + `␊ + Message:␊ + 1 | // eslint-disable-next-line no-eval␊ + 2 | eval();␊ + > 3 | // eslint-disable-next-line␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Specify the rules you want to disable.␊ + 4 | eval();␊ + ` diff --git a/test/snapshots/no-abusive-eslint-disable.js.snap b/test/snapshots/no-abusive-eslint-disable.js.snap index d8238e7fb6..20e0bb1397 100644 Binary files a/test/snapshots/no-abusive-eslint-disable.js.snap and b/test/snapshots/no-abusive-eslint-disable.js.snap differ