diff --git a/.eslintrc.yml b/.eslintrc.yml index c72b072..9f8befb 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -2,22 +2,62 @@ extends: - plugin:@eslint-community/mysticatea/es2015 - plugin:@eslint-community/mysticatea/+eslint-plugin +rules: + "@eslint-community/mysticatea/node/file-extension-in-import": + - error + - always + - ".ts": "always" + "@eslint-community/mysticatea/ts/naming-convention": + - off + "@eslint-community/mysticatea/ts/prefer-readonly-parameter-types": + - off + "@eslint-community/mysticatea/ts/no-unsafe-member-access": + - off + "@eslint-community/mysticatea/ts/no-var-requires": + - off + "@eslint-community/mysticatea/ts/no-require-imports": + - off + "@eslint-community/mysticatea/ts/no-unsafe-assignment": + - off + "@eslint-community/mysticatea/ts/no-unsafe-call": + - off + "@eslint-community/mysticatea/ts/no-unsafe-argument": + - off + "@eslint-community/mysticatea/ts/no-unsafe-return": + - off + "@eslint-community/mysticatea/ts/ban-ts-comment": + - off + "@eslint-community/mysticatea/ts/no-non-null-asserted-optional-chain": + - off + "@eslint-community/mysticatea/ts/prefer-optional-chain": + - off + "@eslint-community/mysticatea/ts/prefer-nullish-coalescing": + - off + "@eslint-community/mysticatea/ts/no-floating-promises": + - off + "@eslint-community/mysticatea/ts/no-unnecessary-type-assertion": + - off + "no-shadow": + - off + "@eslint-community/mysticatea/node/no-missing-import": off + "@eslint-community/mysticatea/node/no-unpublished-import": off + overrides: - files: "docs/.vuepress/components/*.vue" parserOptions: parser: "@babel/eslint-parser" - - files: "lib/rules/*.js" + - files: "lib/rules/*.ts" rules: "@eslint-community/mysticatea/eslint-plugin/require-meta-docs-url": - error - pattern: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/{{name}}.html" - - files: ["lib/configs.js", "lib/rules.js", "lib/utils.js"] + - files: ["lib/configs.ts", "lib/rules.ts", "lib/utils.ts"] rules: "@eslint-community/mysticatea/node/global-require": off - - files: ["tests/**/*.js", "scripts/**/*.js"] + - files: ["tests/**/*.ts", "scripts/**/*.ts"] rules: "@eslint-community/mysticatea/node/global-require": off "@eslint-community/mysticatea/node/no-sync": off diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f9e4ee..9d2677e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: โŽ” Setup Node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22 - name: ๐Ÿ“ฅ Install dependencies run: npm install @@ -43,33 +43,32 @@ jobs: run: npm run lint test: - name: - ๐Ÿงช Test (Node@${{ matrix.node }} - ESLint@${{ matrix.eslint }} - ${{ + name: ๐Ÿงช Test (Node@${{ matrix.node }} - ESLint@${{ matrix.eslint }} - ${{ matrix.os }}) strategy: fail-fast: false matrix: eslint: [8] - node: [12.22.0, 12, 14.17.0, 14, 16.0.0, 16, 18.0.0, 18, 20, 22, 24] + node: [22, 24] os: [ubuntu-latest] include: # On other platforms - os: windows-latest eslint: 8 - node: 18 + node: 22 - os: macos-latest eslint: 8 - node: 18 + node: 22 # On ESLint 9 - eslint: 9 - node: 18 + node: 22 os: ubuntu-latest # On old ESLint versions - eslint: 7 - node: 18 + node: 22 os: ubuntu-latest - eslint: 6 - node: 18 + node: 22 os: ubuntu-latest # On the minimum supported ESLint/Node.js version - eslint: 6.0.0 @@ -84,25 +83,94 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} + check-latest: true - name: ๐Ÿ“ฅ Install dependencies run: npm install --legacy-peer-deps + - name: ๐Ÿ“ฅ Build + run: npm run build + - name: ๐Ÿ“ฅ Install ESLint v${{ matrix.eslint }} run: npm install --save-dev eslint@${{ matrix.eslint }} - name: โ–ถ๏ธ Run test script + env: + NODE_OPTIONS: --experimental-transform-types run: npm run test - name: โฌ†๏ธ Upload coverage report uses: codecov/codecov-action@v4 + are-the-types-wrong: + name: ๐Ÿค” Are the types wrong? + runs-on: ubuntu-latest + steps: + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v4 + + - name: โŽ” Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: ๐Ÿ“ฅ Install dependencies + run: npm install --legacy-peer-deps + + - name: โ–ถ๏ธ Run check-exports script + run: npm run check-exports + + - name: โ–ถ๏ธ Run typecheck script + run: npm run typecheck + + type-tests: + name: ๐Ÿงช Type tests with ESLint ${{ matrix.eslint }} and TypeScript ${{ matrix.ts }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + eslint: [8, 9] + ts: ["5.7", "5.8", "5.9"] + + steps: + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v4 + + - name: โŽ” Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: ๐Ÿ“ฅ Install dependencies + run: npm install + + - name: Pack the package + id: pack + run: npm install "$(npm pack | tail -n1)" + + - name: ๐Ÿ“ฅ Uninstall @types/eslint + if: matrix.eslint != 8 + run: npm uninstall @types/eslint + + - name: ๐Ÿ“ฅ Install ESLint version ${{ matrix.eslint }} + run: npm install --save-dev eslint@${{ matrix.eslint }} + + - name: ๐Ÿ“ฅ Install TypeScript version ${{ matrix.ts }} + run: npm install --save-dev typescript@${{ matrix.ts }} -f + + - name: โ–ถ๏ธ Run typecheck script + run: npm run typecheck + + - name: ๐Ÿ“ List version of ESLint + run: npm why eslint @types/eslint + release: name: ๐Ÿš€ Release - needs: [ lint, test ] + needs: [lint, test] runs-on: ubuntu-latest - if: - github.repository == 'eslint-community/eslint-plugin-eslint-comments' && + if: github.repository == 'eslint-community/eslint-plugin-eslint-comments' && contains('refs/heads/main,refs/heads/next,refs/heads/beta,refs/heads/alpha', github.ref) && github.event_name == 'push' steps: diff --git a/.gitignore b/.gitignore index 5303a54..5556b2c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /node_modules /npm-debug.log /test.js +dist/ diff --git a/configs.js b/configs.js deleted file mode 100644 index 7e0bd30..0000000 --- a/configs.js +++ /dev/null @@ -1,22 +0,0 @@ -"use strict" - -const { rules: rulesRecommended } = require("./lib/configs/recommended") -const rules = require("./lib/rules") -const { name, version } = require("./package.json") - -const plugin = { - meta: { name, version }, - rules, -} - -module.exports = { - recommended: { - name: '@eslint-community/eslint-comments/recommended', - plugins: { - "@eslint-community/eslint-comments": plugin, - }, - rules: rulesRecommended, - }, -} - -module.exports.default = module.exports diff --git a/configs.ts b/configs.ts new file mode 100644 index 0000000..9c9a073 --- /dev/null +++ b/configs.ts @@ -0,0 +1,64 @@ +import type { ESLint, Linter, Rule } from "eslint" +import { rulesRecommended } from "./lib/configs/recommended.ts" +import { rules } from "./lib/rules.ts" +import packageJson from "./package.json" with { type: "json" } + +const plugin: { + meta: { + name: string + version: string + } + rules: { + "disable-enable-pair": Rule.RuleModule + "no-aggregating-enable": Rule.RuleModule + "no-duplicate-disable": Rule.RuleModule + "no-restricted-disable": Rule.RuleModule + "no-unlimited": Rule.RuleModule + "no-unused-disable": Rule.RuleModule + "no-unused-enable": Rule.RuleModule + "no-use": Rule.RuleModule + "require-description": Rule.RuleModule + } +} = { + meta: { + name: packageJson.name, + version: packageJson.version, + }, + rules, +} as const satisfies ESLint.Plugin + +export const recommended: { + name: "@eslint-community/eslint-comments/recommended" + plugins: { + "@eslint-community/eslint-comments": { + meta: { + name: string + version: string + } + rules: { + "disable-enable-pair": Rule.RuleModule + "no-aggregating-enable": Rule.RuleModule + "no-duplicate-disable": Rule.RuleModule + "no-restricted-disable": Rule.RuleModule + "no-unlimited": Rule.RuleModule + "no-unused-disable": Rule.RuleModule + "no-unused-enable": Rule.RuleModule + "no-use": Rule.RuleModule + "require-description": Rule.RuleModule + } + } + } + rules: { + "@eslint-community/eslint-comments/disable-enable-pair": "error" + "@eslint-community/eslint-comments/no-aggregating-enable": "error" + "@eslint-community/eslint-comments/no-duplicate-disable": "error" + "@eslint-community/eslint-comments/no-unlimited-disable": "error" + "@eslint-community/eslint-comments/no-unused-enable": "error" + } +} = { + name: "@eslint-community/eslint-comments/recommended", + plugins: { + "@eslint-community/eslint-comments": plugin, + }, + rules: rulesRecommended, +} as const satisfies Linter.FlatConfig diff --git a/docs/.vitepress/public/service-worker.js b/docs/.vitepress/public/service-worker.ts similarity index 100% rename from docs/.vitepress/public/service-worker.js rename to docs/.vitepress/public/service-worker.ts diff --git a/index.js b/index.js deleted file mode 100644 index 5ace35b..0000000 --- a/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** DON'T EDIT THIS FILE WHICH WAS CREATED BY 'scripts/generate-index.js'. */ -"use strict" - -module.exports = { - configs: require("./lib/configs"), - rules: require("./lib/rules"), - utils: require("./lib/utils"), -} diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..9fe428c --- /dev/null +++ b/index.ts @@ -0,0 +1,3 @@ +export * as configs from "./lib/configs.ts" +export { rules } from "./lib/rules.ts" +export * as utils from "./lib/utils.ts" diff --git a/lib/configs.js b/lib/configs.js deleted file mode 100644 index 4a330b4..0000000 --- a/lib/configs.js +++ /dev/null @@ -1,6 +0,0 @@ -/** DON'T EDIT THIS FILE; was created by scripts. */ -"use strict" - -module.exports = { - recommended: require("./configs/recommended"), -} diff --git a/lib/configs.ts b/lib/configs.ts new file mode 100644 index 0000000..3e512d1 --- /dev/null +++ b/lib/configs.ts @@ -0,0 +1 @@ +export * as recommended from "./configs/recommended.ts" diff --git a/lib/configs/recommended.js b/lib/configs/recommended.js deleted file mode 100644 index 49bad50..0000000 --- a/lib/configs/recommended.js +++ /dev/null @@ -1,13 +0,0 @@ -/** DON'T EDIT THIS FILE; was created by scripts. */ -"use strict" - -module.exports = { - plugins: ["@eslint-community/eslint-comments"], - rules: { - "@eslint-community/eslint-comments/disable-enable-pair": "error", - "@eslint-community/eslint-comments/no-aggregating-enable": "error", - "@eslint-community/eslint-comments/no-duplicate-disable": "error", - "@eslint-community/eslint-comments/no-unlimited-disable": "error", - "@eslint-community/eslint-comments/no-unused-enable": "error", - }, -} diff --git a/lib/configs/recommended.ts b/lib/configs/recommended.ts new file mode 100644 index 0000000..aee96fa --- /dev/null +++ b/lib/configs/recommended.ts @@ -0,0 +1,20 @@ +import type { BaseConfig, RulesConfig } from "@eslint/core" +import type { Linter } from "eslint" + +export const plugins: string[] = [ + "@eslint-community/eslint-comments", +] as const satisfies BaseConfig["plugins"] + +export const rulesRecommended: { + "@eslint-community/eslint-comments/disable-enable-pair": "error" + "@eslint-community/eslint-comments/no-aggregating-enable": "error" + "@eslint-community/eslint-comments/no-duplicate-disable": "error" + "@eslint-community/eslint-comments/no-unlimited-disable": "error" + "@eslint-community/eslint-comments/no-unused-enable": "error" +} = { + "@eslint-community/eslint-comments/disable-enable-pair": "error", + "@eslint-community/eslint-comments/no-aggregating-enable": "error", + "@eslint-community/eslint-comments/no-duplicate-disable": "error", + "@eslint-community/eslint-comments/no-unlimited-disable": "error", + "@eslint-community/eslint-comments/no-unused-enable": "error", +} as const satisfies Linter.RulesRecord satisfies RulesConfig diff --git a/lib/internal/disabled-area.js b/lib/internal/disabled-area.ts similarity index 63% rename from lib/internal/disabled-area.js rename to lib/internal/disabled-area.ts index f7dca6a..6a44b3d 100644 --- a/lib/internal/disabled-area.js +++ b/lib/internal/disabled-area.ts @@ -2,17 +2,38 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { AST, Linter, Rule, SourceCode } from "eslint" +import * as utils from "./utils.ts" -const utils = require("./utils") const DELIMITER = /[\s,]+/gu -const pool = new WeakMap() +const pool = new WeakMap< + AST.Program, + DisabledAreaForLanguagePlugin | DisabledAreaForLegacy +>() class DisabledArea { + public areas: (AST.SourceLocation & + NonNullable> & { + comment: AST.Program["comments"][number] + kind: string + })[] + public duplicateDisableDirectives: { + comment: AST.Program["comments"][number] + ruleId: string | null + }[] + public unusedEnableDirectives: { + comment: AST.Program["comments"][number] + ruleId: string | null + }[] + public numberOfRelatedDisableDirectives: Map< + AST.Program["comments"][number], + number + > + /** * Constructor. */ - constructor() { + public constructor() { this.areas = [] this.duplicateDisableDirectives = [] this.unusedEnableDirectives = [] @@ -29,7 +50,12 @@ class DisabledArea { * @returns {void} * @protected */ - _disable(comment, location, ruleIds, kind) { + protected _disable( + comment: AST.Program["comments"][number], + location: AST.SourceLocation["start"], + ruleIds: string[] | null, + kind: Lowercase, + ): void { if (ruleIds) { for (const ruleId of ruleIds) { if (this._getArea(ruleId, location) != null) { @@ -41,7 +67,7 @@ class DisabledArea { ruleId, kind, start: location, - end: null, + end: null as any, }) } } else { @@ -54,7 +80,7 @@ class DisabledArea { ruleId: null, kind, start: location, - end: null, + end: null as any, }) } } @@ -69,7 +95,12 @@ class DisabledArea { * @returns {void} * @protected */ - _enable(comment, location, ruleIds, kind) { + protected _enable( + comment: AST.Program["comments"][number], + location: AST.SourceLocation["start"], + ruleIds: string[] | null, + kind: Lowercase, + ): void { const relatedDisableDirectives = new Set() if (ruleIds) { @@ -114,7 +145,7 @@ class DisabledArea { this.numberOfRelatedDisableDirectives.set( comment, - relatedDisableDirectives.size + relatedDisableDirectives.size, ) } @@ -126,7 +157,16 @@ class DisabledArea { * @returns {object|null} The area of the given ruleId and location. * @private */ - _getArea(ruleId, location) { + private _getArea( + ruleId: string | null, + location: AST.SourceLocation["start"], + ): + | (AST.SourceLocation & + Pick & { + comment: AST.Program["comments"][number] + kind: string + }) + | null { for (let i = this.areas.length - 1; i >= 0; --i) { const area = this.areas[i] @@ -150,9 +190,9 @@ class DisabledAreaForLanguagePlugin extends DisabledArea { * @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) { + public _scan(sourceCode: SourceCode): void { + const disableDirectives = (sourceCode as any).getDisableDirectives?.()! + for (const directive of disableDirectives!.directives) { if ( ![ "disable", @@ -167,25 +207,32 @@ class DisabledAreaForLanguagePlugin extends DisabledArea { ? directive.value.split(DELIMITER) : null - const loc = sourceCode.getLoc(directive.node) + const loc: AST.SourceLocation = (sourceCode as any).getLoc( + directive.node, + ) if (directive.type === "disable") { - this._disable(directive.node, loc.start, ruleIds, "block") + this._disable( + directive.node as any, + loc.start, + ruleIds, + "block", + ) } else if (directive.type === "enable") { - this._enable(directive.node, loc.start, ruleIds, "block") + this._enable(directive.node as any, loc.start, ruleIds, "block") } else if (directive.type === "disable-line") { - const line = loc.start.line + const { line } = loc.start 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") + this._disable(directive.node as any, start, ruleIds, "line") + this._enable(directive.node as any, end, ruleIds, "line") } else if (directive.type === "disable-next-line") { - const line = loc.start.line + const { line } = loc.start 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") + this._disable(directive.node as any, start, ruleIds, "line") + this._enable(directive.node as any, end, ruleIds, "line") } } } @@ -198,14 +245,16 @@ class DisabledAreaForLegacy extends DisabledArea { * @param {eslint.SourceCode} sourceCode - The source code to scan. * @returns {void} */ - _scan(sourceCode) { - for (const comment of sourceCode.getAllComments()) { + public _scan(sourceCode: SourceCode): void { + for (const comment of ( + sourceCode as any + ).getAllComments() as AST.Program["comments"]) { const directiveComment = utils.parseDirectiveComment(comment) if (directiveComment == null) { continue } - const kind = directiveComment.kind + const { kind } = directiveComment if ( ![ "eslint-disable", @@ -221,18 +270,18 @@ class DisabledAreaForLegacy extends DisabledArea { : null if (kind === "eslint-disable") { - this._disable(comment, comment.loc.start, ruleIds, "block") + this._disable(comment, comment.loc!.start, ruleIds, "block") } else if (kind === "eslint-enable") { - this._enable(comment, comment.loc.start, ruleIds, "block") + this._enable(comment, comment.loc!.start, ruleIds, "block") } else if (kind === "eslint-disable-line") { - const line = comment.loc.start.line + const { line } = comment.loc!.start const start = { line, column: 0 } const end = { line: line + 1, column: -1 } this._disable(comment, start, ruleIds, "line") this._enable(comment, end, ruleIds, "line") } else if (kind === "eslint-disable-next-line") { - const line = comment.loc.start.line + const { line } = comment.loc!.start const start = { line: line + 1, column: 0 } const end = { line: line + 2, column: -1 } @@ -243,26 +292,24 @@ class DisabledAreaForLegacy extends 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) +/** + * 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. + */ +export function getDisabledArea(context: Rule.RuleContext): DisabledArea { + const sourceCode = context.sourceCode || context.getSourceCode() + let retv = pool.get(sourceCode.ast) - if (retv == null) { - retv = - typeof sourceCode.getDisableDirectives === "function" - ? new DisabledAreaForLanguagePlugin() - : new DisabledAreaForLegacy() - retv._scan(sourceCode) - pool.set(sourceCode.ast, retv) - } + if (retv == null) { + retv = + typeof (sourceCode as any).getDisableDirectives === "function" + ? new DisabledAreaForLanguagePlugin() + : new DisabledAreaForLegacy() + retv._scan(sourceCode) + pool.set(sourceCode.ast, retv) + } - return retv - }, + return retv } diff --git a/lib/internal/get-all-directive-comments.js b/lib/internal/get-all-directive-comments.ts similarity index 54% rename from lib/internal/get-all-directive-comments.js rename to lib/internal/get-all-directive-comments.ts index f471c0a..a2d0556 100644 --- a/lib/internal/get-all-directive-comments.js +++ b/lib/internal/get-all-directive-comments.ts @@ -1,16 +1,40 @@ -"use strict" +import type { + RuleContext, + RuleContextTypeOptions, + SourceCodeBaseTypeOptions, + SourceLocation, + SourceRange, + TextSourceCode, +} from "@eslint/core" +import type { AST, SourceCode } from "eslint" +import * as utils from "./utils.ts" -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. - */ +interface DirectiveComment { + /** + * The kind of directive comment. + */ + kind: string + /** + * The directive value if it is `eslint-` comment. + */ + value?: string + /** + * The description of the directive comment. + */ + description: string + /** + * The node of the directive comment. + */ + node: object + /** + * The range of the directive comment. + */ + range: SourceRange + /** + * The location of the directive comment. + */ + loc: SourceLocation +} const pool = new WeakMap() @@ -18,7 +42,9 @@ const pool = new WeakMap() * @param {import('eslint').SourceCode} sourceCode - The source code to scan. * @returns {DirectiveComment[]} The directive comments. */ -function getAllDirectiveCommentsFromAllComments(sourceCode) { +function getAllDirectiveCommentsFromAllComments( + sourceCode: SourceCode, +): DirectiveComment[] { return sourceCode .getAllComments() .map((comment) => ({ @@ -28,14 +54,14 @@ function getAllDirectiveCommentsFromAllComments(sourceCode) { .filter(({ directiveComment }) => Boolean(directiveComment)) .map( ({ comment, directiveComment }) => - /** @type {DirectiveComment} */ ({ - kind: directiveComment.kind, - value: directiveComment.value, - description: directiveComment.description, + ({ + kind: directiveComment!.kind, + value: directiveComment!.value, + description: directiveComment!.description, node: comment, range: comment.range, loc: comment.loc, - }) + } as DirectiveComment), ) } @@ -43,10 +69,12 @@ function getAllDirectiveCommentsFromAllComments(sourceCode) { * @param {import('@eslint/core').TextSourceCode} sourceCode - The source code to scan. * @returns {DirectiveComment[]} The directive comments. */ -function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { - const result = sourceCode.getDisableDirectives().directives.map( +function getAllDirectiveCommentsFromInlineConfigNodes( + sourceCode: TextSourceCode, +) { + const result = sourceCode.getDisableDirectives!()!.directives.map( (directive) => - /** @type {DirectiveComment} */ ({ + ({ kind: `eslint-${directive.type}`, value: directive.value, description: directive.justification, @@ -55,12 +83,11 @@ function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { get loc() { return sourceCode.getLoc(directive.node) }, - }) + } as DirectiveComment), ) return result.concat( - sourceCode - .getInlineConfigNodes() + sourceCode.getInlineConfigNodes!()! .map((node) => ({ node, range: sourceCode.getRange(node), @@ -72,8 +99,8 @@ function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { !result.some( (comment) => comment.range[0] <= range[1] && - range[0] <= comment.range[1] - ) + range[0] <= comment.range[1], + ), ) .map(({ node, range }) => { const nodeText = sourceCode.text.slice(range[0], range[1]) @@ -94,25 +121,25 @@ function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { "eslint-disable-line", "eslint-disable-next-line", "eslint-enable", - ].includes(directiveComment.kind) + ].includes(directiveComment.kind), ) .map( ({ directiveComment, node, range }) => - /** @type {DirectiveComment} */ ({ - kind: directiveComment.kind, - value: directiveComment.value, - description: directiveComment.description, + ({ + kind: directiveComment!.kind, + value: directiveComment!.value, + description: directiveComment!.description, node, range, get loc() { return sourceCode.getLoc(node) }, - }) - ) + } as DirectiveComment), + ), ) } -function extractCommentContent(text) { +function extractCommentContent(text: string): string { // 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 @@ -129,26 +156,32 @@ function extractCommentContent(text) { : text } -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) { - result = - typeof sourceCode.getInlineConfigNodes === "function" && - typeof sourceCode.getDisableDirectives === "function" - ? getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) - : getAllDirectiveCommentsFromAllComments(sourceCode) - pool.set(sourceCode.ast, result) +/** + * 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. + */ +export function getAllDirectiveComments( + context: RuleContext< + RuleContextTypeOptions & { + Code: TextSourceCode< + SourceCodeBaseTypeOptions & { RootNode: AST.Program } + > } + >, +): DirectiveComment[] { + const sourceCode = context.sourceCode || (context as any).getSourceCode() + let result = pool.get(sourceCode.ast) + + if (result == null) { + result = + typeof sourceCode.getInlineConfigNodes === "function" && + typeof sourceCode.getDisableDirectives === "function" + ? getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) + : getAllDirectiveCommentsFromAllComments(sourceCode as any) + pool.set(sourceCode.ast, result) + } - return result - }, + return result } diff --git a/lib/internal/get-linters.js b/lib/internal/get-linters.ts similarity index 63% rename from lib/internal/get-linters.js rename to lib/internal/get-linters.ts index c85ecf3..8d11af3 100644 --- a/lib/internal/get-linters.js +++ b/lib/internal/get-linters.ts @@ -2,29 +2,30 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - -const path = require("path") +import type { Linter } from "eslint" +import { createRequire } from "node:module" +import * as path from "node:path" const needle = `${path.sep}node_modules${path.sep}eslint${path.sep}` -module.exports = () => { +const require = createRequire(__filename) + +const getLinters = (): (typeof Linter)[] => { const eslintPaths = new Set( Object.keys(require.cache) .filter((id) => id.includes(needle)) - .map((id) => id.slice(0, id.indexOf(needle) + needle.length)) + .map((id) => id.slice(0, id.indexOf(needle) + needle.length)), ) const linters = [] for (const eslintPath of eslintPaths) { try { - // eslint-disable-next-line @eslint-community/mysticatea/node/global-require - const linter = require(eslintPath).Linter + const linter: typeof Linter = require(eslintPath).Linter if (linter) { linters.push(linter) } } catch (error) { - if (error.code !== "MODULE_NOT_FOUND") { + if ((error as any).code !== "MODULE_NOT_FOUND") { throw error } } @@ -32,3 +33,5 @@ module.exports = () => { return linters } + +export default getLinters diff --git a/lib/internal/utils.js b/lib/internal/utils.js deleted file mode 100644 index bf88709..0000000 --- a/lib/internal/utils.js +++ /dev/null @@ -1,189 +0,0 @@ -/** - * @author Toru Nagashima - * See LICENSE file in root directory for full license. - */ -"use strict" - -const escapeStringRegexp = require("escape-string-regexp") -const LINE_PATTERN = /[^\r\n\u2028\u2029]*(?:\r\n|[\r\n\u2028\u2029]|$)/gu - -const DIRECTIVE_PATTERN = - /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u -const LINE_COMMENT_PATTERN = /^eslint-disable-(next-)?line$/u - -module.exports = { - /** - * Make the location ignoring `eslint-disable` comments. - * - * @param {object} location - The location to convert. - * @returns {object} Converted location. - */ - toForceLocation(location) { - return { - start: { - line: location.start.line, - column: -1, - }, - end: location.end, - } - }, - - /** - * 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(context, comment, ruleId) { - const commentLoc = getLoc(context, comment) - if (ruleId == null) { - return module.exports.toForceLocation(commentLoc) - } - - const lines = comment.value.match(LINE_PATTERN) - //eslint-disable-next-line require-unicode-regexp - const ruleIdPattern = new RegExp( - `([\\s,]|^)${escapeStringRegexp(ruleId)}(?:[\\s,]|$)` - ) - - { - const m = ruleIdPattern.exec(lines[0]) - if (m != null) { - const start = commentLoc.start - return { - start: { - line: start.line, - column: 2 + start.column + m.index + m[1].length, - }, - end: { - line: start.line, - column: - 2 + - start.column + - m.index + - m[1].length + - ruleId.length, - }, - } - } - } - - for (let i = 1; i < lines.length; ++i) { - const m = ruleIdPattern.exec(lines[i]) - if (m != null) { - const start = commentLoc.start - return { - start: { - line: start.line + i, - column: m.index + m[1].length, - }, - end: { - line: start.line + i, - column: m.index + m[1].length + ruleId.length, - }, - } - } - } - - /*istanbul ignore next : foolproof */ - return commentLoc - }, - - /** - * Checks `a` is less than `b` or `a` equals `b`. - * - * @param {{line: number, column: number}} a - A location to compare. - * @param {{line: number, column: number}} b - Another location to compare. - * @returns {boolean} `true` if `a` is less than `b` or `a` equals `b`. - */ - lte(a, b) { - return a.line < b.line || (a.line === b.line && a.column <= b.column) - }, - - /** - * Parse the given comment token as a directive comment. - * - * @param {Token} comment - The comment token 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. - */ - parseDirectiveComment(comment) { - const parsed = parseDirectiveText(comment.value) - if (!parsed) { - return null - } - - const lineCommentSupported = LINE_COMMENT_PATTERN.test(parsed.kind) - - if (comment.type === "Line" && !lineCommentSupported) { - return null - } - - if ( - parsed.kind === "eslint-disable-line" && - comment.loc.start.line !== comment.loc.end.line - ) { - // disable-line comment should not span multiple lines. - return null - } - - return parsed - }, - parseDirectiveText, - - getLoc, -} - -/** - * 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, - } -} - -/** - * Divides and trims description text and directive comments. - * @param {string} value The comment text to strip. - * @returns {{text: string, description: string | null}} The stripped text. - */ -function divideDirectiveComment(value) { - const divided = value.split(/\s-{2,}\s/u) - const text = divided[0].trim() - return { - text, - 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 && context.getSourceCode()) - return sourceCode && typeof sourceCode.getLoc === "function" - ? sourceCode.getLoc(nodeOrToken) - : nodeOrToken.loc -} diff --git a/lib/internal/utils.ts b/lib/internal/utils.ts new file mode 100644 index 0000000..2e10749 --- /dev/null +++ b/lib/internal/utils.ts @@ -0,0 +1,203 @@ +/** + * @author Toru Nagashima + * See LICENSE file in root directory for full license. + */ +import escapeStringRegexp from "escape-string-regexp" +import type { AST, Linter, Rule } from "eslint" + +const LINE_PATTERN = /[^\r\n\u2028\u2029]*(?:\r\n|[\r\n\u2028\u2029]|$)/gu + +const DIRECTIVE_PATTERN = + /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u +const LINE_COMMENT_PATTERN = /^eslint-disable-(next-)?line$/u + +/** + * Make the location ignoring `eslint-disable` comments. + * + * @param {object} location - The location to convert. + * @returns {object} Converted location. + */ +export function toForceLocation( + location: AST.SourceLocation, +): AST.SourceLocation { + return { + start: { + line: location.start.line, + column: -1, + }, + end: location.end, + } +} + +/** + * 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. + */ +export function toRuleIdLocation( + context: Rule.RuleContext, + comment: AST.Program["comments"][number], + ruleId: string | null | undefined, +): AST.SourceLocation | null | undefined { + const commentLoc = getLoc(context, comment) + if (ruleId == null) { + return toForceLocation(commentLoc) + } + + const lines = comment.value.match(LINE_PATTERN)! + //eslint-disable-next-line require-unicode-regexp + const ruleIdPattern = new RegExp( + `([\\s,]|^)${escapeStringRegexp(ruleId)}(?:[\\s,]|$)`, + ) + + { + const m = ruleIdPattern.exec(lines[0]) + if (m != null) { + const { start } = commentLoc + return { + start: { + line: start.line, + column: 2 + start.column + m.index + m[1].length, + }, + end: { + line: start.line, + column: + 2 + + start.column + + m.index + + m[1].length + + ruleId.length, + }, + } + } + } + + for (let i = 1; i < lines.length; ++i) { + const m = ruleIdPattern.exec(lines[i]) + if (m != null) { + const { start } = commentLoc + return { + start: { + line: start.line + i, + column: m.index + m[1].length, + }, + end: { + line: start.line + i, + column: m.index + m[1].length + ruleId.length, + }, + } + } + } + + /*istanbul ignore next : foolproof */ + return commentLoc +} + +/** + * Checks `a` is less than `b` or `a` equals `b`. + * + * @param {{line: number, column: number}} a - A location to compare. + * @param {{line: number, column: number}} b - Another location to compare. + * @returns {boolean} `true` if `a` is less than `b` or `a` equals `b`. + */ +export function lte( + a: Pick, + b: Pick, +): boolean { + return a.line < b.line || (a.line === b.line && a.column <= b.column) +} + +/** + * Parse the given comment token as a directive comment. + * + * @param {Token} comment - The comment token 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. + */ +export function parseDirectiveComment( + comment: AST.Program["comments"][number], +): { kind: string; value: string; description: string | null } | null { + const parsed = parseDirectiveText(comment.value) + if (!parsed) { + return null + } + + const lineCommentSupported = LINE_COMMENT_PATTERN.test(parsed.kind) + + if (comment.type === "Line" && !lineCommentSupported) { + return null + } + + if ( + parsed.kind === "eslint-disable-line" && + comment.loc!.start.line !== comment.loc!.end.line + ) { + // disable-line comment should not span multiple lines. + return null + } + + return parsed +} + +/** + * 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. + */ +export function parseDirectiveText( + textToParse: string, +): { kind: string; value: string; description: string | null } | null { + 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, + } +} + +/** + * Divides and trims description text and directive comments. + * @param {string} value The comment text to strip. + * @returns {{text: string, description: string | null}} The stripped text. + */ +function divideDirectiveComment(value: string): { + text: string + description: string | null +} { + const divided = value.split(/\s-{2,}\s/u) + const text = divided[0].trim() + return { + text, + 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. + */ +export function getLoc( + context: Rule.RuleContext, + nodeOrToken: { loc?: AST.SourceLocation | null | undefined }, +): AST.SourceLocation { + const sourceCode = + context.sourceCode || (context.getSourceCode && context.getSourceCode()) + return sourceCode && typeof (sourceCode as any).getLoc === "function" + ? (sourceCode as any).getLoc(nodeOrToken) + : nodeOrToken.loc! +} diff --git a/lib/rules.js b/lib/rules.js deleted file mode 100644 index c91b9f5..0000000 --- a/lib/rules.js +++ /dev/null @@ -1,14 +0,0 @@ -/** DON'T EDIT THIS FILE; was created by scripts. */ -"use strict" - -module.exports = { - "disable-enable-pair": require("./rules/disable-enable-pair"), - "no-aggregating-enable": require("./rules/no-aggregating-enable"), - "no-duplicate-disable": require("./rules/no-duplicate-disable"), - "no-restricted-disable": require("./rules/no-restricted-disable"), - "no-unlimited-disable": require("./rules/no-unlimited-disable"), - "no-unused-disable": require("./rules/no-unused-disable"), - "no-unused-enable": require("./rules/no-unused-enable"), - "no-use": require("./rules/no-use"), - "require-description": require("./rules/require-description"), -} diff --git a/lib/rules.ts b/lib/rules.ts new file mode 100644 index 0000000..1791b2e --- /dev/null +++ b/lib/rules.ts @@ -0,0 +1,42 @@ +import type { Rule } from "eslint" +import disableEnablePair from "./rules/disable-enable-pair.ts" +import noAggregatingEnable from "./rules/no-aggregating-enable.ts" +import noDuplicateDisable from "./rules/no-duplicate-disable.ts" +import noRestrictedDisable from "./rules/no-restricted-disable.ts" +import noUnlimitedDisable from "./rules/no-unlimited-disable.ts" +import noUnusedDisable from "./rules/no-unused-disable.ts" +import noUnusedEnable from "./rules/no-unused-enable.ts" +import noUse from "./rules/no-use.ts" +import requireDescription from "./rules/require-description.ts" + +export const rules = { + "disable-enable-pair": disableEnablePair, + "no-aggregating-enable": noAggregatingEnable, + "no-duplicate-disable": noDuplicateDisable, + "no-restricted-disable": noRestrictedDisable, + "no-unlimited": noUnlimitedDisable, + "no-unused-disable": noUnusedDisable, + "no-unused-enable": noUnusedEnable, + "no-use": noUse, + "require-description": requireDescription, +} as const satisfies Record satisfies { + "disable-enable-pair": Rule.RuleModule + "no-aggregating-enable": Rule.RuleModule + "no-duplicate-disable": Rule.RuleModule + "no-restricted-disable": Rule.RuleModule + "no-unlimited": Rule.RuleModule + "no-unused-disable": Rule.RuleModule + "no-unused-enable": Rule.RuleModule + "no-use": Rule.RuleModule + "require-description": Rule.RuleModule +} as { + "disable-enable-pair": Rule.RuleModule + "no-aggregating-enable": Rule.RuleModule + "no-duplicate-disable": Rule.RuleModule + "no-restricted-disable": Rule.RuleModule + "no-unlimited": Rule.RuleModule + "no-unused-disable": Rule.RuleModule + "no-unused-enable": Rule.RuleModule + "no-use": Rule.RuleModule + "require-description": Rule.RuleModule +} diff --git a/lib/rules/disable-enable-pair.js b/lib/rules/disable-enable-pair.ts similarity index 69% rename from lib/rules/disable-enable-pair.js rename to lib/rules/disable-enable-pair.ts index ce0330e..4b5a480 100644 --- a/lib/rules/disable-enable-pair.js +++ b/lib/rules/disable-enable-pair.ts @@ -2,12 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getDisabledArea } from "../internal/disabled-area.ts" +import * as utils from "../internal/utils.ts" -const { getDisabledArea } = require("../internal/disabled-area") -const utils = require("../internal/utils") - -module.exports = { +const disableEnablePair: Rule.RuleModule = { meta: { docs: { description: @@ -16,7 +15,7 @@ module.exports = { recommended: true, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/disable-enable-pair.html", }, - fixable: null, + fixable: null as any, messages: { missingPair: "Requires 'eslint-enable' directive.", missingRulePair: @@ -37,11 +36,10 @@ module.exports = { }, create(context) { - const allowWholeFile = + const allowWholeFile: boolean = context.options[0] && context.options[0].allowWholeFile - const disabledArea = getDisabledArea(context) + const disabledArea = getDisabledArea(context as any) - /** @type {import('@eslint/core').TextSourceCode} */ const sourceCode = context.sourceCode || context.getSourceCode() const firstToken = @@ -57,17 +55,27 @@ module.exports = { } if ( allowWholeFile && - utils.lte(area.start, utils.getLoc(context, firstToken).start) + utils.lte( + area.start, + utils.getLoc(context as any, firstToken).start, + ) ) { continue } context.report({ - loc: utils.toRuleIdLocation(context, area.comment, area.ruleId), + loc: utils.toRuleIdLocation( + context as any, + area.comment, + area.ruleId, + )!, messageId: area.ruleId ? "missingRulePair" : "missingPair", - data: area, + data: area as Record, }) } + return {} }, -} +} as const satisfies Rule.RuleModule + +export default disableEnablePair diff --git a/lib/rules/no-aggregating-enable.js b/lib/rules/no-aggregating-enable.ts similarity index 70% rename from lib/rules/no-aggregating-enable.js rename to lib/rules/no-aggregating-enable.ts index d7c1f94..40a35b5 100644 --- a/lib/rules/no-aggregating-enable.js +++ b/lib/rules/no-aggregating-enable.ts @@ -2,12 +2,17 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { + RuleDefinition, + RuleDefinitionTypeOptions, + SourceCode, + SourceCodeBaseTypeOptions, +} from "@eslint/core" +import type { AST, Rule } from "eslint" +import { getDisabledArea } from "../internal/disabled-area.ts" +import * as utils from "../internal/utils.ts" -const { getDisabledArea } = require("../internal/disabled-area") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: @@ -16,7 +21,7 @@ module.exports = { recommended: true, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-aggregating-enable.html", }, - fixable: null, + fixable: null as any, messages: { aggregatingEnable: "This `eslint-enable` comment affects {{count}} `eslint-disable` comments. An `eslint-enable` comment should be for an `eslint-disable` comment.", @@ -34,12 +39,15 @@ module.exports = { if (count >= 2) { context.report({ - loc: utils.toForceLocation(utils.getLoc(context, comment)), + loc: utils.toForceLocation(comment.loc!), messageId: "aggregatingEnable", - data: { count }, + data: { count } as Record, }) } } + return {} }, } + +export default rule diff --git a/lib/rules/no-duplicate-disable.js b/lib/rules/no-duplicate-disable.ts similarity index 68% rename from lib/rules/no-duplicate-disable.js rename to lib/rules/no-duplicate-disable.ts index 78a7aaa..d6863c3 100644 --- a/lib/rules/no-duplicate-disable.js +++ b/lib/rules/no-duplicate-disable.ts @@ -2,12 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getDisabledArea } from "../internal/disabled-area.ts" +import * as utils from "../internal/utils.ts" -const { getDisabledArea } = require("../internal/disabled-area") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: "disallow duplicate `eslint-disable` comments", @@ -15,7 +14,7 @@ module.exports = { recommended: true, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-duplicate-disable.html", }, - fixable: null, + fixable: null as any, messages: { duplicate: "ESLint rules have been disabled already.", duplicateRule: "'{{ruleId}}' rule has been disabled already.", @@ -29,11 +28,18 @@ module.exports = { for (const item of disabledArea.duplicateDisableDirectives) { context.report({ - loc: utils.toRuleIdLocation(context, item.comment, item.ruleId), + loc: utils.toRuleIdLocation( + context as any, + item.comment, + item.ruleId, + )!, messageId: item.ruleId ? "duplicateRule" : "duplicate", - data: item, + data: item as Record, }) } + return {} }, } + +export default rule diff --git a/lib/rules/no-restricted-disable.js b/lib/rules/no-restricted-disable.ts similarity index 78% rename from lib/rules/no-restricted-disable.js rename to lib/rules/no-restricted-disable.ts index b96ab9d..e009e65 100644 --- a/lib/rules/no-restricted-disable.js +++ b/lib/rules/no-restricted-disable.ts @@ -2,13 +2,12 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import ignore from "ignore" +import { getDisabledArea } from "../internal/disabled-area.ts" +import * as utils from "../internal/utils.ts" -const ignore = require("ignore") -const { getDisabledArea } = require("../internal/disabled-area") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: @@ -17,7 +16,7 @@ module.exports = { recommended: false, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-restricted-disable.html", }, - fixable: null, + fixable: null as any, messages: { disallow: "Disabling '{{ruleId}}' is not allowed.", }, @@ -36,7 +35,7 @@ module.exports = { return {} } - const ig = ignore() + const ig = (ignore as any)() for (const pattern of context.options) { ig.add(pattern) } @@ -45,10 +44,10 @@ module.exports = { if (area.ruleId == null || ig.ignores(area.ruleId)) { context.report({ loc: utils.toRuleIdLocation( - context, + context as any, area.comment, - area.ruleId - ), + area.ruleId, + )!, messageId: "disallow", data: { ruleId: area.ruleId || String(context.options), @@ -56,6 +55,9 @@ module.exports = { }) } } + return {} }, } + +export default rule diff --git a/lib/rules/no-unlimited-disable.js b/lib/rules/no-unlimited-disable.ts similarity index 75% rename from lib/rules/no-unlimited-disable.js rename to lib/rules/no-unlimited-disable.ts index 87ae0f0..35dc610 100644 --- a/lib/rules/no-unlimited-disable.js +++ b/lib/rules/no-unlimited-disable.ts @@ -2,14 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getAllDirectiveComments } from "../internal/get-all-directive-comments.ts" +import * as utils from "../internal/utils.ts" -const { - getAllDirectiveComments, -} = require("../internal/get-all-directive-comments") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: @@ -18,7 +15,7 @@ module.exports = { recommended: true, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-unlimited-disable.html", }, - fixable: null, + fixable: null as any, messages: { unexpected: "Unexpected unlimited '{{kind}}' comment. Specify some rule names to disable.", @@ -28,8 +25,10 @@ module.exports = { }, create(context) { - for (const directiveComment of getAllDirectiveComments(context)) { - const kind = directiveComment.kind + for (const directiveComment of getAllDirectiveComments( + context as any, + )) { + const { kind } = directiveComment if ( kind !== "eslint-disable" && kind !== "eslint-disable-line" && @@ -45,6 +44,9 @@ module.exports = { }) } } + return {} }, } + +export default rule diff --git a/lib/rules/no-unused-disable.js b/lib/rules/no-unused-disable.ts similarity index 85% rename from lib/rules/no-unused-disable.js rename to lib/rules/no-unused-disable.ts index 64d1e40..1034a3e 100644 --- a/lib/rules/no-unused-disable.js +++ b/lib/rules/no-unused-disable.ts @@ -2,12 +2,12 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - +import type { Rule } from "eslint" // Patch `Linter#verify` to work. -require("../utils/patch")() +import patch from "../utils/patch.ts" +patch() -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: "disallow unused `eslint-disable` comments", @@ -15,7 +15,7 @@ module.exports = { recommended: false, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-unused-disable.html", }, - fixable: null, + fixable: null as any, // eslint-disable-next-line @eslint-community/mysticatea/eslint-plugin/prefer-message-ids messages: {}, schema: [], @@ -33,3 +33,5 @@ module.exports = { return {} }, } + +export default rule diff --git a/lib/rules/no-unused-enable.js b/lib/rules/no-unused-enable.ts similarity index 68% rename from lib/rules/no-unused-enable.js rename to lib/rules/no-unused-enable.ts index c4e4fba..05aa9b6 100644 --- a/lib/rules/no-unused-enable.js +++ b/lib/rules/no-unused-enable.ts @@ -2,12 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getDisabledArea } from "../internal/disabled-area.ts" +import * as utils from "../internal/utils.ts" -const { getDisabledArea } = require("../internal/disabled-area") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: "disallow unused `eslint-enable` comments", @@ -15,7 +14,7 @@ module.exports = { recommended: true, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-unused-enable.html", }, - fixable: null, + fixable: null as any, messages: { unused: "ESLint rules are re-enabled but those have not been disabled.", unusedRule: @@ -30,11 +29,18 @@ module.exports = { for (const item of disabledArea.unusedEnableDirectives) { context.report({ - loc: utils.toRuleIdLocation(context, item.comment, item.ruleId), + loc: utils.toRuleIdLocation( + context as any, + item.comment, + item.ruleId, + )!, messageId: item.ruleId ? "unusedRule" : "unused", - data: item, + data: item as Record, }) } + return {} }, } + +export default rule diff --git a/lib/rules/no-use.js b/lib/rules/no-use.ts similarity index 83% rename from lib/rules/no-use.js rename to lib/rules/no-use.ts index 5b1afc0..3aa20bc 100644 --- a/lib/rules/no-use.js +++ b/lib/rules/no-use.ts @@ -2,14 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getAllDirectiveComments } from "../internal/get-all-directive-comments.ts" +import * as utils from "../internal/utils.ts" -const { - getAllDirectiveComments, -} = require("../internal/get-all-directive-comments") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: "disallow ESLint directive-comments", @@ -17,7 +14,7 @@ module.exports = { recommended: false, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-use.html", }, - fixable: null, + fixable: null as any, messages: { disallow: "Unexpected ESLint directive comment.", }, @@ -52,10 +49,12 @@ module.exports = { create(context) { const allowed = new Set( - (context.options[0] && context.options[0].allow) || [] + (context.options[0] && context.options[0].allow) || [], ) - for (const directiveComment of getAllDirectiveComments(context)) { + for (const directiveComment of getAllDirectiveComments( + context as any, + )) { if (!allowed.has(directiveComment.kind)) { context.report({ loc: utils.toForceLocation(directiveComment.loc), @@ -63,6 +62,9 @@ module.exports = { }) } } + return {} }, } + +export default rule diff --git a/lib/rules/require-description.js b/lib/rules/require-description.ts similarity index 85% rename from lib/rules/require-description.js rename to lib/rules/require-description.ts index 149840f..c504aba 100644 --- a/lib/rules/require-description.js +++ b/lib/rules/require-description.ts @@ -2,14 +2,11 @@ * @author Yosuke Ota * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getAllDirectiveComments } from "../internal/get-all-directive-comments.ts" +import * as utils from "../internal/utils.ts" -const { - getAllDirectiveComments, -} = require("../internal/get-all-directive-comments") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: @@ -18,7 +15,7 @@ module.exports = { recommended: false, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/require-description.html", }, - fixable: null, + fixable: null as any, messages: { missingDescription: "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", @@ -54,10 +51,12 @@ module.exports = { create(context) { const ignores = new Set( - (context.options[0] && context.options[0].ignore) || [] + (context.options[0] && context.options[0].ignore) || [], ) - for (const directiveComment of getAllDirectiveComments(context)) { + for (const directiveComment of getAllDirectiveComments( + context as any, + )) { if (ignores.has(directiveComment.kind)) { continue } @@ -68,6 +67,9 @@ module.exports = { }) } } + return {} }, } + +export default rule diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index ec0f993..0000000 --- a/lib/utils.js +++ /dev/null @@ -1,6 +0,0 @@ -/** DON'T EDIT THIS FILE; was created by scripts. */ -"use strict" - -module.exports = { - patch: require("./utils/patch"), -} diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..aa0b279 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1 @@ +export { default as patch } from "./utils/patch.ts" diff --git a/lib/utils/patch.js b/lib/utils/patch.ts similarity index 75% rename from lib/utils/patch.js rename to lib/utils/patch.ts index 404b44c..17a5a30 100644 --- a/lib/utils/patch.js +++ b/lib/utils/patch.ts @@ -2,10 +2,9 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - -const getLinters = require("../internal/get-linters") -const { toRuleIdLocation } = require("../internal/utils") +import type { AST, Linter, SourceCode } from "eslint" +import getLinters from "../internal/get-linters.ts" +import { toRuleIdLocation } from "../internal/utils.ts" const quotedName = /'(.+?)'/u /** @@ -14,7 +13,10 @@ const quotedName = /'(.+?)'/u * @param {string} ruleId The rule ID to check. * @returns {number} The severity of the rule. */ -function getSeverity(config, ruleId) { +function getSeverity( + config: Linter.FlatConfig, + ruleId: string, +): Linter.Severity { const rules = config && config.rules const ruleOptions = rules && rules[ruleId] const severity = Array.isArray(ruleOptions) ? ruleOptions[0] : ruleOptions @@ -39,7 +41,10 @@ function getSeverity(config, ruleId) { * @param {SourceCode|undefined} sourceCode The source code object to get. * @returns {Comment|undefined} The gotten comment. */ -function getCommentAt(message, sourceCode) { +function getCommentAt( + message: Linter.LintMessage, + sourceCode: SourceCode | undefined, +): AST.Program["comments"][number] | undefined { if (sourceCode != null) { const loc = { line: message.line, column: message.column - 1 } const index = sourceCode.getIndexFromLoc(loc) @@ -60,7 +65,7 @@ function getCommentAt(message, sourceCode) { * @param {Message} message The message. * @returns {boolean} `true` if the message is a `reportUnusedDisableDirectives` error. */ -function isUnusedDisableDirectiveError(message) { +function isUnusedDisableDirectiveError(message: Linter.LintMessage): boolean { return ( !message.fatal && !message.ruleId && @@ -76,13 +81,18 @@ function isUnusedDisableDirectiveError(message) { * @param {Comment|undefined} comment The directive comment. * @returns {Message} The created error. */ -function createNoUnusedDisableError(ruleId, severity, message, comment) { +function createNoUnusedDisableError( + ruleId: string, + severity: Linter.Severity, + message: Linter.LintMessage, + comment: AST.Program["comments"][number] | undefined, +): Linter.LintMessage { const clone = Object.assign({}, message) const match = quotedName.exec(message.message) const targetRuleId = match && match[1] clone.ruleId = ruleId - clone.severity = severity + clone.severity = severity as Linter.LintMessage["severity"] clone.message = targetRuleId ? `'${targetRuleId}' rule is disabled but never reported.` : "ESLint rules are disabled but never reported." @@ -90,14 +100,14 @@ function createNoUnusedDisableError(ruleId, severity, message, comment) { if (comment != null) { if (targetRuleId) { - const loc = toRuleIdLocation({}, comment, targetRuleId) - clone.line = loc.start.line - clone.column = loc.start.column + 1 - clone.endLine = loc.end.line - clone.endColumn = loc.end.column + 1 + const loc = toRuleIdLocation({} as any, comment, targetRuleId) + clone.line = loc?.start.line! + clone.column = loc?.start.column! + 1 + clone.endLine = loc?.end.line + clone.endColumn = loc?.end.column! + 1 } else { - clone.endLine = comment.loc.end.line - clone.endColumn = comment.loc.end.column + 1 + clone.endLine = comment.loc?.end.line + clone.endColumn = comment.loc?.end.column! + 1 } // Remove the whole node if it is the only rule, otherwise // don't try to fix because it is quite complicated. @@ -108,7 +118,7 @@ function createNoUnusedDisableError(ruleId, severity, message, comment) { { desc: "Remove `eslint-disable` comment.", fix: { - range: comment.range, + range: comment.range!, text: comment.value.includes("\n") ? "\n" : "", }, }, @@ -128,7 +138,13 @@ function createNoUnusedDisableError(ruleId, severity, message, comment) { * @param {boolean} keepAsIs The flag to keep original errors as is. * @returns {Message[]} The converted messages. */ -function convert(messages, sourceCode, ruleId, severity, keepAsIs) { +function convert( + messages: Linter.LintMessage[], + sourceCode: SourceCode | undefined, + ruleId: string, + severity: Linter.Severity, + keepAsIs: boolean, +): Linter.LintMessage[] { for (let i = messages.length - 1; i >= 0; --i) { const message = messages[i] if (!isUnusedDisableDirectiveError(message)) { @@ -139,7 +155,7 @@ function convert(messages, sourceCode, ruleId, severity, keepAsIs) { ruleId, severity, message, - getCommentAt(message, sourceCode) + getCommentAt(message, sourceCode), ) if (keepAsIs) { @@ -152,16 +168,17 @@ function convert(messages, sourceCode, ruleId, severity, keepAsIs) { return messages } -module.exports = ( - ruleId = "@eslint-community/eslint-comments/no-unused-disable" -) => { +const patch = ( + ruleId = "@eslint-community/eslint-comments/no-unused-disable", +): void => { for (const Linter of getLinters()) { + // @ts-expect-error const verify0 = Linter.prototype._verifyWithoutProcessors Object.defineProperty(Linter.prototype, "_verifyWithoutProcessors", { value: function _verifyWithoutProcessors( - textOrSourceCode, - config, - filenameOrOptions + textOrSourceCode: SourceCode | string, + config: Linter.FlatConfig, + filenameOrOptions: Record | string, ) { const severity = getSeverity(config, ruleId) if (severity === 0) { @@ -169,7 +186,7 @@ module.exports = ( this, textOrSourceCode, config, - filenameOrOptions + filenameOrOptions, ) } @@ -178,7 +195,7 @@ module.exports = ( ? { filename: filenameOrOptions } : filenameOrOptions || {} const reportUnusedDisableDirectives = Boolean( - options.reportUnusedDisableDirectives + options.reportUnusedDisableDirectives, ) const messages = verify0.call( this, @@ -186,14 +203,14 @@ module.exports = ( config, Object.assign({}, options, { reportUnusedDisableDirectives: true, - }) + }), ) return convert( messages, this.getSourceCode(), ruleId, severity, - reportUnusedDisableDirectives + reportUnusedDisableDirectives, ) }, configurable: true, @@ -201,3 +218,5 @@ module.exports = ( }) } } + +export default patch diff --git a/package.json b/package.json index e0e4d78..793ee2b 100644 --- a/package.json +++ b/package.json @@ -5,16 +5,44 @@ "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "main": "index.js", - "type": "commonjs", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module", "files": [ - "configs.js", - "lib" + "lib", + "dist" ], "exports": { - "./configs": "./configs.js", - ".": "./index.js" + "./configs": { + "import": { + "types": "./dist/configs.d.ts", + "default": "./dist/configs.js" + }, + "default": { + "types": "./dist/configs.d.cts", + "default": "./dist/configs.cjs" + } + }, + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./package.json": "./package.json" }, + "typesVersions": { + "*": { + "configs": [ + "./dist/configs.d.ts" + ] + } + }, + "sideEffects": false, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" }, @@ -23,36 +51,50 @@ "ignore": "^5.2.4" }, "devDependencies": { + "@arethetypeswrong/cli": "^0.18.2", "@babel/core": "^7.22.9", "@babel/eslint-parser": "^7.22.9", - "@eslint-community/eslint-plugin-mysticatea": "^15.5.1", - "@eslint/core": "^0.13.0", + "@eslint-community/eslint-plugin-mysticatea": "^15.8.0", + "@eslint/core": "^1.0.0", "@eslint/css": "^0.6.0", - "@types/node": "^14.18.54", + "@types/cross-spawn": "^6.0.6", + "@types/eslint": "^8", + "@types/mocha": "^10.0.10", + "@types/node": "^24.10.1", + "@types/rimraf": "^3.0.2", + "@types/semver": "^7.7.1", "@vuepress/plugin-pwa": "^1.9.9", "cross-spawn": "^7.0.3", - "esbuild": "^0.19.3", - "eslint": "^8.46.0", + "esbuild": "^0.27.0", + "eslint": "^8.57.1", + "eslint-v9": "npm:eslint@^9.39.1", + "expect-type": "^1.2.2", "fs-extra": "^10.1.0", "mocha": "^10.4.0", "monaco-editor": "^0.47.0", "nyc": "^15.1.0", "opener": "^1.5.2", "rimraf": "^3.0.2", - "semver": "^7.5.4", - "vite-plugin-eslint4b": "^0.2.1", + "semver": "^7.7.3", + "tsdown": "^0.16.6", + "typescript": "^5.9.3", + "vite-plugin-eslint4b": "^0.6.0", "vitepress": "^1.0.0-rc.15" }, "scripts": { + "build": "rimraf dist/ && tsdown --config-loader='unrun' --config=tsdown.config.mts", + "prepack": "npm run clean && npm run build", "preversion": "npm test", "version": "node scripts/update && git add .", "postversion": "git push && git push --tags", - "clean": "rimraf .nyc_output coverage docs/.vitepress/cache", + "clean": "rimraf .nyc_output/ coverage/ docs/.vitepress/cache/ dist/", "docs:build": "vitepress build docs", "docs:watch": "vitepress dev docs", "lint": "eslint lib scripts tests", - "test": "nyc npm run debug", - "debug": "mocha \"tests/lib/**/*.js\" --reporter dot --timeout 8000", + "test": "set NODE_OPTIONS=--experimental-transform-types && npm run debug", + "debug": "mocha \"tests/lib/**/*.test.ts\" --reporter dot --timeout 8000", + "typecheck": "tsc -p tsconfig.json", + "check-exports": "attw --pack", "coverage": "nyc report --reporter lcov && opener coverage/lcov-report/index.html", "watch": "npm run -s test -- --watch --growl" }, diff --git a/scripts/lib/rules.js b/scripts/lib/rules.ts similarity index 69% rename from scripts/lib/rules.js rename to scripts/lib/rules.ts index f9cc745..fce82f5 100644 --- a/scripts/lib/rules.js +++ b/scripts/lib/rules.ts @@ -2,17 +2,24 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - -const fs = require("fs") -const path = require("path") +import * as fs from "node:fs" +import * as path from "node:path" /** * @type {{id:string,name:string,category:string,description:string,recommended:boolean,fixable:boolean,deprecated:boolean,replacedBy:(string[]|null)}[]} */ -const rules = fs +const rules: { + id: string + name: string + category: string + description: string + recommended: boolean + fixable: boolean + deprecated: boolean + replacedBy: string[] | null +}[] = fs .readdirSync(path.resolve(__dirname, "../../lib/rules")) - .map((fileName) => path.basename(fileName, ".js")) + .map((fileName) => path.basename(fileName, ".ts")) .map((name) => { const meta = require(`../../lib/rules/${name}`).meta return { @@ -27,12 +34,12 @@ const rules = fs } }) -module.exports = rules -module.exports.withCategories = ["Best Practices", "Stylistic Issues"].map( +export default rules +export const withCategories = ["Best Practices", "Stylistic Issues"].map( (category) => ({ category, rules: rules.filter( - (rule) => rule.category === category && !rule.deprecated + (rule) => rule.category === category && !rule.deprecated, ), - }) + }), ) diff --git a/scripts/lib/utils.js b/scripts/lib/utils.ts similarity index 79% rename from scripts/lib/utils.js rename to scripts/lib/utils.ts index a3e0f9a..37b5983 100644 --- a/scripts/lib/utils.js +++ b/scripts/lib/utils.ts @@ -2,11 +2,10 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import { ESLint } from "eslint" +import * as fs from "node:fs" +import * as path from "node:path" -const fs = require("fs") -const path = require("path") -const { ESLint } = require("eslint") const linter = new ESLint({ fix: true }) /** @@ -14,7 +13,7 @@ const linter = new ESLint({ fix: true }) * @param {string} text The text to format. * @returns {Promise} The formatted text. */ -function format(text) { +function format(text: string): Promise { return linter.lintText(text).then(([{ output }]) => output || text) } @@ -23,10 +22,9 @@ function format(text) { * @param {string} dirPath The path to the directory to create index. * @returns {Promise} The index file content. */ -function createIndex(dirPath) { +function createIndex(dirPath: string): Promise { const dirName = path.basename(dirPath) return format(`/** DON'T EDIT THIS FILE; was created by scripts. */ - "use strict" module.exports = { ${fs @@ -38,7 +36,4 @@ function createIndex(dirPath) { `) } -module.exports = { - createIndex, - format, -} +export { createIndex, format } diff --git a/scripts/update-docs-headers.js b/scripts/update-docs-headers.ts similarity index 70% rename from scripts/update-docs-headers.js rename to scripts/update-docs-headers.ts index c73170d..eef960c 100644 --- a/scripts/update-docs-headers.js +++ b/scripts/update-docs-headers.ts @@ -2,11 +2,13 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import * as fs from "node:fs" +import * as path from "node:path" +import { fileURLToPath } from "node:url" +import rules from "./lib/rules.ts" + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const fs = require("fs") -const path = require("path") -const rules = require("./lib/rules") const PLACE_HOLDER = /^#[^\n]*\n+> .+\n+(?:- .+\n)*\n*/u for (const rule of rules) { @@ -18,18 +20,18 @@ for (const rule of rules) { } if (rule.deprecated) { headerLines.push( - `- โš ๏ธ This rule was **deprecated** and replaced by ${rule.replacedBy - .map((id) => `[${id}](${id}.md) rule`) - .join(", ")}.` + `- โš ๏ธ This rule was **deprecated** and replaced by ${rule + .replacedBy!.map((id) => `[${id}](${id}.md) rule`) + .join(", ")}.`, ) } else if (rule.recommended) { headerLines.push( - '- ๐ŸŒŸ The `"extends": "plugin:@eslint-community/eslint-comments/recommended"` property in a configuration file enables this rule.' + '- ๐ŸŒŸ The `"extends": "plugin:@eslint-community/eslint-comments/recommended"` property in a configuration file enables this rule.', ) } if (rule.fixable) { headerLines.push( - "- โœ’๏ธ The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule." + "- โœ’๏ธ The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.", ) } headerLines.push("", "") @@ -38,6 +40,6 @@ for (const rule of rules) { filePath, fs .readFileSync(filePath, "utf8") - .replace(PLACE_HOLDER, headerLines.join("\n")) + .replace(PLACE_HOLDER, headerLines.join("\n")), ) } diff --git a/scripts/update-docs-index.js b/scripts/update-docs-index.ts similarity index 65% rename from scripts/update-docs-index.js rename to scripts/update-docs-index.ts index f45fee5..d125b57 100644 --- a/scripts/update-docs-index.js +++ b/scripts/update-docs-index.ts @@ -2,18 +2,27 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - -const fs = require("fs") -const path = require("path") -const { withCategories } = require("./lib/rules") +import * as fs from "node:fs" +import * as path from "node:path" +import { fileURLToPath } from "node:url" +import { withCategories } from "./lib/rules.ts" +const __dirname = path.dirname(fileURLToPath(import.meta.url)) /** * Convert a given rule to a table row. * @param {{id:string,name:string,category:string,description:string,recommended:boolean,fixable:boolean,deprecated:boolean,replacedBy:string[]}} rule The rule object. * @returns {string} The table row of the rule. */ -function toTableRow(rule) { +function toTableRow(rule: { + id: string + name: string + category: string + description: string + recommended: boolean + fixable: boolean + deprecated: boolean + replacedBy: string[] | null +}): string { const mark = `${rule.recommended ? "๐ŸŒŸ" : ""}${rule.fixable ? "โœ’๏ธ" : ""}` const link = `[@eslint-community/eslint-comments/${rule.name}](./${rule.name}.md)` const description = rule.description || "(no description)" @@ -25,7 +34,22 @@ function toTableRow(rule) { * @param {{category:string,rules:{id:string,name:string,category:string,description:string,recommended:boolean,fixable:boolean,deprecated:boolean,replacedBy:string[]}[]}} categoryInfo The category information to convert. * @returns {string} The section of the category. */ -function toCategorySection({ category, rules }) { +function toCategorySection({ + category, + rules, +}: { + category: string + rules: { + id: string + name: string + category: string + description: string + recommended: boolean + fixable: boolean + deprecated: boolean + replacedBy: string[] | null + }[] +}): string { return `## ${category} | Rule ID | Description | | @@ -42,5 +66,5 @@ fs.writeFileSync( - โœ’๏ธ mark: the rule which is fixable by \`eslint --fix\` command. ${withCategories.map(toCategorySection).join("\n")} -` +`, ) diff --git a/scripts/update-recommended-rules.js b/scripts/update-recommended-rules.ts similarity index 58% rename from scripts/update-recommended-rules.js rename to scripts/update-recommended-rules.ts index a421132..248acb9 100644 --- a/scripts/update-recommended-rules.js +++ b/scripts/update-recommended-rules.ts @@ -2,12 +2,13 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import * as fs from "node:fs" +import * as path from "node:path" +import { fileURLToPath } from "node:url" +import rules from "./lib/rules.ts" +import { format } from "./lib/utils.ts" -const fs = require("fs") -const path = require("path") -const rules = require("./lib/rules") -const { format } = require("./lib/utils") +const __dirname = path.dirname(fileURLToPath(import.meta.url)) // recommended.js format(`/** DON'T EDIT THIS FILE; was created by scripts. */ @@ -22,9 +23,9 @@ module.exports = { .join("\n ")} }, } -`).then((content) => +`).then((content) => { fs.writeFileSync( - path.resolve(__dirname, "../lib/configs/recommended.js"), - content + path.resolve(__dirname, "../lib/configs/recommended.ts"), + content, ) -) +}) diff --git a/scripts/update.js b/scripts/update.js deleted file mode 100644 index 7e45f8c..0000000 --- a/scripts/update.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @author Toru Nagashima - * See LICENSE file in root directory for full license. - */ -"use strict" - -const fs = require("fs") -const path = require("path") -const { createIndex } = require("./lib/utils") - -// docs. -require("./update-docs-headers") -require("./update-docs-index") - -// recommended rules. -require("./update-recommended-rules") - -// indices. -for (const dirPath of [ - path.resolve(__dirname, "../lib/configs"), - path.resolve(__dirname, "../lib/rules"), - path.resolve(__dirname, "../lib/utils"), -]) { - createIndex(dirPath).then((content) => - fs.writeFileSync(`${dirPath}.js`, content) - ) -} diff --git a/scripts/update.ts b/scripts/update.ts new file mode 100644 index 0000000..2c903e8 --- /dev/null +++ b/scripts/update.ts @@ -0,0 +1,28 @@ +/** + * @author Toru Nagashima + * See LICENSE file in root directory for full license. + */ +import * as fs from "node:fs" +import * as path from "node:path" +import { fileURLToPath } from "node:url" +import { createIndex } from "./lib/utils.ts" + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +// docs. +import "./update-docs-headers.ts" +import "./update-docs-index.ts" + +// recommended rules. +import "./update-recommended-rules.ts" + +// indices. +for (const dirPath of [ + path.resolve(__dirname, "../lib/configs"), + path.resolve(__dirname, "../lib/rules"), + path.resolve(__dirname, "../lib/utils"), +]) { + createIndex(dirPath).then((content) => { + fs.writeFileSync(`${dirPath}.ts`, content) + }) +} diff --git a/tests/lib/illegal-eslint-disable-line.js b/tests/lib/illegal-eslint-disable-line.test.ts similarity index 73% rename from tests/lib/illegal-eslint-disable-line.js rename to tests/lib/illegal-eslint-disable-line.test.ts index 495d68c..9e2b14d 100644 --- a/tests/lib/illegal-eslint-disable-line.js +++ b/tests/lib/illegal-eslint-disable-line.test.ts @@ -1,20 +1,20 @@ /** * Test that multi-line eslint-disable-line comments are not false positives. */ -"use strict" -const assert = require("assert") -const fs = require("fs") -const path = require("path") -const spawn = require("cross-spawn") -const rimraf = require("rimraf") +import spawn from "cross-spawn" +import type { Linter } from "eslint" +import * as assert from "node:assert" +import * as fs from "node:fs" +import * as path from "node:path" +import rimraf from "rimraf" /** * Run eslint CLI command with a given source code. * @param {string} code The source code to lint. * @returns {Promise} The result message. */ -function runESLint(code) { +function runESLint(code: string): Promise { return new Promise((resolve, reject) => { const cp = spawn( "eslint", @@ -32,16 +32,16 @@ function runESLint(code) { stdio: ["pipe", "pipe", "inherit"], // eslint-disable-next-line no-process-env, @eslint-community/mysticatea/node/no-process-env env: { ...process.env, ESLINT_USE_FLAT_CONFIG: "false" }, - } + }, ) - const chunks = [] + const chunks: any[] = [] let totalLength = 0 - cp.stdout.on("data", (chunk) => { + cp.stdout?.on("data", (chunk) => { chunks.push(chunk) totalLength += chunk.length }) - cp.stdout.on("end", () => { + cp.stdout?.on("end", () => { try { const resultsStr = String(Buffer.concat(chunks, totalLength)) const results = JSON.parse(resultsStr) @@ -52,19 +52,18 @@ function runESLint(code) { }) cp.on("error", reject) - cp.stdin.end(code) + cp.stdin?.end(code) }) } describe("multi-line eslint-disable-line comments", () => { + // Register this plugin. + const selfPath = path.resolve(process.cwd()) + const pluginPath = path.resolve( + process.cwd(), + "node_modules/@eslint-community/eslint-plugin-eslint-comments", + ) before(() => { - // Register this plugin. - const selfPath = path.resolve(__dirname, "../../") - const pluginPath = path.resolve( - __dirname, - "../../node_modules/@eslint-community/eslint-plugin-eslint-comments" - ) - fs.mkdirSync(path.dirname(pluginPath), { recursive: true }) if (fs.existsSync(pluginPath)) { rimraf.sync(pluginPath) @@ -73,6 +72,10 @@ describe("multi-line eslint-disable-line comments", () => { fs.symlinkSync(selfPath, pluginPath, "junction") }) + after(() => { + rimraf.sync(pluginPath) + }) + describe("`@eslint-community/eslint-comments/*` rules are valid", () => { for (const code of [ `/* eslint @eslint-community/eslint-comments/no-use:[error, {allow: ['eslint']}] */ @@ -88,10 +91,10 @@ no-undef*/ runESLint(code).then((messages) => { assert.strictEqual(messages.length > 0, true) const normalMessages = messages.filter( - (message) => message.ruleId != null + (message) => message.ruleId != null, ) - assert.deepStrictEqual(normalMessages, []) - }) + assert.strictEqual(normalMessages.length, 0) + }), ) } }) diff --git a/tests/lib/rules/disable-enable-pair.js b/tests/lib/rules/disable-enable-pair.test.ts similarity index 95% rename from tests/lib/rules/disable-enable-pair.js rename to tests/lib/rules/disable-enable-pair.test.ts index b1e3197..e58801e 100644 --- a/tests/lib/rules/disable-enable-pair.js +++ b/tests/lib/rules/disable-enable-pair.test.ts @@ -2,11 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/disable-enable-pair.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/disable-enable-pair") const tester = new RuleTester() tester.run("disable-enable-pair", rule, { @@ -102,10 +102,10 @@ var foo = 1 a {} `, plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -254,7 +254,7 @@ console.log(); { code: "/* eslint-disable no-unused-vars */ a {}", plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: [ @@ -267,7 +267,7 @@ console.log(); endColumn: 33, }, ], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/no-aggregating-enable.js b/tests/lib/rules/no-aggregating-enable.test.ts similarity index 91% rename from tests/lib/rules/no-aggregating-enable.js rename to tests/lib/rules/no-aggregating-enable.test.ts index 979ddce..4447dec 100644 --- a/tests/lib/rules/no-aggregating-enable.js +++ b/tests/lib/rules/no-aggregating-enable.test.ts @@ -2,11 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/no-aggregating-enable.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-aggregating-enable") const tester = new RuleTester() tester.run("no-aggregating-enable", rule, { @@ -42,10 +42,10 @@ tester.run("no-aggregating-enable", rule, { /*eslint-enable no-shadow*/ a {}`, plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -106,13 +106,13 @@ tester.run("no-aggregating-enable", rule, { /*eslint-enable*/ a {}`, plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: [ "This `eslint-enable` comment affects 2 `eslint-disable` comments. An `eslint-enable` comment should be for an `eslint-disable` comment.", ], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/no-duplicate-disable.js b/tests/lib/rules/no-duplicate-disable.test.ts similarity index 93% rename from tests/lib/rules/no-duplicate-disable.js rename to tests/lib/rules/no-duplicate-disable.test.ts index 3ba9652..9fb8104 100644 --- a/tests/lib/rules/no-duplicate-disable.js +++ b/tests/lib/rules/no-duplicate-disable.test.ts @@ -2,11 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/no-duplicate-disable.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-duplicate-disable") const tester = new RuleTester() tester.run("no-duplicate-disable", rule, { @@ -40,10 +40,10 @@ tester.run("no-duplicate-disable", rule, { /*eslint-disable eqeqeq*/ a {}`, plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -168,7 +168,7 @@ a {}`, /* eslint-disable-line no-undef */ a {}`, plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: [ @@ -181,7 +181,7 @@ a {}`, endColumn: 32, }, ], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/no-restricted-disable.js b/tests/lib/rules/no-restricted-disable.test.ts similarity index 93% rename from tests/lib/rules/no-restricted-disable.js rename to tests/lib/rules/no-restricted-disable.test.ts index e515838..abb9699 100644 --- a/tests/lib/rules/no-restricted-disable.js +++ b/tests/lib/rules/no-restricted-disable.test.ts @@ -2,18 +2,21 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/no-restricted-disable.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-restricted-disable") const coreRules = new Linter({ configType: "eslintrc" }).getRules() let tester = null +// @ts-expect-error if (typeof RuleTester.prototype.defineRule === "function") { // ESLint < 9 tester = new RuleTester() + // @ts-expect-error tester.defineRule("foo/no-undef", coreRules.get("no-undef")) + // @ts-expect-error tester.defineRule("foo/no-redeclare", coreRules.get("no-redeclare")) } else { // ESLint 9 @@ -21,8 +24,8 @@ if (typeof RuleTester.prototype.defineRule === "function") { plugins: { foo: { rules: { - "no-undef": coreRules.get("no-undef"), - "no-redeclare": coreRules.get("no-redeclare"), + "no-undef": coreRules.get("no-undef")!, + "no-redeclare": coreRules.get("no-redeclare")!, }, }, }, @@ -55,10 +58,10 @@ tester.run("no-restricted-disable", rule, { code: "/*eslint-disable eqeqeq*/ a {}", options: ["*", "!eqeqeq"], plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -199,11 +202,11 @@ tester.run("no-restricted-disable", rule, { code: "/*eslint-disable eqeqeq*/ a {}", options: ["eqeqeq"], plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: ["Disabling 'eqeqeq' is not allowed."], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/no-unlimited-disable.js b/tests/lib/rules/no-unlimited-disable.test.ts similarity index 93% rename from tests/lib/rules/no-unlimited-disable.js rename to tests/lib/rules/no-unlimited-disable.test.ts index 34f7427..704e807 100644 --- a/tests/lib/rules/no-unlimited-disable.js +++ b/tests/lib/rules/no-unlimited-disable.test.ts @@ -2,11 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/no-unlimited-disable.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-unlimited-disable") const tester = new RuleTester() tester.run("no-unlimited-disable", rule, { @@ -25,10 +25,10 @@ tester.run("no-unlimited-disable", rule, { { code: "/*eslint-disable-line eqeqeq*/ a {}", plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -122,13 +122,13 @@ tester.run("no-unlimited-disable", rule, { { code: "/* eslint-disable */ a {}", plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: [ "Unexpected unlimited 'eslint-disable' comment. Specify some rule names to disable.", ], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/no-unused-disable.js b/tests/lib/rules/no-unused-disable.test.ts similarity index 81% rename from tests/lib/rules/no-unused-disable.js rename to tests/lib/rules/no-unused-disable.test.ts index ebd0d1e..df96685 100644 --- a/tests/lib/rules/no-unused-disable.js +++ b/tests/lib/rules/no-unused-disable.test.ts @@ -12,15 +12,13 @@ * So it cannot test with `eslint.RuleTester`. * This test confirmes that this rule works file in eslint CLI command. */ -"use strict" - -const assert = require("assert") -const fs = require("fs") -const path = require("path") -const spawn = require("cross-spawn") -const rimraf = require("rimraf") -const semver = require("semver") -const { Linter } = require("eslint") +import spawn from "cross-spawn" +import { Linter } from "eslint" +import * as assert from "node:assert" +import * as fs from "node:fs" +import * as path from "node:path" +import rimraf from "rimraf" +import * as semver from "semver" /** * Run eslint CLI command with a given source code. @@ -28,7 +26,10 @@ const { Linter } = require("eslint") * @param {boolean} [reportUnusedDisableDirectives] The flag to enable `--report-unused-disable-directives` option. * @returns {Promise} The result message. */ -function runESLint(code, reportUnusedDisableDirectives = false) { +function runESLint( + code: string, + reportUnusedDisableDirectives = false, +): Promise[]> { return new Promise((resolve, reject) => { const cp = spawn( "eslint", @@ -51,39 +52,39 @@ function runESLint(code, reportUnusedDisableDirectives = false) { stdio: ["pipe", "pipe", "inherit"], // eslint-disable-next-line no-process-env, @eslint-community/mysticatea/node/no-process-env env: { ...process.env, ESLINT_USE_FLAT_CONFIG: "false" }, - } + }, ) - const chunks = [] + const chunks: any[] = [] let totalLength = 0 - cp.stdout.on("data", (chunk) => { + cp.stdout?.on("data", (chunk) => { chunks.push(chunk) totalLength += chunk.length }) - cp.stdout.on("end", () => { + cp.stdout?.on("end", () => { try { const resultsStr = String(Buffer.concat(chunks, totalLength)) const results = JSON.parse(resultsStr) resolve(results[0].messages) } catch (error) { + console.error(error) reject(error) } }) cp.on("error", reject) - cp.stdin.end(code) + cp.stdin?.end(code) }) } describe("no-unused-disable", () => { + // Register this plugin. + const selfPath = path.resolve(process.cwd()) + const pluginPath = path.resolve( + process.cwd(), + "node_modules/@eslint-community/eslint-plugin-eslint-comments", + ) before(() => { - // Register this plugin. - const selfPath = path.resolve(__dirname, "../../../") - const pluginPath = path.resolve( - __dirname, - "../../../node_modules/@eslint-community/eslint-plugin-eslint-comments" - ) - fs.mkdirSync(path.dirname(pluginPath), { recursive: true }) if (fs.existsSync(pluginPath)) { rimraf.sync(pluginPath) @@ -92,6 +93,10 @@ describe("no-unused-disable", () => { fs.symlinkSync(selfPath, pluginPath, "junction") }) + after(() => { + rimraf.sync(pluginPath) + }) + describe("valid", () => { for (const code of [ `/*eslint no-undef:error*/ @@ -177,7 +182,7 @@ var a = b //eslint-disable-line -- description`, it(code, () => runESLint(code).then((messages) => { assert.strictEqual(messages.length, 0) - }) + }), ) } }) @@ -209,34 +214,34 @@ var a = b //eslint-disable-line`, }, { code: `/*eslint no-undef:off*/ -var a = b /*eslint-disable-line*/`, + var a = b /*eslint-disable-line*/`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 11, + column: 17, endLine: 2, - endColumn: 34, + endColumn: 40, }, ], }, { code: `/*eslint no-undef:off*/ -var a = b //eslint-disable-line no-undef`, + var a = b //eslint-disable-line no-undef`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [34, 64], + range: [40, 70], text: "", }, }, @@ -246,30 +251,30 @@ var a = b //eslint-disable-line no-undef`, }, { code: `/*eslint no-undef:off*/ -var a = b /*eslint-disable-line no-undef*/`, + var a = b /*eslint-disable-line no-undef*/`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, }, ], }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -var a = b //eslint-disable-line no-undef,no-unused-vars`, + var a = b //eslint-disable-line no-undef,no-unused-vars`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [], }, ] @@ -278,9 +283,9 @@ var a = b //eslint-disable-line no-undef,no-unused-vars`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [], }, { @@ -296,16 +301,16 @@ var a = b //eslint-disable-line no-undef,no-unused-vars`, }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -var a = b /*eslint-disable-line no-undef,no-unused-vars*/`, + var a = b /*eslint-disable-line no-undef,no-unused-vars*/`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, }, ] : [ @@ -313,9 +318,9 @@ var a = b /*eslint-disable-line no-undef,no-unused-vars*/`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, }, { message: @@ -329,21 +334,21 @@ var a = b /*eslint-disable-line no-undef,no-unused-vars*/`, }, { code: `/*eslint no-undef:off*/ -//eslint-disable-next-line -var a = b`, + //eslint-disable-next-line + var a = b`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 27, + endColumn: 33, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 50], + range: [30, 56], text: "", }, }, @@ -353,36 +358,36 @@ var a = b`, }, { code: `/*eslint no-undef:off*/ -/*eslint-disable-next-line*/ -var a = b`, + /*eslint-disable-next-line*/ + var a = b`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 29, + endColumn: 35, }, ], }, { code: `/*eslint no-undef:off*/ -//eslint-disable-next-line no-undef -var a = b`, + //eslint-disable-next-line no-undef + var a = b`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 59], + range: [30, 65], text: "", }, }, @@ -392,32 +397,32 @@ var a = b`, }, { code: `/*eslint no-undef:off*/ -/*eslint-disable-next-line no-undef*/ -var a = b`, + /*eslint-disable-next-line no-undef*/ + var a = b`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, }, ], }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -//eslint-disable-next-line no-undef,no-unused-vars -var a = b`, + //eslint-disable-next-line no-undef,no-unused-vars + var a = b`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, suggestions: [], }, ] @@ -426,9 +431,9 @@ var a = b`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, suggestions: [], }, { @@ -444,17 +449,17 @@ var a = b`, }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -/*eslint-disable-next-line no-undef,no-unused-vars*/ -var a = b`, + /*eslint-disable-next-line no-undef,no-unused-vars*/ + var a = b`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, }, ] : [ @@ -462,9 +467,9 @@ var a = b`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, }, { message: @@ -478,21 +483,21 @@ var a = b`, }, { code: `/*eslint no-undef:off*/ -/*eslint-disable*/ -var a = b`, + /*eslint-disable*/ + var a = b`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 19, + endColumn: 25, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 42], + range: [30, 48], text: "", }, }, @@ -502,21 +507,21 @@ var a = b`, }, { code: `/*eslint no-undef:off*/ -/*eslint-disable no-undef*/ -var a = b`, + /*eslint-disable no-undef*/ + var a = b`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 51], + range: [30, 57], text: "", }, }, @@ -526,17 +531,17 @@ var a = b`, }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -/*eslint-disable no-undef,no-unused-vars*/ -var a = b`, + /*eslint-disable no-undef,no-unused-vars*/ + var a = b`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [], }, ] @@ -545,9 +550,9 @@ var a = b`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [], }, { @@ -556,29 +561,29 @@ var a = b`, line: 2, column: 27, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [], }, ], }, { code: `/*eslint no-undef:off*/ -/*eslint-disable*/ -var a = b -/*eslint-enable*/`, + /*eslint-disable*/ + var a = b + /*eslint-enable*/`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 19, + endColumn: 25, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 42], + range: [30, 48], text: "", }, }, @@ -588,22 +593,22 @@ var a = b }, { code: `/*eslint no-undef:off*/ -/*eslint-disable no-undef*/ -var a = b -/*eslint-enable*/`, + /*eslint-disable no-undef*/ + var a = b + /*eslint-enable*/`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 51], + range: [30, 57], text: "", }, }, @@ -613,18 +618,18 @@ var a = b }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -/*eslint-disable no-undef,no-unused-vars*/ -var a = b -/*eslint-enable*/`, + /*eslint-disable no-undef,no-unused-vars*/ + var a = b + /*eslint-enable*/`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [], }, ] @@ -633,9 +638,9 @@ var a = b message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [], }, { @@ -644,29 +649,29 @@ var a = b line: 2, column: 27, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [], }, ], }, { code: `/*eslint no-undef:error*/ -/*eslint-disable*/ -/*eslint-enable*/ -var a = b//eslint-disable-line no-undef`, + /*eslint-disable*/ + /*eslint-enable*/ + var a = b//eslint-disable-line no-undef`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 19, + endColumn: 25, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [26, 44], + range: [32, 50], text: "", }, }, @@ -676,38 +681,38 @@ var a = b//eslint-disable-line no-undef`, }, { code: `/*eslint no-undef:error*/ -/*eslint-disable*/ -/*eslint-enable*/ -var a = b/*eslint-disable-line no-undef*/`, + /*eslint-disable*/ + /*eslint-enable*/ + var a = b/*eslint-disable-line no-undef*/`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 19, + endColumn: 25, }, ], }, { code: `/*eslint no-undef:error*/ -/*eslint-disable no-undef*/ -/*eslint-enable no-undef*/ -var a = b//eslint-disable-line no-undef`, + /*eslint-disable no-undef*/ + /*eslint-enable no-undef*/ + var a = b//eslint-disable-line no-undef`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [26, 53], + range: [32, 59], text: "", }, }, @@ -717,70 +722,70 @@ var a = b//eslint-disable-line no-undef`, }, { code: `/*eslint no-undef:error*/ -/*eslint-disable no-undef*/ -/*eslint-enable no-undef*/ -var a = b/*eslint-disable-line no-undef*/`, + /*eslint-disable no-undef*/ + /*eslint-enable no-undef*/ + var a = b/*eslint-disable-line no-undef*/`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, }, ], }, { code: `/*eslint no-undef:error, no-unused-vars:error*/ -/*eslint-disable no-undef,no-unused-vars*/ -/*eslint-enable no-undef*/ -var a = b//eslint-disable-line no-undef`, + /*eslint-disable no-undef,no-unused-vars*/ + /*eslint-enable no-undef*/ + var a = b//eslint-disable-line no-undef`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [], }, ], }, { code: `/*eslint no-undef:error, no-unused-vars:error*/ -/*eslint-disable no-undef,no-unused-vars*/ -/*eslint-enable no-undef*/ -var a = b/*eslint-disable-line no-undef*/`, + /*eslint-disable no-undef,no-unused-vars*/ + /*eslint-enable no-undef*/ + var a = b/*eslint-disable-line no-undef*/`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, }, ], }, { code: `/*eslint no-undef:error, no-unused-vars:error*/ -/*eslint-disable - no-undef, - no-unused-vars, - eqeqeq -*/ -var a = b -/*eslint-enable*/`, + /*eslint-disable + no-undef, + no-unused-vars, + eqeqeq + */ + var a = b + /*eslint-enable*/`, errors: [ { message: "'eqeqeq' rule is disabled but never reported.", line: 5, - column: 5, + column: 11, endLine: 5, - endColumn: 11, + endColumn: 17, suggestions: [], }, ], @@ -809,7 +814,7 @@ var a = b }, { code: `/*eslint no-undef:off*/ -var a = b //eslint-disable-line`, + var a = b //eslint-disable-line`, errors: [ { message: @@ -819,14 +824,14 @@ var a = b //eslint-disable-line`, message: "ESLint rules are disabled but never reported.", line: 2, - column: 11, + column: 17, endLine: 2, - endColumn: 32, + endColumn: 38, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [34, 55], + range: [40, 61], text: "", }, }, @@ -837,7 +842,7 @@ var a = b //eslint-disable-line`, }, { code: `/*eslint no-undef:off*/ -var a = b /*eslint-disable-line*/`, + var a = b /*eslint-disable-line*/`, errors: [ { message: @@ -847,16 +852,16 @@ var a = b /*eslint-disable-line*/`, message: "ESLint rules are disabled but never reported.", line: 2, - column: 11, + column: 17, endLine: 2, - endColumn: 34, + endColumn: 40, }, ], reportUnusedDisableDirectives: true, }, { code: `/*eslint no-undef:off*/ -var a = b //eslint-disable-line no-undef`, + var a = b //eslint-disable-line no-undef`, errors: [ { message: @@ -866,14 +871,14 @@ var a = b //eslint-disable-line no-undef`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [34, 64], + range: [40, 70], text: "", }, }, @@ -884,7 +889,7 @@ var a = b //eslint-disable-line no-undef`, }, { code: `/*eslint no-undef:off*/ -var a = b /*eslint-disable-line no-undef*/`, + var a = b /*eslint-disable-line no-undef*/`, errors: [ { message: @@ -894,9 +899,9 @@ var a = b /*eslint-disable-line no-undef*/`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, }, ], reportUnusedDisableDirectives: true, @@ -906,15 +911,15 @@ var a = b /*eslint-disable-line no-undef*/`, ? [ { code: `/*eslint no-undef:off*/ -var a = b //eslint-disable-line -- description`, + var a = b //eslint-disable-line -- description`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 11, + column: 17, endLine: 2, - endColumn: 47, + endColumn: 53, }, ], }, @@ -942,13 +947,13 @@ var a = b //eslint-disable-line -- description`, for (const key of Object.keys(expected)) { assert.deepStrictEqual( actual[key], - expected[key], - `'${key}' is not expected.` + expected[key as keyof typeof expected], + `'${key}' is not expected.`, ) } } - } - ) + }, + ), ) } }) diff --git a/tests/lib/rules/no-unused-enable.js b/tests/lib/rules/no-unused-enable.test.ts similarity index 94% rename from tests/lib/rules/no-unused-enable.js rename to tests/lib/rules/no-unused-enable.test.ts index 73c4bf7..91c40dc 100644 --- a/tests/lib/rules/no-unused-enable.js +++ b/tests/lib/rules/no-unused-enable.test.ts @@ -2,11 +2,9 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-unused-enable") +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/no-unused-enable.ts" const tester = new RuleTester() tester.run("no-unused-enable", rule, { diff --git a/tests/lib/rules/no-use.js b/tests/lib/rules/no-use.test.ts similarity index 92% rename from tests/lib/rules/no-use.js rename to tests/lib/rules/no-use.test.ts index 861f372..b96abb3 100644 --- a/tests/lib/rules/no-use.js +++ b/tests/lib/rules/no-use.test.ts @@ -2,11 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import semver from "semver" +import rule from "../../../lib/rules/no-use.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-use") const tester = new RuleTester() tester.run("no-use", rule, { @@ -70,10 +70,10 @@ tester.run("no-use", rule, { code: "/* eslint-disable */ a {}", options: [{ allow: ["eslint-disable"] }], plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -128,11 +128,11 @@ tester.run("no-use", rule, { { code: "/* eslint-disable */ a {}", plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: ["Unexpected ESLint directive comment."], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/require-description.js b/tests/lib/rules/require-description.js deleted file mode 100644 index 56b6f08..0000000 --- a/tests/lib/rules/require-description.js +++ /dev/null @@ -1,241 +0,0 @@ -/** - * @author Yosuke Ota - * See LICENSE file in root directory for full license. - */ -"use strict" - -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/require-description") -const tester = new RuleTester() - -if (!semver.satisfies(Linter.version, ">=7.0.0")) { - // This rule can only be used with ESLint v7.x or later. - return -} - -tester.run("require-description", rule, { - valid: [ - '/* eslint eqeqeq: "off", curly: "error" -- Here\'s a description about why this configuration is necessary. */', - "/* eslint-disable -- description */", - "/* eslint-enable -- description */", - "/* exported -- description */", - "/* global -- description */", - "/* globals -- description */", - "/* eslint-env -- description */", - "/* just eslint in a normal comment */", - "// eslint-disable-line -- description", - "// eslint-disable-next-line -- description", - "/* eslint-disable-line -- description */", - "/* eslint-disable-next-line -- description */", - "// eslint-disable-line eqeqeq -- description", - "// eslint-disable-next-line eqeqeq -- description", - { - code: "/* eslint */", - options: [{ ignore: ["eslint"] }], - }, - { - code: "/* eslint-env */", - options: [{ ignore: ["eslint-env"] }], - }, - { - code: "/* eslint-enable */", - options: [{ ignore: ["eslint-enable"] }], - }, - { - code: "/* eslint-disable */", - options: [{ ignore: ["eslint-disable"] }], - }, - { - code: "// eslint-disable-line", - options: [{ ignore: ["eslint-disable-line"] }], - }, - { - code: "// eslint-disable-next-line", - options: [{ ignore: ["eslint-disable-next-line"] }], - }, - { - code: "/* eslint-disable-line */", - options: [{ ignore: ["eslint-disable-line"] }], - }, - { - code: "/* eslint-disable-next-line */", - options: [{ ignore: ["eslint-disable-next-line"] }], - }, - { - code: "/* exported */", - options: [{ ignore: ["exported"] }], - }, - { - code: "/* global */", - options: [{ ignore: ["global"] }], - }, - { - 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: [ - { - code: "/* eslint */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: '/* eslint eqeqeq: "off", curly: "error" */', - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-env */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-env node */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-enable */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-enable eqeqeq */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable eqeqeq */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "// eslint-disable-line", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "// eslint-disable-line eqeqeq", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "// eslint-disable-next-line", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "// eslint-disable-next-line eqeqeq", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable-line */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable-line eqeqeq */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable-next-line */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable-next-line eqeqeq */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* exported */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* global */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* global _ */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* globals */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* globals _ */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - // empty description - { - code: "/* eslint-disable-next-line eqeqeq -- */", - errors: [ - "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.", - ], - }, - ] - : []), - ], -}) diff --git a/tests/lib/rules/require-description.test.ts b/tests/lib/rules/require-description.test.ts new file mode 100644 index 0000000..29e314a --- /dev/null +++ b/tests/lib/rules/require-description.test.ts @@ -0,0 +1,209 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/require-description.ts" +const tester = new RuleTester() + +if (semver.satisfies(Linter.version, ">=7.0.0")) { + // This rule can only be used with ESLint v7.x or later. + tester.run("require-description", rule, { + valid: [ + '/* eslint eqeqeq: "off", curly: "error" -- Here\'s a description about why this configuration is necessary. */', + "/* eslint-disable -- description */", + "/* eslint-enable -- description */", + "/* exported -- description */", + "/* global -- description */", + "/* globals -- description */", + "/* eslint-env -- description */", + "/* just eslint in a normal comment */", + "// eslint-disable-line -- description", + "// eslint-disable-next-line -- description", + "/* eslint-disable-line -- description */", + "/* eslint-disable-next-line -- description */", + "// eslint-disable-line eqeqeq -- description", + "// eslint-disable-next-line eqeqeq -- description", + { + code: "/* eslint */", + options: [{ ignore: ["eslint"] }], + }, + { + code: "/* eslint-env */", + options: [{ ignore: ["eslint-env"] }], + }, + { + code: "/* eslint-enable */", + options: [{ ignore: ["eslint-enable"] }], + }, + { + code: "/* eslint-disable */", + options: [{ ignore: ["eslint-disable"] }], + }, + { + code: "// eslint-disable-line", + options: [{ ignore: ["eslint-disable-line"] }], + }, + { + code: "// eslint-disable-next-line", + options: [{ ignore: ["eslint-disable-next-line"] }], + }, + { + code: "/* eslint-disable-line */", + options: [{ ignore: ["eslint-disable-line"] }], + }, + { + code: "/* eslint-disable-next-line */", + options: [{ ignore: ["eslint-disable-next-line"] }], + }, + { + code: "/* exported */", + options: [{ ignore: ["exported"] }], + }, + { + code: "/* global */", + options: [{ ignore: ["global"] }], + }, + { + code: "/* globals */", + options: [{ ignore: ["globals"] }], + }, + ], + invalid: [ + { + code: "/* eslint */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: '/* eslint eqeqeq: "off", curly: "error" */', + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-env */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-env node */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-enable */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-enable eqeqeq */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable eqeqeq */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "// eslint-disable-line", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "// eslint-disable-line eqeqeq", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "// eslint-disable-next-line", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "// eslint-disable-next-line eqeqeq", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable-line */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable-line eqeqeq */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable-next-line */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable-next-line eqeqeq */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* exported */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* global */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* global _ */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* globals */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* globals _ */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + // empty description + { + code: "/* eslint-disable-next-line eqeqeq -- */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + ], + }) +} diff --git a/tests/types/configs.test-d.cts b/tests/types/configs.test-d.cts new file mode 100644 index 0000000..0843bf5 --- /dev/null +++ b/tests/types/configs.test-d.cts @@ -0,0 +1,26 @@ +import configs = require("@eslint-community/eslint-plugin-eslint-comments/configs") +import expectTypeModule = require("expect-type") +import eslintV9ConfigModule = require("eslint-v9/config") + +import type { Linter } from "eslint" with { "resolution-mode": "require" }; + +import expectTypeOf = expectTypeModule.expectTypeOf +import defineConfig = eslintV9ConfigModule.defineConfig + +expectTypeOf(configs) + .toHaveProperty("recommended") + .toExtend() + +expectTypeOf([configs.recommended]).toExtend() + +configs.recommended satisfies Linter.FlatConfig + +expectTypeOf().toExtend() + +expectTypeOf(defineConfig).toBeCallableWith< + Pick< + typeof configs.recommended, + | "rules" + | "name" + >[] +>() diff --git a/tests/types/configs.test-d.mts b/tests/types/configs.test-d.mts new file mode 100644 index 0000000..345cc9e --- /dev/null +++ b/tests/types/configs.test-d.mts @@ -0,0 +1,30 @@ +import * as configs from "@eslint-community/eslint-plugin-eslint-comments/configs"; +import type { Linter } from "eslint" with { "resolution-mode": "import" }; +import { defineConfig } from "eslint-v9/config"; +import { expectTypeOf } from "expect-type"; + +expectTypeOf(configs) + .toHaveProperty("recommended") + .toExtend() + +expectTypeOf([configs.recommended]).toExtend() + +expectTypeOf(configs.recommended).toExtend() + +expectTypeOf(configs) + .toHaveProperty("recommended") + .toExtend() + +expectTypeOf([configs.recommended]).toExtend() + +configs.recommended satisfies Linter.FlatConfig + +expectTypeOf().toExtend() + +expectTypeOf(defineConfig).toBeCallableWith< + Pick< + typeof configs.recommended, + | "rules" + | "name" + >[] +>() diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..d089852 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "noEmit": false, + "outDir": "./dist" + }, + "extends": "./tsconfig.json", + "include": ["lib", "configs.ts", "index.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..35026e2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "allowSyntheticDefaultImports": true, + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "lib": ["ESNext"], + "module": "nodenext", + "moduleDetection": "force", + "moduleResolution": "nodenext", + "noEmit": true, + "noEmitOnError": true, + "noErrorTruncation": true, + "outDir": "./dist", + "resolveJsonModule": true, + "rewriteRelativeImportExtensions": true, + "rootDir": "./", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "esnext", + "types": ["node", "mocha"], + "useDefineForClassFields": true, + "useUnknownInCatchVariables": true + }, + "include": ["**/*"] +} diff --git a/tsdown.config.mts b/tsdown.config.mts new file mode 100644 index 0000000..3959914 --- /dev/null +++ b/tsdown.config.mts @@ -0,0 +1,57 @@ +import * as path from "node:path" +import { fileURLToPath } from "node:url" +import type { InlineConfig, UserConfig } from "tsdown" +import { defineConfig } from "tsdown" +import packageJson from "./package.json" with { type: "json" } + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +const tsdownConfig = defineConfig((cliOptions) => { + const commonOptions = { + clean: false, + cwd: __dirname, + dts: { + emitJs: false, + newContext: true, + oxc: false, + resolver: "tsc", + sideEffects: false, + sourcemap: true, + }, + entry: { + index: "index.ts", + configs: "configs.ts", + }, + minify: "dce-only", + treeshake: { + annotations: true, + commonjs: true, + moduleSideEffects: false, + }, + failOnWarn: true, + fixedExtension: false, + format: ["es", "cjs"], + hash: false, + nodeProtocol: true, + shims: true, + sourcemap: true, + outExtensions: ({ format }) => ({ + dts: format === "cjs" ? ".d.cts" : ".d.ts", + js: format === "cjs" ? ".cjs" : ".js", + }), + outDir: "dist", + platform: "node", + target: ["esnext", "node20"], + tsconfig: path.join(__dirname, "tsconfig.build.json"), + ...cliOptions, + } as const satisfies InlineConfig + + return [ + { + ...commonOptions, + name: `${packageJson.name} Modern Dual Format`, + }, + ] as const satisfies UserConfig[] +}) + +export default tsdownConfig