From 24fd76588774f1306f8138f56972020762d5576f Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:45:37 +0100 Subject: [PATCH 1/9] feat: move to ESM only --- .eslint-doc-generatorrc.js | 6 +- .prettierrc.js | 4 +- commitlint.config.js | 4 +- configs/all-type-checked.js | 6 +- configs/all.js | 6 +- configs/recommended.js | 6 +- configs/rules-recommended.js | 6 +- configs/rules.js | 6 +- configs/tests-recommended.js | 6 +- configs/tests.js | 6 +- eslint.config.js | 21 +- lib/index.js | 83 +- lib/rules/consistent-output.js | 8 +- lib/rules/fixer-return.js | 10 +- lib/rules/meta-property-ordering.js | 7 +- lib/rules/no-deprecated-context-methods.js | 8 +- lib/rules/no-deprecated-report-api.js | 8 +- lib/rules/no-identical-tests.js | 8 +- lib/rules/no-meta-schema-default.js | 10 +- lib/rules/no-missing-message-ids.js | 8 +- lib/rules/no-missing-placeholders.js | 10 +- lib/rules/no-only-tests.js | 12 +- lib/rules/no-property-in-node.js | 8 +- lib/rules/no-unused-message-ids.js | 8 +- lib/rules/no-unused-placeholders.js | 10 +- lib/rules/no-useless-token-range.js | 8 +- lib/rules/prefer-message-ids.js | 10 +- lib/rules/prefer-object-rule.js | 8 +- lib/rules/prefer-output-null.js | 8 +- lib/rules/prefer-placeholders.js | 10 +- lib/rules/prefer-replace-text.js | 8 +- lib/rules/report-message-format.js | 10 +- lib/rules/require-meta-default-options.js | 8 +- lib/rules/require-meta-docs-description.js | 10 +- lib/rules/require-meta-docs-recommended.js | 10 +- lib/rules/require-meta-docs-url.js | 12 +- lib/rules/require-meta-fixable.js | 10 +- lib/rules/require-meta-has-suggestions.js | 10 +- lib/rules/require-meta-schema-description.js | 10 +- lib/rules/require-meta-schema.js | 8 +- lib/rules/require-meta-type.js | 10 +- lib/rules/test-case-property-ordering.js | 8 +- lib/rules/test-case-shorthand-strings.js | 8 +- lib/utils.js | 1224 ++++++++--------- package.json | 1 + tests/lib/eslint-rule-tester.js | 14 +- tests/lib/index.js | 6 +- tests/lib/rule-setup.js | 20 +- tests/lib/rules/consistent-output.js | 6 +- tests/lib/rules/fixer-return.js | 6 +- tests/lib/rules/meta-property-ordering.js | 6 +- .../rules/no-deprecated-context-methods.js | 6 +- tests/lib/rules/no-deprecated-report-api.js | 6 +- tests/lib/rules/no-identical-tests.js | 6 +- tests/lib/rules/no-meta-schema-default.js | 6 +- tests/lib/rules/no-missing-message-ids.js | 6 +- tests/lib/rules/no-missing-placeholders.js | 6 +- tests/lib/rules/no-only-tests.js | 6 +- tests/lib/rules/no-property-in-node.js | 15 +- tests/lib/rules/no-unused-message-ids.js | 6 +- tests/lib/rules/no-unused-placeholders.js | 6 +- tests/lib/rules/no-useless-token-range.js | 6 +- tests/lib/rules/prefer-message-ids.js | 6 +- tests/lib/rules/prefer-object-rule.js | 6 +- tests/lib/rules/prefer-output-null.js | 6 +- tests/lib/rules/prefer-placeholders.js | 6 +- tests/lib/rules/prefer-replace-text.js | 6 +- tests/lib/rules/report-message-format.js | 6 +- .../lib/rules/require-meta-default-options.js | 9 +- .../rules/require-meta-docs-description.js | 9 +- .../rules/require-meta-docs-recommended.js | 9 +- tests/lib/rules/require-meta-docs-url.js | 6 +- tests/lib/rules/require-meta-fixable.js | 6 +- .../lib/rules/require-meta-has-suggestions.js | 6 +- .../rules/require-meta-schema-description.js | 6 +- tests/lib/rules/require-meta-schema.js | 6 +- tests/lib/rules/require-meta-type.js | 6 +- .../lib/rules/test-case-property-ordering.js | 6 +- .../lib/rules/test-case-shorthand-strings.js | 6 +- tests/lib/utils.js | 18 +- 80 files changed, 942 insertions(+), 986 deletions(-) diff --git a/.eslint-doc-generatorrc.js b/.eslint-doc-generatorrc.js index 5f25c0a6..41f1041c 100644 --- a/.eslint-doc-generatorrc.js +++ b/.eslint-doc-generatorrc.js @@ -1,9 +1,7 @@ -'use strict'; - -const prettier = require('prettier'); +import prettier from 'prettier'; /** @type {import('eslint-doc-generator').GenerateOptions} */ -module.exports = { +export default { ignoreConfig: [ 'all', 'all-type-checked', diff --git a/.prettierrc.js b/.prettierrc.js index 534e6d35..ae4b0277 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,5 +1,3 @@ -'use strict'; - -module.exports = { +export default { singleQuote: true, }; diff --git a/commitlint.config.js b/commitlint.config.js index 0cf61d7e..3f5e287f 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,3 +1 @@ -'use strict'; - -module.exports = { extends: ['@commitlint/config-conventional'] }; +export default { extends: ['@commitlint/config-conventional'] }; diff --git a/configs/all-type-checked.js b/configs/all-type-checked.js index 59bbf2b3..84309cba 100644 --- a/configs/all-type-checked.js +++ b/configs/all-type-checked.js @@ -3,8 +3,8 @@ * @author 唯然 */ -'use strict'; +import plugin from '../lib/index.js'; -const plugin = require('../lib/index.js'); +const config = plugin.configs['flat/all-type-checked']; -module.exports = plugin.configs['flat/all-type-checked']; +export default config; diff --git a/configs/all.js b/configs/all.js index e237d769..cf48e487 100644 --- a/configs/all.js +++ b/configs/all.js @@ -4,8 +4,8 @@ * @author 唯然 */ -'use strict'; +import plugin from '../lib/index.js'; -const plugin = require('../lib/index.js'); +const config = plugin.configs['flat/all']; -module.exports = plugin.configs['flat/all']; +export default config; diff --git a/configs/recommended.js b/configs/recommended.js index bb282caa..17c2ffad 100644 --- a/configs/recommended.js +++ b/configs/recommended.js @@ -4,8 +4,8 @@ * @author 唯然 */ -'use strict'; +import plugin from '../lib/index.js'; -const plugin = require('../lib/index.js'); +const config = plugin.configs['flat/recommended']; -module.exports = plugin.configs['flat/recommended']; +export default config; diff --git a/configs/rules-recommended.js b/configs/rules-recommended.js index 09d8f57a..8e79f61a 100644 --- a/configs/rules-recommended.js +++ b/configs/rules-recommended.js @@ -4,8 +4,8 @@ * @author 唯然 */ -'use strict'; +import plugin from '../lib/index.js'; -const plugin = require('../lib/index.js'); +const config = plugin.configs['flat/rules-recommended']; -module.exports = plugin.configs['flat/rules-recommended']; +export default config; diff --git a/configs/rules.js b/configs/rules.js index 71ca5852..f3414360 100644 --- a/configs/rules.js +++ b/configs/rules.js @@ -4,8 +4,8 @@ * @author 唯然 */ -'use strict'; +import plugin from '../lib/index.js'; -const plugin = require('../lib/index.js'); +const config = plugin.configs['flat/rules']; -module.exports = plugin.configs['flat/rules']; +export default config; diff --git a/configs/tests-recommended.js b/configs/tests-recommended.js index 0a467239..367f5d17 100644 --- a/configs/tests-recommended.js +++ b/configs/tests-recommended.js @@ -4,8 +4,8 @@ * @author 唯然 */ -'use strict'; +import plugin from '../lib/index.js'; -const plugin = require('../lib/index.js'); +const config = plugin.configs['flat/tests-recommended']; -module.exports = plugin.configs['flat/tests-recommended']; +export default config; diff --git a/configs/tests.js b/configs/tests.js index 184d3bab..38ced25c 100644 --- a/configs/tests.js +++ b/configs/tests.js @@ -4,8 +4,8 @@ * @author 唯然 */ -'use strict'; +import plugin from '../lib/index.js'; -const plugin = require('../lib/index.js'); +const config = plugin.configs['flat/tests']; -module.exports = plugin.configs['flat/tests']; +export default config; diff --git a/eslint.config.js b/eslint.config.js index 24f9f4ea..41bcd60f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,18 +1,19 @@ -'use strict'; - -const js = require('@eslint/js'); -const { FlatCompat } = require('@eslint/eslintrc'); -const globals = require('globals'); -const markdown = require('eslint-plugin-markdown'); -const pluginN = require('eslint-plugin-n'); -const eslintPluginConfig = require('eslint-plugin-eslint-plugin/configs/all'); +import js from '@eslint/js'; +import { FlatCompat } from '@eslint/eslintrc'; +import globals from 'globals'; +import markdown from 'eslint-plugin-markdown'; +import pluginN from 'eslint-plugin-n'; +import eslintPluginConfig from 'eslint-plugin-eslint-plugin/configs/all'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +const dirname = path.dirname(fileURLToPath(import.meta.url)); const compat = new FlatCompat({ - baseDirectory: __dirname, + baseDirectory: dirname, recommendedConfig: js.configs.recommended, }); -module.exports = [ +export default [ ...compat.extends( 'not-an-aardvark/node', 'plugin:@eslint-community/eslint-comments/recommended', diff --git a/lib/index.js b/lib/index.js index d71340a3..5bc6adda 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,15 +3,43 @@ * @author Teddy Katz */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const fs = require('fs'); -const path = require('path'); -const packageMetadata = require('../package'); +import packageMetadata from '../package.json' with { type: 'json' }; +import consistentOutput from './rules/consistent-output.js'; +import fixerReturn from './rules/fixer-return.js'; +import metaPropertyOrdering from './rules/meta-property-ordering.js'; +import noDeprecatedContextMethods from './rules/no-deprecated-context-methods.js'; +import noDeprecatedReportApi from './rules/no-deprecated-report-api.js'; +import noIdenticalTests from './rules/no-identical-tests.js'; +import noMetaSchemaDefault from './rules/no-meta-schema-default.js'; +import noMissingMessageIds from './rules/no-missing-message-ids.js'; +import noMissingPlaceholders from './rules/no-missing-placeholders.js'; +import noOnlyTests from './rules/no-only-tests.js'; +import noPropertyInNode from './rules/no-property-in-node.js'; +import noUnusedMessageIds from './rules/no-unused-message-ids.js'; +import noUnusedPlaceholders from './rules/no-unused-placeholders.js'; +import noUselessTokenRange from './rules/no-useless-token-range.js'; +import preferMessageIds from './rules/prefer-message-ids.js'; +import preferObjectRule from './rules/prefer-object-rule.js'; +import preferOutputNull from './rules/prefer-output-null.js'; +import preferPlaceholders from './rules/prefer-placeholders.js'; +import preferReplaceText from './rules/prefer-replace-text.js'; +import reportMessageFormat from './rules/report-message-format.js'; +import requireMetaDefaultOptions from './rules/require-meta-default-options.js'; +import requireMetaDocsDescription from './rules/require-meta-docs-description.js'; +import requireMetaDocsRecommended from './rules/require-meta-docs-recommended.js'; +import requireMetaDocsUrl from './rules/require-meta-docs-url.js'; +import requireMetaFixable from './rules/require-meta-fixable.js'; +import requireMetaHasSuggestions from './rules/require-meta-has-suggestions.js'; +import requireMetaSchemaDescription from './rules/require-meta-schema-description.js'; +import requireMetaSchema from './rules/require-meta-schema.js'; +import requireMetaType from './rules/require-meta-type.js'; +import testCasePropertyOrdering from './rules/test-case-property-ordering.js'; +import testCaseShorthandStrings from './rules/test-case-shorthand-strings.js'; + const PLUGIN_NAME = packageMetadata.name.replace(/^eslint-plugin-/, ''); const configFilters = { @@ -31,16 +59,39 @@ const configFilters = { // ------------------------------------------------------------------------------ // import all rules in lib/rules -const allRules = Object.fromEntries( - fs - .readdirSync(`${__dirname}/rules`) - .filter((fileName) => fileName.endsWith('.js') && /^[^._]/.test(fileName)) - .map((fileName) => fileName.replace(/\.js$/, '')) - .map((ruleName) => [ - ruleName, - require(path.join(__dirname, 'rules', ruleName)), - ]), -); +const allRules = { + 'consistent-output': consistentOutput, + 'fixer-return': fixerReturn, + 'meta-property-ordering': metaPropertyOrdering, + 'no-deprecated-context-methods': noDeprecatedContextMethods, + 'no-deprecated-report-api': noDeprecatedReportApi, + 'no-identical-tests': noIdenticalTests, + 'no-meta-schema-default': noMetaSchemaDefault, + 'no-missing-message-ids': noMissingMessageIds, + 'no-missing-placeholders': noMissingPlaceholders, + 'no-only-tests': noOnlyTests, + 'no-property-in-node': noPropertyInNode, + 'no-unused-message-ids': noUnusedMessageIds, + 'no-unused-placeholders': noUnusedPlaceholders, + 'no-useless-token-range': noUselessTokenRange, + 'prefer-message-ids': preferMessageIds, + 'prefer-object-rule': preferObjectRule, + 'prefer-output-null': preferOutputNull, + 'prefer-placeholders': preferPlaceholders, + 'prefer-replace-text': preferReplaceText, + 'report-message-format': reportMessageFormat, + 'require-meta-default-options': requireMetaDefaultOptions, + 'require-meta-docs-description': requireMetaDocsDescription, + 'require-meta-docs-recommended': requireMetaDocsRecommended, + 'require-meta-docs-url': requireMetaDocsUrl, + 'require-meta-fixable': requireMetaFixable, + 'require-meta-has-suggestions': requireMetaHasSuggestions, + 'require-meta-schema-description': requireMetaSchemaDescription, + 'require-meta-schema': requireMetaSchema, + 'require-meta-type': requireMetaType, + 'test-case-property-ordering': testCasePropertyOrdering, + 'test-case-shorthand-strings': testCaseShorthandStrings, +}; /** @type {import("eslint").ESLint.Plugin} */ const plugin = { @@ -87,4 +138,4 @@ Object.assign( }, {}), ); -module.exports = plugin; +export default plugin; diff --git a/lib/rules/consistent-output.js b/lib/rules/consistent-output.js index 42cbf3ae..b0eb7f93 100644 --- a/lib/rules/consistent-output.js +++ b/lib/rules/consistent-output.js @@ -3,16 +3,14 @@ * @author Teddy Katz */ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -71,3 +69,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/fixer-return.js b/lib/rules/fixer-return.js index 11e7d57f..0f29d8ac 100644 --- a/lib/rules/fixer-return.js +++ b/lib/rules/fixer-return.js @@ -3,21 +3,19 @@ * @author 薛定谔的猫 */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const utils = require('../utils'); -const { getStaticValue } = require('@eslint-community/eslint-utils'); +import * as utils from '../utils.js'; +import { getStaticValue } from '@eslint-community/eslint-utils'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -167,3 +165,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/meta-property-ordering.js b/lib/rules/meta-property-ordering.js index 27e29218..1871187e 100644 --- a/lib/rules/meta-property-ordering.js +++ b/lib/rules/meta-property-ordering.js @@ -2,9 +2,8 @@ * @fileoverview Enforces the order of meta properties */ -'use strict'; +import * as utils from '../utils.js'; -const utils = require('../utils'); const { getKeyName, getRuleInfo } = utils; const defaultOrder = [ @@ -24,7 +23,7 @@ const defaultOrder = [ // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -111,3 +110,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/no-deprecated-context-methods.js b/lib/rules/no-deprecated-context-methods.js index c4f6134b..416ec988 100644 --- a/lib/rules/no-deprecated-context-methods.js +++ b/lib/rules/no-deprecated-context-methods.js @@ -3,9 +3,7 @@ * @author Teddy Katz */ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; const DEPRECATED_PASSTHROUGHS = { getSource: 'getText', @@ -35,7 +33,7 @@ const DEPRECATED_PASSTHROUGHS = { // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -96,3 +94,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/no-deprecated-report-api.js b/lib/rules/no-deprecated-report-api.js index eb0c07c2..a0e3f830 100644 --- a/lib/rules/no-deprecated-report-api.js +++ b/lib/rules/no-deprecated-report-api.js @@ -3,16 +3,14 @@ * @author Teddy Katz */ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -81,3 +79,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/no-identical-tests.js b/lib/rules/no-identical-tests.js index c7c5867f..2dfce64c 100644 --- a/lib/rules/no-identical-tests.js +++ b/lib/rules/no-identical-tests.js @@ -3,16 +3,14 @@ * @author 薛定谔的猫 */ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -85,3 +83,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/no-meta-schema-default.js b/lib/rules/no-meta-schema-default.js index ccba978a..26190088 100644 --- a/lib/rules/no-meta-schema-default.js +++ b/lib/rules/no-meta-schema-default.js @@ -1,14 +1,12 @@ -'use strict'; - -const { getStaticValue } = require('@eslint-community/eslint-utils'); -const utils = require('../utils'); +import { getStaticValue } from '@eslint-community/eslint-utils'; +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -111,3 +109,5 @@ module.exports = { } }, }; + +export default rule; diff --git a/lib/rules/no-missing-message-ids.js b/lib/rules/no-missing-message-ids.js index 0bc2d9ed..214a9e35 100644 --- a/lib/rules/no-missing-message-ids.js +++ b/lib/rules/no-missing-message-ids.js @@ -1,13 +1,11 @@ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -99,3 +97,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/no-missing-placeholders.js b/lib/rules/no-missing-placeholders.js index 996e8004..f4f126db 100644 --- a/lib/rules/no-missing-placeholders.js +++ b/lib/rules/no-missing-placeholders.js @@ -3,17 +3,15 @@ * @author Teddy Katz */ -'use strict'; - -const utils = require('../utils'); -const { getStaticValue } = require('@eslint-community/eslint-utils'); +import * as utils from '../utils.js'; +import { getStaticValue } from '@eslint-community/eslint-utils'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -129,3 +127,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/no-only-tests.js b/lib/rules/no-only-tests.js index 3aa267d2..43bf79d1 100644 --- a/lib/rules/no-only-tests.js +++ b/lib/rules/no-only-tests.js @@ -1,14 +1,12 @@ -'use strict'; - -const utils = require('../utils'); -const { +import * as utils from '../utils.js'; +import { isCommaToken, isOpeningBraceToken, isClosingBraceToken, -} = require('@eslint-community/eslint-utils'); +} from '@eslint-community/eslint-utils'; /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -96,3 +94,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/no-property-in-node.js b/lib/rules/no-property-in-node.js index f21e0060..5a92b749 100644 --- a/lib/rules/no-property-in-node.js +++ b/lib/rules/no-property-in-node.js @@ -1,6 +1,4 @@ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; const defaultTypedNodeSourceFileTesters = [ /@types[/\\]estree[/\\]index\.d\.ts/, @@ -47,7 +45,7 @@ function isAstNodeType(type, typedNodeSourceFileTesters) { } /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -108,3 +106,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/no-unused-message-ids.js b/lib/rules/no-unused-message-ids.js index 69036140..492e817d 100644 --- a/lib/rules/no-unused-message-ids.js +++ b/lib/rules/no-unused-message-ids.js @@ -1,13 +1,11 @@ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -137,3 +135,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/no-unused-placeholders.js b/lib/rules/no-unused-placeholders.js index d7169c9a..7fbf9cf6 100644 --- a/lib/rules/no-unused-placeholders.js +++ b/lib/rules/no-unused-placeholders.js @@ -3,17 +3,15 @@ * @author 薛定谔的猫 */ -'use strict'; - -const utils = require('../utils'); -const { getStaticValue } = require('@eslint-community/eslint-utils'); +import * as utils from '../utils.js'; +import { getStaticValue } from '@eslint-community/eslint-utils'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -125,3 +123,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/no-useless-token-range.js b/lib/rules/no-useless-token-range.js index 6c6d6214..5cf35fec 100644 --- a/lib/rules/no-useless-token-range.js +++ b/lib/rules/no-useless-token-range.js @@ -3,16 +3,14 @@ * @author Teddy Katz */ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -172,3 +170,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/prefer-message-ids.js b/lib/rules/prefer-message-ids.js index cdf66642..cefbab65 100644 --- a/lib/rules/prefer-message-ids.js +++ b/lib/rules/prefer-message-ids.js @@ -1,14 +1,12 @@ -'use strict'; - -const utils = require('../utils'); -const { getStaticValue } = require('@eslint-community/eslint-utils'); +import * as utils from '../utils.js'; +import { getStaticValue } from '@eslint-community/eslint-utils'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -107,3 +105,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/prefer-object-rule.js b/lib/rules/prefer-object-rule.js index 1b0e98f1..85faaedf 100644 --- a/lib/rules/prefer-object-rule.js +++ b/lib/rules/prefer-object-rule.js @@ -2,16 +2,14 @@ * @author Brad Zacher */ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -81,3 +79,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/prefer-output-null.js b/lib/rules/prefer-output-null.js index 4d9e90e5..99819814 100644 --- a/lib/rules/prefer-output-null.js +++ b/lib/rules/prefer-output-null.js @@ -3,16 +3,14 @@ * @author 薛定谔的猫 */ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -75,3 +73,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/prefer-placeholders.js b/lib/rules/prefer-placeholders.js index f5c88fc1..4ace1011 100644 --- a/lib/rules/prefer-placeholders.js +++ b/lib/rules/prefer-placeholders.js @@ -3,17 +3,15 @@ * @author Teddy Katz */ -'use strict'; - -const utils = require('../utils'); -const { findVariable } = require('@eslint-community/eslint-utils'); +import * as utils from '../utils.js'; +import { findVariable } from '@eslint-community/eslint-utils'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -100,3 +98,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/prefer-replace-text.js b/lib/rules/prefer-replace-text.js index 173cc509..2a1041e1 100644 --- a/lib/rules/prefer-replace-text.js +++ b/lib/rules/prefer-replace-text.js @@ -3,16 +3,14 @@ * @author 薛定谔的猫 */ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -89,3 +87,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/report-message-format.js b/lib/rules/report-message-format.js index 4fce24b8..b9d7588f 100644 --- a/lib/rules/report-message-format.js +++ b/lib/rules/report-message-format.js @@ -3,17 +3,15 @@ * @author Teddy Katz */ -'use strict'; - -const { getStaticValue } = require('@eslint-community/eslint-utils'); -const utils = require('../utils'); +import { getStaticValue } from '@eslint-community/eslint-utils'; +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -137,3 +135,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/require-meta-default-options.js b/lib/rules/require-meta-default-options.js index 9ac37575..1e5d0a9c 100644 --- a/lib/rules/require-meta-default-options.js +++ b/lib/rules/require-meta-default-options.js @@ -1,9 +1,7 @@ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -102,3 +100,5 @@ module.exports = { return {}; }, }; + +export default rule; diff --git a/lib/rules/require-meta-docs-description.js b/lib/rules/require-meta-docs-description.js index b3cf5272..d2101144 100644 --- a/lib/rules/require-meta-docs-description.js +++ b/lib/rules/require-meta-docs-description.js @@ -1,7 +1,5 @@ -'use strict'; - -const { getStaticValue } = require('@eslint-community/eslint-utils'); -const utils = require('../utils'); +import { getStaticValue } from '@eslint-community/eslint-utils'; +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition @@ -10,7 +8,7 @@ const utils = require('../utils'); const DEFAULT_PATTERN = new RegExp('^(enforce|require|disallow)'); /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -102,3 +100,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/require-meta-docs-recommended.js b/lib/rules/require-meta-docs-recommended.js index 67851ffc..5ba260c4 100644 --- a/lib/rules/require-meta-docs-recommended.js +++ b/lib/rules/require-meta-docs-recommended.js @@ -1,7 +1,5 @@ -'use strict'; - -const { getStaticValue } = require('@eslint-community/eslint-utils'); -const utils = require('../utils'); +import { getStaticValue } from '@eslint-community/eslint-utils'; +import * as utils from '../utils.js'; /** * @param {import('eslint').Rule.RuleFixer} fixer @@ -22,7 +20,7 @@ function insertRecommendedProperty(fixer, objectNode, recommendedValue) { } /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -124,3 +122,5 @@ module.exports = { return {}; }, }; + +export default rule; diff --git a/lib/rules/require-meta-docs-url.js b/lib/rules/require-meta-docs-url.js index cb3e35d3..12bb2b35 100644 --- a/lib/rules/require-meta-docs-url.js +++ b/lib/rules/require-meta-docs-url.js @@ -2,22 +2,20 @@ * @author Toru Nagashima */ -'use strict'; - // ----------------------------------------------------------------------------- // Requirements // ----------------------------------------------------------------------------- -const path = require('path'); -const utils = require('../utils'); -const { getStaticValue } = require('@eslint-community/eslint-utils'); +import path from 'path'; +import * as utils from '../utils.js'; +import { getStaticValue } from '@eslint-community/eslint-utils'; // ----------------------------------------------------------------------------- // Rule Definition // ----------------------------------------------------------------------------- /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -165,3 +163,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/require-meta-fixable.js b/lib/rules/require-meta-fixable.js index e69ca4fc..0eb99eb6 100644 --- a/lib/rules/require-meta-fixable.js +++ b/lib/rules/require-meta-fixable.js @@ -3,17 +3,15 @@ * @author Teddy Katz */ -'use strict'; - -const { getStaticValue } = require('@eslint-community/eslint-utils'); -const utils = require('../utils'); +import { getStaticValue } from '@eslint-community/eslint-utils'; +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -136,3 +134,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/require-meta-has-suggestions.js b/lib/rules/require-meta-has-suggestions.js index c8f75b3f..b9e2f44b 100644 --- a/lib/rules/require-meta-has-suggestions.js +++ b/lib/rules/require-meta-has-suggestions.js @@ -1,14 +1,12 @@ -'use strict'; - -const utils = require('../utils'); -const { getStaticValue } = require('@eslint-community/eslint-utils'); +import * as utils from '../utils.js'; +import { getStaticValue } from '@eslint-community/eslint-utils'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -165,3 +163,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/require-meta-schema-description.js b/lib/rules/require-meta-schema-description.js index 842d576f..f9bfe7b0 100644 --- a/lib/rules/require-meta-schema-description.js +++ b/lib/rules/require-meta-schema-description.js @@ -1,14 +1,12 @@ -'use strict'; - -const { getStaticValue } = require('@eslint-community/eslint-utils'); -const utils = require('../utils'); +import { getStaticValue } from '@eslint-community/eslint-utils'; +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -112,3 +110,5 @@ module.exports = { } }, }; + +export default rule; diff --git a/lib/rules/require-meta-schema.js b/lib/rules/require-meta-schema.js index 59cf4575..45d28c91 100644 --- a/lib/rules/require-meta-schema.js +++ b/lib/rules/require-meta-schema.js @@ -1,13 +1,11 @@ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -139,3 +137,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/require-meta-type.js b/lib/rules/require-meta-type.js index a6aec027..d0d77103 100644 --- a/lib/rules/require-meta-type.js +++ b/lib/rules/require-meta-type.js @@ -3,10 +3,8 @@ * @author 薛定谔的猫 */ -'use strict'; - -const { getStaticValue } = require('@eslint-community/eslint-utils'); -const utils = require('../utils'); +import { getStaticValue } from '@eslint-community/eslint-utils'; +import * as utils from '../utils.js'; const VALID_TYPES = new Set(['problem', 'suggestion', 'layout']); // ------------------------------------------------------------------------------ @@ -14,7 +12,7 @@ const VALID_TYPES = new Set(['problem', 'suggestion', 'layout']); // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -75,3 +73,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/test-case-property-ordering.js b/lib/rules/test-case-property-ordering.js index f6b4dd2f..911d39a1 100644 --- a/lib/rules/test-case-property-ordering.js +++ b/lib/rules/test-case-property-ordering.js @@ -3,9 +3,7 @@ * @author 薛定谔的猫 */ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; const defaultOrder = [ 'filename', @@ -25,7 +23,7 @@ const defaultOrder = [ // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -109,3 +107,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/rules/test-case-shorthand-strings.js b/lib/rules/test-case-shorthand-strings.js index 0cf22333..81cd4f57 100644 --- a/lib/rules/test-case-shorthand-strings.js +++ b/lib/rules/test-case-shorthand-strings.js @@ -3,16 +3,14 @@ * @author Teddy Katz */ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'suggestion', docs: { @@ -127,3 +125,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/lib/utils.js b/lib/utils.js index bc15d2c0..f87cfb8c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,10 +1,5 @@ -'use strict'; - -const { - getStaticValue, - findVariable, -} = require('@eslint-community/eslint-utils'); -const estraverse = require('estraverse'); +import { getStaticValue, findVariable } from '@eslint-community/eslint-utils'; +import estraverse from 'estraverse'; const functionTypes = new Set([ 'FunctionExpression', @@ -46,7 +41,7 @@ const INTERESTING_RULE_KEYS = new Set(['create', 'meta']); */ function collectInterestingProperties(properties, interestingKeys) { return properties.reduce((parsedProps, prop) => { - const keyValue = module.exports.getKeyName(prop); + const keyValue = getKeyName(prop); if (interestingKeys.has(keyValue)) { // In TypeScript, unwrap any usage of `{} as const`. parsedProps[keyValue] = @@ -225,12 +220,12 @@ function getRuleExportsCJS(ast, scopeManager) { ) { exportsVarOverridden = true; if (isFunctionRule(node.right)) { - // Check `module.exports = function (context) { return { ... }; }` + // Check `= function (context) { return { ... }; }` exportsIsFunction = true; return { create: node.right, meta: null, isNewStyle: false }; } else if (node.right.type === 'ObjectExpression') { - // Check `module.exports = { create: function () {}, meta: {} }` + // Check `= { create: function () {}, meta: {} }` return collectInterestingProperties( node.right.properties, @@ -241,13 +236,13 @@ function getRuleExportsCJS(ast, scopeManager) { const possibleRule = findVariableValue(node.right, scopeManager); if (possibleRule) { if (possibleRule.type === 'ObjectExpression') { - // Check `const possibleRule = { ... }; module.exports = possibleRule; + // Check `const possibleRule = { ... }; = possibleRule; return collectInterestingProperties( possibleRule.properties, INTERESTING_RULE_KEYS, ); } else if (isFunctionRule(possibleRule)) { - // Check `const possibleRule = function(context) { return { ... } }; module.exports = possibleRule;` + // Check `const possibleRule = function(context) { return { ... } }; = possibleRule;` return { create: possibleRule, meta: null, isNewStyle: false }; } } @@ -263,7 +258,7 @@ function getRuleExportsCJS(ast, scopeManager) { node.left.property.type === 'Identifier' && INTERESTING_RULE_KEYS.has(node.left.property.name) ) { - // Check `module.exports.create = () => {}` + // Check `create = () => {}` currentExports[node.left.property.name] = node.right; } else if ( @@ -343,685 +338,656 @@ function collectArrayElements(node) { return []; } -module.exports = { - /** - * Performs static analysis on an AST to try to determine the final value of `module.exports`. - * @param {{ast: ASTNode, scopeManager?: ScopeManager}} sourceCode The object contains `Program` AST node, and optional `scopeManager` - * @returns {Object} An object with keys `meta`, `create`, and `isNewStyle`. `meta` and `create` correspond to the AST nodes - for the final values of `module.exports.meta` and `module.exports.create`. `isNewStyle` will be `true` if `module.exports` - is an object, and `false` if module.exports is just the `create` function. If no valid ESLint rule info can be extracted - from the file, the return value will be `null`. - */ - getRuleInfo({ ast, scopeManager }) { - const exportNodes = - ast.sourceType === 'module' - ? getRuleExportsESM(ast, scopeManager) - : getRuleExportsCJS(ast, scopeManager); - - const createExists = Object.prototype.hasOwnProperty.call( - exportNodes, - 'create', - ); - if (!createExists) { - return null; - } +/** +* Performs static analysis on an AST to try to determine the final value of `. +* @param {{ast: ASTNode, scopeManager?: ScopeManager}} sourceCode The object contains `Program` AST node, and optional `scopeManager` +* @returns {Object} An object with keys `meta`, `create`, and `isNewStyle`. `meta` and `create` correspond to the AST nodes +for the final values of `meta` and `create`. `isNewStyle` will be `true` if ` +is an object, and `false` if is just the `create` function. If no valid ESLint rule info can be extracted +from the file, the return value will be `null`. +*/ +export function getRuleInfo({ ast, scopeManager }) { + const exportNodes = + ast.sourceType === 'module' + ? getRuleExportsESM(ast, scopeManager) + : getRuleExportsCJS(ast, scopeManager); + + const createExists = Object.prototype.hasOwnProperty.call( + exportNodes, + 'create', + ); + if (!createExists) { + return null; + } - // If create/meta are defined in variables, get their values. - for (const key of Object.keys(exportNodes)) { - if (exportNodes[key] && exportNodes[key].type === 'Identifier') { - const value = findVariableValue(exportNodes[key], scopeManager); - if (value) { - exportNodes[key] = value; - } + // If create/meta are defined in variables, get their values. + for (const key of Object.keys(exportNodes)) { + if (exportNodes[key] && exportNodes[key].type === 'Identifier') { + const value = findVariableValue(exportNodes[key], scopeManager); + if (value) { + exportNodes[key] = value; } } + } - const createIsFunction = isNormalFunctionExpression(exportNodes.create); - if (!createIsFunction) { - return null; - } + const createIsFunction = isNormalFunctionExpression(exportNodes.create); + if (!createIsFunction) { + return null; + } - return Object.assign({ isNewStyle: true, meta: null }, exportNodes); - }, + return Object.assign({ isNewStyle: true, meta: null }, exportNodes); +} - /** - * Gets all the identifiers referring to the `context` variable in a rule source file. Note that this function will - * only work correctly after traversing the AST has started (e.g. in the first `Program` node). - * @param {RuleContext} scopeManager - * @param {ASTNode} ast The `Program` node for the file - * @returns {Set} A Set of all `Identifier` nodes that are references to the `context` value for the file - */ - getContextIdentifiers(scopeManager, ast) { - const ruleInfo = module.exports.getRuleInfo({ ast, scopeManager }); +/** + * Gets all the identifiers referring to the `context` variable in a rule source file. Note that this function will + * only work correctly after traversing the AST has started (e.g. in the first `Program` node). + * @param {RuleContext} scopeManager + * @param {ASTNode} ast The `Program` node for the file + * @returns {Set} A Set of all `Identifier` nodes that are references to the `context` value for the file + */ +export function getContextIdentifiers(scopeManager, ast) { + const ruleInfo = getRuleInfo({ ast, scopeManager }); - if ( - !ruleInfo || - ruleInfo.create.params.length === 0 || - ruleInfo.create.params[0].type !== 'Identifier' - ) { - return new Set(); - } + if ( + !ruleInfo || + ruleInfo.create.params.length === 0 || + ruleInfo.create.params[0].type !== 'Identifier' + ) { + return new Set(); + } - return new Set( - scopeManager - .getDeclaredVariables(ruleInfo.create) - .find((variable) => variable.name === ruleInfo.create.params[0].name) - .references.map((ref) => ref.identifier), - ); - }, - - /** - * Gets the key name of a Property, if it can be determined statically. - * @param {ASTNode} node The `Property` node - * @param {Scope} scope - * @returns {string|null} The key name, or `null` if the name cannot be determined statically. - */ - getKeyName(property, scope) { - if (!property.key) { - // likely a SpreadElement or another non-standard node - return null; - } - if (property.key.type === 'Identifier') { - if (property.computed) { - // Variable key: { [myVariable]: 'hello world' } - if (scope) { - const staticValue = getStaticValue(property.key, scope); - return staticValue ? staticValue.value : null; - } - // TODO: ensure scope is always passed to getKeyName() so we don't need to handle the case where it's not passed. - return null; - } - return property.key.name; - } - if (property.key.type === 'Literal') { - return '' + property.key.value; - } - if ( - property.key.type === 'TemplateLiteral' && - property.key.quasis.length === 1 - ) { - return property.key.quasis[0].value.cooked; - } + return new Set( + scopeManager + .getDeclaredVariables(ruleInfo.create) + .find((variable) => variable.name === ruleInfo.create.params[0].name) + .references.map((ref) => ref.identifier), + ); +} + +/** + * Gets the key name of a Property, if it can be determined statically. + * @param {ASTNode} node The `Property` node + * @param {Scope} scope + * @returns {string|null} The key name, or `null` if the name cannot be determined statically. + */ +export function getKeyName(property, scope) { + if (!property.key) { + // likely a SpreadElement or another non-standard node return null; - }, - - /** - * Extracts the body of a function if the given node is a function - * - * @param {ASTNode} node - * @returns {ExpressionStatement[]} - */ - extractFunctionBody(node) { - if ( - node.type === 'ArrowFunctionExpression' || - node.type === 'FunctionExpression' - ) { - if (node.body.type === 'BlockStatement') { - return node.body.body; + } + if (property.key.type === 'Identifier') { + if (property.computed) { + // Variable key: { [myVariable]: 'hello world' } + if (scope) { + const staticValue = getStaticValue(property.key, scope); + return staticValue ? staticValue.value : null; } - - return [node.body]; + // TODO: ensure scope is always passed to getKeyName() so we don't need to handle the case where it's not passed. + return null; } + return property.key.name; + } + if (property.key.type === 'Literal') { + return '' + property.key.value; + } + if ( + property.key.type === 'TemplateLiteral' && + property.key.quasis.length === 1 + ) { + return property.key.quasis[0].value.cooked; + } + return null; +} - return []; - }, - - /** - * Checks the given statements for possible test info - * - * @param {RuleContext} context The `context` variable for the source file itself - * @param {ASTNode[]} statements The statements to check - * @param {Set} variableIdentifiers - * @returns {CallExpression[]} - */ - checkStatementsForTestInfo( - context, - statements, - variableIdentifiers = new Set(), +/** + * Extracts the body of a function if the given node is a function + * + * @param {ASTNode} node + * @returns {ExpressionStatement[]} + */ +export function extractFunctionBody(node) { + if ( + node.type === 'ArrowFunctionExpression' || + node.type === 'FunctionExpression' ) { - const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 - const runCalls = []; - - for (const statement of statements) { - if (statement.type === 'VariableDeclaration') { - for (const declarator of statement.declarations) { - if (!declarator.init) { - continue; - } + if (node.body.type === 'BlockStatement') { + return node.body.body; + } - const extracted = module.exports.extractFunctionBody(declarator.init); + return [node.body]; + } - runCalls.push( - ...module.exports.checkStatementsForTestInfo( - context, - extracted, - variableIdentifiers, - ), - ); + return []; +} - if ( - isRuleTesterConstruction(declarator.init) && - declarator.id.type === 'Identifier' - ) { - const vars = sourceCode.getDeclaredVariables - ? sourceCode.getDeclaredVariables(declarator) - : context.getDeclaredVariables(declarator); - vars.forEach((variable) => { - variable.references - .filter((ref) => ref.isRead()) - .forEach((ref) => variableIdentifiers.add(ref.identifier)); - }); - } +/** + * Checks the given statements for possible test info + * + * @param {RuleContext} context The `context` variable for the source file itself + * @param {ASTNode[]} statements The statements to check + * @param {Set} variableIdentifiers + * @returns {CallExpression[]} + */ +export function checkStatementsForTestInfo( + context, + statements, + variableIdentifiers = new Set(), +) { + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 + const runCalls = []; + + for (const statement of statements) { + if (statement.type === 'VariableDeclaration') { + for (const declarator of statement.declarations) { + if (!declarator.init) { + continue; } - } - if (statement.type === 'FunctionDeclaration') { + const extracted = extractFunctionBody(declarator.init); + runCalls.push( - ...module.exports.checkStatementsForTestInfo( + ...checkStatementsForTestInfo( context, - statement.body.body, + extracted, variableIdentifiers, ), ); + + if ( + isRuleTesterConstruction(declarator.init) && + declarator.id.type === 'Identifier' + ) { + const vars = sourceCode.getDeclaredVariables + ? sourceCode.getDeclaredVariables(declarator) + : context.getDeclaredVariables(declarator); + vars.forEach((variable) => { + variable.references + .filter((ref) => ref.isRead()) + .forEach((ref) => variableIdentifiers.add(ref.identifier)); + }); + } } + } - if (statement.type === 'IfStatement') { - const body = - statement.consequent.type === 'BlockStatement' - ? statement.consequent.body - : [statement.consequent]; + if (statement.type === 'FunctionDeclaration') { + runCalls.push( + ...checkStatementsForTestInfo( + context, + statement.body.body, + variableIdentifiers, + ), + ); + } - runCalls.push( - ...module.exports.checkStatementsForTestInfo( - context, - body, - variableIdentifiers, - ), - ); + if (statement.type === 'IfStatement') { + const body = + statement.consequent.type === 'BlockStatement' + ? statement.consequent.body + : [statement.consequent]; - continue; - } + runCalls.push( + ...checkStatementsForTestInfo(context, body, variableIdentifiers), + ); - const expression = - statement.type === 'ExpressionStatement' - ? statement.expression - : statement; + continue; + } - if (expression.type !== 'CallExpression') { - continue; - } + const expression = + statement.type === 'ExpressionStatement' + ? statement.expression + : statement; - for (const arg of expression.arguments) { - const extracted = module.exports.extractFunctionBody(arg); + if (expression.type !== 'CallExpression') { + continue; + } - runCalls.push( - ...module.exports.checkStatementsForTestInfo( - context, - extracted, - variableIdentifiers, - ), - ); - } + for (const arg of expression.arguments) { + const extracted = extractFunctionBody(arg); - if ( - expression.callee.type === 'MemberExpression' && - (isRuleTesterConstruction(expression.callee.object) || - variableIdentifiers.has(expression.callee.object)) && - expression.callee.property.type === 'Identifier' && - expression.callee.property.name === 'run' - ) { - runCalls.push(expression); - } + runCalls.push( + ...checkStatementsForTestInfo(context, extracted, variableIdentifiers), + ); } - return runCalls; - }, - - /** - * Performs static analysis on an AST to try to find test cases - * @param {RuleContext} context The `context` variable for the source file itself - * @param {ASTNode} ast The `Program` node for the file. - * @returns {object} An object with `valid` and `invalid` keys containing a list of AST nodes corresponding to tests - */ - getTestInfo(context, ast) { - const runCalls = module.exports.checkStatementsForTestInfo( - context, - ast.body, - ); + if ( + expression.callee.type === 'MemberExpression' && + (isRuleTesterConstruction(expression.callee.object) || + variableIdentifiers.has(expression.callee.object)) && + expression.callee.property.type === 'Identifier' && + expression.callee.property.name === 'run' + ) { + runCalls.push(expression); + } + } + + return runCalls; +} + +/** + * Performs static analysis on an AST to try to find test cases + * @param {RuleContext} context The `context` variable for the source file itself + * @param {ASTNode} ast The `Program` node for the file. + * @returns {object} An object with `valid` and `invalid` keys containing a list of AST nodes corresponding to tests + */ +export function getTestInfo(context, ast) { + const runCalls = checkStatementsForTestInfo(context, ast.body); + + return runCalls + .filter( + (call) => + call.arguments.length >= 3 && + call.arguments[2].type === 'ObjectExpression', + ) + .map((call) => call.arguments[2]) + .map((run) => { + const validProperty = run.properties.find( + (prop) => getKeyName(prop) === 'valid', + ); + const invalidProperty = run.properties.find( + (prop) => getKeyName(prop) === 'invalid', + ); + + return { + valid: + validProperty && validProperty.value.type === 'ArrayExpression' + ? validProperty.value.elements.filter(Boolean) + : [], + invalid: + invalidProperty && invalidProperty.value.type === 'ArrayExpression' + ? invalidProperty.value.elements.filter(Boolean) + : [], + }; + }); +} + +/** + * Gets information on a report, given the ASTNode of context.report(). + * @param {ASTNode} node The ASTNode of context.report() + * @param {Context} context + */ +export function getReportInfo(node, context) { + const reportArgs = node.arguments; + + // If there is exactly one argument, the API expects an object. + // Otherwise, if the second argument is a string, the arguments are interpreted as + // ['node', 'message', 'data', 'fix']. + // Otherwise, the arguments are interpreted as ['node', 'loc', 'message', 'data', 'fix']. + + if (reportArgs.length === 0) { + return null; + } + + if (reportArgs.length === 1) { + if (reportArgs[0].type === 'ObjectExpression') { + return reportArgs[0].properties.reduce((reportInfo, property) => { + const propName = getKeyName(property); + + if (propName !== null) { + return Object.assign(reportInfo, { [propName]: property.value }); + } + return reportInfo; + }, {}); + } + return null; + } + + let keys; + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: use context.sourceCode when dropping eslint < v9 + const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when dropping eslint < v9 + const secondArgStaticValue = getStaticValue(reportArgs[1], scope); - return runCalls + if ( + (secondArgStaticValue && typeof secondArgStaticValue.value === 'string') || + reportArgs[1].type === 'TemplateLiteral' + ) { + keys = ['node', 'message', 'data', 'fix']; + } else if ( + reportArgs[1].type === 'ObjectExpression' || + reportArgs[1].type === 'ArrayExpression' || + (reportArgs[1].type === 'Literal' && + typeof reportArgs[1].value !== 'string') || + (secondArgStaticValue && + ['object', 'number'].includes(typeof secondArgStaticValue.value)) + ) { + keys = ['node', 'loc', 'message', 'data', 'fix']; + } else { + // Otherwise, we can't statically determine what argument means what, so no safe fix is possible. + return null; + } + + return Object.fromEntries( + keys + .slice(0, reportArgs.length) + .map((key, index) => [key, reportArgs[index]]), + ); +} + +/** + * Gets a set of all `sourceCode` identifiers. + * @param {ScopeManager} scopeManager + * @param {ASTNode} ast The AST of the file. This must have `parent` properties. + * @returns {Set} A set of all identifiers referring to the `SourceCode` object. + */ +export function getSourceCodeIdentifiers(scopeManager, ast) { + return new Set( + [...getContextIdentifiers(scopeManager, ast)] .filter( - (call) => - call.arguments.length >= 3 && - call.arguments[2].type === 'ObjectExpression', + (identifier) => + identifier.parent && + identifier.parent.type === 'MemberExpression' && + identifier === identifier.parent.object && + identifier.parent.property.type === 'Identifier' && + identifier.parent.property.name === 'getSourceCode' && + identifier.parent.parent.type === 'CallExpression' && + identifier.parent === identifier.parent.parent.callee && + identifier.parent.parent.parent.type === 'VariableDeclarator' && + identifier.parent.parent === identifier.parent.parent.parent.init && + identifier.parent.parent.parent.id.type === 'Identifier', ) - .map((call) => call.arguments[2]) - .map((run) => { - const validProperty = run.properties.find( - (prop) => module.exports.getKeyName(prop) === 'valid', - ); - const invalidProperty = run.properties.find( - (prop) => module.exports.getKeyName(prop) === 'invalid', - ); + .flatMap((identifier) => + scopeManager.getDeclaredVariables(identifier.parent.parent.parent), + ) + .flatMap((variable) => variable.references) + .map((ref) => ref.identifier), + ); +} + +/** + * Insert a given property into a given object literal. + * @param {SourceCodeFixer} fixer The fixer. + * @param {Node} node The ObjectExpression node to insert a property. + * @param {string} propertyText The property code to insert. + * @returns {void} + */ +export function insertProperty(fixer, node, propertyText, sourceCode) { + if (node.properties.length === 0) { + return fixer.replaceText(node, `{\n${propertyText}\n}`); + } + return fixer.insertTextAfter( + sourceCode.getLastToken(node.properties.at(-1)), + `,\n${propertyText}`, + ); +} +/** + * Collect all context.report({...}) violation/suggestion-related nodes into a standardized array for convenience. + * @param {Object} reportInfo - Result of getReportInfo(). + * @returns {messageId?: String, message?: String, data?: Object, fix?: Function}[] + */ +export function collectReportViolationAndSuggestionData(reportInfo) { + return [ + // Violation message + { + messageId: reportInfo.messageId, + message: reportInfo.message, + data: reportInfo.data, + fix: reportInfo.fix, + }, + // Suggestion messages + ...collectArrayElements(reportInfo.suggest) + .map((suggestObjNode) => { + if (suggestObjNode.type !== 'ObjectExpression') { + // Ignore non-objects (like variables or function calls). + return null; + } return { - valid: - validProperty && validProperty.value.type === 'ArrayExpression' - ? validProperty.value.elements.filter(Boolean) - : [], - invalid: - invalidProperty && invalidProperty.value.type === 'ArrayExpression' - ? invalidProperty.value.elements.filter(Boolean) - : [], + messageId: findObjectPropertyValueByKeyName( + suggestObjNode, + 'messageId', + ), + message: findObjectPropertyValueByKeyName(suggestObjNode, 'desc'), // Note: suggestion message named `desc` + data: findObjectPropertyValueByKeyName(suggestObjNode, 'data'), + fix: findObjectPropertyValueByKeyName(suggestObjNode, 'fix'), }; - }); - }, - - /** - * Gets information on a report, given the ASTNode of context.report(). - * @param {ASTNode} node The ASTNode of context.report() - * @param {Context} context - */ - getReportInfo(node, context) { - const reportArgs = node.arguments; - - // If there is exactly one argument, the API expects an object. - // Otherwise, if the second argument is a string, the arguments are interpreted as - // ['node', 'message', 'data', 'fix']. - // Otherwise, the arguments are interpreted as ['node', 'loc', 'message', 'data', 'fix']. - - if (reportArgs.length === 0) { - return null; - } + }) + .filter((item) => item !== null), + ]; +} - if (reportArgs.length === 1) { - if (reportArgs[0].type === 'ObjectExpression') { - return reportArgs[0].properties.reduce((reportInfo, property) => { - const propName = module.exports.getKeyName(property); +/** + * Whether the provided node represents an autofixer function. + * @param {Node} node + * @param {Node[]} contextIdentifiers + * @returns {boolean} + */ +export function isAutoFixerFunction(node, contextIdentifiers) { + const parent = node.parent; + return ( + ['FunctionExpression', 'ArrowFunctionExpression'].includes(node.type) && + parent.parent.type === 'ObjectExpression' && + parent.parent.parent.type === 'CallExpression' && + contextIdentifiers.has(parent.parent.parent.callee.object) && + parent.parent.parent.callee.property.name === 'report' && + getReportInfo(parent.parent.parent).fix === node + ); +} - if (propName !== null) { - return Object.assign(reportInfo, { [propName]: property.value }); - } - return reportInfo; - }, {}); +/** + * Whether the provided node represents a suggestion fixer function. + * @param {Node} node + * @param {Node[]} contextIdentifiers + * @returns {boolean} + */ +export function isSuggestionFixerFunction(node, contextIdentifiers) { + const parent = node.parent; + return ( + (node.type === 'FunctionExpression' || + node.type === 'ArrowFunctionExpression') && + parent.type === 'Property' && + parent.key.type === 'Identifier' && + parent.key.name === 'fix' && + parent.parent.type === 'ObjectExpression' && + parent.parent.parent.type === 'ArrayExpression' && + parent.parent.parent.parent.type === 'Property' && + parent.parent.parent.parent.key.type === 'Identifier' && + parent.parent.parent.parent.key.name === 'suggest' && + parent.parent.parent.parent.parent.type === 'ObjectExpression' && + parent.parent.parent.parent.parent.parent.type === 'CallExpression' && + contextIdentifiers.has( + parent.parent.parent.parent.parent.parent.callee.object, + ) && + parent.parent.parent.parent.parent.parent.callee.property.name === + 'report' && + getReportInfo(parent.parent.parent.parent.parent.parent).suggest === + parent.parent.parent + ); +} + +/** + * List all properties contained in an object. + * Evaluates and includes any properties that may be behind spreads. + * @param {Node} objectNode + * @param {ScopeManager} scopeManager + * @returns {Node[]} the list of all properties that could be found + */ +export function evaluateObjectProperties(objectNode, scopeManager) { + if (!objectNode || objectNode.type !== 'ObjectExpression') { + return []; + } + + return objectNode.properties.flatMap((property) => { + if (property.type === 'SpreadElement') { + const value = findVariableValue(property.argument, scopeManager); + if (value && value.type === 'ObjectExpression') { + return value.properties; } - return null; + return []; } + return [property]; + }); +} - let keys; - const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: use context.sourceCode when dropping eslint < v9 - const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when dropping eslint < v9 - const secondArgStaticValue = getStaticValue(reportArgs[1], scope); +export function getMetaDocsProperty(propertyName, ruleInfo, scopeManager) { + const metaNode = ruleInfo.meta; - if ( - (secondArgStaticValue && - typeof secondArgStaticValue.value === 'string') || - reportArgs[1].type === 'TemplateLiteral' - ) { - keys = ['node', 'message', 'data', 'fix']; - } else if ( - reportArgs[1].type === 'ObjectExpression' || - reportArgs[1].type === 'ArrayExpression' || - (reportArgs[1].type === 'Literal' && - typeof reportArgs[1].value !== 'string') || - (secondArgStaticValue && - ['object', 'number'].includes(typeof secondArgStaticValue.value)) - ) { - keys = ['node', 'loc', 'message', 'data', 'fix']; - } else { - // Otherwise, we can't statically determine what argument means what, so no safe fix is possible. - return null; - } + const docsNode = evaluateObjectProperties(metaNode, scopeManager).find( + (p) => p.type === 'Property' && getKeyName(p) === 'docs', + ); - return Object.fromEntries( - keys - .slice(0, reportArgs.length) - .map((key, index) => [key, reportArgs[index]]), - ); - }, - - /** - * Gets a set of all `sourceCode` identifiers. - * @param {ScopeManager} scopeManager - * @param {ASTNode} ast The AST of the file. This must have `parent` properties. - * @returns {Set} A set of all identifiers referring to the `SourceCode` object. - */ - getSourceCodeIdentifiers(scopeManager, ast) { - return new Set( - [...module.exports.getContextIdentifiers(scopeManager, ast)] - .filter( - (identifier) => - identifier.parent && - identifier.parent.type === 'MemberExpression' && - identifier === identifier.parent.object && - identifier.parent.property.type === 'Identifier' && - identifier.parent.property.name === 'getSourceCode' && - identifier.parent.parent.type === 'CallExpression' && - identifier.parent === identifier.parent.parent.callee && - identifier.parent.parent.parent.type === 'VariableDeclarator' && - identifier.parent.parent === identifier.parent.parent.parent.init && - identifier.parent.parent.parent.id.type === 'Identifier', - ) - .flatMap((identifier) => - scopeManager.getDeclaredVariables(identifier.parent.parent.parent), - ) - .flatMap((variable) => variable.references) - .map((ref) => ref.identifier), - ); - }, - - /** - * Insert a given property into a given object literal. - * @param {SourceCodeFixer} fixer The fixer. - * @param {Node} node The ObjectExpression node to insert a property. - * @param {string} propertyText The property code to insert. - * @returns {void} - */ - insertProperty(fixer, node, propertyText, sourceCode) { - if (node.properties.length === 0) { - return fixer.replaceText(node, `{\n${propertyText}\n}`); + const metaPropertyNode = evaluateObjectProperties( + docsNode?.value, + scopeManager, + ).find((p) => p.type === 'Property' && getKeyName(p) === propertyName); + + return { docsNode, metaNode, metaPropertyNode }; +} + +/** + * Get the `meta.messages` node from a rule. + * @param {RuleInfo} ruleInfo + * @param {ScopeManager} scopeManager + * @returns {Node|undefined} + */ +export function getMessagesNode(ruleInfo, scopeManager) { + if (!ruleInfo) { + return; + } + + const metaNode = ruleInfo.meta; + const messagesNode = evaluateObjectProperties(metaNode, scopeManager).find( + (p) => p.type === 'Property' && getKeyName(p) === 'messages', + ); + + if (messagesNode) { + if (messagesNode.value.type === 'ObjectExpression') { + return messagesNode.value; } - return fixer.insertTextAfter( - sourceCode.getLastToken(node.properties.at(-1)), - `,\n${propertyText}`, - ); - }, - - /** - * Collect all context.report({...}) violation/suggestion-related nodes into a standardized array for convenience. - * @param {Object} reportInfo - Result of getReportInfo(). - * @returns {messageId?: String, message?: String, data?: Object, fix?: Function}[] - */ - collectReportViolationAndSuggestionData(reportInfo) { - return [ - // Violation message - { - messageId: reportInfo.messageId, - message: reportInfo.message, - data: reportInfo.data, - fix: reportInfo.fix, - }, - // Suggestion messages - ...collectArrayElements(reportInfo.suggest) - .map((suggestObjNode) => { - if (suggestObjNode.type !== 'ObjectExpression') { - // Ignore non-objects (like variables or function calls). - return null; - } - return { - messageId: findObjectPropertyValueByKeyName( - suggestObjNode, - 'messageId', - ), - message: findObjectPropertyValueByKeyName(suggestObjNode, 'desc'), // Note: suggestion message named `desc` - data: findObjectPropertyValueByKeyName(suggestObjNode, 'data'), - fix: findObjectPropertyValueByKeyName(suggestObjNode, 'fix'), - }; - }) - .filter((item) => item !== null), - ]; - }, - - /** - * Whether the provided node represents an autofixer function. - * @param {Node} node - * @param {Node[]} contextIdentifiers - * @returns {boolean} - */ - isAutoFixerFunction(node, contextIdentifiers) { - const parent = node.parent; - return ( - ['FunctionExpression', 'ArrowFunctionExpression'].includes(node.type) && - parent.parent.type === 'ObjectExpression' && - parent.parent.parent.type === 'CallExpression' && - contextIdentifiers.has(parent.parent.parent.callee.object) && - parent.parent.parent.callee.property.name === 'report' && - module.exports.getReportInfo(parent.parent.parent).fix === node - ); - }, - - /** - * Whether the provided node represents a suggestion fixer function. - * @param {Node} node - * @param {Node[]} contextIdentifiers - * @returns {boolean} - */ - isSuggestionFixerFunction(node, contextIdentifiers) { - const parent = node.parent; - return ( - (node.type === 'FunctionExpression' || - node.type === 'ArrowFunctionExpression') && - parent.type === 'Property' && - parent.key.type === 'Identifier' && - parent.key.name === 'fix' && - parent.parent.type === 'ObjectExpression' && - parent.parent.parent.type === 'ArrayExpression' && - parent.parent.parent.parent.type === 'Property' && - parent.parent.parent.parent.key.type === 'Identifier' && - parent.parent.parent.parent.key.name === 'suggest' && - parent.parent.parent.parent.parent.type === 'ObjectExpression' && - parent.parent.parent.parent.parent.parent.type === 'CallExpression' && - contextIdentifiers.has( - parent.parent.parent.parent.parent.parent.callee.object, - ) && - parent.parent.parent.parent.parent.parent.callee.property.name === - 'report' && - module.exports.getReportInfo(parent.parent.parent.parent.parent.parent) - .suggest === parent.parent.parent - ); - }, - - /** - * List all properties contained in an object. - * Evaluates and includes any properties that may be behind spreads. - * @param {Node} objectNode - * @param {ScopeManager} scopeManager - * @returns {Node[]} the list of all properties that could be found - */ - evaluateObjectProperties(objectNode, scopeManager) { - if (!objectNode || objectNode.type !== 'ObjectExpression') { - return []; + const value = findVariableValue(messagesNode.value, scopeManager); + if (value && value.type === 'ObjectExpression') { + return value; } + } +} - return objectNode.properties.flatMap((property) => { - if (property.type === 'SpreadElement') { - const value = findVariableValue(property.argument, scopeManager); - if (value && value.type === 'ObjectExpression') { - return value.properties; - } - return []; - } - return [property]; - }); - }, +/** + * Get the list of messageId properties from `meta.messages` for a rule. + * @param {RuleInfo} ruleInfo + * @param {ScopeManager} scopeManager + * @returns {Node[]|undefined} + */ +export function getMessageIdNodes(ruleInfo, scopeManager) { + const messagesNode = getMessagesNode(ruleInfo, scopeManager); - getMetaDocsProperty(propertyName, ruleInfo, scopeManager) { - const metaNode = ruleInfo.meta; + return messagesNode && messagesNode.type === 'ObjectExpression' + ? evaluateObjectProperties(messagesNode, scopeManager) + : undefined; +} - const docsNode = module.exports - .evaluateObjectProperties(metaNode, scopeManager) - .find( - (p) => p.type === 'Property' && module.exports.getKeyName(p) === 'docs', - ); +/** + * Get the messageId property from a rule's `meta.messages` that matches the given `messageId`. + * @param {String} messageId - the messageId to check for + * @param {RuleInfo} ruleInfo + * @param {ScopeManager} scopeManager + * @param {Scope} scope + * @returns {Node|undefined} The matching messageId property from `meta.messages`. + */ +export function getMessageIdNodeById(messageId, ruleInfo, scopeManager, scope) { + return getMessageIdNodes(ruleInfo, scopeManager).find( + (p) => p.type === 'Property' && getKeyName(p, scope) === messageId, + ); +} - const metaPropertyNode = module.exports - .evaluateObjectProperties(docsNode?.value, scopeManager) - .find( - (p) => - p.type === 'Property' && - module.exports.getKeyName(p) === propertyName, - ); +export function getMetaSchemaNode(metaNode, scopeManager) { + return evaluateObjectProperties(metaNode, scopeManager).find( + (p) => p.type === 'Property' && getKeyName(p) === 'schema', + ); +} + +export function getMetaSchemaNodeProperty(schemaNode, scopeManager) { + if (!schemaNode) { + return null; + } - return { docsNode, metaNode, metaPropertyNode }; - }, - - /** - * Get the `meta.messages` node from a rule. - * @param {RuleInfo} ruleInfo - * @param {ScopeManager} scopeManager - * @returns {Node|undefined} - */ - getMessagesNode(ruleInfo, scopeManager) { - if (!ruleInfo) { + let { value } = schemaNode; + if (value.type === 'Identifier' && value.name !== 'undefined') { + const variable = findVariable( + scopeManager.acquire(value) || scopeManager.globalScope, + value, + ); + + // If we can't find the declarator, we have to assume it's in correct type + if ( + !variable || + !variable.defs || + !variable.defs[0] || + !variable.defs[0].node || + variable.defs[0].node.type !== 'VariableDeclarator' || + !variable.defs[0].node.init + ) { return; } - const metaNode = ruleInfo.meta; - const messagesNode = module.exports - .evaluateObjectProperties(metaNode, scopeManager) - .find( - (p) => - p.type === 'Property' && module.exports.getKeyName(p) === 'messages', - ); + value = variable.defs[0].node.init; + } - if (messagesNode) { - if (messagesNode.value.type === 'ObjectExpression') { - return messagesNode.value; - } - const value = findVariableValue(messagesNode.value, scopeManager); - if (value && value.type === 'ObjectExpression') { - return value; - } - } - }, - - /** - * Get the list of messageId properties from `meta.messages` for a rule. - * @param {RuleInfo} ruleInfo - * @param {ScopeManager} scopeManager - * @returns {Node[]|undefined} - */ - getMessageIdNodes(ruleInfo, scopeManager) { - const messagesNode = module.exports.getMessagesNode(ruleInfo, scopeManager); - - return messagesNode && messagesNode.type === 'ObjectExpression' - ? module.exports.evaluateObjectProperties(messagesNode, scopeManager) - : undefined; - }, - - /** - * Get the messageId property from a rule's `meta.messages` that matches the given `messageId`. - * @param {String} messageId - the messageId to check for - * @param {RuleInfo} ruleInfo - * @param {ScopeManager} scopeManager - * @param {Scope} scope - * @returns {Node|undefined} The matching messageId property from `meta.messages`. - */ - getMessageIdNodeById(messageId, ruleInfo, scopeManager, scope) { - return module.exports - .getMessageIdNodes(ruleInfo, scopeManager) - .find( - (p) => - p.type === 'Property' && - module.exports.getKeyName(p, scope) === messageId, - ); - }, - - getMetaSchemaNode(metaNode, scopeManager) { - return module.exports - .evaluateObjectProperties(metaNode, scopeManager) - .find( - (p) => - p.type === 'Property' && module.exports.getKeyName(p) === 'schema', - ); - }, + return value; +} - getMetaSchemaNodeProperty(schemaNode, scopeManager) { - if (!schemaNode) { - return null; +/** + * Get the possible values that a variable was initialized to at some point. + * @param {Node} node - the Identifier node for the variable. + * @param {ScopeManager} scopeManager + * @returns {Node[]} the values that the given variable could be initialized to. + */ +export function findPossibleVariableValues(node, scopeManager) { + const variable = findVariable( + scopeManager.acquire(node) || scopeManager.globalScope, + node, + ); + return ((variable && variable.references) || []).flatMap((ref) => { + if ( + ref.writeExpr && + (ref.writeExpr.parent.type !== 'AssignmentExpression' || + ref.writeExpr.parent.operator === '=') + ) { + // Given node `x`, get `123` from `x = 123;`. + // Ignore assignments with other operators like `x += 'abc';'`; + return [ref.writeExpr]; } + return []; + }); +} - let { value } = schemaNode; - if (value.type === 'Identifier' && value.name !== 'undefined') { - const variable = findVariable( - scopeManager.acquire(value) || scopeManager.globalScope, - value, - ); +/** + * @param {Node} node + * @returns {boolean} Whether the node is an Identifier with name `undefined`. + */ +export function isUndefinedIdentifier(node) { + return node.type === 'Identifier' && node.name === 'undefined'; +} - // If we can't find the declarator, we have to assume it's in correct type - if ( - !variable || - !variable.defs || - !variable.defs[0] || - !variable.defs[0].node || - variable.defs[0].node.type !== 'VariableDeclarator' || - !variable.defs[0].node.init - ) { - return; - } +/** + * Check whether a variable's definition is from a function parameter. + * @param {Node} node - the Identifier node for the variable. + * @param {ScopeManager} scopeManager + * @returns {boolean} whether the variable comes from a function parameter + */ +export function isVariableFromParameter(node, scopeManager) { + const variable = findVariable( + scopeManager.acquire(node) || scopeManager.globalScope, + node, + ); - value = variable.defs[0].node.init; - } + return variable?.defs[0]?.type === 'Parameter'; +} - return value; - }, +export function getSourceCode(context) { + // TODO: remove contet.getSourceCode() when dropping eslint < v9 + return context.sourceCode || context.getSourceCode(); +} - /** - * Get the possible values that a variable was initialized to at some point. - * @param {Node} node - the Identifier node for the variable. - * @param {ScopeManager} scopeManager - * @returns {Node[]} the values that the given variable could be initialized to. - */ - findPossibleVariableValues(node, scopeManager) { - const variable = findVariable( - scopeManager.acquire(node) || scopeManager.globalScope, - node, - ); - return ((variable && variable.references) || []).flatMap((ref) => { - if ( - ref.writeExpr && - (ref.writeExpr.parent.type !== 'AssignmentExpression' || - ref.writeExpr.parent.operator === '=') - ) { - // Given node `x`, get `123` from `x = 123;`. - // Ignore assignments with other operators like `x += 'abc';'`; - return [ref.writeExpr]; - } - return []; - }); - }, - - /** - * @param {Node} node - * @returns {boolean} Whether the node is an Identifier with name `undefined`. - */ - isUndefinedIdentifier(node) { - return node.type === 'Identifier' && node.name === 'undefined'; - }, - - /** - * Check whether a variable's definition is from a function parameter. - * @param {Node} node - the Identifier node for the variable. - * @param {ScopeManager} scopeManager - * @returns {boolean} whether the variable comes from a function parameter - */ - isVariableFromParameter(node, scopeManager) { - const variable = findVariable( - scopeManager.acquire(node) || scopeManager.globalScope, - node, - ); +export function getScope(context) { + // TODO: remove contet.getScope() when dropping eslint < v9 + const sourceCode = context.sourceCode || context.getSourceCode(); + return sourceCode.getScope?.(sourceCode.ast) || context.getScope(); +} + +export function getparserServices(context) { + // TODO: remove context.parserServices when dropping eslint < v9 + return (context.sourceCode || context).parserServices; +} - return variable?.defs[0]?.type === 'Parameter'; - }, - - getSourceCode(context) { - // TODO: remove contet.getSourceCode() when dropping eslint < v9 - return context.sourceCode || context.getSourceCode(); - }, - - getScope(context) { - // TODO: remove contet.getScope() when dropping eslint < v9 - const sourceCode = context.sourceCode || context.getSourceCode(); - return sourceCode.getScope?.(sourceCode.ast) || context.getScope(); - }, - - getparserServices(context) { - // TODO: remove context.parserServices when dropping eslint < v9 - return (context.sourceCode || context).parserServices; - }, - - getFilename(context) { - // TODO: just use context.filename when dropping eslint < v9 - return context.filename || context.getFilename(); - }, -}; +export function getFilename(context) { + // TODO: just use context.filename when dropping eslint < v9 + return context.filename || context.getFilename(); +} diff --git a/package.json b/package.json index 5f259b41..c66a74bb 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "An ESLint plugin for linting ESLint plugins", "author": "Teddy Katz", "main": "./lib/index.js", + "type": "module", "exports": { ".": "./lib/index.js", "./configs/*": "./configs/*.js", diff --git a/tests/lib/eslint-rule-tester.js b/tests/lib/eslint-rule-tester.js index dd16a8e2..71411be5 100644 --- a/tests/lib/eslint-rule-tester.js +++ b/tests/lib/eslint-rule-tester.js @@ -3,13 +3,15 @@ * @author 唯然 */ -'use strict'; +import { RuleTester as ESLintRuleTester } from 'eslint'; +import * as unsupportedApi from 'eslint/use-at-your-own-risk'; +import packageConfig from 'eslint/package.json' with { type: 'json' }; -const eslintVersion = require('eslint/package.json').version; -const { RuleTester } = require('eslint'); -const { FlatRuleTester } = require('eslint/use-at-your-own-risk'); +const { version: eslintVersion } = packageConfig; + +const FlatRuleTester = unsupportedApi.FlatRuleTester; // greater than or equal to ESLint v9 -exports.gteEslintV9 = +eslintVersion.split('.')[0] >= 9; +export const gteEslintV9 = +eslintVersion.split('.')[0] >= 9; -exports.RuleTester = exports.gteEslintV9 ? RuleTester : FlatRuleTester; +export const RuleTester = gteEslintV9 ? ESLintRuleTester : FlatRuleTester; diff --git a/tests/lib/index.js b/tests/lib/index.js index 794d0d57..2cd9db32 100644 --- a/tests/lib/index.js +++ b/tests/lib/index.js @@ -1,7 +1,5 @@ -'use strict'; - -const assert = require('chai').assert; -const plugin = require('../..'); +import { assert } from 'chai'; +import plugin from '../../lib/index.js'; const RULE_NAMES = Object.keys(plugin.rules); diff --git a/tests/lib/rule-setup.js b/tests/lib/rule-setup.js index a1c75e82..24c8fcd1 100644 --- a/tests/lib/rule-setup.js +++ b/tests/lib/rule-setup.js @@ -1,15 +1,15 @@ -'use strict'; - -const { readdirSync, readFileSync } = require('fs'); -const path = require('path'); -const assert = require('chai').assert; -const plugin = require('../..'); +import { readdirSync, readFileSync } from 'fs'; +import path from 'path'; +import { assert } from 'chai'; +import plugin from '../../lib/index.js'; +import { fileURLToPath } from 'url'; const RULE_NAMES = Object.keys(plugin.rules); +const dirname = path.dirname(fileURLToPath(import.meta.url)); describe('rule setup is correct', () => { it('should have a list of exported rules and rules directory that match', () => { - const filePath = path.join(__dirname, '..', 'lib', 'rules'); + const filePath = path.join(dirname, '..', 'lib', 'rules'); const files = readdirSync(filePath); assert.deepStrictEqual( @@ -34,7 +34,7 @@ describe('rule setup is correct', () => { it('should have the right contents', () => { const filePath = path.join( - __dirname, + dirname, '..', '..', 'lib', @@ -53,7 +53,7 @@ describe('rule setup is correct', () => { }); it('should have tests for all rules', () => { - const filePath = path.join(__dirname, 'rules'); + const filePath = path.join(dirname, 'rules'); const files = readdirSync(filePath); assert.deepStrictEqual( @@ -65,7 +65,7 @@ describe('rule setup is correct', () => { }); it('should have documentation for all rules', () => { - const filePath = path.join(__dirname, '..', '..', 'docs', 'rules'); + const filePath = path.join(dirname, '..', '..', 'docs', 'rules'); const files = readdirSync(filePath); assert.deepStrictEqual( diff --git a/tests/lib/rules/consistent-output.js b/tests/lib/rules/consistent-output.js index 75ecabe8..bdb9f8fe 100644 --- a/tests/lib/rules/consistent-output.js +++ b/tests/lib/rules/consistent-output.js @@ -3,14 +3,12 @@ * @author Teddy Katz */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/consistent-output'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/consistent-output.js'; +import { RuleTester } from '../eslint-rule-tester.js'; const ERROR = { messageId: 'missingOutput', type: 'ObjectExpression' }; diff --git a/tests/lib/rules/fixer-return.js b/tests/lib/rules/fixer-return.js index 349a9bf4..3ab91da0 100644 --- a/tests/lib/rules/fixer-return.js +++ b/tests/lib/rules/fixer-return.js @@ -3,14 +3,12 @@ * @author 薛定谔的猫 */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/fixer-return'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/fixer-return.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/meta-property-ordering.js b/tests/lib/rules/meta-property-ordering.js index 1d4dfec8..36a638b0 100644 --- a/tests/lib/rules/meta-property-ordering.js +++ b/tests/lib/rules/meta-property-ordering.js @@ -2,14 +2,12 @@ * @fileoverview Enforces the order of meta properties */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/meta-property-ordering'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/meta-property-ordering.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/no-deprecated-context-methods.js b/tests/lib/rules/no-deprecated-context-methods.js index 84770ccd..628973db 100644 --- a/tests/lib/rules/no-deprecated-context-methods.js +++ b/tests/lib/rules/no-deprecated-context-methods.js @@ -3,14 +3,12 @@ * @author Teddy Katz */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-deprecated-context-methods'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/no-deprecated-context-methods.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/no-deprecated-report-api.js b/tests/lib/rules/no-deprecated-report-api.js index 6483fbde..95523e16 100644 --- a/tests/lib/rules/no-deprecated-report-api.js +++ b/tests/lib/rules/no-deprecated-report-api.js @@ -3,14 +3,12 @@ * @author Teddy Katz */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-deprecated-report-api'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/no-deprecated-report-api.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/no-identical-tests.js b/tests/lib/rules/no-identical-tests.js index 6f0e444e..ceb92ee6 100644 --- a/tests/lib/rules/no-identical-tests.js +++ b/tests/lib/rules/no-identical-tests.js @@ -3,14 +3,12 @@ * @author 薛定谔的猫 */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-identical-tests'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/no-identical-tests.js'; +import { RuleTester } from '../eslint-rule-tester.js'; const ERROR_OBJECT_TEST = { messageId: 'identical', type: 'ObjectExpression' }; const ERROR_STRING_TEST = { messageId: 'identical', type: 'Literal' }; diff --git a/tests/lib/rules/no-meta-schema-default.js b/tests/lib/rules/no-meta-schema-default.js index 869f4971..5e9ebecb 100644 --- a/tests/lib/rules/no-meta-schema-default.js +++ b/tests/lib/rules/no-meta-schema-default.js @@ -1,11 +1,9 @@ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-meta-schema-default'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/no-meta-schema-default.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/no-missing-message-ids.js b/tests/lib/rules/no-missing-message-ids.js index 78b9e600..aa616292 100644 --- a/tests/lib/rules/no-missing-message-ids.js +++ b/tests/lib/rules/no-missing-message-ids.js @@ -1,11 +1,9 @@ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-missing-message-ids'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/no-missing-message-ids.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/no-missing-placeholders.js b/tests/lib/rules/no-missing-placeholders.js index 43bb137b..7c50c3ad 100644 --- a/tests/lib/rules/no-missing-placeholders.js +++ b/tests/lib/rules/no-missing-placeholders.js @@ -3,14 +3,12 @@ * @author Teddy Katz */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-missing-placeholders'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/no-missing-placeholders.js'; +import { RuleTester } from '../eslint-rule-tester.js'; /** * Create an error for the given key diff --git a/tests/lib/rules/no-only-tests.js b/tests/lib/rules/no-only-tests.js index 0df7e600..a732316f 100644 --- a/tests/lib/rules/no-only-tests.js +++ b/tests/lib/rules/no-only-tests.js @@ -1,11 +1,9 @@ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-only-tests'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/no-only-tests.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/no-property-in-node.js b/tests/lib/rules/no-property-in-node.js index 62181a3b..599e29d8 100644 --- a/tests/lib/rules/no-property-in-node.js +++ b/tests/lib/rules/no-property-in-node.js @@ -1,15 +1,16 @@ -'use strict'; - -const RuleTester = require('../eslint-rule-tester').RuleTester; -const path = require('path'); -const rule = require('../../../lib/rules/no-property-in-node'); +import { RuleTester } from '../eslint-rule-tester.js'; +import path from 'path'; +import rule from '../../../lib/rules/no-property-in-node.js'; +import { fileURLToPath } from 'url'; +import parser from '@typescript-eslint/parser'; +const dirname = path.dirname(fileURLToPath(import.meta.url)); const ruleTester = new RuleTester({ languageOptions: { - parser: require('@typescript-eslint/parser'), + parser, parserOptions: { project: './tsconfig.json', - tsconfigRootDir: path.join(__dirname, '../fixtures'), + tsconfigRootDir: path.join(dirname, '../fixtures'), }, }, }); diff --git a/tests/lib/rules/no-unused-message-ids.js b/tests/lib/rules/no-unused-message-ids.js index 8f5f6fa8..41ee0f8c 100644 --- a/tests/lib/rules/no-unused-message-ids.js +++ b/tests/lib/rules/no-unused-message-ids.js @@ -1,11 +1,9 @@ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-unused-message-ids'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/no-unused-message-ids.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/no-unused-placeholders.js b/tests/lib/rules/no-unused-placeholders.js index fd93b777..bb444a17 100644 --- a/tests/lib/rules/no-unused-placeholders.js +++ b/tests/lib/rules/no-unused-placeholders.js @@ -3,14 +3,12 @@ * @author 薛定谔的猫 */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-unused-placeholders'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/no-unused-placeholders.js'; +import { RuleTester } from '../eslint-rule-tester.js'; /** * Create an error for the given key diff --git a/tests/lib/rules/no-useless-token-range.js b/tests/lib/rules/no-useless-token-range.js index c4b13960..832f240e 100644 --- a/tests/lib/rules/no-useless-token-range.js +++ b/tests/lib/rules/no-useless-token-range.js @@ -3,14 +3,12 @@ * @author Teddy Katz */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-useless-token-range'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/no-useless-token-range.js'; +import { RuleTester } from '../eslint-rule-tester.js'; /** * Wraps a code sample as an eslint rule diff --git a/tests/lib/rules/prefer-message-ids.js b/tests/lib/rules/prefer-message-ids.js index ac54184e..e9250c00 100644 --- a/tests/lib/rules/prefer-message-ids.js +++ b/tests/lib/rules/prefer-message-ids.js @@ -1,11 +1,9 @@ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/prefer-message-ids'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/prefer-message-ids.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/prefer-object-rule.js b/tests/lib/rules/prefer-object-rule.js index 9c0aae74..744ba8d0 100644 --- a/tests/lib/rules/prefer-object-rule.js +++ b/tests/lib/rules/prefer-object-rule.js @@ -2,14 +2,12 @@ * @author Brad Zacher */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/prefer-object-rule'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/prefer-object-rule.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/prefer-output-null.js b/tests/lib/rules/prefer-output-null.js index b7c0eda1..cd6a7685 100644 --- a/tests/lib/rules/prefer-output-null.js +++ b/tests/lib/rules/prefer-output-null.js @@ -3,14 +3,12 @@ * @author 薛定谔的猫 */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/prefer-output-null'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/prefer-output-null.js'; +import { RuleTester } from '../eslint-rule-tester.js'; const ERROR = { messageId: 'useOutputNull', type: 'Property' }; diff --git a/tests/lib/rules/prefer-placeholders.js b/tests/lib/rules/prefer-placeholders.js index f7fe49e0..aa977fa6 100644 --- a/tests/lib/rules/prefer-placeholders.js +++ b/tests/lib/rules/prefer-placeholders.js @@ -3,14 +3,12 @@ * @author Teddy Katz */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/prefer-placeholders'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/prefer-placeholders.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/prefer-replace-text.js b/tests/lib/rules/prefer-replace-text.js index c83a9559..9003a1e3 100644 --- a/tests/lib/rules/prefer-replace-text.js +++ b/tests/lib/rules/prefer-replace-text.js @@ -3,14 +3,12 @@ * @author 薛定谔的猫 */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/prefer-replace-text'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/prefer-replace-text.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/report-message-format.js b/tests/lib/rules/report-message-format.js index 7ca4cf10..08f2f1ea 100644 --- a/tests/lib/rules/report-message-format.js +++ b/tests/lib/rules/report-message-format.js @@ -3,14 +3,12 @@ * @author Teddy Katz */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/report-message-format'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/report-message-format.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/require-meta-default-options.js b/tests/lib/rules/require-meta-default-options.js index da2b4209..382f2bcf 100644 --- a/tests/lib/rules/require-meta-default-options.js +++ b/tests/lib/rules/require-meta-default-options.js @@ -1,7 +1,6 @@ -'use strict'; - -const rule = require('../../../lib/rules/require-meta-default-options'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/require-meta-default-options.js'; +import { RuleTester } from '../eslint-rule-tester.js'; +import parser from '@typescript-eslint/parser'; const ruleTester = new RuleTester({ languageOptions: { sourceType: 'commonjs' }, @@ -149,7 +148,7 @@ ruleTester.run('require-meta-default-options', rule, { const ruleTesterTypeScript = new RuleTester({ languageOptions: { - parser: require('@typescript-eslint/parser'), + parser, parserOptions: { sourceType: 'module' }, }, }); diff --git a/tests/lib/rules/require-meta-docs-description.js b/tests/lib/rules/require-meta-docs-description.js index 2b4ded37..8039dcf3 100644 --- a/tests/lib/rules/require-meta-docs-description.js +++ b/tests/lib/rules/require-meta-docs-description.js @@ -1,11 +1,10 @@ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/require-meta-docs-description'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/require-meta-docs-description.js'; +import { RuleTester } from '../eslint-rule-tester.js'; +import parser from '@typescript-eslint/parser'; // ------------------------------------------------------------------------------ // Tests @@ -298,7 +297,7 @@ ruleTester.run('require-meta-docs-description', rule, { const ruleTesterTypeScript = new RuleTester({ languageOptions: { sourceType: 'module', - parser: require('@typescript-eslint/parser'), + parser, }, }); ruleTesterTypeScript.run('require-meta-docs-description (TypeScript)', rule, { diff --git a/tests/lib/rules/require-meta-docs-recommended.js b/tests/lib/rules/require-meta-docs-recommended.js index 1a58bacc..6b1430dd 100644 --- a/tests/lib/rules/require-meta-docs-recommended.js +++ b/tests/lib/rules/require-meta-docs-recommended.js @@ -1,7 +1,6 @@ -'use strict'; - -const rule = require('../../../lib/rules/require-meta-docs-recommended'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/require-meta-docs-recommended.js'; +import { RuleTester } from '../eslint-rule-tester.js'; +import parser from '@typescript-eslint/parser'; const ruleTester = new RuleTester({ languageOptions: { sourceType: 'commonjs' }, @@ -269,7 +268,7 @@ ruleTester.run('require-meta-docs-recommended', rule, { const ruleTesterTypeScript = new RuleTester({ languageOptions: { - parser: require('@typescript-eslint/parser'), + parser, parserOptions: { sourceType: 'module' }, }, }); diff --git a/tests/lib/rules/require-meta-docs-url.js b/tests/lib/rules/require-meta-docs-url.js index da7a9048..e523861a 100644 --- a/tests/lib/rules/require-meta-docs-url.js +++ b/tests/lib/rules/require-meta-docs-url.js @@ -4,14 +4,12 @@ * See LICENSE file in root directory for full license. */ -'use strict'; - // ----------------------------------------------------------------------------- // Requirements // ----------------------------------------------------------------------------- -const RuleTester = require('../eslint-rule-tester').RuleTester; -const rule = require('../../../lib/rules/require-meta-docs-url'); +import { RuleTester } from '../eslint-rule-tester.js'; +import rule from '../../../lib/rules/require-meta-docs-url.js'; // ----------------------------------------------------------------------------- // Tests diff --git a/tests/lib/rules/require-meta-fixable.js b/tests/lib/rules/require-meta-fixable.js index 8aef6c04..c33a3c34 100644 --- a/tests/lib/rules/require-meta-fixable.js +++ b/tests/lib/rules/require-meta-fixable.js @@ -3,14 +3,12 @@ * @author Teddy Katz */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/require-meta-fixable'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/require-meta-fixable.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/require-meta-has-suggestions.js b/tests/lib/rules/require-meta-has-suggestions.js index 0a1e96ab..49f5a1cb 100644 --- a/tests/lib/rules/require-meta-has-suggestions.js +++ b/tests/lib/rules/require-meta-has-suggestions.js @@ -1,11 +1,9 @@ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/require-meta-has-suggestions'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/require-meta-has-suggestions.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/require-meta-schema-description.js b/tests/lib/rules/require-meta-schema-description.js index 1b3f4256..7ab0a886 100644 --- a/tests/lib/rules/require-meta-schema-description.js +++ b/tests/lib/rules/require-meta-schema-description.js @@ -1,11 +1,9 @@ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/require-meta-schema-description'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/require-meta-schema-description.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/require-meta-schema.js b/tests/lib/rules/require-meta-schema.js index dfb1754d..2aaecb3f 100644 --- a/tests/lib/rules/require-meta-schema.js +++ b/tests/lib/rules/require-meta-schema.js @@ -1,11 +1,9 @@ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/require-meta-schema'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/require-meta-schema.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/require-meta-type.js b/tests/lib/rules/require-meta-type.js index 2180e23e..5b74c227 100644 --- a/tests/lib/rules/require-meta-type.js +++ b/tests/lib/rules/require-meta-type.js @@ -3,14 +3,12 @@ * @author 唯然 */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/require-meta-type'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/require-meta-type.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/test-case-property-ordering.js b/tests/lib/rules/test-case-property-ordering.js index 149278d1..32364f4b 100644 --- a/tests/lib/rules/test-case-property-ordering.js +++ b/tests/lib/rules/test-case-property-ordering.js @@ -3,14 +3,12 @@ * @author 薛定谔的猫 */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/test-case-property-ordering'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/test-case-property-ordering.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/test-case-shorthand-strings.js b/tests/lib/rules/test-case-shorthand-strings.js index 4deddade..6fa75944 100644 --- a/tests/lib/rules/test-case-shorthand-strings.js +++ b/tests/lib/rules/test-case-shorthand-strings.js @@ -3,14 +3,12 @@ * @author Teddy Katz */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/test-case-shorthand-strings'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/test-case-shorthand-strings.js'; +import { RuleTester } from '../eslint-rule-tester.js'; /** * Returns the code for some valid test cases diff --git a/tests/lib/utils.js b/tests/lib/utils.js index d299abcc..1591190e 100644 --- a/tests/lib/utils.js +++ b/tests/lib/utils.js @@ -1,13 +1,11 @@ -'use strict'; - -const { inspect } = require('util'); -const lodash = require('lodash'); -const espree = require('espree'); -const eslintScope = require('eslint-scope'); -const estraverse = require('estraverse'); -const assert = require('chai').assert; -const utils = require('../../lib/utils'); -const typescriptEslintParser = require('@typescript-eslint/parser'); +import { inspect } from 'util'; +import lodash from 'lodash'; +import * as espree from 'espree'; +import * as eslintScope from 'eslint-scope'; +import * as estraverse from 'estraverse'; +import { assert } from 'chai'; +import * as utils from '../../lib/utils.js'; +import typescriptEslintParser from '@typescript-eslint/parser'; describe('utils', () => { describe('getRuleInfo', () => { From 5f0025676eb857723465ff40a9cb129070688a50 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:39:40 +0100 Subject: [PATCH 2/9] chore: move to c8 for esm support --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c66a74bb..87f4e4b4 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "lint:js-docs": "eslint --no-inline-config \"**/*.md\"", "lint:package-json": "npmPkgJsonLint .", "release": "release-it", - "test": "nyc --all --check-coverage --include lib mocha tests --recursive", + "test": "c8 --check-coverage --all --include lib mocha tests --recursive", "test:remote": "eslint-remote-tester -c ./eslint-remote-tester.config.mjs", "update:eslint-docs": "eslint-doc-generator" }, @@ -44,7 +44,7 @@ "@eslint-community/eslint-utils": "^4.4.0", "estraverse": "^5.3.0" }, - "nyc": { + "c8": { "branches": 95, "functions": 99, "lines": 99, @@ -61,6 +61,7 @@ "@types/estree": "^1.0.5", "@typescript-eslint/parser": "^7.7.0", "@typescript-eslint/utils": "^7.7.0", + "c8": "^10.1.3", "chai": "^4.5.0", "eslint": "^9.16.0", "eslint-config-not-an-aardvark": "^2.1.0", @@ -81,7 +82,6 @@ "mocha": "^11.0.0", "npm-package-json-lint": "^8.0.0", "npm-run-all2": "^7.0.1", - "nyc": "^17.1.0", "prettier": "^3.4.1", "release-it": "^17.2.0", "typescript": "^5.7.2" From 89ad42541cbc55ad29d7886f3fdbe798dd77849f Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 19 Jun 2025 10:45:42 +0100 Subject: [PATCH 3/9] chore: revert some mistaken replaces --- .eslint-doc-generatorrc.js | 4 +++- lib/utils.js | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.eslint-doc-generatorrc.js b/.eslint-doc-generatorrc.js index 41f1041c..a3756beb 100644 --- a/.eslint-doc-generatorrc.js +++ b/.eslint-doc-generatorrc.js @@ -1,7 +1,7 @@ import prettier from 'prettier'; /** @type {import('eslint-doc-generator').GenerateOptions} */ -export default { +const config = { ignoreConfig: [ 'all', 'all-type-checked', @@ -27,3 +27,5 @@ export default { urlConfigs: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin#presets', }; + +export default config; diff --git a/lib/utils.js b/lib/utils.js index f87cfb8c..448fb1bd 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -220,12 +220,12 @@ function getRuleExportsCJS(ast, scopeManager) { ) { exportsVarOverridden = true; if (isFunctionRule(node.right)) { - // Check `= function (context) { return { ... }; }` + // Check `module.exports = function (context) { return { ... }; }` exportsIsFunction = true; return { create: node.right, meta: null, isNewStyle: false }; } else if (node.right.type === 'ObjectExpression') { - // Check `= { create: function () {}, meta: {} }` + // Check `module.exports = { create: function () {}, meta: {} }` return collectInterestingProperties( node.right.properties, @@ -236,13 +236,13 @@ function getRuleExportsCJS(ast, scopeManager) { const possibleRule = findVariableValue(node.right, scopeManager); if (possibleRule) { if (possibleRule.type === 'ObjectExpression') { - // Check `const possibleRule = { ... }; = possibleRule; + // Check `const possibleRule = { ... }; module.exports = possibleRule; return collectInterestingProperties( possibleRule.properties, INTERESTING_RULE_KEYS, ); } else if (isFunctionRule(possibleRule)) { - // Check `const possibleRule = function(context) { return { ... } }; = possibleRule;` + // Check `const possibleRule = function(context) { return { ... } }; module.exports = possibleRule;` return { create: possibleRule, meta: null, isNewStyle: false }; } } @@ -258,7 +258,7 @@ function getRuleExportsCJS(ast, scopeManager) { node.left.property.type === 'Identifier' && INTERESTING_RULE_KEYS.has(node.left.property.name) ) { - // Check `create = () => {}` + // Check `module.exports.create = () => {}` currentExports[node.left.property.name] = node.right; } else if ( From ba2fa3c49c6a6ff13e4c270837089ed8b9de9338 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 19 Jun 2025 10:49:42 +0100 Subject: [PATCH 4/9] fix: add no-meta-replaced-by to index --- lib/index.js | 2 ++ lib/rules/no-meta-replaced-by.js | 8 ++++---- tests/lib/rules/no-meta-replaced-by.js | 6 ++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/index.js b/lib/index.js index 5bc6adda..83f4a93e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -14,6 +14,7 @@ import metaPropertyOrdering from './rules/meta-property-ordering.js'; import noDeprecatedContextMethods from './rules/no-deprecated-context-methods.js'; import noDeprecatedReportApi from './rules/no-deprecated-report-api.js'; import noIdenticalTests from './rules/no-identical-tests.js'; +import noMetaReplacedBy from './rules/no-meta-replaced-by.js'; import noMetaSchemaDefault from './rules/no-meta-schema-default.js'; import noMissingMessageIds from './rules/no-missing-message-ids.js'; import noMissingPlaceholders from './rules/no-missing-placeholders.js'; @@ -66,6 +67,7 @@ const allRules = { 'no-deprecated-context-methods': noDeprecatedContextMethods, 'no-deprecated-report-api': noDeprecatedReportApi, 'no-identical-tests': noIdenticalTests, + 'no-meta-replaced-by': noMetaReplacedBy, 'no-meta-schema-default': noMetaSchemaDefault, 'no-missing-message-ids': noMissingMessageIds, 'no-missing-placeholders': noMissingPlaceholders, diff --git a/lib/rules/no-meta-replaced-by.js b/lib/rules/no-meta-replaced-by.js index 36aa1297..e6f59523 100644 --- a/lib/rules/no-meta-replaced-by.js +++ b/lib/rules/no-meta-replaced-by.js @@ -2,16 +2,14 @@ * @fileoverview Disallows the usage of `meta.replacedBy` property */ -'use strict'; - -const utils = require('../utils'); +import * as utils from '../utils.js'; // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const rule = { meta: { type: 'problem', docs: { @@ -61,3 +59,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/tests/lib/rules/no-meta-replaced-by.js b/tests/lib/rules/no-meta-replaced-by.js index d72f41ac..7995e8f0 100644 --- a/tests/lib/rules/no-meta-replaced-by.js +++ b/tests/lib/rules/no-meta-replaced-by.js @@ -2,14 +2,12 @@ * @fileoverview Disallows the usage of `meta.replacedBy` property */ -'use strict'; - // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/no-meta-replaced-by'); -const RuleTester = require('../eslint-rule-tester').RuleTester; +import rule from '../../../lib/rules/no-meta-replaced-by.js'; +import { RuleTester } from '../eslint-rule-tester.js'; // ------------------------------------------------------------------------------ // Tests From 8a8fb60a41a2449b59d2880cd73ad84dd7eed8be Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Thu, 19 Jun 2025 14:29:15 -0400 Subject: [PATCH 5/9] mocha timeout from 2000 to 3000 for slow no-property-in-node test --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c6fbfb8e..f95f5cdd 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "lint:js-docs": "eslint --no-inline-config \"**/*.md\"", "lint:package-json": "npmPkgJsonLint .", "release": "release-it", - "test": "c8 --check-coverage --all --include lib mocha tests --recursive", + "test": "c8 --check-coverage --all --include lib mocha tests --recursive --timeout 3000", "test:remote": "eslint-remote-tester -c ./eslint-remote-tester.config.mjs", "update:eslint-docs": "eslint-doc-generator" }, From 478a904ce6060cb8c724e289fcaee7466b70d02f Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Thu, 19 Jun 2025 14:29:29 -0400 Subject: [PATCH 6/9] git ignore coverage/ --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 642ad460..088a70a6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ yarn.lock # eslint-remote-tester eslint-remote-tester-results + +coverage/ From b1f33d706e1303cdff13b0703254a63d466cf39a Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Thu, 19 Jun 2025 14:31:49 -0400 Subject: [PATCH 7/9] Revert "mocha timeout from 2000 to 3000 for slow no-property-in-node test" This reverts commit 8a8fb60a41a2449b59d2880cd73ad84dd7eed8be. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f95f5cdd..c6fbfb8e 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "lint:js-docs": "eslint --no-inline-config \"**/*.md\"", "lint:package-json": "npmPkgJsonLint .", "release": "release-it", - "test": "c8 --check-coverage --all --include lib mocha tests --recursive --timeout 3000", + "test": "c8 --check-coverage --all --include lib mocha tests --recursive", "test:remote": "eslint-remote-tester -c ./eslint-remote-tester.config.mjs", "update:eslint-docs": "eslint-doc-generator" }, From d2d56670b24a96571424c940fc021b0514d68872 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:41:57 +0100 Subject: [PATCH 8/9] chore: remove duplicate gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 632b5523..281b1c34 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,3 @@ yarn.lock # eslint-remote-tester eslint-remote-tester-results - -coverage/ From 6fb250666a3fb954555815d90a47389bf102387a Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:46:28 +0100 Subject: [PATCH 9/9] chore: revert comment change --- lib/utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 448fb1bd..a672d491 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -339,11 +339,11 @@ function collectArrayElements(node) { } /** -* Performs static analysis on an AST to try to determine the final value of `. +* Performs static analysis on an AST to try to determine the final value of `module.exports`. * @param {{ast: ASTNode, scopeManager?: ScopeManager}} sourceCode The object contains `Program` AST node, and optional `scopeManager` * @returns {Object} An object with keys `meta`, `create`, and `isNewStyle`. `meta` and `create` correspond to the AST nodes -for the final values of `meta` and `create`. `isNewStyle` will be `true` if ` -is an object, and `false` if is just the `create` function. If no valid ESLint rule info can be extracted +for the final values of `module.exports.meta` and `module.exports.create`. `isNewStyle` will be `true` if `module.exports` +is an object, and `false` if `module.exports` is just the `create` function. If no valid ESLint rule info can be extracted from the file, the return value will be `null`. */ export function getRuleInfo({ ast, scopeManager }) {