diff --git a/.eslint-doc-generatorrc.js b/.eslint-doc-generatorrc.js index 5c4ef3b6..47100791 100644 --- a/.eslint-doc-generatorrc.js +++ b/.eslint-doc-generatorrc.js @@ -1,14 +1,22 @@ 'use strict'; +const prettier = require('prettier'); + /** @type {import('eslint-doc-generator').GenerateOptions} */ module.exports = { ignoreConfig: [ 'all', + 'all-type-checked', 'rules', 'rules-recommended', 'tests', 'tests-recommended', ], + postprocess: async (content, path) => + prettier.format(content, { + ...(await prettier.resolveConfig(path)), + parser: 'markdown', + }), ruleDocSectionInclude: ['Rule Details'], ruleListSplit: 'meta.docs.category', urlConfigs: diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index e62078f0..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: 2 -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: daily - - - package-ecosystem: npm - directory: / - schedule: - interval: weekly diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 00000000..7499aa8c --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["github>eslint/eslint//.github/renovate.json5"] +} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8df690e3..1ad441b9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,8 +19,8 @@ jobs: os: - ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install @@ -29,8 +29,8 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: "18.x" - run: npm install @@ -39,8 +39,8 @@ jobs: eslint7: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: "18.x" - run: npm install diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..3d6d3b08 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,44 @@ +on: + push: + branches: + - main +name: release-please +jobs: + release-please: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + id-token: write + steps: + - uses: google-github-actions/release-please-action@v3 + id: release + with: + release-type: node + package-name: 'eslint-plugin-eslint-plugin' + pull-request-title-pattern: 'chore: release${component} ${version}' + changelog-types: > + [ + { "type": "feat", "section": "Features", "hidden": false }, + { "type": "fix", "section": "Bug Fixes", "hidden": false }, + { "type": "docs", "section": "Documentation", "hidden": false }, + { "type": "build", "section": "Build Related", "hidden": false }, + { "type": "chore", "section": "Chores", "hidden": false }, + { "type": "perf", "section": "Chores", "hidden": false }, + { "type": "ci", "section": "Chores", "hidden": false }, + { "type": "refactor", "section": "Chores", "hidden": false }, + { "type": "test", "section": "Chores", "hidden": false } + ] + - uses: actions/checkout@v4 + if: ${{ steps.release.outputs.release_created }} + - uses: actions/setup-node@v4 + with: + node-version: lts/* + registry-url: https://registry.npmjs.org + if: ${{ steps.release.outputs.release_created }} + - run: | + npm install + npm publish --provenance + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + if: ${{ steps.release.outputs.release_created }} diff --git a/CHANGELOG.md b/CHANGELOG.md index b52ff858..6cd01949 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ +## [5.3.0](https://github.com/eslint-community/eslint-plugin-eslint-plugin/compare/v5.2.1...v5.3.0) (2024-02-06) +### Features + +* add no-property-in-node rule ([#433](https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/433)) ([d2b9372](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/d2b9372279d39b9ca9db2c0874b7dfab6a19c208)) + + +### Documentation + +* add another justification for prefer-message-ids rule ([#426](https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/426)) ([209d178](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/209d1784becc8541f973658a687d3ab3ca5bf9f4)) + + +### Chores + +* add release-please ([#421](https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/421)) ([d7331ca](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/d7331caec0cdc53a5733ba68672e691be931c2a5)) +* config renovate ([#409](https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/409)) ([3c845be](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/3c845be79474d0e9364ae79714adaa6072a143d8)) +* postprocess with prettier in eslint-doc-generator ([#435](https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/435)) ([015c207](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/015c207caeefdc732bd245b56df576d2699c22c6)) +* remove dependentbot.yml infavor of renovate ([#439](https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/439)) ([6ae0ee6](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/6ae0ee669bcab78fb04fcae818d84afcb37e8e3b)) +* replace dependency npm-run-all with npm-run-all2 ^5.0.0 ([#427](https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/427)) ([062743d](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/062743d963c982fdc67a8f44b2229c9136402b2c)) +* update dependency markdownlint-cli to ^0.38.0 ([#410](https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/410)) ([6b53c5b](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/6b53c5b7b8bc9e19dcb86796ab29019f89c449fc)) +* update dependency markdownlint-cli to ^0.39.0 ([#431](https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/431)) ([f005a2c](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/f005a2c0231b8b77f6862dca81b4a6e3099e0493)) + +### [5.2.1](https://github.com/eslint-community/eslint-plugin-eslint-plugin/compare/v5.2.0...v5.2.1) (2023-12-11) + + +### Bug Fixes + +* replace context.getScope() with sourceCode.getScope() ([6aed8bb](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/6aed8bbc54abea5c74157c0e34148e56c88a6a7b)) + +## [5.2.0](https://github.com/eslint-community/eslint-plugin-eslint-plugin/compare/v5.1.1...v5.2.0) (2023-12-11) + + +### Features + +* preparing for eslint v9 ([#400](https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/400)) ([35e14cd](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/35e14cd7bc1fd865fa11efd955afe600ef2bbc22)) + ### [5.1.1](https://github.com/eslint-community/eslint-plugin-eslint-plugin/compare/v5.1.0...v5.1.1) (2023-07-19) @@ -610,4 +645,4 @@ - New: no-deprecated-report-api rule [`06a6e5a`](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/06a6e5ae81328ba37e8360ca5ad7498939059031) - New: initial commit [`8b0ae4f`](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/8b0ae4f30014e9526af02ecba518f5edfd38c2b9) -- New: Add a 'recommended' config [`7b9ec01`](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/7b9ec012286f4c16af27e79db7e449916c56c3c6) \ No newline at end of file +- New: Add a 'recommended' config [`7b9ec01`](https://github.com/eslint-community/eslint-plugin-eslint-plugin/commit/7b9ec012286f4c16af27e79db7e449916c56c3c6) diff --git a/README.md b/README.md index 18728fed..d3c7169a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,19 @@ -# eslint-plugin-eslint-plugin ![CI](https://github.com/eslint-community/eslint-plugin-eslint-plugin/workflows/CI/badge.svg) [![NPM version](https://img.shields.io/npm/v/eslint-plugin-eslint-plugin.svg?style=flat)](https://npmjs.org/package/eslint-plugin-eslint-plugin) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) +# eslint-plugin-eslint-plugin ![CI](https://github.com/eslint-community/eslint-plugin-eslint-plugin/workflows/CI/badge.svg) [![NPM version](https://img.shields.io/npm/v/eslint-plugin-eslint-plugin.svg?style=flat)](https://npmjs.org/package/eslint-plugin-eslint-plugin) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) An ESLint plugin for linting ESLint plugins. Rules written in CJS, ESM, and TypeScript are all supported. -* [Installation](#Installation) -* [Usage](#Usage) -* [Rules](#Rules) -* [Presets](#Presets) - * [Semantic versioning policy](#Semanticversioningpolicy) - * [Preset usage](#Presetusage) + +- [Installation](#installation) +- [Usage](#usage) + - [**.eslintrc.json**](#eslintrcjson) + - [`eslint.config.js` (requires eslint\>=v8.23.0)](#eslintconfigjs-requires-eslintv8230) +- [Rules](#rules) + - [Rules](#rules-1) + - [Tests](#tests) +- [Presets](#presets) + - [Semantic versioning policy](#semantic-versioning-policy) + - [Preset usage](#preset-usage) ## Presets -| | Name | Description | -|:--|:--------------------|:--------------------------------------------------------------------------| -| ✅ | `recommended` | enables all recommended rules in this plugin | -| | `rules-recommended` | enables all recommended rules that are aimed at linting ESLint rule files | -| | `tests-recommended` | enables all recommended rules that are aimed at linting ESLint test files | -| | `all` | enables all rules in this plugin | -| | `rules` | enables all rules that are aimed at linting ESLint rule files | -| | `tests` | enables all rules that are aimed at linting ESLint test files | +| | Name | Description | +| :-- | :------------------ | :--------------------------------------------------------------------------- | +| ✅ | `recommended` | enables all recommended rules in this plugin | +| | `rules-recommended` | enables all recommended rules that are aimed at linting ESLint rule files | +| | `tests-recommended` | enables all recommended rules that are aimed at linting ESLint test files | +| | `all` | enables all rules in this plugin, including those requiring type information | +| | `all-type-checked` | enables all rules in this plugin, including those requiring type information | +| | `rules` | enables all rules that are aimed at linting ESLint rule files | +| | `tests` | enables all rules that are aimed at linting ESLint test files | ### Semantic versioning policy @@ -137,9 +143,7 @@ Presets are enabled by adding a line to the `extends` list in your config file. ```json { - "extends": [ - "plugin:eslint-plugin/recommended" - ] + "extends": ["plugin:eslint-plugin/recommended"] } ``` @@ -147,15 +151,15 @@ Or to apply linting only to the appropriate rule or test files: ```json { - "overrides": [ - { - "files": ["lib/rules/*.{js,ts}"], - "extends": ["plugin:eslint-plugin/rules-recommended"] - }, - { - "files": ["tests/lib/rules/*.{js,ts}"], - "extends": ["plugin:eslint-plugin/tests-recommended"] - }, - ] + "overrides": [ + { + "files": ["lib/rules/*.{js,ts}"], + "extends": ["plugin:eslint-plugin/rules-recommended"] + }, + { + "files": ["tests/lib/rules/*.{js,ts}"], + "extends": ["plugin:eslint-plugin/tests-recommended"] + } + ] } ``` diff --git a/configs/all-type-checked.js b/configs/all-type-checked.js new file mode 100644 index 00000000..e32d57d2 --- /dev/null +++ b/configs/all-type-checked.js @@ -0,0 +1,8 @@ +'use strict'; + +const mod = require('../lib/index.js'); + +module.exports = { + plugins: { 'eslint-plugin': mod }, + rules: mod.configs['all-type-checked'].rules, +}; diff --git a/docs/rules/consistent-output.md b/docs/rules/consistent-output.md index aae5ca8d..c5d69f32 100644 --- a/docs/rules/consistent-output.md +++ b/docs/rules/consistent-output.md @@ -66,8 +66,8 @@ new RuleTester().run('example-rule', rule, { This rule takes an optional string enum option with one of the following values: -* `"consistent"` - (default) if any invalid test cases have output assertions, then all invalid test cases must have output assertions -* `"always"` - always require invalid test cases to have output assertions +- `"consistent"` - (default) if any invalid test cases have output assertions, then all invalid test cases must have output assertions +- `"always"` - always require invalid test cases to have output assertions ## When Not To Use It @@ -77,4 +77,4 @@ As mentioned in the introduction, the need for this rule is reduced as of ESLint ## Further Reading -* [`RuleTester` documentation](http://eslint.org/docs/developer-guide/working-with-plugins#testing) +- [`RuleTester` documentation](http://eslint.org/docs/developer-guide/working-with-plugins#testing) diff --git a/docs/rules/meta-property-ordering.md b/docs/rules/meta-property-ordering.md index 2593d6d9..973138f5 100644 --- a/docs/rules/meta-property-ordering.md +++ b/docs/rules/meta-property-ordering.md @@ -12,7 +12,7 @@ This rule enforces that meta properties of a rule are placed in a consistent ord This rule has an array option: -* `['type', 'docs', 'fixable', 'hasSuggestions', 'schema', 'messages', 'deprecated', 'replacedBy']` (default): The order that the properties of `meta` should be placed in. +- `['type', 'docs', 'fixable', 'hasSuggestions', 'schema', 'messages', 'deprecated', 'replacedBy']` (default): The order that the properties of `meta` should be placed in. Examples of **incorrect** code for this rule: diff --git a/docs/rules/no-deprecated-context-methods.md b/docs/rules/no-deprecated-context-methods.md index 16d999d6..50795425 100644 --- a/docs/rules/no-deprecated-context-methods.md +++ b/docs/rules/no-deprecated-context-methods.md @@ -10,26 +10,26 @@ This rule disallows the use of deprecated methods on rule `context` objects. The deprecated methods are: -* `getSource` -* `getSourceLines` -* `getAllComments` -* `getNodeByRangeIndex` -* `getComments` -* `getCommentsBefore` -* `getCommentsAfter` -* `getCommentsInside` -* `getJSDocComment` -* `getFirstToken` -* `getFirstTokens` -* `getLastToken` -* `getLastTokens` -* `getTokenAfter` -* `getTokenBefore` -* `getTokenByRangeStart` -* `getTokens` -* `getTokensAfter` -* `getTokensBefore` -* `getTokensBetween` +- `getSource` +- `getSourceLines` +- `getAllComments` +- `getNodeByRangeIndex` +- `getComments` +- `getCommentsBefore` +- `getCommentsAfter` +- `getCommentsInside` +- `getJSDocComment` +- `getFirstToken` +- `getFirstTokens` +- `getLastToken` +- `getLastTokens` +- `getTokenAfter` +- `getTokenBefore` +- `getTokenByRangeStart` +- `getTokens` +- `getTokensAfter` +- `getTokensBefore` +- `getTokensBetween` Instead of using these methods, you should use the equivalent methods on [`SourceCode`](https://eslint.org/docs/developer-guide/working-with-rules#contextgetsourcecode), e.g. `context.getSourceCode().getText()` instead of `context.getSource()`. @@ -71,4 +71,4 @@ If you need to support very old versions of ESLint where `SourceCode` doesn't ex ## Further Reading -* [`SourceCode` API](https://eslint.org/docs/developer-guide/working-with-rules#contextgetsourcecode) +- [`SourceCode` API](https://eslint.org/docs/developer-guide/working-with-rules#contextgetsourcecode) diff --git a/docs/rules/no-deprecated-report-api.md b/docs/rules/no-deprecated-report-api.md index 1bac7e94..407faf61 100644 --- a/docs/rules/no-deprecated-report-api.md +++ b/docs/rules/no-deprecated-report-api.md @@ -8,8 +8,8 @@ ESLint has two APIs that rules can use to report problems. -* The [deprecated API](http://eslint.org/docs/developer-guide/working-with-rules-deprecated) accepts multiple arguments: `context.report(node, [loc], message)`. -* The ["new API"](http://eslint.org/docs/developer-guide/working-with-rules#contextreport) accepts a single argument: an object containing information about the reported problem. +- The [deprecated API](http://eslint.org/docs/developer-guide/working-with-rules-deprecated) accepts multiple arguments: `context.report(node, [loc], message)`. +- The ["new API"](http://eslint.org/docs/developer-guide/working-with-rules#contextreport) accepts a single argument: an object containing information about the reported problem. It is recommended that all rules use the new API. @@ -41,5 +41,5 @@ module.exports = { ## Further Reading -* [Deprecated rule API](http://eslint.org/docs/developer-guide/working-with-rules-deprecated) -* [New rule API](http://eslint.org/docs/developer-guide/working-with-rules) +- [Deprecated rule API](http://eslint.org/docs/developer-guide/working-with-rules-deprecated) +- [New rule API](http://eslint.org/docs/developer-guide/working-with-rules) diff --git a/docs/rules/no-missing-message-ids.md b/docs/rules/no-missing-message-ids.md index 592d6d24..383ecf1b 100644 --- a/docs/rules/no-missing-message-ids.md +++ b/docs/rules/no-missing-message-ids.md @@ -58,6 +58,6 @@ module.exports = { ## Further Reading -* [messageIds API](https://eslint.org/docs/developer-guide/working-with-rules#messageids) -* [no-unused-message-ids](./no-unused-message-ids.md) rule -* [prefer-message-ids](./prefer-message-ids.md) rule +- [messageIds API](https://eslint.org/docs/developer-guide/working-with-rules#messageids) +- [no-unused-message-ids](./no-unused-message-ids.md) rule +- [prefer-message-ids](./prefer-message-ids.md) rule diff --git a/docs/rules/no-missing-placeholders.md b/docs/rules/no-missing-placeholders.md index 6e2ac6ca..acead6a3 100644 --- a/docs/rules/no-missing-placeholders.md +++ b/docs/rules/no-missing-placeholders.md @@ -74,4 +74,4 @@ If you want to use rule messages that actually contain double-curly bracket text ## Further Reading -* [context.report() API](http://eslint.org/docs/developer-guide/working-with-rules#contextreport) +- [context.report() API](http://eslint.org/docs/developer-guide/working-with-rules#contextreport) diff --git a/docs/rules/no-property-in-node.md b/docs/rules/no-property-in-node.md new file mode 100644 index 00000000..22da26dd --- /dev/null +++ b/docs/rules/no-property-in-node.md @@ -0,0 +1,56 @@ +# Disallow using `in` to narrow node types instead of looking at properties (`eslint-plugin/no-property-in-node`) + +💭 This rule requires type information. + + + +When working with a node of type `ESTree.Node` or `TSESTree.Node`, it can be tempting to use the `'in'` operator to narrow the node's type. +`'in'` narrowing is susceptible to confusing behavior from quirks of ASTs, such as node properties sometimes being omitted from nodes and other times explicitly being set to `null` or `undefined`. + +Instead, checking a node's `type` property is generally considered preferable. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +/* eslint eslint-plugin/no-property-in-node: error */ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + /* ... */ + }, + create(context) { + return { + 'ClassDeclaration, FunctionDeclaration'(node) { + if ('superClass' in node) { + console.log('This is a class declaration:', node); + } + }, + }; + }, +}; +``` + +Examples of **correct** code for this rule: + +```ts +/* eslint eslint-plugin/no-property-in-node: error */ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + /* ... */ + }, + create(context) { + return { + 'ClassDeclaration, FunctionDeclaration'(node) { + if (node.type === 'ClassDeclaration') { + console.log('This is a class declaration:', node); + } + }, + }; + }, +}; +``` diff --git a/docs/rules/no-unused-message-ids.md b/docs/rules/no-unused-message-ids.md index c691d67c..99660b00 100644 --- a/docs/rules/no-unused-message-ids.md +++ b/docs/rules/no-unused-message-ids.md @@ -59,6 +59,6 @@ module.exports = { ## Further Reading -* [messageIds API](https://eslint.org/docs/developer-guide/working-with-rules#messageids) -* [no-missing-message-ids](./no-missing-message-ids.md) rule -* [prefer-message-ids](./prefer-message-ids.md) rule +- [messageIds API](https://eslint.org/docs/developer-guide/working-with-rules#messageids) +- [no-missing-message-ids](./no-missing-message-ids.md) rule +- [prefer-message-ids](./prefer-message-ids.md) rule diff --git a/docs/rules/no-unused-placeholders.md b/docs/rules/no-unused-placeholders.md index fb2df80f..0f5454e1 100644 --- a/docs/rules/no-unused-placeholders.md +++ b/docs/rules/no-unused-placeholders.md @@ -57,5 +57,5 @@ If you want to allow unused placeholders, you should turn off this rule. ## Further Reading -* [context.report() API](http://eslint.org/docs/developer-guide/working-with-rules#contextreport) -* [no-missing-placeholders](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-placeholders.md) +- [context.report() API](http://eslint.org/docs/developer-guide/working-with-rules#contextreport) +- [no-missing-placeholders](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-placeholders.md) diff --git a/docs/rules/no-useless-token-range.md b/docs/rules/no-useless-token-range.md index e1bfaade..cb935d72 100644 --- a/docs/rules/no-useless-token-range.md +++ b/docs/rules/no-useless-token-range.md @@ -44,8 +44,8 @@ module.exports = { ## Known Limitations -* To ensure that your `SourceCode` instances can be detected, your rule must assign `context.getSourceCode()` to a variable somewhere. +- To ensure that your `SourceCode` instances can be detected, your rule must assign `context.getSourceCode()` to a variable somewhere. ## Further Reading -* [`SourceCode` API](https://eslint.org/docs/developer-guide/working-with-rules#contextgetsourcecode) +- [`SourceCode` API](https://eslint.org/docs/developer-guide/working-with-rules#contextgetsourcecode) diff --git a/docs/rules/prefer-message-ids.md b/docs/rules/prefer-message-ids.md index 9dc6f91f..45ea9d86 100644 --- a/docs/rules/prefer-message-ids.md +++ b/docs/rules/prefer-message-ids.md @@ -6,8 +6,9 @@ When reporting a rule violation, it's preferred to provide the violation message with the `messageId` property instead of the `message` property. Message IDs provide the following benefits: -* Rule violation messages can be stored in a central `meta.messages` object for convenient management -* Rule violation messages do not need to be repeated in both the rule file and rule test file +- Rule violation messages can be stored in a central `meta.messages` object for convenient management +- Rule violation messages do not need to be repeated in both the rule file and rule test file +- As a result, the barrier for changing rule violation messages is lower, encouraging more frequent contributions to improve and optimize them for the greatest clarity and usefulness ## Rule Details @@ -58,6 +59,6 @@ module.exports = { ## Further Reading -* [messageIds API](https://eslint.org/docs/developer-guide/working-with-rules#messageids) -* [no-invalid-message-ids](./no-invalid-message-ids.md) rule -* [no-missing-message-ids](./no-missing-message-ids.md) rule +- [messageIds API](https://eslint.org/docs/developer-guide/working-with-rules#messageids) +- [no-invalid-message-ids](./no-invalid-message-ids.md) rule +- [no-missing-message-ids](./no-missing-message-ids.md) rule diff --git a/docs/rules/prefer-object-rule.md b/docs/rules/prefer-object-rule.md index a3a1b66a..fa3df403 100644 --- a/docs/rules/prefer-object-rule.md +++ b/docs/rules/prefer-object-rule.md @@ -34,7 +34,9 @@ Examples of **correct** code for this rule: /* eslint eslint-plugin/prefer-object-rule: error */ module.exports = { - meta: { /* ... */ }, + meta: { + /* ... */ + }, create(context) { return { Program() { diff --git a/docs/rules/prefer-placeholders.md b/docs/rules/prefer-placeholders.md index 3b6de5ce..20e29822 100644 --- a/docs/rules/prefer-placeholders.md +++ b/docs/rules/prefer-placeholders.md @@ -14,8 +14,8 @@ context.report({ Using placeholders is often preferred over using dynamic report messages, for a few reasons: -* They can help enforce a separation of the message and the data. -* It will be easier to migrate when ESLint starts supporting placing lint messages in metadata (see [#6740](https://github.com/eslint/eslint/issues/6740)) +- They can help enforce a separation of the message and the data. +- It will be easier to migrate when ESLint starts supporting placing lint messages in metadata (see [#6740](https://github.com/eslint/eslint/issues/6740)) ## Rule Details @@ -63,4 +63,4 @@ If you need to use string concatenation in your report messages for some reason, ## Further Reading -* [context.report() API](http://eslint.org/docs/developer-guide/working-with-rules#contextreport) +- [context.report() API](http://eslint.org/docs/developer-guide/working-with-rules#contextreport) diff --git a/docs/rules/prefer-replace-text.md b/docs/rules/prefer-replace-text.md index eec386f4..7d233ae1 100644 --- a/docs/rules/prefer-replace-text.md +++ b/docs/rules/prefer-replace-text.md @@ -53,4 +53,4 @@ module.exports = { ## Further Reading -* [Applying Fixes](https://eslint.org/docs/developer-guide/working-with-rules#applying-fixes) +- [Applying Fixes](https://eslint.org/docs/developer-guide/working-with-rules#applying-fixes) diff --git a/docs/rules/report-message-format.md b/docs/rules/report-message-format.md index b73b4dde..aa324885 100644 --- a/docs/rules/report-message-format.md +++ b/docs/rules/report-message-format.md @@ -16,16 +16,14 @@ For example, in order to mandate that all report messages begin with a capital l ```json { - "rules": { - "eslint-plugin/report-message-format": ["error", "^[A-Z].*\\.$"] - }, - "plugins": [ - "eslint-plugin" - ] + "rules": { + "eslint-plugin/report-message-format": ["error", "^[A-Z].*\\.$"] + }, + "plugins": ["eslint-plugin"] } ``` -Note that since this rule uses static analysis and does not actually run your code, it will attempt to match report messages *before* placeholders are inserted. +Note that since this rule uses static analysis and does not actually run your code, it will attempt to match report messages _before_ placeholders are inserted. Examples of **incorrect** code for this rule: diff --git a/docs/rules/require-meta-docs-description.md b/docs/rules/require-meta-docs-description.md index 8de0b5bf..8de51bb9 100644 --- a/docs/rules/require-meta-docs-description.md +++ b/docs/rules/require-meta-docs-description.md @@ -6,9 +6,9 @@ Defining a clear and consistent description for each rule helps developers under In particular, each rule description should begin with an allowed prefix: -* `enforce` -* `require` -* `disallow` +- `enforce` +- `require` +- `disallow` ## Rule Details @@ -49,10 +49,14 @@ module.exports = { ## Options -This rule takes an optional object containing: + -* `String` — `pattern` — A regular expression that the description must match. Use `'.+'` to allow anything. Defaults to `^(enforce|require|disallow)`. +| Name | Description | Type | Default | +| :-------- | :---------------------------------------------------------------------------------- | :----- | :------------------------------ | +| `pattern` | A regular expression that the description must match. Use `'.+'` to allow anything. | String | `^(enforce\|require\|disallow)` | + + ## Further Reading -* [working-with-rules#options-schemas](https://eslint.org/docs/developer-guide/working-with-rules#options-schemas) +- [working-with-rules#options-schemas](https://eslint.org/docs/developer-guide/working-with-rules#options-schemas) diff --git a/docs/rules/require-meta-docs-url.md b/docs/rules/require-meta-docs-url.md index 7c2f1740..4bb71a41 100644 --- a/docs/rules/require-meta-docs-url.md +++ b/docs/rules/require-meta-docs-url.md @@ -77,18 +77,25 @@ module.exports = { ## Options -This rule has an option. + + +| Name | Description | Type | +| :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----- | +| `pattern` | A pattern to enforce rule's document URL. It replaces `{{name}}` placeholder by each rule name. The rule name is the basename of each rule file. Omitting this allows any URL. | String | + + ```json { - "eslint-plugin/require-meta-docs-url": ["error", { - "pattern": "https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/master/docs/rules/{{name}}.md" - }] + "eslint-plugin/require-meta-docs-url": [ + "error", + { + "pattern": "https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/master/docs/rules/{{name}}.md" + } + ] } ``` -- `pattern` (`string`) ... A pattern to enforce rule's document URL. It replaces `{{name}}` placeholder by each rule name. The rule name is the basename of each rule file. Default is `undefined` which allows any URL. - If you set the `pattern` option, this rule adds `meta.docs.url` property automatically when you execute `eslint --fix` command. ## Version specific URL diff --git a/docs/rules/require-meta-fixable.md b/docs/rules/require-meta-fixable.md index 28148a3e..e5f52ee9 100644 --- a/docs/rules/require-meta-fixable.md +++ b/docs/rules/require-meta-fixable.md @@ -92,11 +92,15 @@ module.exports = { ## Options -This rule takes an optional object containing: + -* `boolean` — `catchNoFixerButFixableProperty` — default `false` - Whether the rule should attempt to detect rules that do not have a fixer but enable the `meta.fixable` property. This option is off by default because it increases the chance of false positives since fixers can't always be detected when helper functions are used. +| Name | Description | Type | Default | +| :------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :------ | +| `catchNoFixerButFixableProperty` | Whether the rule should attempt to detect rules that do not have a fixer but enable the `meta.fixable` property. This option is off by default because it increases the chance of false positives since fixers can't always be detected when helper functions are used. | Boolean | `false` | + + ## Further Reading -* [ESLint's autofix API](http://eslint.org/docs/developer-guide/working-with-rules#applying-fixes) -* [ESLint's rule basics mentioning `meta.fixable`](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics) +- [ESLint's autofix API](http://eslint.org/docs/developer-guide/working-with-rules#applying-fixes) +- [ESLint's rule basics mentioning `meta.fixable`](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics) diff --git a/docs/rules/require-meta-has-suggestions.md b/docs/rules/require-meta-has-suggestions.md index c64846f8..89c59e5a 100644 --- a/docs/rules/require-meta-has-suggestions.md +++ b/docs/rules/require-meta-has-suggestions.md @@ -88,5 +88,5 @@ module.exports = { ## Further Reading -* [ESLint's suggestion API](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) -* [ESLint rule basics describing the `meta.hasSuggestions` property](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics) +- [ESLint's suggestion API](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) +- [ESLint rule basics describing the `meta.hasSuggestions` property](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics) diff --git a/docs/rules/require-meta-schema.md b/docs/rules/require-meta-schema.md index 066eb0ba..de1b6825 100644 --- a/docs/rules/require-meta-schema.md +++ b/docs/rules/require-meta-schema.md @@ -75,9 +75,13 @@ module.exports = { ## Options -This rule takes an optional object containing: + -* `boolean` — `requireSchemaPropertyWhenOptionless` — Whether the rule should require the `meta.schema` property to be specified (with `schema: []`) for rules that have no options. Defaults to `true`. +| Name | Description | Type | Default | +| :------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------- | :------ | :------ | +| `requireSchemaPropertyWhenOptionless` | Whether the rule should require the `meta.schema` property to be specified (with `schema: []`) for rules that have no options. | Boolean | `true` | + + ## When Not To Use It @@ -85,4 +89,4 @@ As mentioned in the introduction, the need for this rule is reduced as of ESLint ## Further Reading -* [working-with-rules#options-schemas](https://eslint.org/docs/developer-guide/working-with-rules#options-schemas) +- [working-with-rules#options-schemas](https://eslint.org/docs/developer-guide/working-with-rules#options-schemas) diff --git a/docs/rules/require-meta-type.md b/docs/rules/require-meta-type.md index 672fcb0d..92f18265 100644 --- a/docs/rules/require-meta-type.md +++ b/docs/rules/require-meta-type.md @@ -12,9 +12,9 @@ Fixes in custom rules will not be applied when using `--fix-type` unless they in This rule aims to require ESLint rules to have a valid `meta.type` property with one of the following values: -* `"problem"` means the rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. -* `"suggestion"` means the rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. -* `"layout"` means the rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. +- `"problem"` means the rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. +- `"suggestion"` means the rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. +- `"layout"` means the rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. Examples of **incorrect** code for this rule: @@ -51,6 +51,6 @@ module.exports = { ## Further Reading -* [command-line-interface#--fix-type](https://eslint.org/docs/user-guide/command-line-interface#--fix-type) -* [working-with-rules#rule-basics](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics) -* [ESLint v5.9.0 released](https://eslint.org/blog/2018/11/eslint-v5.9.0-released#the-fix-type-option) +- [command-line-interface#--fix-type](https://eslint.org/docs/user-guide/command-line-interface#--fix-type) +- [working-with-rules#rule-basics](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics) +- [ESLint v5.9.0 released](https://eslint.org/blog/2018/11/eslint-v5.9.0-released#the-fix-type-option) diff --git a/docs/rules/test-case-property-ordering.md b/docs/rules/test-case-property-ordering.md index 1a308249..19b895ef 100644 --- a/docs/rules/test-case-property-ordering.md +++ b/docs/rules/test-case-property-ordering.md @@ -12,7 +12,7 @@ This rule enforces that the properties of RuleTester test cases are arranged in This rule has an array option: -* `["code", "output", "options", "parserOptions", "errors"]` (default): The properties of a test case should be placed in a consistent order. +- `["code", "output", "options", "parserOptions", "errors"]` (default): The properties of a test case should be placed in a consistent order. Examples of **incorrect** code for this rule: diff --git a/docs/rules/test-case-shorthand-strings.md b/docs/rules/test-case-shorthand-strings.md index f0133895..0eb4acb8 100644 --- a/docs/rules/test-case-shorthand-strings.md +++ b/docs/rules/test-case-shorthand-strings.md @@ -33,10 +33,10 @@ This rule aims to enforce or disallow the use of strings as test cases. This rule has a string option: -* `as-needed` (default): Requires the use of shorthand strings wherever possible. -* `never`: Disallows the use of shorthand strings. -* `consistent`: Requires that either all valid test cases use shorthand strings, or that no valid test cases use them. -* `consistent-as-needed`: Requires that all valid test cases use the longer object form if there are any valid test cases that require the object form. Otherwise, requires all valid test cases to use shorthand strings. +- `as-needed` (default): Requires the use of shorthand strings wherever possible. +- `never`: Disallows the use of shorthand strings. +- `consistent`: Requires that either all valid test cases use shorthand strings, or that no valid test cases use them. +- `consistent-as-needed`: Requires that all valid test cases use the longer object form if there are any valid test cases that require the object form. Otherwise, requires all valid test cases to use shorthand strings. #### `as-needed` @@ -237,10 +237,10 @@ ruleTester.run('example-rule', rule, { ## Known Limitations -* Test cases which are neither object literals nor string literals are ignored by this rule. -* In order to find your test cases, your test file needs to match the following common pattern: - * `new RuleTester()` or `new (require('eslint')).RuleTester()` is called at the top level of the file - * `ruleTester.run` is called at the top level with the same variable (or in the same expression) as the `new RuleTester` instantiation +- Test cases which are neither object literals nor string literals are ignored by this rule. +- In order to find your test cases, your test file needs to match the following common pattern: + - `new RuleTester()` or `new (require('eslint')).RuleTester()` is called at the top level of the file + - `ruleTester.run` is called at the top level with the same variable (or in the same expression) as the `new RuleTester` instantiation ## When Not To Use It @@ -248,4 +248,4 @@ If you don't care about consistent usage of shorthand strings, you should not tu ## Further Reading -* [`RuleTester` documentation](http://eslint.org/docs/developer-guide/working-with-plugins#testing) +- [`RuleTester` documentation](http://eslint.org/docs/developer-guide/working-with-plugins#testing) diff --git a/lib/index.js b/lib/index.js index 4ff4ef76..b09550a2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -15,7 +15,8 @@ const packageMetadata = require('../package'); const PLUGIN_NAME = packageMetadata.name.replace(/^eslint-plugin-/, ''); const configFilters = { - all: () => true, + all: (rule) => !rule.meta.docs.requiresTypeChecking, + 'all-type-checked': () => true, recommended: (rule) => rule.meta.docs.recommended, rules: (rule) => rule.meta.docs.category === 'Rules', tests: (rule) => rule.meta.docs.category === 'Tests', diff --git a/lib/rules/consistent-output.js b/lib/rules/consistent-output.js index a5c621d7..21a1c3ee 100644 --- a/lib/rules/consistent-output.js +++ b/lib/rules/consistent-output.js @@ -27,6 +27,7 @@ module.exports = { { type: 'string', enum: ['always', 'consistent'], + default: 'consistent', }, ], messages: { diff --git a/lib/rules/fixer-return.js b/lib/rules/fixer-return.js index 05e6867f..ac231d8f 100644 --- a/lib/rules/fixer-return.js +++ b/lib/rules/fixer-return.js @@ -76,12 +76,13 @@ module.exports = { * @returns {boolean} */ function isFix(node) { + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: use context.sourceCode when dropping eslint < v9 if (node.type === 'ArrayExpression' && node.elements.length === 0) { // An empty array is not a fix. return false; } - - const staticValue = getStaticValue(node, context.getScope()); + const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < 9.0.0 + const staticValue = getStaticValue(node, scope); if (!staticValue) { // If we can't find a static value, assume it's a real fix value. return true; @@ -98,7 +99,7 @@ module.exports = { return { Program(ast) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 contextIdentifiers = utils.getContextIdentifiers( sourceCode.scopeManager, ast @@ -148,7 +149,8 @@ module.exports = { // Ensure the current (arrow) fixer function returned a fix. 'ArrowFunctionExpression:exit'(node) { if (funcInfo.shouldCheck) { - const loc = context.getSourceCode().getTokenBefore(node.body).loc; // Show violation on arrow (=>). + const sourceCode = context.sourceCode || context.getSourceCode(); + const loc = sourceCode.getTokenBefore(node.body).loc; // Show violation on arrow (=>). if (node.expression) { // When the return is implied (no curly braces around the body), we have to check the single body node directly. if (!isFix(node.body)) { diff --git a/lib/rules/meta-property-ordering.js b/lib/rules/meta-property-ordering.js index 8c19c12e..d07215d3 100644 --- a/lib/rules/meta-property-ordering.js +++ b/lib/rules/meta-property-ordering.js @@ -34,7 +34,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const ruleInfo = getRuleInfo(sourceCode); if (!ruleInfo) { return {}; diff --git a/lib/rules/no-deprecated-context-methods.js b/lib/rules/no-deprecated-context-methods.js index 97ced0e5..3d68edbb 100644 --- a/lib/rules/no-deprecated-context-methods.js +++ b/lib/rules/no-deprecated-context-methods.js @@ -54,7 +54,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 // ---------------------------------------------------------------------- // Public diff --git a/lib/rules/no-deprecated-report-api.js b/lib/rules/no-deprecated-report-api.js index d6c7b373..59ff825d 100644 --- a/lib/rules/no-deprecated-report-api.js +++ b/lib/rules/no-deprecated-report-api.js @@ -30,7 +30,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 let contextIdentifiers; // ---------------------------------------------------------------------- @@ -60,7 +60,7 @@ module.exports = { fix(fixer) { const openingParen = sourceCode.getTokenBefore(node.arguments[0]); const closingParen = sourceCode.getLastToken(node); - const reportInfo = utils.getReportInfo(node.arguments, context); + const reportInfo = utils.getReportInfo(node, context); if (!reportInfo) { return null; diff --git a/lib/rules/no-identical-tests.js b/lib/rules/no-identical-tests.js index d8679c5a..429eb626 100644 --- a/lib/rules/no-identical-tests.js +++ b/lib/rules/no-identical-tests.js @@ -32,7 +32,7 @@ module.exports = { // ---------------------------------------------------------------------- // Public // ---------------------------------------------------------------------- - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 // ---------------------------------------------------------------------- // Helpers diff --git a/lib/rules/no-missing-message-ids.js b/lib/rules/no-missing-message-ids.js index dec2f17b..73d12dd9 100644 --- a/lib/rules/no-missing-message-ids.js +++ b/lib/rules/no-missing-message-ids.js @@ -26,7 +26,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const { scopeManager } = sourceCode; const ruleInfo = utils.getRuleInfo(sourceCode); if (!ruleInfo) { @@ -48,6 +48,7 @@ module.exports = { }, CallExpression(node) { + const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < 9.0.0 // Check for messageId properties used in known calls to context.report(); if ( node.callee.type === 'MemberExpression' && @@ -55,7 +56,7 @@ module.exports = { node.callee.property.type === 'Identifier' && node.callee.property.name === 'report' ) { - const reportInfo = utils.getReportInfo(node.arguments, context); + const reportInfo = utils.getReportInfo(node, context); if (!reportInfo) { return; } @@ -80,7 +81,7 @@ module.exports = { val.value, ruleInfo, scopeManager, - context.getScope() + scope ) ) // Couldn't find this messageId in `meta.messages`. diff --git a/lib/rules/no-missing-placeholders.js b/lib/rules/no-missing-placeholders.js index a70fbd92..f505d4f4 100644 --- a/lib/rules/no-missing-placeholders.js +++ b/lib/rules/no-missing-placeholders.js @@ -31,7 +31,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const { scopeManager } = sourceCode; let contextIdentifiers; @@ -48,13 +48,14 @@ module.exports = { contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast); }, CallExpression(node) { + const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < 9.0.0 if ( node.callee.type === 'MemberExpression' && contextIdentifiers.has(node.callee.object) && node.callee.property.type === 'Identifier' && node.callee.property.name === 'report' ) { - const reportInfo = utils.getReportInfo(node.arguments, context); + const reportInfo = utils.getReportInfo(node, context); if (!reportInfo) { return; } @@ -75,7 +76,7 @@ module.exports = { obj.messageId.value, ruleInfo, scopeManager, - context.getScope() + scope ); if (correspondingMessage) { obj.message = correspondingMessage.value; @@ -89,10 +90,7 @@ module.exports = { messageId, data, } of reportMessagesAndDataArray.filter((obj) => obj.message)) { - const messageStaticValue = getStaticValue( - message, - context.getScope() - ); + const messageStaticValue = getStaticValue(message, scope); if ( ((message.type === 'Literal' && typeof message.value === 'string') || diff --git a/lib/rules/no-only-tests.js b/lib/rules/no-only-tests.js index 49206f61..fc016cc8 100644 --- a/lib/rules/no-only-tests.js +++ b/lib/rules/no-only-tests.js @@ -51,7 +51,8 @@ module.exports = { { messageId: 'removeOnly', *fix(fixer) { - const sourceCode = context.getSourceCode(); + const sourceCode = + context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const tokenBefore = sourceCode.getTokenBefore(onlyProperty); diff --git a/lib/rules/no-property-in-node.js b/lib/rules/no-property-in-node.js new file mode 100644 index 00000000..f3e2e0c8 --- /dev/null +++ b/lib/rules/no-property-in-node.js @@ -0,0 +1,86 @@ +'use strict'; + +const typedNodeSourceFileTesters = [ + /@types[/\\]estree[/\\]index\.d\.ts/, + /@typescript-eslint[/\\]types[/\\]dist[/\\]generated[/\\]ast-spec\.d\.ts/, +]; + +/** + * Given a TypeScript type, determines whether the type appears to be for a known + * AST type from the typings of @typescript-eslint/types or estree. + * We check based on two rough conditions: + * - The type has a 'kind' property (as AST node types all do) + * - The type is declared in one of those package's .d.ts types + * + * @example + * ``` + * module.exports = { + * create(context) { + * BinaryExpression(node) { + * const type = services.getTypeAtLocation(node.right); + * // ^^^^ + * // This variable's type will be TSESTree.BinaryExpression + * } + * } + * } + * ``` + * + * @param {import('typescript').Type} type + * @returns Whether the type seems to include a known ESTree or TSESTree AST node. + */ +function isAstNodeType(type) { + return (type.types || [type]) + .filter((typePart) => typePart.getProperty('type')) + .flatMap( + (typePart) => (typePart.symbol && typePart.symbol.declarations) || [] + ) + .some((declaration) => { + const fileName = declaration.getSourceFile().fileName; + return ( + fileName && + typedNodeSourceFileTesters.some((tester) => tester.test(fileName)) + ); + }); +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: + 'disallow using `in` to narrow node types instead of looking at properties', + category: 'Rules', + recommended: false, + requiresTypeChecking: true, + url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-property-in-node.md', + }, + schema: [], + messages: { + in: 'Prefer checking specific node properties instead of a broad `in`.', + }, + }, + + create(context) { + return { + 'BinaryExpression[operator=in]'(node) { + // TODO: Switch this to ESLintUtils.getParserServices with typescript-eslint@>=6 + // https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/269 + const services = (context.sourceCode || context).parserServices; + if (!services.program) { + throw new Error( + 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.' + ); + } + + const checker = services.program.getTypeChecker(); + const tsNode = services.esTreeNodeToTSNodeMap.get(node.right); + const type = checker.getTypeAtLocation(tsNode); + + if (isAstNodeType(type)) { + context.report({ messageId: 'in', node }); + } + }, + }; + }, +}; diff --git a/lib/rules/no-unused-message-ids.js b/lib/rules/no-unused-message-ids.js index 2bd1b451..86840d15 100644 --- a/lib/rules/no-unused-message-ids.js +++ b/lib/rules/no-unused-message-ids.js @@ -24,7 +24,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const { scopeManager } = sourceCode; const ruleInfo = utils.getRuleInfo(sourceCode); if (!ruleInfo) { @@ -47,7 +47,7 @@ module.exports = { contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast); }, - 'Program:exit'() { + 'Program:exit'(ast) { if (hasSeenUnknownMessageId || !hasSeenViolationReport) { /* Bail out when the rule is likely to have false positives. @@ -57,9 +57,10 @@ module.exports = { return; } + const scope = sourceCode.getScope?.(ast) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < 9.0.0 + const messageIdNodesUnused = messageIdNodes.filter( - (node) => - !messageIdsUsed.has(utils.getKeyName(node, context.getScope())) + (node) => !messageIdsUsed.has(utils.getKeyName(node, scope)) ); // Report any messageIds that were never used. @@ -68,7 +69,7 @@ module.exports = { node: messageIdNode, messageId: 'unusedMessage', data: { - messageId: utils.getKeyName(messageIdNode, context.getScope()), + messageId: utils.getKeyName(messageIdNode, scope), }, }); } @@ -82,7 +83,7 @@ module.exports = { node.callee.property.type === 'Identifier' && node.callee.property.name === 'report' ) { - const reportInfo = utils.getReportInfo(node.arguments, context); + const reportInfo = utils.getReportInfo(node, context); if (!reportInfo) { return; } diff --git a/lib/rules/no-unused-placeholders.js b/lib/rules/no-unused-placeholders.js index 51defefb..edace9c3 100644 --- a/lib/rules/no-unused-placeholders.js +++ b/lib/rules/no-unused-placeholders.js @@ -31,7 +31,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const { scopeManager } = sourceCode; let contextIdentifiers; @@ -47,13 +47,14 @@ module.exports = { contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast); }, CallExpression(node) { + const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < 9.0.0 if ( node.callee.type === 'MemberExpression' && contextIdentifiers.has(node.callee.object) && node.callee.property.type === 'Identifier' && node.callee.property.name === 'report' ) { - const reportInfo = utils.getReportInfo(node.arguments, context); + const reportInfo = utils.getReportInfo(node, context); if (!reportInfo) { return; } @@ -74,7 +75,7 @@ module.exports = { obj.messageId.value, ruleInfo, scopeManager, - context.getScope() + scope ); if (correspondingMessage) { obj.message = correspondingMessage.value; @@ -86,10 +87,7 @@ module.exports = { for (const { message, data } of reportMessagesAndDataArray.filter( (obj) => obj.message )) { - const messageStaticValue = getStaticValue( - message, - context.getScope() - ); + const messageStaticValue = getStaticValue(message, scope); if ( ((message.type === 'Literal' && typeof message.value === 'string') || diff --git a/lib/rules/no-useless-token-range.js b/lib/rules/no-useless-token-range.js index e62d7316..ea488190 100644 --- a/lib/rules/no-useless-token-range.js +++ b/lib/rules/no-useless-token-range.js @@ -30,7 +30,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 // ---------------------------------------------------------------------- // Helpers diff --git a/lib/rules/prefer-message-ids.js b/lib/rules/prefer-message-ids.js index 539d7104..48bf348a 100644 --- a/lib/rules/prefer-message-ids.js +++ b/lib/rules/prefer-message-ids.js @@ -29,7 +29,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const ruleInfo = utils.getRuleInfo(sourceCode); if (!ruleInfo) { return {}; @@ -43,6 +43,7 @@ module.exports = { return { Program(ast) { + const scope = sourceCode.getScope?.(ast) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0 contextIdentifiers = utils.getContextIdentifiers( sourceCode.scopeManager, ast @@ -64,10 +65,7 @@ module.exports = { return; } - const staticValue = getStaticValue( - messagesNode.value, - context.getScope() - ); + const staticValue = getStaticValue(messagesNode.value, scope); if (!staticValue) { return; } @@ -90,7 +88,7 @@ module.exports = { node.callee.property.type === 'Identifier' && node.callee.property.name === 'report' ) { - const reportInfo = utils.getReportInfo(node.arguments, context); + const reportInfo = utils.getReportInfo(node, context); if (!reportInfo) { return; } diff --git a/lib/rules/prefer-object-rule.js b/lib/rules/prefer-object-rule.js index 46bc4ceb..4cb11fcd 100644 --- a/lib/rules/prefer-object-rule.js +++ b/lib/rules/prefer-object-rule.js @@ -32,7 +32,7 @@ module.exports = { // Public // ---------------------------------------------------------------------- - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const ruleInfo = utils.getRuleInfo(sourceCode); if (!ruleInfo) { return {}; diff --git a/lib/rules/prefer-output-null.js b/lib/rules/prefer-output-null.js index 4403b7e8..d58e4307 100644 --- a/lib/rules/prefer-output-null.js +++ b/lib/rules/prefer-output-null.js @@ -35,7 +35,7 @@ module.exports = { // Public // ---------------------------------------------------------------------- - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 return { Program(ast) { diff --git a/lib/rules/prefer-placeholders.js b/lib/rules/prefer-placeholders.js index cc29d4b4..bd3457c3 100644 --- a/lib/rules/prefer-placeholders.js +++ b/lib/rules/prefer-placeholders.js @@ -33,7 +33,7 @@ module.exports = { create(context) { let contextIdentifiers; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const { scopeManager } = sourceCode; // ---------------------------------------------------------------------- @@ -51,7 +51,7 @@ module.exports = { node.callee.property.type === 'Identifier' && node.callee.property.name === 'report' ) { - const reportInfo = utils.getReportInfo(node.arguments, context); + const reportInfo = utils.getReportInfo(node, context); if (!reportInfo) { return; diff --git a/lib/rules/prefer-replace-text.js b/lib/rules/prefer-replace-text.js index c83beeaa..a9517ed2 100644 --- a/lib/rules/prefer-replace-text.js +++ b/lib/rules/prefer-replace-text.js @@ -30,7 +30,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 let funcInfo = { upper: null, codePath: null, diff --git a/lib/rules/report-message-format.js b/lib/rules/report-message-format.js index 770524c0..d22d9cfa 100644 --- a/lib/rules/report-message-format.js +++ b/lib/rules/report-message-format.js @@ -38,8 +38,8 @@ module.exports = { * @param {ASTNode} message The message AST node * @returns {void} */ - function processMessageNode(message) { - const staticValue = getStaticValue(message, context.getScope()); + function processMessageNode(message, scope) { + const staticValue = getStaticValue(message, scope); if ( (message.type === 'Literal' && typeof message.value === 'string' && @@ -57,7 +57,7 @@ module.exports = { } } - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const ruleInfo = utils.getRuleInfo(sourceCode); if (!ruleInfo) { return {}; @@ -69,6 +69,7 @@ module.exports = { return { Program(ast) { + const scope = sourceCode.getScope?.(ast) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0 contextIdentifiers = utils.getContextIdentifiers( sourceCode.scopeManager, ast @@ -93,21 +94,22 @@ module.exports = { messagesObject.value.properties .filter((prop) => prop.type === 'Property') .map((prop) => prop.value) - .forEach(processMessageNode); + .forEach((it) => processMessageNode(it, scope)); }, CallExpression(node) { + const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0 if ( node.callee.type === 'MemberExpression' && contextIdentifiers.has(node.callee.object) && node.callee.property.type === 'Identifier' && node.callee.property.name === 'report' ) { - const reportInfo = utils.getReportInfo(node.arguments, context); + const reportInfo = utils.getReportInfo(node, context); const message = reportInfo && reportInfo.message; const suggest = reportInfo && reportInfo.suggest; if (message) { - processMessageNode(message); + processMessageNode(message, scope); } if (suggest && suggest.type === 'ArrayExpression') { @@ -122,7 +124,7 @@ module.exports = { prop.key.name === 'message' ) .map((prop) => prop.value) - .forEach(processMessageNode); + .forEach((it) => processMessageNode(it, scope)); } } }, diff --git a/lib/rules/require-meta-docs-description.js b/lib/rules/require-meta-docs-description.js index 0a9b390d..3afd6d8d 100644 --- a/lib/rules/require-meta-docs-description.js +++ b/lib/rules/require-meta-docs-description.js @@ -27,6 +27,9 @@ module.exports = { properties: { pattern: { type: 'string', + description: + "A regular expression that the description must match. Use `'.+'` to allow anything.", + default: '^(enforce|require|disallow)', }, }, additionalProperties: false, @@ -42,14 +45,15 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const ruleInfo = utils.getRuleInfo(sourceCode); if (!ruleInfo) { return {}; } return { - Program() { + Program(ast) { + const scope = sourceCode.getScope?.(ast) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0 const { scopeManager } = sourceCode; const pattern = @@ -77,10 +81,7 @@ module.exports = { return; } - const staticValue = getStaticValue( - descriptionNode.value, - context.getScope() - ); + const staticValue = getStaticValue(descriptionNode.value, scope); if (!staticValue) { // Ignore non-static values since we can't determine what they look like. return; diff --git a/lib/rules/require-meta-docs-url.js b/lib/rules/require-meta-docs-url.js index 0894c67a..d52f1984 100644 --- a/lib/rules/require-meta-docs-url.js +++ b/lib/rules/require-meta-docs-url.js @@ -31,7 +31,11 @@ module.exports = { { type: 'object', properties: { - pattern: { type: 'string' }, + pattern: { + type: 'string', + description: + "A pattern to enforce rule's document URL. It replaces `{{name}}` placeholder by each rule name. The rule name is the basename of each rule file. Omitting this allows any URL.", + }, }, additionalProperties: false, }, @@ -50,7 +54,7 @@ module.exports = { */ create(context) { const options = context.options[0] || {}; - const filename = context.getFilename(); + const filename = context.filename || context.getFilename(); // TODO: just use context.filename when dropping eslint < v9 const ruleName = filename === '' ? undefined @@ -72,14 +76,15 @@ module.exports = { ); } - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const ruleInfo = util.getRuleInfo(sourceCode); if (!ruleInfo) { return {}; } return { - Program() { + Program(ast) { + const scope = sourceCode.getScope?.(ast) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0 const { scopeManager } = sourceCode; const metaNode = ruleInfo.meta; @@ -94,7 +99,7 @@ module.exports = { .find((p) => p.type === 'Property' && util.getKeyName(p) === 'url'); const staticValue = urlPropNode - ? getStaticValue(urlPropNode.value, context.getScope()) + ? getStaticValue(urlPropNode.value, scope) : undefined; if (urlPropNode && !staticValue) { // Ignore non-static values since we can't determine what they look like. diff --git a/lib/rules/require-meta-fixable.js b/lib/rules/require-meta-fixable.js index 74b79e7a..3b9ec681 100644 --- a/lib/rules/require-meta-fixable.js +++ b/lib/rules/require-meta-fixable.js @@ -29,6 +29,8 @@ module.exports = { catchNoFixerButFixableProperty: { type: 'boolean', default: false, + description: + "Whether the rule should attempt to detect rules that do not have a fixer but enable the `meta.fixable` property. This option is off by default because it increases the chance of false positives since fixers can't always be detected when helper functions are used.", }, }, additionalProperties: false, @@ -47,7 +49,7 @@ module.exports = { const catchNoFixerButFixableProperty = context.options[0] && context.options[0].catchNoFixerButFixableProperty; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const { scopeManager } = sourceCode; const ruleInfo = utils.getRuleInfo(sourceCode); let contextIdentifiers; @@ -76,7 +78,8 @@ module.exports = { usesFixFunctions = true; } }, - 'Program:exit'() { + 'Program:exit'(ast) { + const scope = sourceCode.getScope?.(ast) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0 const metaFixableProp = ruleInfo && utils @@ -84,10 +87,7 @@ module.exports = { .find((prop) => utils.getKeyName(prop) === 'fixable'); if (metaFixableProp) { - const staticValue = getStaticValue( - metaFixableProp.value, - context.getScope() - ); + const staticValue = getStaticValue(metaFixableProp.value, scope); if (!staticValue) { // Ignore non-static values since we can't determine what they look like. return; diff --git a/lib/rules/require-meta-has-suggestions.js b/lib/rules/require-meta-has-suggestions.js index 0a8f5282..3166841e 100644 --- a/lib/rules/require-meta-has-suggestions.js +++ b/lib/rules/require-meta-has-suggestions.js @@ -29,7 +29,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const { scopeManager } = sourceCode; const ruleInfo = utils.getRuleInfo(sourceCode); if (!ruleInfo) { @@ -44,7 +44,8 @@ module.exports = { * @returns {boolean} whether this property should be considered to contain suggestions */ function doesPropertyContainSuggestions(node) { - const staticValue = getStaticValue(node.value, context.getScope()); + const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0 + const staticValue = getStaticValue(node.value, scope); if ( !staticValue || (Array.isArray(staticValue.value) && staticValue.value.length > 0) || @@ -94,14 +95,15 @@ module.exports = { ruleReportsSuggestions = true; } }, - 'Program:exit'() { + 'Program:exit'(node) { + const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0 const metaNode = ruleInfo && ruleInfo.meta; const hasSuggestionsProperty = utils .evaluateObjectProperties(metaNode, scopeManager) .find((prop) => utils.getKeyName(prop) === 'hasSuggestions'); const hasSuggestionsStaticValue = hasSuggestionsProperty && - getStaticValue(hasSuggestionsProperty.value, context.getScope()); + getStaticValue(hasSuggestionsProperty.value, scope); if (ruleReportsSuggestions) { if (!hasSuggestionsProperty) { diff --git a/lib/rules/require-meta-schema.js b/lib/rules/require-meta-schema.js index 339d14bc..30d24109 100644 --- a/lib/rules/require-meta-schema.js +++ b/lib/rules/require-meta-schema.js @@ -25,6 +25,8 @@ module.exports = { requireSchemaPropertyWhenOptionless: { type: 'boolean', default: true, + description: + 'Whether the rule should require the `meta.schema` property to be specified (with `schema: []`) for rules that have no options.', }, }, additionalProperties: false, @@ -41,7 +43,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const { scopeManager } = sourceCode; const ruleInfo = utils.getRuleInfo(sourceCode); if (!ruleInfo) { diff --git a/lib/rules/require-meta-type.js b/lib/rules/require-meta-type.js index 9a56066b..1b3a14c4 100644 --- a/lib/rules/require-meta-type.js +++ b/lib/rules/require-meta-type.js @@ -38,14 +38,15 @@ module.exports = { // Public // ---------------------------------------------------------------------- - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 const ruleInfo = utils.getRuleInfo(sourceCode); if (!ruleInfo) { return {}; } return { - Program() { + Program(node) { + const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0 const { scopeManager } = sourceCode; const metaNode = ruleInfo.meta; @@ -61,7 +62,7 @@ module.exports = { return; } - const staticValue = getStaticValue(typeNode.value, context.getScope()); + const staticValue = getStaticValue(typeNode.value, scope); if (!staticValue) { // Ignore non-static values since we can't determine what they look like. return; diff --git a/lib/rules/test-case-property-ordering.js b/lib/rules/test-case-property-ordering.js index 66aa1b57..73cb700f 100644 --- a/lib/rules/test-case-property-ordering.js +++ b/lib/rules/test-case-property-ordering.js @@ -50,7 +50,7 @@ module.exports = { 'env', 'errors', ]; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 return { Program(ast) { diff --git a/lib/rules/test-case-shorthand-strings.js b/lib/rules/test-case-shorthand-strings.js index 5decd6c6..84b16e20 100644 --- a/lib/rules/test-case-shorthand-strings.js +++ b/lib/rules/test-case-shorthand-strings.js @@ -34,7 +34,7 @@ module.exports = { create(context) { const shorthandOption = context.options[0] || 'as-needed'; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 // ---------------------------------------------------------------------- // Helpers diff --git a/lib/utils.js b/lib/utils.js index 19f6b7fd..4ad42c24 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -444,6 +444,7 @@ module.exports = { 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) { @@ -467,7 +468,10 @@ module.exports = { isRuleTesterConstruction(declarator.init) && declarator.id.type === 'Identifier' ) { - context.getDeclaredVariables(declarator).forEach((variable) => { + 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)); @@ -579,11 +583,13 @@ module.exports = { }, /** - * Gets information on a report, given the arguments passed to context.report(). - * @param {ASTNode[]} reportArgs The arguments passed to context.report() + * Gets information on a report, given the ASTNode of context.report(). + * @param {ASTNode} node The ASTNode of context.report() * @param {Context} context */ - getReportInfo(reportArgs, 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']. @@ -608,11 +614,10 @@ module.exports = { } 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); - const secondArgStaticValue = getStaticValue( - reportArgs[1], - context.getScope() - ); if ( (secondArgStaticValue && typeof secondArgStaticValue.value === 'string') || @@ -736,7 +741,7 @@ module.exports = { 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.arguments).fix === node + module.exports.getReportInfo(parent.parent.parent).fix === node ); }, @@ -766,9 +771,8 @@ module.exports = { ) && parent.parent.parent.parent.parent.parent.callee.property.name === 'report' && - module.exports.getReportInfo( - parent.parent.parent.parent.parent.parent.arguments - ).suggest === parent.parent.parent + module.exports.getReportInfo(parent.parent.parent.parent.parent.parent) + .suggest === parent.parent.parent ); }, diff --git a/package.json b/package.json index 18404d10..e78c3464 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-eslint-plugin", - "version": "5.1.1", + "version": "5.3.0", "description": "An ESLint plugin for linting ESLint plugins", "author": "Teddy Katz", "main": "./lib/index.js", @@ -44,7 +44,7 @@ "estraverse": "^5.3.0" }, "nyc": { - "branches": 99, + "branches": 94, "functions": 99, "lines": 99, "statements": 99 @@ -55,13 +55,16 @@ "@eslint/eslintrc": "^2.0.2", "@eslint/js": "^8.37.0", "@release-it/conventional-changelog": "^4.3.0", - "@typescript-eslint/parser": "^5.36.2", + "@types/eslint": "^8.56.2", + "@types/estree": "^1.0.5", + "@typescript-eslint/parser": "^5.62.0", + "@typescript-eslint/utils": "^5.62.0", "chai": "^4.3.6", "dirty-chai": "^2.0.1", "eslint": "^8.23.0", "eslint-config-not-an-aardvark": "^2.1.0", "eslint-config-prettier": "^8.5.0", - "eslint-doc-generator": "^1.4.3", + "eslint-doc-generator": "^1.6.1", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-eslint-plugin": "file:./", "eslint-plugin-markdown": "^3.0.0", @@ -74,14 +77,14 @@ "globals": "^13.20.0", "husky": "^8.0.1", "lodash": "^4.17.21", - "markdownlint-cli": "^0.35.0", + "markdownlint-cli": "^0.39.0", "mocha": "^10.0.0", "npm-package-json-lint": "^7.0.0", - "npm-run-all": "^4.1.5", + "npm-run-all2": "^5.0.0", "nyc": "^15.1.0", "prettier": "^2.7.1", "release-it": "^14.14.3", - "typescript": "^5.0.4" + "typescript": "5.1.3" }, "peerDependencies": { "eslint": ">=7.0.0" diff --git a/tests/lib/fixtures/estree.ts b/tests/lib/fixtures/estree.ts new file mode 100644 index 00000000..e69de29b diff --git a/tests/lib/fixtures/file.ts b/tests/lib/fixtures/file.ts new file mode 100644 index 00000000..e69de29b diff --git a/tests/lib/fixtures/tsconfig.json b/tests/lib/fixtures/tsconfig.json new file mode 100644 index 00000000..0d505be7 --- /dev/null +++ b/tests/lib/fixtures/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "moduleResolution": "NodeNext" + } +} diff --git a/tests/lib/rules/no-property-in-node.js b/tests/lib/rules/no-property-in-node.js new file mode 100644 index 00000000..39e4587f --- /dev/null +++ b/tests/lib/rules/no-property-in-node.js @@ -0,0 +1,165 @@ +'use strict'; + +const RuleTester = require('eslint').RuleTester; +const path = require('path'); +const rule = require('../../../lib/rules/no-property-in-node'); + +const ruleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: path.join(__dirname, '../fixtures'), + }, +}); + +ruleTester.run('no-property-in-node', rule, { + valid: [ + `'a' in window;`, + ` + declare const node: Node; + 'a' in node; + `, + ` + type Node = { unrelated: true; }; + declare const node: Node; + 'a' in node; + `, + ` + interface Node { + unrelated: true; + }; + declare const node: Node; + 'a' in node; + `, + ` + declare const node: UnresolvedType; + 'a' in node; + `, + ` + import * as ESTree from 'estree'; + declare const loc: ESTree.SourceLocation; + 'a' in loc; + `, + ` + import * as ESTree from 'estree'; + declare const node: ESTree.Node; + a.superClass; + `, + ` + import * as ESTree from 'estree'; + declare const node: ESTree.Node; + a.type; + `, + ` + import * as ESTree from 'estree'; + declare const node: ESTree.Node; + a.type === 'ClassDeclaration'; + `, + ` + import * as ESTree from 'estree'; + declare const node: ESTree.ClassDeclaration | ESTree.FunctionDeclaration; + a.type === 'ClassDeclaration'; + `, + ` + import { TSESTree } from '@typescript-eslint/utils'; + declare const node: TSESTree.Node; + node.superClass; + `, + ` + import { TSESTree } from '@typescript-eslint/utils'; + declare const node: TSESTree.Node; + node.type; + `, + ` + import { TSESTree } from '@typescript-eslint/utils'; + declare const node: TSESTree.ClassDeclaration | TSESTree.FunctionDeclaration; + node.type === 'ClassDeclaration'; + `, + ` + import * as eslint from 'eslint'; + const listener: eslint.Rule.RuleListener = { + ClassDeclaration(node) { + node.type; + }, + }; + `, + ` + import * as eslint from 'eslint'; + const listener: eslint.Rule.RuleListener = { + 'ClassDeclaration, FunctionDeclaration'(node) { + node.type === 'ClassDeclaration'; + }, + }; + `, + ], + invalid: [ + { + code: ` + import { TSESTree } from '@typescript-eslint/utils'; + declare const node: TSESTree.Node; + 'a' in node; + `, + errors: [ + { + column: 9, + line: 4, + endColumn: 20, + endLine: 4, + messageId: 'in', + }, + ], + }, + { + code: ` + import { TSESTree } from '@typescript-eslint/utils'; + type Other = { key: true }; + declare const node: TSESTree.Node | Other; + 'a' in node; + `, + errors: [ + { + column: 9, + line: 5, + endColumn: 20, + endLine: 5, + messageId: 'in', + }, + ], + }, + { + code: ` + import * as ESTree from 'estree'; + declare const node: ESTree.Node; + 'a' in node; + `, + errors: [ + { + column: 9, + line: 4, + endColumn: 20, + endLine: 4, + messageId: 'in', + }, + ], + }, + { + code: ` + import * as eslint from 'eslint'; + const listener: eslint.Rule.RuleListener = { + ClassDeclaration(node) { + 'a' in node; + }, + }; + `, + errors: [ + { + column: 13, + line: 5, + endColumn: 24, + endLine: 5, + messageId: 'in', + }, + ], + }, + ], +}); diff --git a/tests/lib/utils.js b/tests/lib/utils.js index ebebacca..fe771824 100644 --- a/tests/lib/utils.js +++ b/tests/lib/utils.js @@ -761,8 +761,14 @@ describe('utils', () => { sourceType: 'script', nodejsScope: true, }); + const context = { + sourceCode: { + getDeclaredVariables: + scopeManager.getDeclaredVariables.bind(scopeManager), + }, + }; // mock object assert.deepEqual( - utils.getTestInfo(scopeManager, ast), + utils.getTestInfo(context, ast), [], 'Expected no tests to be found' ); @@ -827,7 +833,13 @@ describe('utils', () => { sourceType: 'script', nodejsScope: true, }); - const testInfo = utils.getTestInfo(scopeManager, ast); + const context = { + sourceCode: { + getDeclaredVariables: + scopeManager.getDeclaredVariables.bind(scopeManager), + }, + }; // mock object + const testInfo = utils.getTestInfo(context, ast); assert.strictEqual( testInfo.length, @@ -1021,7 +1033,13 @@ describe('utils', () => { sourceType: 'script', nodejsScope: true, }); - const testInfo = utils.getTestInfo(scopeManager, ast); + const context = { + sourceCode: { + getDeclaredVariables: + scopeManager.getDeclaredVariables.bind(scopeManager), + }, + }; // mock object + const testInfo = utils.getTestInfo(context, ast); assert.strictEqual( testInfo.length, @@ -1094,13 +1112,20 @@ describe('utils', () => { for (const args of CASES.keys()) { it(args.join(', '), () => { - const parsedArgs = espree.parse(`context.report(${args.join(', ')})`, { + const node = espree.parse(`context.report(${args.join(', ')})`, { ecmaVersion: 6, loc: false, range: false, - }).body[0].expression.arguments; - const context = { getScope() {} }; // mock object - const reportInfo = utils.getReportInfo(parsedArgs, context); + }).body[0].expression; + const parsedArgs = node.arguments; + const context = { + sourceCode: { + getScope() { + return {}; + }, + }, + }; // mock object + const reportInfo = utils.getReportInfo(node, context); assert.deepEqual(reportInfo, CASES.get(args)(parsedArgs)); }); @@ -1272,9 +1297,15 @@ describe('utils', () => { ecmaVersion: 6, range: true, }); - const context = { getScope() {} }; // mock object + const context = { + sourceCode: { + getScope() { + return {}; + }, + }, + }; // mock object const reportNode = ast.body[0].expression; - const reportInfo = utils.getReportInfo(reportNode.arguments, context); + const reportInfo = utils.getReportInfo(reportNode, context); const data = utils.collectReportViolationAndSuggestionData(reportInfo); assert( lodash.isMatch(data, testCase.shouldMatch),