From 36da40c1846da0a6730a750ad7f701b543d8017b Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Tue, 3 Sep 2024 08:27:44 +1200 Subject: [PATCH 01/50] [Test] add explicit marker for trailing whitespace in cases --- tests/src/rules/dynamic-import-chunkname.js | 4 ++-- tests/src/rules/no-duplicates.js | 10 +++++----- tests/src/rules/no-import-module-exports.js | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/src/rules/dynamic-import-chunkname.js b/tests/src/rules/dynamic-import-chunkname.js index 6afd834ab..81e018af7 100644 --- a/tests/src/rules/dynamic-import-chunkname.js +++ b/tests/src/rules/dynamic-import-chunkname.js @@ -1001,7 +1001,7 @@ ruleTester.run('dynamic-import-chunkname', rule, { { desc: 'Remove webpackChunkName', output: `import( - + ${''} /* webpackMode: "eager" */ 'someModule' )`, @@ -1010,7 +1010,7 @@ ruleTester.run('dynamic-import-chunkname', rule, { desc: 'Remove webpackMode', output: `import( /* webpackChunkName: "someModule" */ - + ${''} 'someModule' )`, }, diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index e682f2235..c46f9df85 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -455,28 +455,28 @@ import {x,y} from './foo' import { BULK_ACTIONS_ENABLED } from '../constants'; - + ${''} const TestComponent = () => { return
; } - + ${''} export default TestComponent; `, output: ` import { DEFAULT_FILTER_KEYS, BULK_DISABLED, - + ${''} BULK_ACTIONS_ENABLED } from '../constants'; import React from 'react'; - + ${''} const TestComponent = () => { return
; } - + ${''} export default TestComponent; `, errors: ["'../constants' imported multiple times.", "'../constants' imported multiple times."], diff --git a/tests/src/rules/no-import-module-exports.js b/tests/src/rules/no-import-module-exports.js index c2bf7ed13..aa927857e 100644 --- a/tests/src/rules/no-import-module-exports.js +++ b/tests/src/rules/no-import-module-exports.js @@ -74,13 +74,13 @@ ruleTester.run('no-import-module-exports', rule, { import fs from 'fs/promises'; const subscriptions = new Map(); - + ${''} export default async (client) => { /** * loads all modules and their subscriptions */ const modules = await fs.readdir('./src/modules'); - + ${''} await Promise.all( modules.map(async (moduleName) => { // Loads the module @@ -97,7 +97,7 @@ ruleTester.run('no-import-module-exports', rule, { } }) ); - + ${''} /** * Setting up all events. * binds all events inside the subscriptions map to call all functions provided From 40726405d77d036ee757bb890b78fae57a78fec1 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Tue, 3 Sep 2024 13:19:35 +1200 Subject: [PATCH 02/50] [Test] `namespace`: ensure valid case is actually included --- tests/src/rules/namespace.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index 1475ae9b7..3f768a571 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -336,10 +336,10 @@ const invalid = [].concat( test({ parser, code: `import { b } from "./${folder}/a"; console.log(b.c.d.e)` }), test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.c.d.e.f)` }), test({ parser, code: `import * as a from "./${folder}/a"; var {b:{c:{d:{e}}}} = a` }), - test({ parser, code: `import { b } from "./${folder}/a"; var {c:{d:{e}}} = b` })); - - // deep namespaces should include explicitly exported defaults - test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.default)` }), + test({ parser, code: `import { b } from "./${folder}/a"; var {c:{d:{e}}} = b` }), + // deep namespaces should include explicitly exported defaults + test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.default)` }), + ); invalid.push( test({ @@ -371,7 +371,8 @@ const invalid = [].concat( parser, code: `import * as a from "./${folder}/a"; var {b:{c:{ e }}} = a`, errors: ["'e' not found in deeply imported namespace 'a.b.c'."], - })); + }), + ); }); ruleTester.run('namespace', rule, { valid, invalid }); From d683a4b0c934076f8729104dd8be04d756df1054 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 2 Sep 2024 23:05:07 -0700 Subject: [PATCH 03/50] [resolvers/webpack] v0.13.9 --- resolvers/webpack/CHANGELOG.md | 3 +++ resolvers/webpack/package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index cd49cc3f4..79b2837e3 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -5,7 +5,10 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +## 0.13.9 - 2024-09-02 - [refactor] simplify loop ([#3029], thanks [@fregante]) +- [meta] add `repository.directory` field +- [refactor] avoid hoisting, misc cleanup ## 0.13.8 - 2023-10-22 - [refactor] use `hasown` instead of `has` diff --git a/resolvers/webpack/package.json b/resolvers/webpack/package.json index 38465bcde..60e5c900f 100644 --- a/resolvers/webpack/package.json +++ b/resolvers/webpack/package.json @@ -1,6 +1,6 @@ { "name": "eslint-import-resolver-webpack", - "version": "0.13.8", + "version": "0.13.9", "description": "Resolve paths to dependencies, given a webpack.config.js. Plugin for eslint-plugin-import.", "main": "index.js", "scripts": { From ba77832bfc0ceaa61aab7177bcfc316c1d77fc27 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 2 Sep 2024 23:07:55 -0700 Subject: [PATCH 04/50] [utils] v2.9.0 --- utils/CHANGELOG.md | 7 +++++++ utils/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index 43bd0e022..27102bc73 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,11 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +## v2.9.0 - 2024-09-02 + +### New +- add support for Flat Config ([#3018], thanks [@michaelfaith]) + ## v2.8.2 - 2024-08-25 ### Fixed @@ -151,6 +156,7 @@ Yanked due to critical issue with cache key resulting from #839. - `unambiguous.test()` regex is now properly in multiline mode [#3039]: https://github.com/import-js/eslint-plugin-import/pull/3039 +[#3018]: https://github.com/import-js/eslint-plugin-import/pull/3018 [#2963]: https://github.com/import-js/eslint-plugin-import/pull/2963 [#2755]: https://github.com/import-js/eslint-plugin-import/pull/2755 [#2714]: https://github.com/import-js/eslint-plugin-import/pull/2714 @@ -197,6 +203,7 @@ Yanked due to critical issue with cache key resulting from #839. [@manuth]: https://github.com/manuth [@maxkomarychev]: https://github.com/maxkomarychev [@mgwalker]: https://github.com/mgwalker +[@michaelfaith]: https://github.com/michaelfaith [@Mysak0CZ]: https://github.com/Mysak0CZ [@nicolo-ribaudo]: https://github.com/nicolo-ribaudo [@pmcelhaney]: https://github.com/pmcelhaney diff --git a/utils/package.json b/utils/package.json index 6d69e2414..fe3541ada 100644 --- a/utils/package.json +++ b/utils/package.json @@ -1,6 +1,6 @@ { "name": "eslint-module-utils", - "version": "2.8.2", + "version": "2.9.0", "description": "Core utilities to support eslint-plugin-import and other module-related plugins.", "engines": { "node": ">=4" From 59cf5b4d2c9498f417db4d04821c6d7133a9d39b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 2 Sep 2024 23:09:11 -0700 Subject: [PATCH 05/50] [Deps] update `eslint-module-utils` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fff8d85c3..b9fe66eaf 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.2", + "eslint-module-utils": "^2.9.0", "hasown": "^2.0.2", "is-core-module": "^2.15.1", "is-glob": "^4.0.3", From e73a65ed368a5425c653915070c78536ebb070df Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 2 Sep 2024 23:09:39 -0700 Subject: [PATCH 06/50] Bump to 2.30.0 --- CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9022dc887..cf97fff94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## [Unreleased] +## [2.30.0] - 2024-09-02 + ### Added - [`dynamic-import-chunkname`]: add `allowEmpty` option to allow empty leading comments ([#2942], thanks [@JiangWeixian]) - [`dynamic-import-chunkname`]: Allow empty chunk name when webpackMode: 'eager' is set; add suggestions to remove name in eager mode ([#3004], thanks [@amsardesai]) @@ -1615,7 +1617,8 @@ for info on changes for earlier releases. [#119]: https://github.com/import-js/eslint-plugin-import/issues/119 [#89]: https://github.com/import-js/eslint-plugin-import/issues/89 -[Unreleased]: https://github.com/import-js/eslint-plugin-import/compare/v2.29.1...HEAD +[Unreleased]: https://github.com/import-js/eslint-plugin-import/compare/v2.30.0...HEAD +[2.30.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.29.1...v2.30.0 [2.29.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.29.0...v2.29.1 [2.29.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.28.1...v2.29.0 [2.28.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.28.0...v2.28.1 diff --git a/package.json b/package.json index b9fe66eaf..852272034 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@exodus/eslint-plugin-import", - "version": "2.29.1-exodus-1", + "version": "2.30.0", "description": "Import with sanity.", "engines": { "node": ">=4" From 75585c44ee24ab83a71dafd4b9967bc3ae437370 Mon Sep 17 00:00:00 2001 From: michael faith Date: Thu, 5 Sep 2024 06:29:19 -0500 Subject: [PATCH 07/50] [Fix] `ExportMap` / flat config: include `languageOptions` in context This change fixes a bug with flat config support. There is a function called `childContext` that's used by the ExportBuilder to "cleanse" the context object. This function wasn't including the new `languageOptions` object, which contains the parser. So by the time this cleansed context made it to the parse function, `languageOptions` wasn't there anymore. Since `parserPath` was still being included in non-flat config scenarios, the parse function made it through ok and used `parserPath`. However, once you shift to flat config, `parserPath` is no longer defined, and the actual parser object needs to be there. Fixes #3051 --- CHANGELOG.md | 4 +++ src/exportMap/childContext.js | 3 +- tests/src/exportMap/childContext.js | 51 +++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/src/exportMap/childContext.js diff --git a/CHANGELOG.md b/CHANGELOG.md index cf97fff94..4efcdb5bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## [Unreleased] +### Fixed +- `ExportMap` / flat config: include `languageOptions` in context ([#3052], thanks [@michaelfaith]) + ## [2.30.0] - 2024-09-02 ### Added @@ -1129,6 +1132,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#3052]: https://github.com/import-js/eslint-plugin-import/pull/3052 [#3036]: https://github.com/import-js/eslint-plugin-import/pull/3036 [#3033]: https://github.com/import-js/eslint-plugin-import/pull/3033 [#3018]: https://github.com/import-js/eslint-plugin-import/pull/3018 diff --git a/src/exportMap/childContext.js b/src/exportMap/childContext.js index 5f82b8e57..3534c5913 100644 --- a/src/exportMap/childContext.js +++ b/src/exportMap/childContext.js @@ -10,7 +10,7 @@ let prevSettings = ''; * also calculate a cacheKey, where parts of the cacheKey hash are memoized */ export default function childContext(path, context) { - const { settings, parserOptions, parserPath } = context; + const { settings, parserOptions, parserPath, languageOptions } = context; if (JSON.stringify(settings) !== prevSettings) { settingsHash = hashObject({ settings }).digest('hex'); @@ -28,5 +28,6 @@ export default function childContext(path, context) { parserOptions, parserPath, path, + languageOptions, }; } diff --git a/tests/src/exportMap/childContext.js b/tests/src/exportMap/childContext.js new file mode 100644 index 000000000..06fa04afe --- /dev/null +++ b/tests/src/exportMap/childContext.js @@ -0,0 +1,51 @@ +import { expect } from 'chai'; + +import childContext from '../../../src/exportMap/childContext'; + +describe('childContext', () => { + const settings = { + setting1: true, + setting2: false, + }; + const parserOptions = { + ecmaVersion: 'latest', + sourceType: 'module', + }; + const parserPath = 'path/to/parser'; + const path = 'path/to/src/file'; + const languageOptions = { + ecmaVersion: 2024, + sourceType: 'module', + parser: {}, + }; + + // https://github.com/import-js/eslint-plugin-import/issues/3051 + it('should pass context properties through, if present', () => { + const mockContext = { + settings, + parserOptions, + parserPath, + languageOptions, + }; + + const result = childContext(path, mockContext); + + expect(result.settings).to.deep.equal(settings); + expect(result.parserOptions).to.deep.equal(parserOptions); + expect(result.parserPath).to.equal(parserPath); + expect(result.languageOptions).to.deep.equal(languageOptions); + }); + + it('should add path and cacheKey to context', () => { + const mockContext = { + settings, + parserOptions, + parserPath, + }; + + const result = childContext(path, mockContext); + + expect(result.path).to.equal(path); + expect(result.cacheKey).to.be.a('string'); + }); +}); From 55add49502235cea8c85bc03a31f2b264bb72d41 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 5 Sep 2024 11:18:52 -0700 Subject: [PATCH 08/50] [meta] fix links in old changelog entries --- CHANGELOG.md | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4efcdb5bc..88fd1dbc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,7 +65,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - [`no-duplicates`]: remove duplicate identifiers in duplicate imports ([#2577], thanks [@joe-matsec]) - [`consistent-type-specifier-style`]: fix accidental removal of comma in certain cases ([#2754], thanks [@bradzacher]) - [Perf] `ExportMap`: Improve `ExportMap.for` performance on larger codebases ([#2756], thanks [@leipert]) -- [`no-extraneous-dependencies`]/TypeScript: do not error when importing inline type from dev dependencies ([#1820], thanks [@andyogo]) +- [`no-extraneous-dependencies`]/TypeScript: do not error when importing inline type from dev dependencies ([#2735], thanks [@andyogo]) - [`newline-after-import`]/TypeScript: do not error when re-exporting a namespaced import ([#2832], thanks [@laurens-dg]) - [`order`]: partial fix for [#2687] (thanks [@ljharb]) - [`no-duplicates`]: Detect across type and regular imports ([#2835], thanks [@benkrejci]) @@ -321,7 +321,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - [`no-webpack-loader-syntax`]/TypeScript: avoid crash on missing name ([#1947], thanks [@leonardodino]) - [`no-extraneous-dependencies`]: Add package.json cache ([#1948], thanks [@fa93hws]) - [`prefer-default-export`]: handle empty array destructuring ([#1965], thanks [@ljharb]) -- [`no-unused-modules`]: make type imports mark a module as used (fixes #1924) ([#1974], thanks [@cherryblossom000]) +- [`no-unused-modules`]: make type imports mark a module as used (fixes [#1924]) ([#1974], thanks [@cherryblossom000]) - [`no-cycle`]: fix perf regression ([#1944], thanks [@Blasz]) - [`first`]: fix handling of `import = require` ([#1963], thanks [@MatthiasKunnen]) - [`no-cycle`]/[`extensions`]: fix isExternalModule usage ([#1696], thanks [@paztis]) @@ -349,7 +349,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - [`dynamic-import-chunkname`]: allow single quotes to match Webpack support ([#1848], thanks [@straub]) ### Changed -- [`export`]: add tests for a name collision with `export * from` ([#1704], thanks @tomprats) +- [`export`]: add tests for a name collision with `export * from` ([#1704], thanks [@tomprats]) ## [2.22.0] - 2020-06-26 @@ -1473,10 +1473,7 @@ for info on changes for earlier releases. [#297]: https://github.com/import-js/eslint-plugin-import/pull/297 [#296]: https://github.com/import-js/eslint-plugin-import/pull/296 [#290]: https://github.com/import-js/eslint-plugin-import/pull/290 -[#289]: https://github.com/import-js/eslint-plugin-import/pull/289 [#288]: https://github.com/import-js/eslint-plugin-import/pull/288 -[#287]: https://github.com/import-js/eslint-plugin-import/pull/287 -[#278]: https://github.com/import-js/eslint-plugin-import/pull/278 [#261]: https://github.com/import-js/eslint-plugin-import/pull/261 [#256]: https://github.com/import-js/eslint-plugin-import/pull/256 [#254]: https://github.com/import-js/eslint-plugin-import/pull/254 @@ -1488,7 +1485,6 @@ for info on changes for earlier releases. [#239]: https://github.com/import-js/eslint-plugin-import/pull/239 [#228]: https://github.com/import-js/eslint-plugin-import/pull/228 [#211]: https://github.com/import-js/eslint-plugin-import/pull/211 -[#164]: https://github.com/import-js/eslint-plugin-import/pull/164 [#157]: https://github.com/import-js/eslint-plugin-import/pull/157 [ljharb#37]: https://github.com/ljharb/eslint-plugin-import/pull/37 @@ -1601,7 +1597,6 @@ for info on changes for earlier releases. [#313]: https://github.com/import-js/eslint-plugin-import/issues/313 [#311]: https://github.com/import-js/eslint-plugin-import/issues/311 [#306]: https://github.com/import-js/eslint-plugin-import/issues/306 -[#286]: https://github.com/import-js/eslint-plugin-import/issues/286 [#283]: https://github.com/import-js/eslint-plugin-import/issues/283 [#281]: https://github.com/import-js/eslint-plugin-import/issues/281 [#275]: https://github.com/import-js/eslint-plugin-import/issues/275 @@ -1651,10 +1646,9 @@ for info on changes for earlier releases. [2.22.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.21.1...v2.22.0 [2.21.2]: https://github.com/import-js/eslint-plugin-import/compare/v2.21.1...v2.21.2 [2.21.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.21.0...v2.21.1 -[2.21.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.20.2...v2.21.0 -[2.20.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.20.1...v2.20.2 -[2.20.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.20.0...v2.20.1 -[2.19.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.19.1...v2.20.0 +[2.21.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.20.1...v2.21.0 +[2.20.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.20.0...v2.20.1 +[2.20.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.19.1...v2.20.0 [2.19.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.19.0...v2.19.1 [2.19.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.18.2...v2.19.0 [2.18.2]: https://github.com/import-js/eslint-plugin-import/compare/v2.18.1...v2.18.2 @@ -1966,7 +1960,6 @@ for info on changes for earlier releases. [@sveyret]: https://github.com/sveyret [@swernerx]: https://github.com/swernerx [@syymza]: https://github.com/syymza -[@taion]: https://github.com/taion [@TakeScoop]: https://github.com/TakeScoop [@tapayne88]: https://github.com/tapayne88 [@Taranys]: https://github.com/Taranys From dd58fd6ebc4075c4cb5660d42aae88c9ae26ade7 Mon Sep 17 00:00:00 2001 From: michael faith Date: Wed, 4 Sep 2024 04:03:47 -0500 Subject: [PATCH 09/50] [utils] [new] add context compatibility helpers This change adds helper functions to `eslint-module-utils` in order to add eslint v9 support to `eslint-plugin-import` in a backwards compatible way. Contributes to #2996 --- utils/CHANGELOG.md | 4 +++ utils/contextCompat.d.ts | 38 +++++++++++++++++++++ utils/contextCompat.js | 72 ++++++++++++++++++++++++++++++++++++++++ utils/package.json | 1 + utils/resolve.js | 3 +- 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 utils/contextCompat.d.ts create mode 100644 utils/contextCompat.js diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index 27102bc73..df6b727ad 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +### New +- add context compatibility helpers ([#3049], thanks [@michaelfaith]) + ## v2.9.0 - 2024-09-02 ### New @@ -155,6 +158,7 @@ Yanked due to critical issue with cache key resulting from #839. ### Fixed - `unambiguous.test()` regex is now properly in multiline mode +[#3049]: https://github.com/import-js/eslint-plugin-import/pull/3049 [#3039]: https://github.com/import-js/eslint-plugin-import/pull/3039 [#3018]: https://github.com/import-js/eslint-plugin-import/pull/3018 [#2963]: https://github.com/import-js/eslint-plugin-import/pull/2963 diff --git a/utils/contextCompat.d.ts b/utils/contextCompat.d.ts new file mode 100644 index 000000000..43fe0a91b --- /dev/null +++ b/utils/contextCompat.d.ts @@ -0,0 +1,38 @@ +import { Scope, SourceCode, Rule } from 'eslint'; +import * as ESTree from 'estree'; + +type LegacyContext = { + getFilename: () => string, + getPhysicalFilename: () => string, + getSourceCode: () => SourceCode, + getScope: never, + getAncestors: never, + getDeclaredVariables: never, +}; + +type NewContext = { + filename: string, + sourceCode: SourceCode, + getPhysicalFilename?: () => string, + getScope: () => Scope.Scope, + getAncestors: () => ESTree.Node[], + getDeclaredVariables: (node: ESTree.Node) => Scope.Variable[], +}; + +export type Context = LegacyContext | NewContext | Rule.RuleContext; + +declare function getAncestors(context: Context, node: ESTree.Node): ESTree.Node[]; +declare function getDeclaredVariables(context: Context, node: ESTree.Node): Scope.Variable[]; +declare function getFilename(context: Context): string; +declare function getPhysicalFilename(context: Context): string; +declare function getScope(context: Context, node: ESTree.Node): Scope.Scope; +declare function getSourceCode(context: Context): SourceCode; + +export { + getAncestors, + getDeclaredVariables, + getFilename, + getPhysicalFilename, + getScope, + getSourceCode, +}; diff --git a/utils/contextCompat.js b/utils/contextCompat.js new file mode 100644 index 000000000..b1bdc598e --- /dev/null +++ b/utils/contextCompat.js @@ -0,0 +1,72 @@ +'use strict'; + +exports.__esModule = true; + +/** @type {import('./contextCompat').getAncestors} */ +function getAncestors(context, node) { + const sourceCode = getSourceCode(context); + + if (sourceCode && sourceCode.getAncestors) { + return sourceCode.getAncestors(node); + } + + return context.getAncestors(); +} + +/** @type {import('./contextCompat').getDeclaredVariables} */ +function getDeclaredVariables(context, node) { + const sourceCode = getSourceCode(context); + + if (sourceCode && sourceCode.getDeclaredVariables) { + return sourceCode.getDeclaredVariables(node); + } + + return context.getDeclaredVariables(node); +} + +/** @type {import('./contextCompat').getFilename} */ +function getFilename(context) { + if ('filename' in context) { + return context.filename; + } + + return context.getFilename(); +} + +/** @type {import('./contextCompat').getPhysicalFilename} */ +function getPhysicalFilename(context) { + if (context.getPhysicalFilename) { + return context.getPhysicalFilename(); + } + + return getFilename(context); +} + +/** @type {import('./contextCompat').getScope} */ +function getScope(context, node) { + const sourceCode = getSourceCode(context); + + if (sourceCode && sourceCode.getScope) { + return sourceCode.getScope(node); + } + + return context.getScope(); +} + +/** @type {import('./contextCompat').getSourceCode} */ +function getSourceCode(context) { + if ('sourceCode' in context) { + return context.sourceCode; + } + + return context.getSourceCode(); +} + +module.exports = { + getAncestors, + getDeclaredVariables, + getFilename, + getPhysicalFilename, + getScope, + getSourceCode, +}; diff --git a/utils/package.json b/utils/package.json index fe3541ada..d5968f7e9 100644 --- a/utils/package.json +++ b/utils/package.json @@ -7,6 +7,7 @@ }, "main": false, "exports": { + "./contextCompat": "./contextCompat.js", "./ModuleCache": "./ModuleCache.js", "./ModuleCache.js": "./ModuleCache.js", "./declaredScope": "./declaredScope.js", diff --git a/utils/resolve.js b/utils/resolve.js index 5a3084351..b332d2ec2 100644 --- a/utils/resolve.js +++ b/utils/resolve.js @@ -5,6 +5,7 @@ exports.__esModule = true; const fs = require('fs'); const Module = require('module'); const path = require('path'); +const { getPhysicalFilename } = require('./contextCompat'); const hashObject = require('./hash').hashObject; const ModuleCache = require('./ModuleCache').default; @@ -229,7 +230,7 @@ const erroredContexts = new Set(); */ function resolve(p, context) { try { - return relative(p, context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(), context.settings); + return relative(p, getPhysicalFilename(context), context.settings); } catch (err) { if (!erroredContexts.has(context)) { // The `err.stack` string starts with `err.name` followed by colon and `err.message`. From 0281df9c67f83fa4fed75eb4878aa104fb564c34 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 5 Sep 2024 15:52:35 -0700 Subject: [PATCH 10/50] [utils] v2.10.0 --- utils/CHANGELOG.md | 2 ++ utils/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index df6b727ad..089219e7d 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,8 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +## v2.10.0 - 2024-09-05 + ### New - add context compatibility helpers ([#3049], thanks [@michaelfaith]) diff --git a/utils/package.json b/utils/package.json index d5968f7e9..f7afde88f 100644 --- a/utils/package.json +++ b/utils/package.json @@ -1,6 +1,6 @@ { "name": "eslint-module-utils", - "version": "2.9.0", + "version": "2.10.0", "description": "Core utilities to support eslint-plugin-import and other module-related plugins.", "engines": { "node": ">=4" From dec2444e5a973949bf45b2b7c911fcea57222883 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 5 Sep 2024 14:19:37 -0700 Subject: [PATCH 11/50] [Refactor] use `contextCompat` helpers --- package.json | 2 +- src/core/packagePath.js | 3 ++- src/rules/consistent-type-specifier-style.js | 4 +++- src/rules/dynamic-import-chunkname.js | 4 +++- src/rules/first.js | 6 ++++-- src/rules/named.js | 6 ++++-- src/rules/newline-after-import.js | 4 +++- src/rules/no-absolute-path.js | 5 +++-- src/rules/no-amd.js | 4 +++- src/rules/no-commonjs.js | 6 ++++-- src/rules/no-cycle.js | 6 ++++-- src/rules/no-default-export.js | 6 ++++-- src/rules/no-duplicates.js | 3 ++- src/rules/no-empty-named-blocks.js | 4 +++- src/rules/no-extraneous-dependencies.js | 8 ++++--- src/rules/no-import-module-exports.js | 7 ++++--- src/rules/no-mutable-exports.js | 6 ++++-- src/rules/no-namespace.js | 6 ++++-- src/rules/no-relative-packages.js | 3 ++- src/rules/no-relative-parent-imports.js | 7 ++++--- src/rules/no-restricted-paths.js | 7 ++++--- src/rules/no-self-import.js | 4 +++- src/rules/no-unassigned-import.js | 3 ++- src/rules/no-unused-modules.js | 3 ++- src/rules/no-useless-path-segments.js | 3 ++- src/rules/order.js | 22 +++++++++++++------- 26 files changed, 93 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 852272034..50cebd26b 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.9.0", + "eslint-module-utils": "^2.10.0", "hasown": "^2.0.2", "is-core-module": "^2.15.1", "is-glob": "^4.0.3", diff --git a/src/core/packagePath.js b/src/core/packagePath.js index 142f44aa4..f45f54326 100644 --- a/src/core/packagePath.js +++ b/src/core/packagePath.js @@ -1,4 +1,5 @@ import { dirname } from 'path'; +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; import pkgUp from 'eslint-module-utils/pkgUp'; import readPkgUp from 'eslint-module-utils/readPkgUp'; @@ -8,7 +9,7 @@ export function getFilePackagePath(filePath) { } export function getContextPackagePath(context) { - return getFilePackagePath(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()); + return getFilePackagePath(getPhysicalFilename(context)); } export function getFilePackageName(filePath) { diff --git a/src/rules/consistent-type-specifier-style.js b/src/rules/consistent-type-specifier-style.js index 9119976b1..ee5ff9fbc 100644 --- a/src/rules/consistent-type-specifier-style.js +++ b/src/rules/consistent-type-specifier-style.js @@ -1,3 +1,5 @@ +import { getSourceCode } from 'eslint-module-utils/contextCompat'; + import docsUrl from '../docsUrl'; function isComma(token) { @@ -55,7 +57,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); if (context.options[0] === 'prefer-inline') { return { diff --git a/src/rules/dynamic-import-chunkname.js b/src/rules/dynamic-import-chunkname.js index a72b04d12..12a765008 100644 --- a/src/rules/dynamic-import-chunkname.js +++ b/src/rules/dynamic-import-chunkname.js @@ -1,4 +1,6 @@ +import { getSourceCode } from 'eslint-module-utils/contextCompat'; import vm from 'vm'; + import docsUrl from '../docsUrl'; module.exports = { @@ -43,7 +45,7 @@ module.exports = { const eagerModeRegex = new RegExp(eagerModeFormat); function run(node, arg) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const leadingComments = sourceCode.getCommentsBefore ? sourceCode.getCommentsBefore(arg) // This method is available in ESLint >= 4. : sourceCode.getComments(arg).leading; // This method is deprecated in ESLint 7. diff --git a/src/rules/first.js b/src/rules/first.js index f8cc273a3..e7df26ac9 100644 --- a/src/rules/first.js +++ b/src/rules/first.js @@ -1,3 +1,5 @@ +import { getDeclaredVariables, getSourceCode } from 'eslint-module-utils/contextCompat'; + import docsUrl from '../docsUrl'; function getImportValue(node) { @@ -38,7 +40,7 @@ module.exports = { } const absoluteFirst = context.options[0] === 'absolute-first'; const message = 'Import in body of module; reorder to top.'; - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const originSourceCode = sourceCode.getText(); let nonImportCount = 0; let anyExpressions = false; @@ -66,7 +68,7 @@ module.exports = { } } if (nonImportCount > 0) { - for (const variable of context.getDeclaredVariables(node)) { + for (const variable of getDeclaredVariables(context, node)) { if (!shouldSort) { break; } const references = variable.references; if (references.length) { diff --git a/src/rules/named.js b/src/rules/named.js index ed7e5e018..ab5f3103f 100644 --- a/src/rules/named.js +++ b/src/rules/named.js @@ -1,4 +1,6 @@ import * as path from 'path'; +import { getFilename, getPhysicalFilename } from 'eslint-module-utils/contextCompat'; + import ExportMapBuilder from '../exportMap/builder'; import docsUrl from '../docsUrl'; @@ -67,7 +69,7 @@ module.exports = { if (!deepLookup.found) { if (deepLookup.path.length > 1) { const deepPath = deepLookup.path - .map((i) => path.relative(path.dirname(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()), i.path)) + .map((i) => path.relative(path.dirname(getPhysicalFilename(context)), i.path)) .join(' -> '); context.report(im[key], `${name} not found via ${deepPath}`); @@ -121,7 +123,7 @@ module.exports = { if (!deepLookup.found) { if (deepLookup.path.length > 1) { const deepPath = deepLookup.path - .map((i) => path.relative(path.dirname(context.getFilename()), i.path)) + .map((i) => path.relative(path.dirname(getFilename(context)), i.path)) .join(' -> '); context.report(im.key, `${im.key.name} not found via ${deepPath}`); diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js index d10b87d78..bf550bad9 100644 --- a/src/rules/newline-after-import.js +++ b/src/rules/newline-after-import.js @@ -3,6 +3,8 @@ * @author Radek Benkel */ +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; + import isStaticRequire from '../core/staticRequire'; import docsUrl from '../docsUrl'; @@ -193,7 +195,7 @@ module.exports = { } }, 'Program:exit'() { - log('exit processing for', context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()); + log('exit processing for', getPhysicalFilename(context)); const scopeBody = getScopeBody(context.getScope()); log('got scope:', scopeBody); diff --git a/src/rules/no-absolute-path.js b/src/rules/no-absolute-path.js index 04f67383f..0dbd8cb86 100644 --- a/src/rules/no-absolute-path.js +++ b/src/rules/no-absolute-path.js @@ -1,5 +1,7 @@ import path from 'path'; +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; + import { isAbsolute } from '../core/importType'; import docsUrl from '../docsUrl'; @@ -22,9 +24,8 @@ module.exports = { node: source, message: 'Do not import modules using an absolute path', fix(fixer) { - const resolvedContext = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); // node.js and web imports work with posix style paths ("/") - let relativePath = path.posix.relative(path.dirname(resolvedContext), source.value); + let relativePath = path.posix.relative(path.dirname(getPhysicalFilename(context)), source.value); if (!relativePath.startsWith('.')) { relativePath = `./${relativePath}`; } diff --git a/src/rules/no-amd.js b/src/rules/no-amd.js index 5edfe3e69..05ed0a521 100644 --- a/src/rules/no-amd.js +++ b/src/rules/no-amd.js @@ -3,6 +3,8 @@ * @author Jamund Ferguson */ +import { getScope } from 'eslint-module-utils/contextCompat'; + import docsUrl from '../docsUrl'; //------------------------------------------------------------------------------ @@ -23,7 +25,7 @@ module.exports = { create(context) { return { CallExpression(node) { - if (context.getScope().type !== 'module') { return; } + if (getScope(context, node).type !== 'module') { return; } if (node.callee.type !== 'Identifier') { return; } if (node.callee.name !== 'require' && node.callee.name !== 'define') { return; } diff --git a/src/rules/no-commonjs.js b/src/rules/no-commonjs.js index dde509222..33b77da59 100644 --- a/src/rules/no-commonjs.js +++ b/src/rules/no-commonjs.js @@ -3,6 +3,8 @@ * @author Jamund Ferguson */ +import { getScope } from 'eslint-module-utils/contextCompat'; + import docsUrl from '../docsUrl'; const EXPORT_MESSAGE = 'Expected "export" or "export default"'; @@ -107,7 +109,7 @@ module.exports = { // exports. if (node.object.name === 'exports') { - const isInScope = context.getScope() + const isInScope = getScope(context, node) .variables .some((variable) => variable.name === 'exports'); if (!isInScope) { @@ -117,7 +119,7 @@ module.exports = { }, CallExpression(call) { - if (!validateScope(context.getScope())) { return; } + if (!validateScope(getScope(context, call))) { return; } if (call.callee.type !== 'Identifier') { return; } if (call.callee.name !== 'require') { return; } diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index be8c288dd..d7c748d80 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -3,11 +3,13 @@ * @author Ben Mosher */ +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; +import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; import resolve from 'eslint-module-utils/resolve'; + import ExportMapBuilder from '../exportMap/builder'; import StronglyConnectedComponentsBuilder from '../scc'; import { isExternalModule } from '../core/importType'; -import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; import docsUrl from '../docsUrl'; const traversed = new Set(); @@ -57,7 +59,7 @@ module.exports = { }, create(context) { - const myPath = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); + const myPath = getPhysicalFilename(context); if (myPath === '') { return {}; } // can't cycle-check a non-file const options = context.options[0] || {}; diff --git a/src/rules/no-default-export.js b/src/rules/no-default-export.js index dabbae543..fcb4f1b2f 100644 --- a/src/rules/no-default-export.js +++ b/src/rules/no-default-export.js @@ -1,3 +1,5 @@ +import { getSourceCode } from 'eslint-module-utils/contextCompat'; + import docsUrl from '../docsUrl'; module.exports = { @@ -22,7 +24,7 @@ module.exports = { return { ExportDefaultDeclaration(node) { - const { loc } = context.getSourceCode().getFirstTokens(node)[1] || {}; + const { loc } = getSourceCode(context).getFirstTokens(node)[1] || {}; context.report({ node, message: preferNamed, loc }); }, @@ -30,7 +32,7 @@ module.exports = { node.specifiers .filter((specifier) => (specifier.exported.name || specifier.exported.value) === 'default') .forEach((specifier) => { - const { loc } = context.getSourceCode().getFirstTokens(node)[1] || {}; + const { loc } = getSourceCode(context).getFirstTokens(node)[1] || {}; if (specifier.type === 'ExportDefaultSpecifier') { context.report({ node, message: preferNamed, loc }); } else if (specifier.type === 'ExportSpecifier') { diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index d9fb1a130..32557802f 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -1,3 +1,4 @@ +import { getSourceCode } from 'eslint-module-utils/contextCompat'; import resolve from 'eslint-module-utils/resolve'; import semver from 'semver'; import flatMap from 'array.prototype.flatmap'; @@ -260,7 +261,7 @@ function checkImports(imported, context) { if (nodes.length > 1) { const message = `'${module}' imported multiple times.`; const [first, ...rest] = nodes; - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const fix = getFix(first, rest, sourceCode, context); context.report({ diff --git a/src/rules/no-empty-named-blocks.js b/src/rules/no-empty-named-blocks.js index 3ec1501b8..d68ecee38 100644 --- a/src/rules/no-empty-named-blocks.js +++ b/src/rules/no-empty-named-blocks.js @@ -1,3 +1,5 @@ +import { getSourceCode } from 'eslint-module-utils/contextCompat'; + import docsUrl from '../docsUrl'; function getEmptyBlockRange(tokens, index) { @@ -72,7 +74,7 @@ module.exports = { fix(fixer) { // Remove the empty block and the 'from' token, leaving the import only for its side // effects, e.g. `import 'mod'` - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const fromToken = program.tokens.find((t) => t.value === 'from'); const importToken = program.tokens.find((t) => t.value === 'import'); const hasSpaceAfterFrom = sourceCode.isSpaceBetween(fromToken, sourceCode.getTokenAfter(fromToken)); diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 0fe42f56f..bf0a1ed47 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -1,9 +1,11 @@ import path from 'path'; import fs from 'fs'; -import pkgUp from 'eslint-module-utils/pkgUp'; import minimatch from 'minimatch'; +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; +import pkgUp from 'eslint-module-utils/pkgUp'; import resolve from 'eslint-module-utils/resolve'; import moduleVisitor from 'eslint-module-utils/moduleVisitor'; + import importType from '../core/importType'; import { getFilePackageName } from '../core/packagePath'; import docsUrl from '../docsUrl'; @@ -84,7 +86,7 @@ function getDependencies(context, packageDir) { }); } else { const packageJsonPath = pkgUp({ - cwd: context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(), + cwd: getPhysicalFilename(context), normalize: false, }); @@ -283,7 +285,7 @@ module.exports = { create(context) { const options = context.options[0] || {}; - const filename = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); + const filename = getPhysicalFilename(context); const deps = getDependencies(context, options.packageDir) || extractDepFields({}); const depsOptions = { diff --git a/src/rules/no-import-module-exports.js b/src/rules/no-import-module-exports.js index bc4605c39..bf6fba61b 100644 --- a/src/rules/no-import-module-exports.js +++ b/src/rules/no-import-module-exports.js @@ -1,9 +1,10 @@ import minimatch from 'minimatch'; import path from 'path'; +import { getPhysicalFilename, getSourceCode } from 'eslint-module-utils/contextCompat'; import pkgUp from 'eslint-module-utils/pkgUp'; function getEntryPoint(context) { - const pkgPath = pkgUp({ cwd: context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename() }); + const pkgPath = pkgUp({ cwd: getPhysicalFilename(context) }); try { return require.resolve(path.dirname(pkgPath)); } catch (error) { @@ -14,7 +15,7 @@ function getEntryPoint(context) { } function findScope(context, identifier) { - const { scopeManager } = context.getSourceCode(); + const { scopeManager } = getSourceCode(context); return scopeManager && scopeManager.scopes.slice().reverse().find((scope) => scope.variables.some((variable) => variable.identifiers.some((node) => node.name === identifier))); } @@ -50,7 +51,7 @@ module.exports = { let alreadyReported = false; function report(node) { - const fileName = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); + const fileName = getPhysicalFilename(context); const isEntryPoint = entryPoint === fileName; const isIdentifier = node.object.type === 'Identifier'; const hasKeywords = (/^(module|exports)$/).test(node.object.name); diff --git a/src/rules/no-mutable-exports.js b/src/rules/no-mutable-exports.js index 433d64e16..c3d18b2c9 100644 --- a/src/rules/no-mutable-exports.js +++ b/src/rules/no-mutable-exports.js @@ -1,3 +1,5 @@ +import { getScope } from 'eslint-module-utils/contextCompat'; + import docsUrl from '../docsUrl'; module.exports = { @@ -32,7 +34,7 @@ module.exports = { } function handleExportDefault(node) { - const scope = context.getScope(); + const scope = getScope(context, node); if (node.declaration.name) { checkDeclarationsInScope(scope, node.declaration.name); @@ -40,7 +42,7 @@ module.exports = { } function handleExportNamed(node) { - const scope = context.getScope(); + const scope = getScope(context, node); if (node.declaration) { checkDeclaration(node.declaration); diff --git a/src/rules/no-namespace.js b/src/rules/no-namespace.js index 3c6617a41..7ab60bd21 100644 --- a/src/rules/no-namespace.js +++ b/src/rules/no-namespace.js @@ -4,6 +4,8 @@ */ import minimatch from 'minimatch'; +import { getScope, getSourceCode } from 'eslint-module-utils/contextCompat'; + import docsUrl from '../docsUrl'; /** @@ -108,7 +110,7 @@ module.exports = { return; } - const scopeVariables = context.getScope().variables; + const scopeVariables = getScope(context, node).variables; const namespaceVariable = scopeVariables.find((variable) => variable.defs[0].node === node); const namespaceReferences = namespaceVariable.references; const namespaceIdentifiers = namespaceReferences.map((reference) => reference.identifier); @@ -118,7 +120,7 @@ module.exports = { node, message: `Unexpected namespace import.`, fix: canFix && ((fixer) => { - const scopeManager = context.getSourceCode().scopeManager; + const { scopeManager } = getSourceCode(context); const fixes = []; // Pass 1: Collect variable names that are already in scope for each reference we want diff --git a/src/rules/no-relative-packages.js b/src/rules/no-relative-packages.js index 1d215519f..ebc280ff9 100644 --- a/src/rules/no-relative-packages.js +++ b/src/rules/no-relative-packages.js @@ -1,6 +1,7 @@ import path from 'path'; import readPkgUp from 'eslint-module-utils/readPkgUp'; +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; import resolve from 'eslint-module-utils/resolve'; import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; import importType from '../core/importType'; @@ -26,7 +27,7 @@ function checkImportForRelativePackage(context, importPath, node) { } const resolvedImport = resolve(importPath, context); - const resolvedContext = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); + const resolvedContext = getPhysicalFilename(context); if (!resolvedImport || !resolvedContext) { return; diff --git a/src/rules/no-relative-parent-imports.js b/src/rules/no-relative-parent-imports.js index decd2ef7d..94972d3dd 100644 --- a/src/rules/no-relative-parent-imports.js +++ b/src/rules/no-relative-parent-imports.js @@ -1,9 +1,10 @@ -import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; -import docsUrl from '../docsUrl'; import { basename, dirname, relative } from 'path'; +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; +import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; import resolve from 'eslint-module-utils/resolve'; import importType from '../core/importType'; +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -17,7 +18,7 @@ module.exports = { }, create: function noRelativePackages(context) { - const myPath = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); + const myPath = getPhysicalFilename(context); if (myPath === '') { return {}; } // can't check a non-file function checkSourceValue(sourceNode) { diff --git a/src/rules/no-restricted-paths.js b/src/rules/no-restricted-paths.js index 75952dd05..2e1bc608c 100644 --- a/src/rules/no-restricted-paths.js +++ b/src/rules/no-restricted-paths.js @@ -1,11 +1,12 @@ import path from 'path'; - +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; import resolve from 'eslint-module-utils/resolve'; import moduleVisitor from 'eslint-module-utils/moduleVisitor'; import isGlob from 'is-glob'; import { Minimatch } from 'minimatch'; -import docsUrl from '../docsUrl'; + import importType from '../core/importType'; +import docsUrl from '../docsUrl'; const containsPath = (filepath, target) => { const relative = path.relative(target, filepath); @@ -85,7 +86,7 @@ module.exports = { const options = context.options[0] || {}; const restrictedPaths = options.zones || []; const basePath = options.basePath || process.cwd(); - const currentFilename = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); + const currentFilename = getPhysicalFilename(context); const matchingZones = restrictedPaths.filter( (zone) => [].concat(zone.target) .map((target) => path.resolve(basePath, target)) diff --git a/src/rules/no-self-import.js b/src/rules/no-self-import.js index 0ba0f6669..99c534270 100644 --- a/src/rules/no-self-import.js +++ b/src/rules/no-self-import.js @@ -3,12 +3,14 @@ * @author Gio d'Amelio */ +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; import resolve from 'eslint-module-utils/resolve'; import moduleVisitor from 'eslint-module-utils/moduleVisitor'; + import docsUrl from '../docsUrl'; function isImportingSelf(context, node, requireName) { - const filePath = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); + const filePath = getPhysicalFilename(context); // If the input is from stdin, this test can't fail if (filePath !== '' && filePath === resolve(requireName, context)) { diff --git a/src/rules/no-unassigned-import.js b/src/rules/no-unassigned-import.js index 0af9f2e9f..fec232afe 100644 --- a/src/rules/no-unassigned-import.js +++ b/src/rules/no-unassigned-import.js @@ -1,5 +1,6 @@ import path from 'path'; import minimatch from 'minimatch'; +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; import isStaticRequire from '../core/staticRequire'; import docsUrl from '../docsUrl'; @@ -31,7 +32,7 @@ function testIsAllow(globs, filename, source) { function create(context) { const options = context.options[0] || {}; - const filename = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); + const filename = getPhysicalFilename(context); const isAllow = (source) => testIsAllow(options.allow, filename, source); return { diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index ccb5932cb..d6822cccd 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -4,6 +4,7 @@ * @author René Fermann */ +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; import { getFileExtensions } from 'eslint-module-utils/ignore'; import resolve from 'eslint-module-utils/resolve'; import visit from 'eslint-module-utils/visit'; @@ -609,7 +610,7 @@ module.exports = { doPreparation(src, ignoreExports, context); } - const file = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); + const file = getPhysicalFilename(context); const checkExportPresence = (node) => { if (!missingExports) { diff --git a/src/rules/no-useless-path-segments.js b/src/rules/no-useless-path-segments.js index 390a7546d..2d8dd3526 100644 --- a/src/rules/no-useless-path-segments.js +++ b/src/rules/no-useless-path-segments.js @@ -3,6 +3,7 @@ * @author Thomas Grainger */ +import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; import { getFileExtensions } from 'eslint-module-utils/ignore'; import moduleVisitor from 'eslint-module-utils/moduleVisitor'; import resolve from 'eslint-module-utils/resolve'; @@ -60,7 +61,7 @@ module.exports = { }, create(context) { - const currentDir = path.dirname(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()); + const currentDir = path.dirname(getPhysicalFilename(context)); const options = context.options[0]; function checkSourceValue(source) { diff --git a/src/rules/order.js b/src/rules/order.js index 1b25273c6..23821830f 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -3,6 +3,7 @@ import minimatch from 'minimatch'; import includes from 'array-includes'; import groupBy from 'object.groupby'; +import { getSourceCode } from 'eslint-module-utils/contextCompat'; import importType from '../core/importType'; import isStaticRequire from '../core/staticRequire'; @@ -199,7 +200,7 @@ function makeImportDescription(node) { } function fixOutOfOrder(context, firstNode, secondNode, order) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const firstRoot = findRootNode(firstNode.node); const firstRootStart = findStartOfLineWithComments(sourceCode, firstRoot); @@ -499,7 +500,10 @@ function convertPathGroupsForRanks(pathGroups) { function fixNewLineAfterImport(context, previousImport) { const prevRoot = findRootNode(previousImport.node); const tokensToEndOfLine = takeTokensAfterWhile( - context.getSourceCode(), prevRoot, commentOnSameLineAs(prevRoot)); + getSourceCode(context), + prevRoot, + commentOnSameLineAs(prevRoot), + ); let endOfLine = prevRoot.range[1]; if (tokensToEndOfLine.length > 0) { @@ -509,7 +513,7 @@ function fixNewLineAfterImport(context, previousImport) { } function removeNewLineAfterImport(context, currentImport, previousImport) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const prevRoot = findRootNode(previousImport.node); const currRoot = findRootNode(currentImport.node); const rangeToRemove = [ @@ -524,7 +528,7 @@ function removeNewLineAfterImport(context, currentImport, previousImport) { function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, distinctGroup) { const getNumberOfEmptyLinesBetween = (currentImport, previousImport) => { - const linesBetweenImports = context.getSourceCode().lines.slice( + const linesBetweenImports = getSourceCode(context).lines.slice( previousImport.node.loc.end.line, currentImport.node.loc.start.line - 1, ); @@ -720,22 +724,24 @@ module.exports = { } }, TSImportEqualsDeclaration: function handleImports(node) { - let displayName; - let value; - let type; // skip "export import"s if (node.isExport) { return; } + + let displayName; + let value; + let type; if (node.moduleReference.type === 'TSExternalModuleReference') { value = node.moduleReference.expression.value; displayName = value; type = 'import'; } else { value = ''; - displayName = context.getSourceCode().getText(node.moduleReference); + displayName = getSourceCode(context).getText(node.moduleReference); type = 'import:object'; } + registerNode( context, { From 164e41aef4bbae5528489bdec9e27d5fc4405fb2 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 6 Sep 2024 12:37:27 +1200 Subject: [PATCH 12/50] [Refactor] migrate some more places to contextCompat helpers --- src/importDeclaration.js | 6 ++++-- src/rules/namespace.js | 2 +- src/rules/newline-after-import.js | 6 +++--- src/rules/no-named-as-default-member.js | 2 +- src/rules/no-named-as-default.js | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/importDeclaration.js b/src/importDeclaration.js index 0d5e1870a..49446b260 100644 --- a/src/importDeclaration.js +++ b/src/importDeclaration.js @@ -1,4 +1,6 @@ -export default function importDeclaration(context) { - const ancestors = context.getAncestors(); +import { getAncestors } from 'eslint-module-utils/contextCompat'; + +export default function importDeclaration(context, node) { + const ancestors = getAncestors(context, node); return ancestors[ancestors.length - 1]; } diff --git a/src/rules/namespace.js b/src/rules/namespace.js index 60a4220de..6feee0d23 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -87,7 +87,7 @@ module.exports = { // same as above, but does not add names to local map ExportNamespaceSpecifier(namespace) { - const declaration = importDeclaration(context); + const declaration = importDeclaration(context, namespace); const imports = ExportMapBuilder.get(declaration.source.value, context); if (imports == null) { return null; } diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js index bf550bad9..c645d2bc6 100644 --- a/src/rules/newline-after-import.js +++ b/src/rules/newline-after-import.js @@ -3,7 +3,7 @@ * @author Radek Benkel */ -import { getPhysicalFilename } from 'eslint-module-utils/contextCompat'; +import { getPhysicalFilename, getScope } from 'eslint-module-utils/contextCompat'; import isStaticRequire from '../core/staticRequire'; import docsUrl from '../docsUrl'; @@ -194,9 +194,9 @@ module.exports = { requireCalls.push(node); } }, - 'Program:exit'() { + 'Program:exit'(node) { log('exit processing for', getPhysicalFilename(context)); - const scopeBody = getScopeBody(context.getScope()); + const scopeBody = getScopeBody(getScope(context, node)); log('got scope:', scopeBody); requireCalls.forEach((node, index) => { diff --git a/src/rules/no-named-as-default-member.js b/src/rules/no-named-as-default-member.js index 54bec64a2..c6abc46a8 100644 --- a/src/rules/no-named-as-default-member.js +++ b/src/rules/no-named-as-default-member.js @@ -35,7 +35,7 @@ module.exports = { return { ImportDefaultSpecifier(node) { - const declaration = importDeclaration(context); + const declaration = importDeclaration(context, node); const exportMap = ExportMapBuilder.get(declaration.source.value, context); if (exportMap == null) { return; } diff --git a/src/rules/no-named-as-default.js b/src/rules/no-named-as-default.js index 5b24f8e88..c5adc7afe 100644 --- a/src/rules/no-named-as-default.js +++ b/src/rules/no-named-as-default.js @@ -18,7 +18,7 @@ module.exports = { // #566: default is a valid specifier if (defaultSpecifier[nameKey].name === 'default') { return; } - const declaration = importDeclaration(context); + const declaration = importDeclaration(context, defaultSpecifier); const imports = ExportMapBuilder.get(declaration.source.value, context); if (imports == null) { return; } From e5d26c455029b624549d24866b29f0756f84c2b4 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 6 Sep 2024 12:37:27 +1200 Subject: [PATCH 13/50] [Tests] `no-empty-named-blocks`: add a `message` --- tests/src/rules/no-empty-named-blocks.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/src/rules/no-empty-named-blocks.js b/tests/src/rules/no-empty-named-blocks.js index f65e5a204..87965a140 100644 --- a/tests/src/rules/no-empty-named-blocks.js +++ b/tests/src/rules/no-empty-named-blocks.js @@ -10,6 +10,7 @@ function generateSuggestionsTestCases(cases, parser) { code, parser, errors: [{ + message: 'Unexpected empty named import block', suggestions: [ { desc: 'Remove unused import', From 2e88bef57d1541d2e7d23da36bb8f9e5fbe0a81f Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 6 Sep 2024 13:27:26 +1200 Subject: [PATCH 14/50] [Tests] remove more duplicates --- tests/src/rules/no-cycle.js | 10 ---------- tests/src/rules/no-extraneous-dependencies.js | 11 ----------- tests/src/rules/no-unresolved.js | 9 --------- 3 files changed, 30 deletions(-) diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js index efc0fb6eb..d005727e3 100644 --- a/tests/src/rules/no-cycle.js +++ b/tests/src/rules/no-cycle.js @@ -232,16 +232,6 @@ const cases = { errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)], parser: parsers.BABEL_OLD, }), - test({ - code: `import { foo } from "./${testDialect}/depth-two"`, - options: [{ maxDepth: Infinity }], - errors: [error(`Dependency cycle via ./depth-one:1`)], - }), - test({ - code: `import { foo } from "./${testDialect}/depth-two"`, - options: [{ maxDepth: '∞' }], - errors: [error(`Dependency cycle via ./depth-one:1`)], - }), test({ code: `function bar(){ return import("./${testDialect}/depth-one"); } // #2265 5`, errors: [error(`Dependency cycle detected.`)], diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index 4b221de35..dd01c141d 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -43,11 +43,9 @@ ruleTester.run('no-extraneous-dependencies', rule, { test({ code: `export { foo } from "${pkg}"` }), test({ code: `export * from "${pkg}"` }), ]), - test({ code: 'import "eslint"' }), test({ code: 'import "eslint/lib/api"' }), test({ code: 'import "fs"' }), test({ code: 'import "./foo"' }), - test({ code: 'import "@org/package"' }), test({ code: 'import "electron"', settings: { 'import/core-modules': ['electron'] } }), test({ @@ -386,15 +384,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { }], }), - test({ - code: 'import "not-a-dependency"', - filename: path.join(packageDirMonoRepoRoot, 'foo.js'), - options: [{ packageDir: packageDirMonoRepoRoot }], - errors: [{ - message: `'not-a-dependency' should be listed in the project's dependencies. Run 'npm i -S not-a-dependency' to add it`, - }], - }), - test({ code: 'import "esm-package-not-in-pkg-json/esm-module";', errors: [{ diff --git a/tests/src/rules/no-unresolved.js b/tests/src/rules/no-unresolved.js index 04a53d887..9bf1a42d4 100644 --- a/tests/src/rules/no-unresolved.js +++ b/tests/src/rules/no-unresolved.js @@ -136,15 +136,6 @@ function runResolverTests(resolver) { ], }), - rest({ - code: "import bar from './baz';", - errors: [ - { - message: "Unable to resolve path to module './baz'.", - type: 'Literal', - }, - ], - }), rest({ code: "import bar from './baz';", errors: [ From 62b91b2b35a2e362a2fea50e219b389ee79bdb2e Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 6 Sep 2024 17:28:04 +1200 Subject: [PATCH 15/50] [utils] [new] `declaredScope`: take a `node` for modern eslint versions --- utils/CHANGELOG.md | 3 +++ utils/declaredScope.d.ts | 4 +++- utils/declaredScope.js | 6 ++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index 089219e7d..00067e186 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +### New +- `declaredScope`: take a `node` for modern eslint versions (thanks [@michaelfaith]) + ## v2.10.0 - 2024-09-05 ### New diff --git a/utils/declaredScope.d.ts b/utils/declaredScope.d.ts index e37200d87..90053e8e7 100644 --- a/utils/declaredScope.d.ts +++ b/utils/declaredScope.d.ts @@ -1,8 +1,10 @@ import { Rule, Scope } from 'eslint'; +import * as ESTree from 'estree'; declare function declaredScope( context: Rule.RuleContext, - name: string + name: string, + node?: ESTree.Node, ): Scope.Scope['type'] | undefined; export default declaredScope; diff --git a/utils/declaredScope.js b/utils/declaredScope.js index 0f0a3d945..aa3e38b47 100644 --- a/utils/declaredScope.js +++ b/utils/declaredScope.js @@ -2,9 +2,11 @@ exports.__esModule = true; +const { getScope } = require('./contextCompat'); + /** @type {import('./declaredScope').default} */ -exports.default = function declaredScope(context, name) { - const references = context.getScope().references; +exports.default = function declaredScope(context, name, node) { + const references = (node ? getScope(context, node) : context.getScope()).references; const reference = references.find((x) => x.identifier.name === name); if (!reference || !reference.resolved) { return undefined; } return reference.resolved.scope.type; From ecb47ee3c59a6807d5a5f30531fdec38c50b8301 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 5 Sep 2024 22:41:26 -0700 Subject: [PATCH 16/50] [utils] v2.11.0 --- utils/CHANGELOG.md | 2 ++ utils/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index 00067e186..6b2a5e5e1 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,8 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +## v2.11.0 - 2024-09-05 + ### New - `declaredScope`: take a `node` for modern eslint versions (thanks [@michaelfaith]) diff --git a/utils/package.json b/utils/package.json index f7afde88f..df63ac168 100644 --- a/utils/package.json +++ b/utils/package.json @@ -1,6 +1,6 @@ { "name": "eslint-module-utils", - "version": "2.10.0", + "version": "2.11.0", "description": "Core utilities to support eslint-plugin-import and other module-related plugins.", "engines": { "node": ">=4" From 4468692f752cfd7f63169b016ef31ad69992eca4 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 6 Sep 2024 14:35:41 +1200 Subject: [PATCH 17/50] [Refactor] `namespace`, `no-deprecated`: update `declaredScope` to use new `getScope` when possible --- package.json | 2 +- src/rules/namespace.js | 4 ++-- src/rules/no-deprecated.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 50cebd26b..28ebcf0ee 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.10.0", + "eslint-module-utils": "^2.11.0", "hasown": "^2.0.2", "is-core-module": "^2.15.1", "is-glob": "^4.0.3", diff --git a/src/rules/namespace.js b/src/rules/namespace.js index 6feee0d23..b2de7f225 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -110,7 +110,7 @@ module.exports = { MemberExpression(dereference) { if (dereference.object.type !== 'Identifier') { return; } if (!namespaces.has(dereference.object.name)) { return; } - if (declaredScope(context, dereference.object.name) !== 'module') { return; } + if (declaredScope(context, dereference.object.name, dereference) !== 'module') { return; } if (dereference.parent.type === 'AssignmentExpression' && dereference.parent.left === dereference) { context.report( @@ -158,7 +158,7 @@ module.exports = { if (!namespaces.has(init.name)) { return; } // check for redefinition in intermediate scopes - if (declaredScope(context, init.name) !== 'module') { return; } + if (declaredScope(context, init.name, init) !== 'module') { return; } // DFS traverse child namespaces function testKey(pattern, namespace, path = [init.name]) { diff --git a/src/rules/no-deprecated.js b/src/rules/no-deprecated.js index b4299a51d..9559046b9 100644 --- a/src/rules/no-deprecated.js +++ b/src/rules/no-deprecated.js @@ -98,7 +98,7 @@ module.exports = { if (!deprecated.has(node.name)) { return; } - if (declaredScope(context, node.name) !== 'module') { return; } + if (declaredScope(context, node.name, node) !== 'module') { return; } context.report({ node, message: message(deprecated.get(node.name)), @@ -109,7 +109,7 @@ module.exports = { if (dereference.object.type !== 'Identifier') { return; } if (!namespaces.has(dereference.object.name)) { return; } - if (declaredScope(context, dereference.object.name) !== 'module') { return; } + if (declaredScope(context, dereference.object.name, dereference) !== 'module') { return; } // go deep let namespace = namespaces.get(dereference.object.name); From 020b595fe5ccdb62863822ee5ac555988d473563 Mon Sep 17 00:00:00 2001 From: Manuel Thalmann Date: Fri, 30 Aug 2024 18:19:23 +0200 Subject: [PATCH 18/50] [New] `order`: allow validating named imports --- CHANGELOG.md | 4 + docs/rules/order.md | 72 +++++++ package.json | 1 + src/rules/order.js | 409 ++++++++++++++++++++++++++++++++---- tests/src/rules/order.js | 438 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 880 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88fd1dbc2..89c752b4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## [Unreleased] +### Added +- [`order`]: allow validating named imports ([#3043], thanks [@manuth]) + ### Fixed - `ExportMap` / flat config: include `languageOptions` in context ([#3052], thanks [@michaelfaith]) @@ -1133,6 +1136,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md [#3052]: https://github.com/import-js/eslint-plugin-import/pull/3052 +[#3043]: https://github.com/import-js/eslint-plugin-import/pull/3043 [#3036]: https://github.com/import-js/eslint-plugin-import/pull/3036 [#3033]: https://github.com/import-js/eslint-plugin-import/pull/3033 [#3018]: https://github.com/import-js/eslint-plugin-import/pull/3018 diff --git a/docs/rules/order.md b/docs/rules/order.md index 67849bb7e..676279953 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -285,6 +285,78 @@ import index from './'; import sibling from './foo'; ``` +### `named: true|false|{ enabled: true|false, import: true|false, export: true|false, require: true|false, cjsExports: true|false, types: mixed|types-first|types-last }` + +Enforce ordering of names within imports and exports: + + - If set to `true`, named imports must be ordered according to the `alphabetize` options + - If set to `false`, named imports can occur in any order + +`enabled` enables the named ordering for all expressions by default. +Use `import`, `export` and `require` and `cjsExports` to override the enablement for the following kind of expressions: + + - `import`: + + ```ts + import { Readline } from "readline"; + ``` + + - `export`: + + ```ts + export { Readline }; + // and + export { Readline } from "readline"; + ``` + + - `require` + + ```ts + const { Readline } = require("readline"); + ``` + + - `cjsExports` + + ```ts + module.exports.Readline = Readline; + // and + module.exports = { Readline }; + ``` + +The `types` option allows you to specify the order of `import`s and `export`s of `type` specifiers. +Following values are possible: + + - `types-first`: forces `type` specifiers to occur first + - `types-last`: forces value specifiers to occur first + - `mixed`: sorts all specifiers in alphabetical order + +The default value is `false`. + +Example setting: + +```ts +{ + named: true, + alphabetize: { + order: 'asc' + } +} +``` + +This will fail the rule check: + +```ts +/* eslint import/order: ["error", {"named": true, "alphabetize": {"order": "asc"}}] */ +import { compose, apply } from 'xcompose'; +``` + +While this will pass: + +```ts +/* eslint import/order: ["error", {"named": true, "alphabetize": {"order": "asc"}}] */ +import { apply, compose } from 'xcompose'; +``` + ### `alphabetize: {order: asc|desc|ignore, orderImportKind: asc|desc|ignore, caseInsensitive: true|false}` Sort the order within each group in alphabetical manner based on **import path**: diff --git a/package.json b/package.json index 28ebcf0ee..0aef4ccf9 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,7 @@ "object.groupby": "^1.0.3", "object.values": "^1.2.0", "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" } } diff --git a/src/rules/order.js b/src/rules/order.js index 23821830f..d6f25ddd3 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -3,20 +3,25 @@ import minimatch from 'minimatch'; import includes from 'array-includes'; import groupBy from 'object.groupby'; -import { getSourceCode } from 'eslint-module-utils/contextCompat'; +import { getScope, getSourceCode } from 'eslint-module-utils/contextCompat'; +import trimEnd from 'string.prototype.trimend'; import importType from '../core/importType'; import isStaticRequire from '../core/staticRequire'; import docsUrl from '../docsUrl'; +const categories = { + named: 'named', + import: 'import', + exports: 'exports', +}; + const defaultGroups = ['builtin', 'external', 'parent', 'sibling', 'index']; // REPORTING AND FIXING function reverse(array) { - return array.map(function (v) { - return { ...v, rank: -v.rank }; - }).reverse(); + return array.map((v) => ({ ...v, rank: -v.rank })).reverse(); } function getTokensOrCommentsAfter(sourceCode, node, count) { @@ -131,6 +136,26 @@ function findStartOfLineWithComments(sourceCode, node) { return result; } +function findSpecifierStart(sourceCode, node) { + let token; + + do { + token = sourceCode.getTokenBefore(node); + } while (token.value !== ',' && token.value !== '{'); + + return token.range[1]; +} + +function findSpecifierEnd(sourceCode, node) { + let token; + + do { + token = sourceCode.getTokenAfter(node); + } while (token.value !== ',' && token.value !== '}'); + + return token.range[0]; +} + function isRequireExpression(expr) { return expr != null && expr.type === 'CallExpression' @@ -170,6 +195,49 @@ function isPlainImportEquals(node) { return node.type === 'TSImportEqualsDeclaration' && node.moduleReference.expression; } +function isCJSExports(context, node) { + if ( + node.type === 'MemberExpression' + && node.object.type === 'Identifier' + && node.property.type === 'Identifier' + && node.object.name === 'module' + && node.property.name === 'exports' + ) { + return getScope(context, node).variables.findIndex((variable) => variable.name === 'module') === -1; + } + if ( + node.type === 'Identifier' + && node.name === 'exports' + ) { + return getScope(context, node).variables.findIndex((variable) => variable.name === 'exports') === -1; + } +} + +function getNamedCJSExports(context, node) { + if (node.type !== 'MemberExpression') { + return; + } + const result = []; + let root = node; + let parent = null; + while (root.type === 'MemberExpression') { + if (root.property.type !== 'Identifier') { + return; + } + result.unshift(root.property.name); + parent = root; + root = root.object; + } + + if (isCJSExports(context, root)) { + return result; + } + + if (isCJSExports(context, parent)) { + return result.slice(1); + } +} + function canCrossNodeWhileReorder(node) { return isSupportedRequireModule(node) || isPlainImportModule(node) || isPlainImportEquals(node); } @@ -190,6 +258,12 @@ function canReorderItems(firstNode, secondNode) { } function makeImportDescription(node) { + if (node.type === 'export') { + if (node.node.exportKind === 'type') { + return 'type export'; + } + return 'export'; + } if (node.node.importKind === 'type') { return 'type import'; } @@ -199,58 +273,123 @@ function makeImportDescription(node) { return 'import'; } -function fixOutOfOrder(context, firstNode, secondNode, order) { +function fixOutOfOrder(context, firstNode, secondNode, order, category) { + const isNamed = category === categories.named; + const isExports = category === categories.exports; const sourceCode = getSourceCode(context); - const firstRoot = findRootNode(firstNode.node); - const firstRootStart = findStartOfLineWithComments(sourceCode, firstRoot); - const firstRootEnd = findEndOfLineWithComments(sourceCode, firstRoot); + const { + firstRoot, + secondRoot, + } = isNamed ? { + firstRoot: firstNode.node, + secondRoot: secondNode.node, + } : { + firstRoot: findRootNode(firstNode.node), + secondRoot: findRootNode(secondNode.node), + }; - const secondRoot = findRootNode(secondNode.node); - const secondRootStart = findStartOfLineWithComments(sourceCode, secondRoot); - const secondRootEnd = findEndOfLineWithComments(sourceCode, secondRoot); - const canFix = canReorderItems(firstRoot, secondRoot); + const { + firstRootStart, + firstRootEnd, + secondRootStart, + secondRootEnd, + } = isNamed ? { + firstRootStart: findSpecifierStart(sourceCode, firstRoot), + firstRootEnd: findSpecifierEnd(sourceCode, firstRoot), + secondRootStart: findSpecifierStart(sourceCode, secondRoot), + secondRootEnd: findSpecifierEnd(sourceCode, secondRoot), + } : { + firstRootStart: findStartOfLineWithComments(sourceCode, firstRoot), + firstRootEnd: findEndOfLineWithComments(sourceCode, firstRoot), + secondRootStart: findStartOfLineWithComments(sourceCode, secondRoot), + secondRootEnd: findEndOfLineWithComments(sourceCode, secondRoot), + }; - let newCode = sourceCode.text.substring(secondRootStart, secondRootEnd); - if (newCode[newCode.length - 1] !== '\n') { - newCode = `${newCode}\n`; + if (firstNode.displayName === secondNode.displayName) { + if (firstNode.alias) { + firstNode.displayName = `${firstNode.displayName} as ${firstNode.alias}`; + } + if (secondNode.alias) { + secondNode.displayName = `${secondNode.displayName} as ${secondNode.alias}`; + } } const firstImport = `${makeImportDescription(firstNode)} of \`${firstNode.displayName}\``; const secondImport = `\`${secondNode.displayName}\` ${makeImportDescription(secondNode)}`; const message = `${secondImport} should occur ${order} ${firstImport}`; - if (order === 'before') { - context.report({ - node: secondNode.node, - message, - fix: canFix && ((fixer) => fixer.replaceTextRange( - [firstRootStart, secondRootEnd], - newCode + sourceCode.text.substring(firstRootStart, secondRootStart), - )), - }); - } else if (order === 'after') { - context.report({ - node: secondNode.node, - message, - fix: canFix && ((fixer) => fixer.replaceTextRange( - [secondRootStart, firstRootEnd], - sourceCode.text.substring(secondRootEnd, firstRootEnd) + newCode, - )), - }); + if (isNamed) { + const firstCode = sourceCode.text.slice(firstRootStart, firstRoot.range[1]); + const firstTrivia = sourceCode.text.slice(firstRoot.range[1], firstRootEnd); + const secondCode = sourceCode.text.slice(secondRootStart, secondRoot.range[1]); + const secondTrivia = sourceCode.text.slice(secondRoot.range[1], secondRootEnd); + + if (order === 'before') { + const trimmedTrivia = trimEnd(secondTrivia); + const gapCode = sourceCode.text.slice(firstRootEnd, secondRootStart - 1); + const whitespaces = secondTrivia.slice(trimmedTrivia.length); + context.report({ + node: secondNode.node, + message, + fix: (fixer) => fixer.replaceTextRange( + [firstRootStart, secondRootEnd], + `${secondCode},${trimmedTrivia}${firstCode}${firstTrivia}${gapCode}${whitespaces}`, + ), + }); + } else if (order === 'after') { + const trimmedTrivia = trimEnd(firstTrivia); + const gapCode = sourceCode.text.slice(secondRootEnd + 1, firstRootStart); + const whitespaces = firstTrivia.slice(trimmedTrivia.length); + context.report({ + node: secondNode.node, + message, + fix: (fixes) => fixes.replaceTextRange( + [secondRootStart, firstRootEnd], + `${gapCode}${firstCode},${trimmedTrivia}${secondCode}${whitespaces}`, + ), + }); + } + } else { + const canFix = isExports || canReorderItems(firstRoot, secondRoot); + let newCode = sourceCode.text.substring(secondRootStart, secondRootEnd); + + if (newCode[newCode.length - 1] !== '\n') { + newCode = `${newCode}\n`; + } + + if (order === 'before') { + context.report({ + node: secondNode.node, + message, + fix: canFix && ((fixer) => fixer.replaceTextRange( + [firstRootStart, secondRootEnd], + newCode + sourceCode.text.substring(firstRootStart, secondRootStart), + )), + }); + } else if (order === 'after') { + context.report({ + node: secondNode.node, + message, + fix: canFix && ((fixer) => fixer.replaceTextRange( + [secondRootStart, firstRootEnd], + sourceCode.text.substring(secondRootEnd, firstRootEnd) + newCode, + )), + }); + } } } -function reportOutOfOrder(context, imported, outOfOrder, order) { +function reportOutOfOrder(context, imported, outOfOrder, order, category) { outOfOrder.forEach(function (imp) { const found = imported.find(function hasHigherRank(importedItem) { return importedItem.rank > imp.rank; }); - fixOutOfOrder(context, found, imp, order); + fixOutOfOrder(context, found, imp, order, category); }); } -function makeOutOfOrderReport(context, imported) { +function makeOutOfOrderReport(context, imported, category) { const outOfOrder = findOutOfOrder(imported); if (!outOfOrder.length) { return; @@ -260,10 +399,10 @@ function makeOutOfOrderReport(context, imported) { const reversedImported = reverse(imported); const reversedOrder = findOutOfOrder(reversedImported); if (reversedOrder.length < outOfOrder.length) { - reportOutOfOrder(context, reversedImported, reversedOrder, 'after'); + reportOutOfOrder(context, reversedImported, reversedOrder, 'after', category); return; } - reportOutOfOrder(context, imported, outOfOrder, 'before'); + reportOutOfOrder(context, imported, outOfOrder, 'before', category); } const compareString = (a, b) => { @@ -642,6 +781,30 @@ module.exports = { 'never', ], }, + named: { + default: false, + oneOf: [{ + type: 'boolean', + }, { + type: 'object', + properties: { + enabled: { type: 'boolean' }, + import: { type: 'boolean' }, + export: { type: 'boolean' }, + require: { type: 'boolean' }, + cjsExports: { type: 'boolean' }, + types: { + type: 'string', + enum: [ + 'mixed', + 'types-first', + 'types-last', + ], + }, + }, + additionalProperties: false, + }], + }, alphabetize: { type: 'object', properties: { @@ -670,10 +833,28 @@ module.exports = { ], }, - create: function importOrderRule(context) { + create(context) { const options = context.options[0] || {}; const newlinesBetweenImports = options['newlines-between'] || 'ignore'; const pathGroupsExcludedImportTypes = new Set(options.pathGroupsExcludedImportTypes || ['builtin', 'external', 'object']); + + const named = { + types: 'mixed', + ...typeof options.named === 'object' ? { + ...options.named, + import: 'import' in options.named ? options.named.import : options.named.enabled, + export: 'export' in options.named ? options.named.export : options.named.enabled, + require: 'require' in options.named ? options.named.require : options.named.enabled, + cjsExports: 'cjsExports' in options.named ? options.named.cjsExports : options.named.enabled, + } : { + import: options.named, + export: options.named, + require: options.named, + cjsExports: options.named, + }, + }; + + const namedGroups = named.types === 'mixed' ? [] : named.types === 'types-last' ? ['value'] : ['type']; const alphabetize = getAlphabetizeConfig(options); const distinctGroup = options.distinctGroup == null ? defaultDistinctGroup : !!options.distinctGroup; let ranks; @@ -696,6 +877,7 @@ module.exports = { }; } const importMap = new Map(); + const exportMap = new Map(); function getBlockImports(node) { if (!importMap.has(node)) { @@ -704,8 +886,38 @@ module.exports = { return importMap.get(node); } + function getBlockExports(node) { + if (!exportMap.has(node)) { + exportMap.set(node, []); + } + return exportMap.get(node); + } + + function makeNamedOrderReport(context, namedImports) { + if (namedImports.length > 1) { + const imports = namedImports.map( + (namedImport) => { + const kind = namedImport.kind || 'value'; + const rank = namedGroups.findIndex((entry) => [].concat(entry).indexOf(kind) > -1); + + return { + displayName: namedImport.value, + rank: rank === -1 ? namedGroups.length : rank, + ...namedImport, + value: `${namedImport.value}:${namedImport.alias || ''}`, + }; + }); + + if (alphabetize.order !== 'ignore') { + mutateRanksToAlphabetize(imports, alphabetize); + } + + makeOutOfOrderReport(context, imports, categories.named); + } + } + return { - ImportDeclaration: function handleImports(node) { + ImportDeclaration(node) { // Ignoring unassigned imports unless warnOnUnassignedImports is set if (node.specifiers.length || options.warnOnUnassignedImports) { const name = node.source.value; @@ -721,9 +933,27 @@ module.exports = { getBlockImports(node.parent), pathGroupsExcludedImportTypes, ); + + if (named.import) { + makeNamedOrderReport( + context, + node.specifiers.filter( + (specifier) => specifier.type === 'ImportSpecifier').map( + (specifier) => ({ + node: specifier, + value: specifier.imported.name, + type: 'import', + kind: specifier.importKind, + ...specifier.local.range[0] !== specifier.imported.range[0] && { + alias: specifier.local.name, + }, + }), + ), + ); + } } }, - TSImportEqualsDeclaration: function handleImports(node) { + TSImportEqualsDeclaration(node) { // skip "export import"s if (node.isExport) { return; @@ -755,7 +985,7 @@ module.exports = { pathGroupsExcludedImportTypes, ); }, - CallExpression: function handleRequires(node) { + CallExpression(node) { if (!isStaticRequire(node)) { return; } @@ -777,7 +1007,90 @@ module.exports = { pathGroupsExcludedImportTypes, ); }, - 'Program:exit': function reportAndReset() { + ...named.require && { + VariableDeclarator(node) { + if (node.id.type === 'ObjectPattern' && isRequireExpression(node.init)) { + for (let i = 0; i < node.id.properties.length; i++) { + if ( + node.id.properties[i].key.type !== 'Identifier' + || node.id.properties[i].value.type !== 'Identifier' + ) { + return; + } + } + makeNamedOrderReport( + context, + node.id.properties.map((prop) => ({ + node: prop, + value: prop.key.name, + type: 'require', + ...prop.key.range[0] !== prop.value.range[0] && { + alias: prop.value.name, + }, + })), + ); + } + }, + }, + ...named.export && { + ExportNamedDeclaration(node) { + makeNamedOrderReport( + context, + node.specifiers.map((specifier) => ({ + node: specifier, + value: specifier.local.name, + type: 'export', + kind: specifier.exportKind, + ...specifier.local.range[0] !== specifier.exported.range[0] && { + alias: specifier.exported.name, + }, + })), + ); + }, + }, + ...named.cjsExports && { + AssignmentExpression(node) { + if (node.parent.type === 'ExpressionStatement') { + if (isCJSExports(context, node.left)) { + if (node.right.type === 'ObjectExpression') { + for (let i = 0; i < node.right.properties.length; i++) { + if ( + node.right.properties[i].key.type !== 'Identifier' + || node.right.properties[i].value.type !== 'Identifier' + ) { + return; + } + } + + makeNamedOrderReport( + context, + node.right.properties.map((prop) => ({ + node: prop, + value: prop.key.name, + type: 'export', + ...prop.key.range[0] !== prop.value.range[0] && { + alias: prop.value.name, + }, + })), + ); + } + } else { + const nameParts = getNamedCJSExports(context, node.left); + if (nameParts && nameParts.length > 0) { + const name = nameParts.join('.'); + getBlockExports(node.parent.parent).push({ + node, + value: name, + displayName: name, + type: 'export', + rank: 0, + }); + } + } + } + }, + }, + 'Program:exit'() { importMap.forEach((imported) => { if (newlinesBetweenImports !== 'ignore') { makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, distinctGroup); @@ -787,10 +1100,18 @@ module.exports = { mutateRanksToAlphabetize(imported, alphabetize); } - makeOutOfOrderReport(context, imported); + makeOutOfOrderReport(context, imported, categories.import); + }); + + exportMap.forEach((exported) => { + if (alphabetize.order !== 'ignore') { + mutateRanksToAlphabetize(exported, alphabetize); + makeOutOfOrderReport(context, exported, categories.exports); + } }); importMap.clear(); + exportMap.clear(); }, }; }, diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index c2d659f83..978c8e34d 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1133,6 +1133,140 @@ ruleTester.run('order', rule, { }, ], }), + // named import order + test({ + code: ` + import { a, B as C, Z } from './Z'; + const { D, n: c, Y } = require('./Z'); + export { C, D }; + export { A, B, C as default } from "./Z"; + + const { ["ignore require-statements with non-identifier imports"]: z, d } = require("./Z"); + exports = { ["ignore exports statements with non-identifiers"]: Z, D }; + `, + options: [{ + named: true, + alphabetize: { order: 'asc', caseInsensitive: true }, + }], + }), + test({ + code: ` + const { b, A } = require('./Z'); + `, + options: [{ + named: true, + alphabetize: { order: 'desc' }, + }], + }), + test({ + code: ` + import { A, B } from "./Z"; + export { Z, A } from "./Z"; + export { N, P } from "./Z"; + const { X, Y } = require("./Z"); + `, + options: [{ + named: { + require: true, + import: true, + export: false, + }, + }], + }), + test({ + code: ` + import { B, A } from "./Z"; + const { D, C } = require("./Z"); + export { B, A } from "./Z"; + `, + options: [{ + named: { + require: false, + import: false, + export: false, + }, + }], + }), + test({ + code: ` + import { B, A, R } from "foo"; + const { D, O, G } = require("tunes"); + export { B, A, Z } from "foo"; + `, + options: [{ + named: { enabled: false }, + }], + }), + test({ + code: ` + import { A as A, A as B, A as C } from "./Z"; + const { a, a: b, a: c } = require("./Z"); + `, + options: [{ + named: true, + }], + }), + test({ + code: ` + import { A, B, C } from "./Z"; + exports = { A, B, C }; + module.exports = { a: A, b: B, c: C }; + `, + options: [{ + named: { + cjsExports: true, + }, + alphabetize: { order: 'asc' }, + }], + }), + test({ + code: ` + module.exports.A = { }; + module.exports.A.B = { }; + module.exports.B = { }; + exports.C = { }; + `, + options: [{ + named: { + cjsExports: true, + }, + alphabetize: { order: 'asc' }, + }], + }), + // ensure other assignments are untouched + test({ + code: ` + var exports = null; + var module = null; + exports = { }; + module = { }; + module.exports = { }; + module.exports.U = { }; + module.exports.N = { }; + module.exports.C = { }; + exports.L = { }; + exports.E = { }; + `, + options: [{ + named: { + cjsExports: true, + }, + alphabetize: { order: 'asc' }, + }], + }), + test({ + code: ` + exports["B"] = { }; + exports["C"] = { }; + exports["A"] = { }; + `, + options: [{ + named: { + cjsExports: true, + }, + alphabetize: { order: 'asc' }, + }], + }), ], invalid: [ // builtin before external module (require) @@ -2742,6 +2876,205 @@ ruleTester.run('order', rule, { message: 'There should be no empty line within import group', }], }), + // named import order + test({ + code: ` + var { B, A: R } = require("./Z"); + import { O as G, D } from "./Z"; + import { K, L, J } from "./Z"; + export { Z, X, Y } from "./Z"; + `, + output: ` + var { A: R, B } = require("./Z"); + import { D, O as G } from "./Z"; + import { J, K, L } from "./Z"; + export { X, Y, Z } from "./Z"; + `, + options: [{ + named: true, + alphabetize: { order: 'asc' }, + }], + errors: [{ + message: '`A` import should occur before import of `B`', + }, { + message: '`D` import should occur before import of `O`', + }, { + message: '`J` import should occur before import of `K`', + }, { + message: '`Z` export should occur after export of `Y`', + }], + }), + test({ + code: ` + import { D, C } from "./Z"; + var { B, A } = require("./Z"); + export { B, A }; + `, + output: ` + import { C, D } from "./Z"; + var { B, A } = require("./Z"); + export { A, B }; + `, + options: [{ + named: { + require: false, + import: true, + export: true, + }, + alphabetize: { order: 'asc' }, + }], + errors: [{ + message: '`C` import should occur before import of `D`', + }, { + message: '`A` export should occur before export of `B`', + }], + }), + test({ + code: ` + import { A as B, A as C, A } from "./Z"; + export { A, A as D, A as B, A as C } from "./Z"; + const { a: b, a: c, a } = require("./Z"); + `, + output: ` + import { A, A as B, A as C } from "./Z"; + export { A, A as B, A as C, A as D } from "./Z"; + const { a, a: b, a: c } = require("./Z"); + `, + options: [{ + named: true, + alphabetize: { order: 'asc' }, + }], + errors: [{ + message: '`A` import should occur before import of `A as B`', + }, { + message: '`A as D` export should occur after export of `A as C`', + }, { + message: '`a` import should occur before import of `a as b`', + }], + }), + test({ + code: ` + import { A, B, C } from "./Z"; + exports = { B, C, A }; + module.exports = { c: C, a: A, b: B }; + `, + output: ` + import { A, B, C } from "./Z"; + exports = { A, B, C }; + module.exports = { a: A, b: B, c: C }; + `, + options: [{ + named: { + cjsExports: true, + }, + alphabetize: { order: 'asc' }, + }], + errors: [{ + message: '`A` export should occur before export of `B`', + }, { + message: '`c` export should occur after export of `b`', + }], + }), + test({ + code: ` + exports.B = { }; + module.exports.A = { }; + module.exports.C = { }; + `, + output: ` + module.exports.A = { }; + exports.B = { }; + module.exports.C = { }; + `, + options: [{ + named: { + cjsExports: true, + }, + alphabetize: { order: 'asc' }, + }], + errors: [{ + message: '`A` export should occur before export of `B`', + }], + }), + test({ + code: ` + exports.A.C = { }; + module.exports.A.A = { }; + exports.A.B = { }; + `, + output: ` + module.exports.A.A = { }; + exports.A.B = { }; + exports.A.C = { }; + `, + options: [{ + named: { + cjsExports: true, + }, + alphabetize: { order: 'asc' }, + }], + errors: [{ + message: '`A.C` export should occur after export of `A.B`', + }], + }), + // multiline named specifiers & trailing commas + test({ + code: ` + const { + F: O, + O: B, + /* Hello World */ + A: R + } = require("./Z"); + import { + Y, + X, + } from "./Z"; + export { + Z, A, + B + } from "./Z"; + module.exports = { + a: A, o: O, + b: B + }; + `, + output: ` + const { + /* Hello World */ + A: R, + F: O, + O: B + } = require("./Z"); + import { + X, + Y, + } from "./Z"; + export { A, + B, + Z + } from "./Z"; + module.exports = { + a: A, + b: B, o: O + }; + `, + options: [{ + named: { + enabled: true, + }, + alphabetize: { order: 'asc' }, + }], + errors: [{ + message: '`A` import should occur before import of `F`', + }, { + message: '`X` import should occur before import of `Y`', + }, { + message: '`Z` export should occur after export of `B`', + }, { + message: '`b` export should occur before export of `o`', + }], + }), // Alphabetize with require ...semver.satisfies(eslintPkg.version, '< 3.0.0') ? [] : [ test({ @@ -2772,6 +3105,9 @@ context('TypeScript', function () { // Type-only imports were added in TypeScript ESTree 2.23.0 .filter((parser) => parser !== parsers.TS_OLD) .forEach((parser) => { + const supportsTypeSpecifiers = semver.satisfies(require('@typescript-eslint/parser/package.json').version, '>= 5'); + const supportsImportTypeSpecifiers = parser !== parsers.TS_NEW || supportsTypeSpecifiers; + const supportsExportTypeSpecifiers = parser === parsers.TS_NEW && supportsTypeSpecifiers; const parserConfig = { parser, settings: { @@ -3238,6 +3574,108 @@ context('TypeScript', function () { }, ], }), + // named import order + test({ + code: ` + import { type Z, A } from "./Z"; + import type N, { E, D } from "./Z"; + import type { L, G } from "./Z"; + `, + output: ` + import { A, type Z } from "./Z"; + import type N, { D, E } from "./Z"; + import type { G, L } from "./Z"; + `, + ...parserConfig, + options: [{ + named: true, + alphabetize: { order: 'asc' }, + }], + errors: [ + { message: `\`A\` import should occur before${supportsImportTypeSpecifiers ? ' type' : ''} import of \`Z\`` }, + { message: '`D` import should occur before import of `E`' }, + { message: '`G` import should occur before import of `L`' }, + ], + }), + test({ + code: ` + const { B, /* Hello World */ A } = require("./Z"); + export { B, A } from "./Z"; + `, + output: ` + const { /* Hello World */ A, B } = require("./Z"); + export { A, B } from "./Z"; + `, + ...parserConfig, + options: [{ + named: true, + alphabetize: { order: 'asc' }, + }], + errors: [{ + message: '`A` import should occur before import of `B`', + }, { + message: '`A` export should occur before export of `B`', + }], + }), + + supportsExportTypeSpecifiers ? [ + test({ + code: ` + export { type B, A }; + `, + output: ` + export { A, type B }; + `, + ...parserConfig, + options: [{ + named: { + enabled: true, + types: 'mixed', + }, + alphabetize: { order: 'asc' }, + }], + errors: [{ + message: '`A` export should occur before type export of `B`', + }], + }), + test({ + code: ` + import { type B, A, default as C } from "./Z"; + `, + output: ` + import { A, default as C, type B } from "./Z"; + `, + ...parserConfig, + options: [{ + named: { + import: true, + types: 'types-last', + }, + alphabetize: { order: 'asc' }, + }], + errors: [{ + message: '`B` type import should occur after import of `default`', + }], + }), + test({ + code: ` + export { A, type Z } from "./Z"; + `, + output: ` + export { type Z, A } from "./Z"; + `, + ...parserConfig, + options: [{ + named: { + enabled: true, + types: 'types-first', + }, + }], + errors: [ + { message: '`Z` type export should occur before export of `A`' }, + ], + }), + ] : [], isCoreModule('node:child_process') && isCoreModule('node:fs/promises') ? [ test({ From 894a542bf41577347e4f7714823590af45abadae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20W=C3=B3dkiewicz?= Date: Tue, 6 Aug 2024 09:08:37 +0200 Subject: [PATCH 19/50] [Fix] `no-named-as-default`: Allow using an identifier if the export is both a named and a default export - add tests for #1594 --- CHANGELOG.md | 3 + src/rules/no-named-as-default.js | 63 ++++++++-- tests/files/no-named-as-default/exports.js | 4 + .../misleading-re-exports.js | 2 + .../no-named-as-default/no-default-export.js | 1 + tests/files/no-named-as-default/re-exports.js | 2 + tests/files/no-named-as-default/something.js | 1 + tests/src/rules/no-named-as-default.js | 110 +++++++++++++++--- 8 files changed, 162 insertions(+), 24 deletions(-) create mode 100644 tests/files/no-named-as-default/exports.js create mode 100644 tests/files/no-named-as-default/misleading-re-exports.js create mode 100644 tests/files/no-named-as-default/no-default-export.js create mode 100644 tests/files/no-named-as-default/re-exports.js create mode 100644 tests/files/no-named-as-default/something.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c752b4e..4e7ef808e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed - `ExportMap` / flat config: include `languageOptions` in context ([#3052], thanks [@michaelfaith]) +- [`no-named-as-default`]: Allow using an identifier if the export is both a named and a default export ([#3032], thanks [@akwodkiewicz]) ## [2.30.0] - 2024-09-02 @@ -1139,6 +1140,7 @@ for info on changes for earlier releases. [#3043]: https://github.com/import-js/eslint-plugin-import/pull/3043 [#3036]: https://github.com/import-js/eslint-plugin-import/pull/3036 [#3033]: https://github.com/import-js/eslint-plugin-import/pull/3033 +[#3032]: https://github.com/import-js/eslint-plugin-import/pull/3032 [#3018]: https://github.com/import-js/eslint-plugin-import/pull/3018 [#3012]: https://github.com/import-js/eslint-plugin-import/pull/3012 [#3011]: https://github.com/import-js/eslint-plugin-import/pull/3011 @@ -1726,6 +1728,7 @@ for info on changes for earlier releases. [@AdriAt360]: https://github.com/AdriAt360 [@ai]: https://github.com/ai [@aks-]: https://github.com/aks- +[@akwodkiewicz]: https://github.com/akwodkiewicz [@aladdin-add]: https://github.com/aladdin-add [@alex-page]: https://github.com/alex-page [@alexgorbatchev]: https://github.com/alexgorbatchev diff --git a/src/rules/no-named-as-default.js b/src/rules/no-named-as-default.js index c5adc7afe..dacd294f4 100644 --- a/src/rules/no-named-as-default.js +++ b/src/rules/no-named-as-default.js @@ -15,28 +15,71 @@ module.exports = { create(context) { function checkDefault(nameKey, defaultSpecifier) { + /** + * For ImportDefaultSpecifier we're interested in the "local" name (`foo` for `import {bar as foo} ...`) + * For ExportDefaultSpecifier we're interested in the "exported" name (`foo` for `export {bar as foo} ...`) + */ + const analyzedName = defaultSpecifier[nameKey].name; + // #566: default is a valid specifier - if (defaultSpecifier[nameKey].name === 'default') { return; } + if (analyzedName === 'default') { return; } const declaration = importDeclaration(context, defaultSpecifier); + /** @type {import('../exportMap').default | null} */ + const importedModule = ExportMapBuilder.get(declaration.source.value, context); + if (importedModule == null) { return; } - const imports = ExportMapBuilder.get(declaration.source.value, context); - if (imports == null) { return; } + if (importedModule.errors.length > 0) { + importedModule.reportErrors(context, declaration); + return; + } - if (imports.errors.length) { - imports.reportErrors(context, declaration); + if (!importedModule.hasDefault) { + // The rule is triggered for default imports/exports, so if the imported module has no default + // this means we're dealing with incorrect source code anyway return; } - if (imports.has('default') && imports.has(defaultSpecifier[nameKey].name)) { + if (!importedModule.has(analyzedName)) { + // The name used locally for the default import was not even used in the imported module. + return; + } - context.report( - defaultSpecifier, - `Using exported name '${defaultSpecifier[nameKey].name}' as identifier for default export.`, - ); + /** + * FIXME: We can verify if a default and a named export are pointing to the same symbol only + * if they are both `reexports`. In case one of the symbols is not a re-export, but defined + * in the file, the ExportMap structure has no info about what actually is being exported -- + * the value in the `namespace` Map is an empty object. + * + * To solve this, it would require not relying on the ExportMap, but on some other way of + * accessing the imported module and its exported values. + * + * Additionally, although `ExportMap.get` is a unified way to get info from both `reexports` + * and `namespace` maps, it does not return valid output we need here, and I think this is + * related to the "cycle safeguards" in the `get` function. + */ + if (importedModule.reexports.has(analyzedName) && importedModule.reexports.has('default')) { + const thingImportedWithNamedImport = importedModule.reexports.get(analyzedName).getImport(); + const thingImportedWithDefaultImport = importedModule.reexports.get('default').getImport(); + + // Case: both imports point to the same file and they both refer to the same symbol in this file. + if ( + thingImportedWithNamedImport.path === thingImportedWithDefaultImport.path + && thingImportedWithNamedImport.local === thingImportedWithDefaultImport.local + ) { + // #1594: the imported module exports the same thing via a default export and a named export + return; + } } + + context.report( + defaultSpecifier, + `Using exported name '${defaultSpecifier[nameKey].name}' as identifier for default ${nameKey === 'local' ? `import` : `export`}.`, + ); + } + return { ImportDefaultSpecifier: checkDefault.bind(null, 'local'), ExportDefaultSpecifier: checkDefault.bind(null, 'exported'), diff --git a/tests/files/no-named-as-default/exports.js b/tests/files/no-named-as-default/exports.js new file mode 100644 index 000000000..62402634f --- /dev/null +++ b/tests/files/no-named-as-default/exports.js @@ -0,0 +1,4 @@ +const variable = 1; + +export { variable }; +export default variable; diff --git a/tests/files/no-named-as-default/misleading-re-exports.js b/tests/files/no-named-as-default/misleading-re-exports.js new file mode 100644 index 000000000..8d36a0866 --- /dev/null +++ b/tests/files/no-named-as-default/misleading-re-exports.js @@ -0,0 +1,2 @@ +export { variable as something } from './exports'; +export { something as default } from './something'; diff --git a/tests/files/no-named-as-default/no-default-export.js b/tests/files/no-named-as-default/no-default-export.js new file mode 100644 index 000000000..db3074797 --- /dev/null +++ b/tests/files/no-named-as-default/no-default-export.js @@ -0,0 +1 @@ +export const foobar = 4; diff --git a/tests/files/no-named-as-default/re-exports.js b/tests/files/no-named-as-default/re-exports.js new file mode 100644 index 000000000..20306c182 --- /dev/null +++ b/tests/files/no-named-as-default/re-exports.js @@ -0,0 +1,2 @@ +export { something as default } from "./something"; +export { something } from "./something"; diff --git a/tests/files/no-named-as-default/something.js b/tests/files/no-named-as-default/something.js new file mode 100644 index 000000000..d8fd6851b --- /dev/null +++ b/tests/files/no-named-as-default/something.js @@ -0,0 +1 @@ +export const something = 42; diff --git a/tests/src/rules/no-named-as-default.js b/tests/src/rules/no-named-as-default.js index c6646a4f0..7fcd6e4f7 100644 --- a/tests/src/rules/no-named-as-default.js +++ b/tests/src/rules/no-named-as-default.js @@ -12,14 +12,20 @@ ruleTester.run('no-named-as-default', rule, { test({ code: 'import bar, { foo } from "./empty-folder";' }), // es7 - test({ code: 'export bar, { foo } from "./bar";', - parser: parsers.BABEL_OLD }), - test({ code: 'export bar from "./bar";', - parser: parsers.BABEL_OLD }), + test({ + code: 'export bar, { foo } from "./bar";', + parser: parsers.BABEL_OLD, + }), + test({ + code: 'export bar from "./bar";', + parser: parsers.BABEL_OLD, + }), // #566: don't false-positive on `default` itself - test({ code: 'export default from "./bar";', - parser: parsers.BABEL_OLD }), + test({ + code: 'export default from "./bar";', + parser: parsers.BABEL_OLD, + }), // es2022: Arbitrary module namespae identifier names testVersion('>= 8.7', () => ({ @@ -27,6 +33,48 @@ ruleTester.run('no-named-as-default', rule, { parserOptions: { ecmaVersion: 2022 }, })), + // #1594: Allow importing as default if object is exported both as default and named + test({ code: 'import something from "./no-named-as-default/re-exports.js";' }), + test({ + code: 'import { something } from "./no-named-as-default/re-exports.js";', + }), + test({ + code: 'import myOwnNameForVariable from "./no-named-as-default/exports.js";', + }), + test({ + code: 'import { variable } from "./no-named-as-default/exports.js";', + }), + test({ + code: 'import variable from "./no-named-as-default/misleading-re-exports.js";', + }), + test({ + // incorrect import + code: 'import foobar from "./no-named-as-default/no-default-export.js";', + }), + // same tests, but for exports + test({ + code: 'export something from "./no-named-as-default/re-exports.js";', + parser: parsers.BABEL_OLD, + }), + test({ + code: 'export { something } from "./no-named-as-default/re-exports.js";', + }), + test({ + code: 'export myOwnNameForVariable from "./no-named-as-default/exports.js";', + parser: parsers.BABEL_OLD, + }), + test({ + code: 'export { variable } from "./no-named-as-default/exports.js";', + }), + test({ + code: 'export variable from "./no-named-as-default/misleading-re-exports.js";', + parser: parsers.BABEL_OLD, + }), + test({ + code: 'export foobar from "./no-named-as-default/no-default-export.js";', + parser: parsers.BABEL_OLD, + }), + ...SYNTAX_CASES, ), @@ -34,13 +82,17 @@ ruleTester.run('no-named-as-default', rule, { test({ code: 'import foo from "./bar";', errors: [{ - message: 'Using exported name \'foo\' as identifier for default export.', - type: 'ImportDefaultSpecifier' }] }), + message: 'Using exported name \'foo\' as identifier for default import.', + type: 'ImportDefaultSpecifier', + }], + }), test({ code: 'import foo, { foo as bar } from "./bar";', errors: [{ - message: 'Using exported name \'foo\' as identifier for default export.', - type: 'ImportDefaultSpecifier' }] }), + message: 'Using exported name \'foo\' as identifier for default import.', + type: 'ImportDefaultSpecifier', + }], + }), // es7 test({ @@ -48,13 +100,17 @@ ruleTester.run('no-named-as-default', rule, { parser: parsers.BABEL_OLD, errors: [{ message: 'Using exported name \'foo\' as identifier for default export.', - type: 'ExportDefaultSpecifier' }] }), + type: 'ExportDefaultSpecifier', + }], + }), test({ code: 'export foo, { foo as bar } from "./bar";', parser: parsers.BABEL_OLD, errors: [{ message: 'Using exported name \'foo\' as identifier for default export.', - type: 'ExportDefaultSpecifier' }] }), + type: 'ExportDefaultSpecifier', + }], + }), test({ code: 'import foo from "./malformed.js"', @@ -68,7 +124,7 @@ ruleTester.run('no-named-as-default', rule, { testVersion('>= 8.7', () => ({ code: 'import foo from "./export-default-string-and-named"', errors: [{ - message: 'Using exported name \'foo\' as identifier for default export.', + message: 'Using exported name \'foo\' as identifier for default import.', type: 'ImportDefaultSpecifier', }], parserOptions: { ecmaVersion: 2022 }, @@ -76,10 +132,36 @@ ruleTester.run('no-named-as-default', rule, { testVersion('>= 8.7', () => ({ code: 'import foo, { foo as bar } from "./export-default-string-and-named"', errors: [{ - message: 'Using exported name \'foo\' as identifier for default export.', + message: 'Using exported name \'foo\' as identifier for default import.', type: 'ImportDefaultSpecifier', }], parserOptions: { ecmaVersion: 2022 }, })), + + // #1594: Allow importing as default if object is exported both as default and named + test({ + code: 'import something from "./no-named-as-default/misleading-re-exports.js";', + parser: parsers.BABEL_OLD, + errors: [{ + message: 'Using exported name \'something\' as identifier for default import.', + type: 'ImportDefaultSpecifier', + }], + }), + // The only cases that are not covered by the fix + test({ + code: 'import variable from "./no-named-as-default/exports.js";', + errors: [{ + message: 'Using exported name \'variable\' as identifier for default import.', + type: 'ImportDefaultSpecifier', + }], + }), + test({ + code: 'export variable from "./no-named-as-default/exports.js";', + parser: parsers.BABEL_OLD, + errors: [{ + message: 'Using exported name \'variable\' as identifier for default export.', + type: 'ExportDefaultSpecifier', + }], + }), ), }); From bd8a2367de78cdbcf1f6cc5a76d46917872c0e31 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Tue, 10 Sep 2024 07:52:43 +1200 Subject: [PATCH 20/50] [utils] [fix] `parse`: remove unneeded extra backticks --- utils/CHANGELOG.md | 5 +++++ utils/parse.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index 6b2a5e5e1..ca733eee0 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +### Fixed +- `parse`: remove unneeded extra backticks ([#3057], thanks [@G-Rath]) + ## v2.11.0 - 2024-09-05 ### New @@ -165,6 +168,7 @@ Yanked due to critical issue with cache key resulting from #839. ### Fixed - `unambiguous.test()` regex is now properly in multiline mode +[#3057]: https://github.com/import-js/eslint-plugin-import/pull/3057 [#3049]: https://github.com/import-js/eslint-plugin-import/pull/3049 [#3039]: https://github.com/import-js/eslint-plugin-import/pull/3039 [#3018]: https://github.com/import-js/eslint-plugin-import/pull/3018 @@ -204,6 +208,7 @@ Yanked due to critical issue with cache key resulting from #839. [@brettz9]: https://github.com/brettz9 [@christophercurrie]: https://github.com/christophercurrie [@DMartens]: https://github.com/DMartens +[@G-Rath]: https://github.com/G-Rath [@hulkish]: https://github.com/hulkish [@Hypnosphi]: https://github.com/Hypnosphi [@iamnapo]: https://github.com/iamnapo diff --git a/utils/parse.js b/utils/parse.js index 75d527b00..21a443eca 100644 --- a/utils/parse.js +++ b/utils/parse.js @@ -161,7 +161,7 @@ exports.default = function parse(path, content, context) { if (!ast || typeof ast !== 'object') { console.warn( // Can only be invalid for custom parser per imports/parser - '`parseForESLint` from parser `' + (typeof parserOrPath === 'string' ? parserOrPath : '`context.languageOptions.parser`') + '` is invalid and will just be ignored' + '`parseForESLint` from parser `' + (typeof parserOrPath === 'string' ? parserOrPath : 'context.languageOptions.parser') + '` is invalid and will just be ignored' ); } else { // @ts-expect-error TODO: FIXME From b5023a45ad0c757257fdfc4a7ce393beaa26f773 Mon Sep 17 00:00:00 2001 From: Joshua O'Brien Date: Fri, 20 Sep 2024 18:37:04 +1000 Subject: [PATCH 21/50] [Docs] `no-relative-packages`: fix typo --- CHANGELOG.md | 5 +++++ docs/rules/no-relative-packages.md | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e7ef808e..32899988a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - `ExportMap` / flat config: include `languageOptions` in context ([#3052], thanks [@michaelfaith]) - [`no-named-as-default`]: Allow using an identifier if the export is both a named and a default export ([#3032], thanks [@akwodkiewicz]) +### Changed +- [Docs] [`no-relative-packages`]: fix typo ([#3066], thanks [@joshuaobrien]) + ## [2.30.0] - 2024-09-02 ### Added @@ -1136,6 +1139,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#3066]: https://github.com/import-js/eslint-plugin-import/pull/3066 [#3052]: https://github.com/import-js/eslint-plugin-import/pull/3052 [#3043]: https://github.com/import-js/eslint-plugin-import/pull/3043 [#3036]: https://github.com/import-js/eslint-plugin-import/pull/3036 @@ -1840,6 +1844,7 @@ for info on changes for earlier releases. [@johnthagen]: https://github.com/johnthagen [@jonboiser]: https://github.com/jonboiser [@josh]: https://github.com/josh +[@joshuaobrien]: https://github.com/joshuaobrien [@JounQin]: https://github.com/JounQin [@jquense]: https://github.com/jquense [@jseminck]: https://github.com/jseminck diff --git a/docs/rules/no-relative-packages.md b/docs/rules/no-relative-packages.md index 4014ed985..ed724a9eb 100644 --- a/docs/rules/no-relative-packages.md +++ b/docs/rules/no-relative-packages.md @@ -6,8 +6,7 @@ Use this rule to prevent importing packages through relative paths. -It's useful in Yarn/Lerna workspaces, were it's possible to import a sibling -package using `../package` relative path, while direct `package` is the correct one. +It's useful in Yarn/Lerna workspaces, where it's possible to import a sibling package using `../package` relative path, while direct `package` is the correct one. ## Examples From 1da5e2b6c273f572b08f9de797436efc2fb8e59c Mon Sep 17 00:00:00 2001 From: michael faith Date: Sun, 15 Sep 2024 10:10:47 -0500 Subject: [PATCH 22/50] [utils] [fix] `parse`: espree parser isn't working with flat config This change addresses an issue with using the espree parser with flat config. `keysFromParser` is responsible for finding the `visitorKeys` from a combination of the parser instance, parserPath, and the results of a call to `parseForESLint`. When using flat config, the `parserPath` will not be on the context object., and there are no results from `parseForESLint`. The espree parser _does_ provide visitor keys as a prop on the parser itself. However, this logic was only returning it when it found that "espree" was somewhere in the `parserPath` (which doesn't exist on flat config). I changed it so that it first does the check for the old-school babel parser using parser path, and then just checks the parser instance for the presence of `VisitorKeys`, rather than using parserPath at all. The reason for the re-order is that if the old babel-eslint parser, for some reason has `VisitorKeys`, having the new condition first, would change the behavior. --- .nycrc | 3 ++- examples/flat/eslint.config.mjs | 1 + examples/flat/src/depth-zero.js | 3 +++ examples/flat/src/es6/depth-one-dynamic.js | 3 +++ examples/legacy/.eslintrc.cjs | 1 + examples/legacy/src/depth-zero.js | 3 +++ examples/legacy/src/es6/depth-one-dynamic.js | 3 +++ utils/CHANGELOG.md | 2 ++ utils/parse.js | 12 +++++++++--- 9 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 examples/flat/src/depth-zero.js create mode 100644 examples/flat/src/es6/depth-one-dynamic.js create mode 100644 examples/legacy/src/depth-zero.js create mode 100644 examples/legacy/src/es6/depth-one-dynamic.js diff --git a/.nycrc b/.nycrc index 5d75e2157..c5396cb18 100644 --- a/.nycrc +++ b/.nycrc @@ -14,6 +14,7 @@ "resolvers/*/test", "scripts", "memo-parser", - "lib" + "lib", + "examples" ] } diff --git a/examples/flat/eslint.config.mjs b/examples/flat/eslint.config.mjs index 370514a65..143265265 100644 --- a/examples/flat/eslint.config.mjs +++ b/examples/flat/eslint.config.mjs @@ -20,6 +20,7 @@ export default [ 'import/no-dynamic-require': 'warn', 'import/no-nodejs-modules': 'warn', 'import/no-unused-modules': ['warn', { unusedExports: true }], + 'import/no-cycle': 'warn', }, }, ]; diff --git a/examples/flat/src/depth-zero.js b/examples/flat/src/depth-zero.js new file mode 100644 index 000000000..8cfde9979 --- /dev/null +++ b/examples/flat/src/depth-zero.js @@ -0,0 +1,3 @@ +import { foo } from "./es6/depth-one-dynamic"; + +foo(); diff --git a/examples/flat/src/es6/depth-one-dynamic.js b/examples/flat/src/es6/depth-one-dynamic.js new file mode 100644 index 000000000..ca129fd62 --- /dev/null +++ b/examples/flat/src/es6/depth-one-dynamic.js @@ -0,0 +1,3 @@ +export function foo() {} + +export const bar = () => import("../depth-zero").then(({foo}) => foo); diff --git a/examples/legacy/.eslintrc.cjs b/examples/legacy/.eslintrc.cjs index e3cec097f..90e065c9d 100644 --- a/examples/legacy/.eslintrc.cjs +++ b/examples/legacy/.eslintrc.cjs @@ -20,5 +20,6 @@ module.exports = { 'import/no-dynamic-require': 'warn', 'import/no-nodejs-modules': 'warn', 'import/no-unused-modules': ['warn', { unusedExports: true }], + 'import/no-cycle': 'warn', }, }; diff --git a/examples/legacy/src/depth-zero.js b/examples/legacy/src/depth-zero.js new file mode 100644 index 000000000..8cfde9979 --- /dev/null +++ b/examples/legacy/src/depth-zero.js @@ -0,0 +1,3 @@ +import { foo } from "./es6/depth-one-dynamic"; + +foo(); diff --git a/examples/legacy/src/es6/depth-one-dynamic.js b/examples/legacy/src/es6/depth-one-dynamic.js new file mode 100644 index 000000000..cda7091cd --- /dev/null +++ b/examples/legacy/src/es6/depth-one-dynamic.js @@ -0,0 +1,3 @@ +export function foo() {} + +export const bar = () => import("../depth-zero").then(({ foo }) => foo); diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index ca733eee0..94fc20f7d 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -7,6 +7,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed - `parse`: remove unneeded extra backticks ([#3057], thanks [@G-Rath]) +- `parse`: espree parser isn't working with flat config ([#3061], thanks [@michaelfaith]) ## v2.11.0 - 2024-09-05 @@ -168,6 +169,7 @@ Yanked due to critical issue with cache key resulting from #839. ### Fixed - `unambiguous.test()` regex is now properly in multiline mode +[#3061]: https://github.com/import-js/eslint-plugin-import/pull/3061 [#3057]: https://github.com/import-js/eslint-plugin-import/pull/3057 [#3049]: https://github.com/import-js/eslint-plugin-import/pull/3049 [#3039]: https://github.com/import-js/eslint-plugin-import/pull/3039 diff --git a/utils/parse.js b/utils/parse.js index 21a443eca..a41937166 100644 --- a/utils/parse.js +++ b/utils/parse.js @@ -29,12 +29,18 @@ function keysFromParser(parserPath, parserInstance, parsedResult) { if (parsedResult && parsedResult.visitorKeys) { return parsedResult.visitorKeys; } - if (typeof parserPath === 'string' && (/.*espree.*/).test(parserPath)) { - return parserInstance.VisitorKeys; - } + // The old babel parser doesn't have a `parseForESLint` eslint function, so we don't end + // up with a `parsedResult` here. It also doesn't expose the visitor keys on the parser itself, + // so we have to try and infer the visitor-keys module from the parserPath. + // This is NOT supported in flat config! if (typeof parserPath === 'string' && (/.*babel-eslint.*/).test(parserPath)) { return getBabelEslintVisitorKeys(parserPath); } + // The espree parser doesn't have the `parseForESLint` function, so we don't ended up with a + // `parsedResult` here, but it does expose the visitor keys on the parser instance that we can use. + if (parserInstance && parserInstance.VisitorKeys) { + return parserInstance.VisitorKeys; + } return null; } From 353d94114b670acbbf02a7718abd9bde172d414d Mon Sep 17 00:00:00 2001 From: michael faith Date: Wed, 18 Sep 2024 17:01:51 -0500 Subject: [PATCH 23/50] [utils] [fix] `parse`: add `ecmaVersion` and `sourceType` to `parserOptions` This change adds `ecmaVersion` and `sourceType` to the options we're passing to the parsers, if they're present on `languageOptions` (which would only be the case for flat config). --- tests/src/core/parse.js | 14 ++++++++++++++ utils/CHANGELOG.md | 1 + utils/parse.js | 16 ++++++++++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/src/core/parse.js b/tests/src/core/parse.js index 275b93982..b21326890 100644 --- a/tests/src/core/parse.js +++ b/tests/src/core/parse.js @@ -138,4 +138,18 @@ describe('parse(content, { settings, ecmaFeatures })', function () { parseStubParser.parse = parseSpy; expect(parse.bind(null, path, content, { settings: {}, parserPath: 'espree', languageOptions: { parserOptions: { sourceType: 'module', ecmaVersion: 2015, ecmaFeatures: { jsx: true } } }, parserOptions: { sourceType: 'script' } })).not.to.throw(Error); }); + + it('passes ecmaVersion and sourceType from languageOptions to parser', () => { + const parseSpy = sinon.spy(); + const languageOptions = { ecmaVersion: 'latest', sourceType: 'module', parserOptions: { ecmaFeatures: { jsx: true } } }; + parseStubParser.parse = parseSpy; + parse(path, content, { settings: {}, parserPath: parseStubParserPath, languageOptions }); + expect(parseSpy.args[0][1], 'custom parser to clone the parserOptions object').to.not.equal(languageOptions); + expect(parseSpy.args[0][1], 'custom parser to get ecmaFeatures in parserOptions which is a clone of ecmaFeatures passed in') + .to.have.property('ecmaFeatures') + .that.is.eql(languageOptions.parserOptions.ecmaFeatures) + .and.is.not.equal(languageOptions.parserOptions.ecmaFeatures); + expect(parseSpy.args[0][1], 'custom parser to get ecmaVersion in parserOptions from languageOptions').to.have.property('ecmaVersion', languageOptions.ecmaVersion); + expect(parseSpy.args[0][1], 'custom parser to get sourceType in parserOptions from languageOptions').to.have.property('sourceType', languageOptions.sourceType); + }); }); diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index 94fc20f7d..704a282da 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -8,6 +8,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed - `parse`: remove unneeded extra backticks ([#3057], thanks [@G-Rath]) - `parse`: espree parser isn't working with flat config ([#3061], thanks [@michaelfaith]) +- `parse`: add `ecmaVersion` and `sourceType` to `parserOptions` ([#3061], thanks [@michaelfaith]) ## v2.11.0 - 2024-09-05 diff --git a/utils/parse.js b/utils/parse.js index a41937166..03022ac40 100644 --- a/utils/parse.js +++ b/utils/parse.js @@ -36,7 +36,7 @@ function keysFromParser(parserPath, parserInstance, parsedResult) { if (typeof parserPath === 'string' && (/.*babel-eslint.*/).test(parserPath)) { return getBabelEslintVisitorKeys(parserPath); } - // The espree parser doesn't have the `parseForESLint` function, so we don't ended up with a + // The espree parser doesn't have the `parseForESLint` function, so we don't end up with a // `parsedResult` here, but it does expose the visitor keys on the parser instance that we can use. if (parserInstance && parserInstance.VisitorKeys) { return parserInstance.VisitorKeys; @@ -113,7 +113,8 @@ exports.default = function parse(path, content, context) { if (context == null) { throw new Error('need context to parse properly'); } // ESLint in "flat" mode only sets context.languageOptions.parserOptions - let parserOptions = context.languageOptions && context.languageOptions.parserOptions || context.parserOptions; + const languageOptions = context.languageOptions; + let parserOptions = languageOptions && languageOptions.parserOptions || context.parserOptions; const parserOrPath = getParser(path, context); if (!parserOrPath) { throw new Error('parserPath or languageOptions.parser is required!'); } @@ -144,6 +145,17 @@ exports.default = function parse(path, content, context) { delete parserOptions.project; delete parserOptions.projects; + // If this is a flat config, we need to add ecmaVersion and sourceType (if present) from languageOptions + if (languageOptions && languageOptions.ecmaVersion) { + parserOptions.ecmaVersion = languageOptions.ecmaVersion; + } + if (languageOptions && languageOptions.sourceType) { + // @ts-expect-error languageOptions is from the flatConfig Linter type in 8.57 while parserOptions is not. + // Non-flat config parserOptions.sourceType doesn't have "commonjs" in the type. Once upgraded to v9 types, + // they'll be the same and this expect-error should be removed. + parserOptions.sourceType = languageOptions.sourceType; + } + // require the parser relative to the main module (i.e., ESLint) const parser = typeof parserOrPath === 'string' ? moduleRequire(parserOrPath) : parserOrPath; From 83bb4d27f8b5711d4239e1d811aed8e6874ae7be Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 23 Sep 2024 15:34:39 -0700 Subject: [PATCH 24/50] [utils] v2.11.1 --- utils/CHANGELOG.md | 2 ++ utils/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index 704a282da..6e71a26f7 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,8 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +## v2.11.1 - 2024-09-23 + ### Fixed - `parse`: remove unneeded extra backticks ([#3057], thanks [@G-Rath]) - `parse`: espree parser isn't working with flat config ([#3061], thanks [@michaelfaith]) diff --git a/utils/package.json b/utils/package.json index df63ac168..709142faf 100644 --- a/utils/package.json +++ b/utils/package.json @@ -1,6 +1,6 @@ { "name": "eslint-module-utils", - "version": "2.11.0", + "version": "2.11.1", "description": "Core utilities to support eslint-plugin-import and other module-related plugins.", "engines": { "node": ">=4" From cbc4fba512f4077b221b8e8ac3893dc464ba138e Mon Sep 17 00:00:00 2001 From: liuxingbaoyu <30521560+liuxingbaoyu@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:01:07 +0800 Subject: [PATCH 25/50] [Fix] `export`: False positive for exported overloaded functions in TS --- CHANGELOG.md | 3 +++ src/rules/export.js | 48 +++++++++++++-------------------------- tests/src/rules/export.js | 26 +++++++++++++++++++-- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32899988a..1dbd46fc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed - `ExportMap` / flat config: include `languageOptions` in context ([#3052], thanks [@michaelfaith]) - [`no-named-as-default`]: Allow using an identifier if the export is both a named and a default export ([#3032], thanks [@akwodkiewicz]) +- [`export`]: False positive for exported overloaded functions in TS ([#3065], thanks [@liuxingbaoyu]) ### Changed - [Docs] [`no-relative-packages`]: fix typo ([#3066], thanks [@joshuaobrien]) @@ -1140,6 +1141,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md [#3066]: https://github.com/import-js/eslint-plugin-import/pull/3066 +[#3065]: https://github.com/import-js/eslint-plugin-import/pull/3065 [#3052]: https://github.com/import-js/eslint-plugin-import/pull/3052 [#3043]: https://github.com/import-js/eslint-plugin-import/pull/3043 [#3036]: https://github.com/import-js/eslint-plugin-import/pull/3036 @@ -1875,6 +1877,7 @@ for info on changes for earlier releases. [@lilling]: https://github.com/lilling [@ljharb]: https://github.com/ljharb [@ljqx]: https://github.com/ljqx +[@liuxingbaoyu]: https://github.com/liuxingbaoyu [@lo1tuma]: https://github.com/lo1tuma [@loganfsmyth]: https://github.com/loganfsmyth [@luczsoma]: https://github.com/luczsoma diff --git a/src/rules/export.js b/src/rules/export.js index 197a0eb51..fbbc39d75 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -2,7 +2,6 @@ import ExportMapBuilder from '../exportMap/builder'; import recursivePatternCapture from '../exportMap/patternCapture'; import docsUrl from '../docsUrl'; import includes from 'array-includes'; -import flatMap from 'array.prototype.flatmap'; /* Notes on TypeScript namespaces aka TSModuleDeclaration: @@ -27,42 +26,25 @@ const rootProgram = 'root'; const tsTypePrefix = 'type:'; /** - * Detect function overloads like: + * remove function overloads like: * ```ts * export function foo(a: number); * export function foo(a: string); - * export function foo(a: number|string) { return a; } * ``` * @param {Set} nodes - * @returns {boolean} */ -function isTypescriptFunctionOverloads(nodes) { - const nodesArr = Array.from(nodes); - - const idents = flatMap( - nodesArr, - (node) => node.declaration && ( - node.declaration.type === 'TSDeclareFunction' // eslint 6+ - || node.declaration.type === 'TSEmptyBodyFunctionDeclaration' // eslint 4-5 - ) - ? node.declaration.id.name - : [], - ); - if (new Set(idents).size !== idents.length) { - return true; - } - - const types = new Set(nodesArr.map((node) => node.parent.type)); - if (!types.has('TSDeclareFunction')) { - return false; - } - if (types.size === 1) { - return true; - } - if (types.size === 2 && types.has('FunctionDeclaration')) { - return true; - } - return false; +function removeTypescriptFunctionOverloads(nodes) { + nodes.forEach((node) => { + const declType = node.type === 'ExportDefaultDeclaration' ? node.declaration.type : node.parent.type; + if ( + // eslint 6+ + declType === 'TSDeclareFunction' + // eslint 4-5 + || declType === 'TSEmptyBodyFunctionDeclaration' + ) { + nodes.delete(node); + } + }); } /** @@ -227,9 +209,11 @@ module.exports = { 'Program:exit'() { for (const [, named] of namespace) { for (const [name, nodes] of named) { + removeTypescriptFunctionOverloads(nodes); + if (nodes.size <= 1) { continue; } - if (isTypescriptFunctionOverloads(nodes) || isTypescriptNamespaceMerging(nodes)) { continue; } + if (isTypescriptNamespaceMerging(nodes)) { continue; } for (const node of nodes) { if (shouldSkipTypescriptNamespace(node, nodes)) { continue; } diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index a7f2bec12..f16a25ecf 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -56,6 +56,15 @@ ruleTester.run('export', rule, { `, parser, })), + getTSParsers().map((parser) => ({ + code: ` + export default function foo(param: string): boolean; + export default function foo(param: string, param1?: number): boolean { + return param && param1; + } + `, + parser, + })), ), invalid: [].concat( @@ -154,6 +163,19 @@ ruleTester.run('export', rule, { ecmaVersion: 2022, }, })), + + getTSParsers().map((parser) => ({ + code: ` + export default function a(): void; + export default function a() {} + export { x as default }; + `, + errors: [ + 'Multiple default exports.', + 'Multiple default exports.', + ], + parser, + })), ), }); @@ -510,7 +532,7 @@ context('TypeScript', function () { }), test({ code: ` - export function Foo(); + export function Foo() { }; export class Foo { } export namespace Foo { } `, @@ -529,7 +551,7 @@ context('TypeScript', function () { test({ code: ` export const Foo = 'bar'; - export function Foo(); + export function Foo() { }; export namespace Foo { } `, errors: [ From 2b9bedb2783f9b850fb1c2b1c1561e99dde649e5 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 23 Sep 2024 22:30:42 -0700 Subject: [PATCH 26/50] [Refactor] add some more type info; switch for-ofs to forEaches --- src/rules/consistent-type-specifier-style.js | 19 ++++++- src/rules/no-cycle.js | 4 +- src/rules/no-duplicates.js | 34 +++++++---- src/rules/no-mutable-exports.js | 59 ++++++++++---------- 4 files changed, 72 insertions(+), 44 deletions(-) diff --git a/src/rules/consistent-type-specifier-style.js b/src/rules/consistent-type-specifier-style.js index ee5ff9fbc..84c33ecd8 100644 --- a/src/rules/consistent-type-specifier-style.js +++ b/src/rules/consistent-type-specifier-style.js @@ -6,6 +6,12 @@ function isComma(token) { return token.type === 'Punctuator' && token.value === ','; } +/** + * @param {import('eslint').Rule.Fix[]} fixes + * @param {import('eslint').Rule.RuleFixer} fixer + * @param {import('eslint').SourceCode.SourceCode} sourceCode + * @param {(ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier)[]} specifiers + * */ function removeSpecifiers(fixes, fixer, sourceCode, specifiers) { for (const specifier of specifiers) { // remove the trailing comma @@ -17,6 +23,7 @@ function removeSpecifiers(fixes, fixer, sourceCode, specifiers) { } } +/** @type {(node: import('estree').Node, sourceCode: import('eslint').SourceCode.SourceCode, specifiers: (ImportSpecifier | ImportNamespaceSpecifier)[], kind: 'type' | 'typeof') => string} */ function getImportText( node, sourceCode, @@ -38,6 +45,7 @@ function getImportText( return `import ${kind} {${names.join(', ')}} from ${sourceString};`; } +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { type: 'suggestion', @@ -102,6 +110,7 @@ module.exports = { // prefer-top-level return { + /** @param {import('estree').ImportDeclaration} node */ ImportDeclaration(node) { if ( // already top-level is valid @@ -120,9 +129,13 @@ module.exports = { return; } + /** @type {typeof node.specifiers} */ const typeSpecifiers = []; + /** @type {typeof node.specifiers} */ const typeofSpecifiers = []; + /** @type {typeof node.specifiers} */ const valueSpecifiers = []; + /** @type {typeof node.specifiers[number]} */ let defaultSpecifier = null; for (const specifier of node.specifiers) { if (specifier.type === 'ImportDefaultSpecifier') { @@ -144,6 +157,7 @@ module.exports = { const newImports = `${typeImport}\n${typeofImport}`.trim(); if (typeSpecifiers.length + typeofSpecifiers.length === node.specifiers.length) { + /** @type {('type' | 'typeof')[]} */ // all specifiers have inline specifiers - so we replace the entire import const kind = [].concat( typeSpecifiers.length > 0 ? 'type' : [], @@ -162,7 +176,7 @@ module.exports = { }); } else { // remove specific specifiers and insert new imports for them - for (const specifier of typeSpecifiers.concat(typeofSpecifiers)) { + typeSpecifiers.concat(typeofSpecifiers).forEach((specifier) => { context.report({ node: specifier, message: 'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.', @@ -170,6 +184,7 @@ module.exports = { kind: specifier.importKind, }, fix(fixer) { + /** @type {import('eslint').Rule.Fix[]} */ const fixes = []; // if there are no value specifiers, then the other report fixer will be called, not this one @@ -215,7 +230,7 @@ module.exports = { ); }, }); - } + }); } }, }; diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index d7c748d80..713503d9f 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -152,9 +152,9 @@ module.exports = { */ if (path === myPath && toTraverse.length > 0) { return true; } if (route.length + 1 < maxDepth) { - for (const { source } of toTraverse) { + toTraverse.forEach(({ source }) => { untraversed.push({ mget: getter, route: route.concat(source) }); - } + }); } } } diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index 32557802f..b8c8d848c 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -74,6 +74,7 @@ function hasProblematicComments(node, sourceCode) { ); } +/** @type {(first: import('estree').ImportDeclaration, rest: import('estree').ImportDeclaration[], sourceCode: import('eslint').SourceCode.SourceCode, context: import('eslint').Rule.RuleContext) => import('eslint').Rule.ReportFixer | undefined} */ function getFix(first, rest, sourceCode, context) { // Sorry ESLint <= 3 users, no autofix for you. Autofixing duplicate imports // requires multiple `fixer.whatever()` calls in the `fix`: We both need to @@ -123,7 +124,7 @@ function getFix(first, rest, sourceCode, context) { isEmpty: !hasSpecifiers(node), }; }) - .filter(Boolean); + .filter((x) => !!x); const unnecessaryImports = restWithoutComments.filter((node) => !hasSpecifiers(node) && !hasNamespace(node) @@ -139,6 +140,7 @@ function getFix(first, rest, sourceCode, context) { return undefined; } + /** @type {import('eslint').Rule.ReportFixer} */ return (fixer) => { const tokens = sourceCode.getTokens(first); const openBrace = tokens.find((token) => isPunctuator(token, '{')); @@ -185,6 +187,7 @@ function getFix(first, rest, sourceCode, context) { ['', !firstHasTrailingComma && !firstIsEmpty, firstExistingIdentifiers], ); + /** @type {import('eslint').Rule.Fix[]} */ const fixes = []; if (shouldAddSpecifiers && preferInline && first.importKind === 'type') { @@ -228,7 +231,7 @@ function getFix(first, rest, sourceCode, context) { } // Remove imports whose specifiers have been moved into the first import. - for (const specifier of specifiers) { + specifiers.forEach((specifier) => { const importNode = specifier.importNode; fixes.push(fixer.remove(importNode)); @@ -237,12 +240,12 @@ function getFix(first, rest, sourceCode, context) { if (charAfterImport === '\n') { fixes.push(fixer.removeRange(charAfterImportRange)); } - } + }); // Remove imports whose default import has been moved to the first import, // and side-effect-only imports that are unnecessary due to the first // import. - for (const node of unnecessaryImports) { + unnecessaryImports.forEach((node) => { fixes.push(fixer.remove(node)); const charAfterImportRange = [node.range[1], node.range[1] + 1]; @@ -250,12 +253,13 @@ function getFix(first, rest, sourceCode, context) { if (charAfterImport === '\n') { fixes.push(fixer.removeRange(charAfterImportRange)); } - } + }); return fixes; }; } +/** @type {(imported: Map, context: import('eslint').Rule.RuleContext) => void} */ function checkImports(imported, context) { for (const [module, nodes] of imported.entries()) { if (nodes.length > 1) { @@ -270,16 +274,17 @@ function checkImports(imported, context) { fix, // Attach the autofix (if any) to the first import. }); - for (const node of rest) { + rest.forEach((node) => { context.report({ node: node.source, message, }); - } + }); } } } +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { type: 'problem', @@ -305,10 +310,13 @@ module.exports = { ], }, + /** @param {import('eslint').Rule.RuleContext} context */ create(context) { + /** @type {boolean} */ // Prepare the resolver from options. - const considerQueryStringOption = context.options[0] - && context.options[0].considerQueryString; + const considerQueryStringOption = context.options[0] && context.options[0].considerQueryString; + /** @type {boolean} */ + const preferInline = context.options[0] && context.options[0]['prefer-inline']; const defaultResolver = (sourcePath) => resolve(sourcePath, context) || sourcePath; const resolver = considerQueryStringOption ? (sourcePath) => { const parts = sourcePath.match(/^([^?]*)\?(.*)$/); @@ -318,11 +326,14 @@ module.exports = { return `${defaultResolver(parts[1])}?${parts[2]}`; } : defaultResolver; + /** @type {Map, nsImported: Map, defaultTypesImported: Map, namedTypesImported: Map}>} */ const moduleMaps = new Map(); + /** @param {import('estree').ImportDeclaration} n */ + /** @returns {typeof moduleMaps[keyof typeof moduleMaps]} */ function getImportMap(n) { if (!moduleMaps.has(n.parent)) { - moduleMaps.set(n.parent, { + moduleMaps.set(n.parent, /** @type {typeof moduleMaps} */ { imported: new Map(), nsImported: new Map(), defaultTypesImported: new Map(), @@ -330,7 +341,6 @@ module.exports = { }); } const map = moduleMaps.get(n.parent); - const preferInline = context.options[0] && context.options[0]['prefer-inline']; if (!preferInline && n.importKind === 'type') { return n.specifiers.length > 0 && n.specifiers[0].type === 'ImportDefaultSpecifier' ? map.defaultTypesImported : map.namedTypesImported; } @@ -342,7 +352,9 @@ module.exports = { } return { + /** @param {import('estree').ImportDeclaration} n */ ImportDeclaration(n) { + /** @type {string} */ // resolved path will cover aliased duplicates const resolvedPath = resolver(n.source.value); const importMap = getImportMap(n); diff --git a/src/rules/no-mutable-exports.js b/src/rules/no-mutable-exports.js index c3d18b2c9..0a0e128dc 100644 --- a/src/rules/no-mutable-exports.js +++ b/src/rules/no-mutable-exports.js @@ -2,6 +2,7 @@ import { getScope } from 'eslint-module-utils/contextCompat'; import docsUrl from '../docsUrl'; +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { type: 'suggestion', @@ -21,41 +22,41 @@ module.exports = { } } + /** @type {(scope: import('eslint').Scope.Scope, name: string) => void} */ function checkDeclarationsInScope({ variables }, name) { - for (const variable of variables) { - if (variable.name === name) { - for (const def of variable.defs) { - if (def.type === 'Variable' && def.parent) { + variables + .filter((variable) => variable.name === name) + .forEach((variable) => { + variable.defs + .filter((def) => def.type === 'Variable' && def.parent) + .forEach((def) => { checkDeclaration(def.parent); - } - } - } - } - } - - function handleExportDefault(node) { - const scope = getScope(context, node); - - if (node.declaration.name) { - checkDeclarationsInScope(scope, node.declaration.name); - } + }); + }); } - function handleExportNamed(node) { - const scope = getScope(context, node); + return { + /** @param {import('estree').ExportDefaultDeclaration} node */ + ExportDefaultDeclaration(node) { + const scope = getScope(context, node); - if (node.declaration) { - checkDeclaration(node.declaration); - } else if (!node.source) { - for (const specifier of node.specifiers) { - checkDeclarationsInScope(scope, specifier.local.name); + if ('name' in node.declaration && node.declaration.name) { + checkDeclarationsInScope(scope, node.declaration.name); } - } - } - - return { - ExportDefaultDeclaration: handleExportDefault, - ExportNamedDeclaration: handleExportNamed, + }, + + /** @param {import('estree').ExportNamedDeclaration} node */ + ExportNamedDeclaration(node) { + const scope = getScope(context, node); + + if ('declaration' in node && node.declaration) { + checkDeclaration(node.declaration); + } else if (!('source' in node) || !node.source) { + node.specifiers.forEach((specifier) => { + checkDeclarationsInScope(scope, specifier.local.name); + }); + } + }, }; }, }; From c7a05a8068bdd2690f886bfb5c90b56dd81aee92 Mon Sep 17 00:00:00 2001 From: Ivan Rubinson Date: Mon, 23 Sep 2024 18:11:12 +0300 Subject: [PATCH 27/50] [Performance] `no-cycle`: dont scc for each linted file --- CHANGELOG.md | 2 ++ src/scc.js | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dbd46fc7..e40edde5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Changed - [Docs] [`no-relative-packages`]: fix typo ([#3066], thanks [@joshuaobrien]) +- [Performance] [`no-cycle`]: dont scc for each linted file ([#3068], thanks [@soryy708]) ## [2.30.0] - 2024-09-02 @@ -1140,6 +1141,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#3068]: https://github.com/import-js/eslint-plugin-import/pull/3068 [#3066]: https://github.com/import-js/eslint-plugin-import/pull/3066 [#3065]: https://github.com/import-js/eslint-plugin-import/pull/3065 [#3052]: https://github.com/import-js/eslint-plugin-import/pull/3052 diff --git a/src/scc.js b/src/scc.js index 44c818bbe..c2b2c637d 100644 --- a/src/scc.js +++ b/src/scc.js @@ -18,12 +18,18 @@ export default class StronglyConnectedComponentsBuilder { } static for(context) { - const cacheKey = context.cacheKey || hashObject(context).digest('hex'); + const settingsHash = hashObject({ + settings: context.settings, + parserOptions: context.parserOptions, + parserPath: context.parserPath, + }).digest('hex'); + const cacheKey = context.path + settingsHash; if (cache.has(cacheKey)) { return cache.get(cacheKey); } const scc = StronglyConnectedComponentsBuilder.calculate(context); - cache.set(cacheKey, scc); + const visitedFiles = Object.keys(scc); + visitedFiles.forEach((filePath) => cache.set(filePath + settingsHash, scc)); return scc; } From d572fcde70c14b98bace0b48741612f92d08b6bf Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 24 Sep 2024 21:51:15 -0700 Subject: [PATCH 28/50] [Deps] update `eslint-module-utils` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0aef4ccf9..75384155b 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.11.0", + "eslint-module-utils": "^2.11.1", "hasown": "^2.0.2", "is-core-module": "^2.15.1", "is-glob": "^4.0.3", From b4c6e607d6c2d6cdbdce22594c014f1f8e211022 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 12 Apr 2024 14:49:34 +1200 Subject: [PATCH 29/50] [Tests] don't include output when nothing has been changed --- tests/src/rule-tester.js | 3 + tests/src/rules/dynamic-import-chunkname.js | 554 +++++--------------- tests/src/rules/newline-after-import.js | 47 +- tests/src/rules/no-commonjs.js | 47 +- tests/src/rules/no-duplicates.js | 145 ++--- tests/src/rules/no-namespace.js | 16 +- tests/src/rules/order.js | 116 +--- tests/src/rules/unambiguous.js | 6 +- 8 files changed, 251 insertions(+), 683 deletions(-) create mode 100644 tests/src/rule-tester.js diff --git a/tests/src/rule-tester.js b/tests/src/rule-tester.js new file mode 100644 index 000000000..89c584612 --- /dev/null +++ b/tests/src/rule-tester.js @@ -0,0 +1,3 @@ +export function withoutAutofixOutput(test) { + return { ...test, output: test.code }; +} diff --git a/tests/src/rules/dynamic-import-chunkname.js b/tests/src/rules/dynamic-import-chunkname.js index 81e018af7..136219672 100644 --- a/tests/src/rules/dynamic-import-chunkname.js +++ b/tests/src/rules/dynamic-import-chunkname.js @@ -1,5 +1,6 @@ import { SYNTAX_CASES, getTSParsers, parsers } from '../utils'; import { RuleTester } from 'eslint'; +import { withoutAutofixOutput } from '../rule-tester'; import semver from 'semver'; const rule = require('rules/dynamic-import-chunkname'); @@ -423,225 +424,172 @@ ruleTester.run('dynamic-import-chunkname', rule, { ], invalid: [ - { + withoutAutofixOutput({ code: `import( // webpackChunkName: "someModule" 'someModule' )`, options, parser, - output: `import( - // webpackChunkName: "someModule" - 'someModule' - )`, errors: [{ message: nonBlockCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: 'import(\'test\')', options, parser, - output: 'import(\'test\')', errors: [{ message: noLeadingCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: someModule */ 'someModule' )`, options, parser, - output: `import( - /* webpackChunkName: someModule */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: "someModule' */ 'someModule' )`, options, parser, - output: `import( - /* webpackChunkName: "someModule' */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: 'someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackChunkName: 'someModule" */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackChunkName "someModule" */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName:"someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackChunkName:"someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: true */ 'someModule' )`, options, parser, - output: `import( - /* webpackChunkName: true */ - 'someModule' - )`, errors: [{ message: chunkNameFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: "my-module-[id]" */ 'someModule' )`, options, parser, - output: `import( - /* webpackChunkName: "my-module-[id]" */ - 'someModule' - )`, errors: [{ message: chunkNameFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: ["request"] */ 'someModule' )`, options, parser, - output: `import( - /* webpackChunkName: ["request"] */ - 'someModule' - )`, errors: [{ message: chunkNameFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /*webpackChunkName: "someModule"*/ 'someModule' )`, options, parser, - output: `import( - /*webpackChunkName: "someModule"*/ - 'someModule' - )`, errors: [{ message: noPaddingCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName : "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackChunkName : "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: "someModule" ; */ 'someModule' )`, options, parser, - output: `import( - /* webpackChunkName: "someModule" ; */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* totally not webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* totally not webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackPrefetch: true */ /* webpackChunk: "someModule" */ @@ -649,339 +597,253 @@ ruleTester.run('dynamic-import-chunkname', rule, { )`, options, parser, - output: `import( - /* webpackPrefetch: true */ - /* webpackChunk: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackPrefetch: true, webpackChunk: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackPrefetch: true, webpackChunk: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: "someModule123" */ 'someModule' )`, options: pickyCommentOptions, parser, - output: `import( - /* webpackChunkName: "someModule123" */ - 'someModule' - )`, errors: [{ message: pickyChunkNameFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackPrefetch: "module", webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackPrefetch: "module", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackPreload: "module", webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackPreload: "module", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackIgnore: "no", webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackIgnore: "no", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackInclude: "someModule", webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackInclude: "someModule", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackInclude: true, webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackInclude: true, webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackExclude: "someModule", webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackExclude: "someModule", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackExclude: true, webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackExclude: true, webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackMode: "fast", webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackMode: "fast", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackMode: true, webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackMode: true, webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackExports: true, webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackExports: true, webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackExports: /default/, webpackChunkName: "someModule" */ 'someModule' )`, options, parser, - output: `import( - /* webpackExports: /default/, webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `dynamicImport( /* webpackChunkName "someModule" */ 'someModule' )`, options: multipleImportFunctionOptions, - output: `dynamicImport( - /* webpackChunkName "someModule" */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `definitelyNotStaticImport( /* webpackChunkName "someModule" */ 'someModule' )`, options: multipleImportFunctionOptions, - output: `definitelyNotStaticImport( - /* webpackChunkName "someModule" */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `dynamicImport( // webpackChunkName: "someModule" 'someModule' )`, options, - output: `dynamicImport( - // webpackChunkName: "someModule" - 'someModule' - )`, errors: [{ message: nonBlockCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: 'dynamicImport(\'test\')', options, - output: 'dynamicImport(\'test\')', errors: [{ message: noLeadingCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `dynamicImport( /* webpackChunkName: someModule */ 'someModule' )`, options, - output: `dynamicImport( - /* webpackChunkName: someModule */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `dynamicImport( /* webpackChunkName "someModule" */ 'someModule' )`, options, - output: `dynamicImport( - /* webpackChunkName "someModule" */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `dynamicImport( /* webpackChunkName:"someModule" */ 'someModule' )`, options, - output: `dynamicImport( - /* webpackChunkName:"someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `dynamicImport( /* webpackChunkName: "someModule123" */ 'someModule' )`, options: pickyCommentOptions, - output: `dynamicImport( - /* webpackChunkName: "someModule123" */ - 'someModule' - )`, errors: [{ message: pickyChunkNameFormatError, type: 'CallExpression', }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: "someModule" */ /* webpackMode: "eager" */ @@ -989,11 +851,6 @@ ruleTester.run('dynamic-import-chunkname', rule, { )`, options, parser, - output: `import( - /* webpackChunkName: "someModule" */ - /* webpackMode: "eager" */ - 'someModule' - )`, errors: [{ message: eagerModeError, type: 'CallExpression', @@ -1016,7 +873,7 @@ ruleTester.run('dynamic-import-chunkname', rule, { }, ], }], - }, + }), ], }); @@ -1343,177 +1200,136 @@ context('TypeScript', () => { }, ], invalid: [ - { + withoutAutofixOutput({ code: `import( // webpackChunkName: "someModule" 'someModule' )`, options, parser: typescriptParser, - output: `import( - // webpackChunkName: "someModule" - 'someModule' - )`, errors: [{ message: nonBlockCommentError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: 'import(\'test\')', options, parser: typescriptParser, - output: 'import(\'test\')', errors: [{ message: noLeadingCommentError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: someModule */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackChunkName: someModule */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName "someModule' */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackChunkName "someModule' */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName 'someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackChunkName 'someModule" */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackChunkName "someModule" */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName:"someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackChunkName:"someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /*webpackChunkName: "someModule"*/ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /*webpackChunkName: "someModule"*/ - 'someModule' - )`, errors: [{ message: noPaddingCommentError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName : "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackChunkName : "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: "someModule" ; */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackChunkName: "someModule" ; */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* totally not webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* totally not webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: invalidSyntaxCommentError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackPrefetch: true */ /* webpackChunk: "someModule" */ @@ -1521,283 +1337,210 @@ context('TypeScript', () => { )`, options, parser: typescriptParser, - output: `import( - /* webpackPrefetch: true */ - /* webpackChunk: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackPrefetch: true, webpackChunk: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackPrefetch: true, webpackChunk: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: true */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackChunkName: true */ - 'someModule' - )`, errors: [{ message: chunkNameFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: "my-module-[id]" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackChunkName: "my-module-[id]" */ - 'someModule' - )`, errors: [{ message: chunkNameFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: ["request"] */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackChunkName: ["request"] */ - 'someModule' - )`, errors: [{ message: chunkNameFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: "someModule123" */ 'someModule' )`, options: pickyCommentOptions, parser: typescriptParser, - output: `import( - /* webpackChunkName: "someModule123" */ - 'someModule' - )`, errors: [{ message: pickyChunkNameFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackPrefetch: "module", webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackPrefetch: "module", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackPreload: "module", webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackPreload: "module", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackIgnore: "no", webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackIgnore: "no", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackInclude: "someModule", webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackInclude: "someModule", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackInclude: true, webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackInclude: true, webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackExclude: "someModule", webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackExclude: "someModule", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackExclude: true, webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackExclude: true, webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackMode: "fast", webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackMode: "fast", webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackMode: true, webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackMode: true, webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackExports: true, webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackExports: true, webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackExports: /default/, webpackChunkName: "someModule" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackExports: /default/, webpackChunkName: "someModule" */ - 'someModule' - )`, errors: [{ message: commentFormatError, type: nodeType, }], - }, - { + }), + withoutAutofixOutput({ code: `import( /* webpackChunkName: "someModule", webpackMode: "eager" */ 'someModule' )`, options, parser: typescriptParser, - output: `import( - /* webpackChunkName: "someModule", webpackMode: "eager" */ - 'someModule' - )`, errors: [{ message: eagerModeError, type: nodeType, @@ -1818,8 +1561,8 @@ context('TypeScript', () => { }, ], }], - }, - { + }), + withoutAutofixOutput({ code: ` import( /* webpackMode: "eager", webpackChunkName: "someModule" */ @@ -1828,12 +1571,6 @@ context('TypeScript', () => { `, options, parser: typescriptParser, - output: ` - import( - /* webpackMode: "eager", webpackChunkName: "someModule" */ - 'someModule' - ) - `, errors: [{ message: eagerModeError, type: nodeType, @@ -1858,8 +1595,8 @@ context('TypeScript', () => { }, ], }], - }, - { + }), + withoutAutofixOutput({ code: ` import( /* webpackMode: "eager", webpackPrefetch: true, webpackChunkName: "someModule" */ @@ -1868,12 +1605,6 @@ context('TypeScript', () => { `, options, parser: typescriptParser, - output: ` - import( - /* webpackMode: "eager", webpackPrefetch: true, webpackChunkName: "someModule" */ - 'someModule' - ) - `, errors: [{ message: eagerModeError, type: nodeType, @@ -1898,8 +1629,8 @@ context('TypeScript', () => { }, ], }], - }, - { + }), + withoutAutofixOutput({ code: ` import( /* webpackChunkName: "someModule" */ @@ -1909,13 +1640,6 @@ context('TypeScript', () => { `, options, parser: typescriptParser, - output: ` - import( - /* webpackChunkName: "someModule" */ - /* webpackMode: "eager" */ - 'someModule' - ) - `, errors: [{ message: eagerModeError, type: nodeType, @@ -1942,7 +1666,7 @@ context('TypeScript', () => { }, ], }], - }, + }), ], }); }); diff --git a/tests/src/rules/newline-after-import.js b/tests/src/rules/newline-after-import.js index b78e891a3..5c827c0d2 100644 --- a/tests/src/rules/newline-after-import.js +++ b/tests/src/rules/newline-after-import.js @@ -1,4 +1,5 @@ import { RuleTester } from 'eslint'; +import { withoutAutofixOutput } from '../rule-tester'; import flatMap from 'array.prototype.flatmap'; import semver from 'semver'; import { version as tsEslintVersion } from 'typescript-eslint-parser/package.json'; @@ -710,9 +711,8 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }], parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, - { + withoutAutofixOutput({ code: `import foo from 'foo';\n\n\n\nexport default function() {};`, - output: `import foo from 'foo';\n\n\n\nexport default function() {};`, options: [{ count: 2, exactCount: true }], errors: [{ line: 1, @@ -720,10 +720,9 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { message: IMPORT_ERROR_MESSAGE_MULTIPLE(2), }], parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, - }, - { + }), + withoutAutofixOutput({ code: `import foo from 'foo';\n\n\n\n\nexport default function() {};`, - output: `import foo from 'foo';\n\n\n\n\nexport default function() {};`, options: [{ count: 2, exactCount: true }], errors: [{ line: 1, @@ -731,7 +730,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { message: IMPORT_ERROR_MESSAGE_MULTIPLE(2), }], parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, - }, + }), { code: `import foo from 'foo';\n// some random comment\nexport default function() {};`, output: `import foo from 'foo';\n\n// some random comment\nexport default function() {};`, @@ -743,9 +742,8 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }], parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, - { + withoutAutofixOutput({ code: `import foo from 'foo';\n// some random comment\n\n\nexport default function() {};`, - output: `import foo from 'foo';\n// some random comment\n\n\nexport default function() {};`, options: [{ count: 2, exactCount: true }], errors: [{ line: 1, @@ -753,10 +751,9 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { message: IMPORT_ERROR_MESSAGE_MULTIPLE(2), }], parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, - }, - { + }), + withoutAutofixOutput({ code: `import foo from 'foo';\n// some random comment\n\n\n\nexport default function() {};`, - output: `import foo from 'foo';\n// some random comment\n\n\n\nexport default function() {};`, options: [{ count: 2, exactCount: true }], errors: [{ line: 1, @@ -764,7 +761,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { message: IMPORT_ERROR_MESSAGE_MULTIPLE(2), }], parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, - }, + }), { code: `import foo from 'foo';\n// some random comment\nexport default function() {};`, output: `import foo from 'foo';\n\n\n// some random comment\nexport default function() {};`, @@ -787,9 +784,8 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }], parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, - { + withoutAutofixOutput({ code: `import foo from 'foo';\n\n\n\n// some random comment\nexport default function() {};`, - output: `import foo from 'foo';\n\n\n\n// some random comment\nexport default function() {};`, options: [{ count: 2, exactCount: true, considerComments: true }], errors: [{ line: 1, @@ -797,19 +793,12 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { message: IMPORT_ERROR_MESSAGE_MULTIPLE(2), }], parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, - }, - { + }), + withoutAutofixOutput({ code: ` import foo from 'foo'; - // Some random single line comment - var bar = 42; - `, - output: ` - import foo from 'foo'; - - // Some random single line comment var bar = 42; `, @@ -820,7 +809,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }], parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, options: [{ considerComments: true, count: 1, exactCount: true }], - }, + }), { code: `import foo from 'foo';export default function() {};`, output: `import foo from 'foo';\n\nexport default function() {};`, @@ -832,9 +821,8 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }], parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, - { + withoutAutofixOutput({ code: `const foo = require('foo');\n\n\n\nconst bar = function() {};`, - output: `const foo = require('foo');\n\n\n\nconst bar = function() {};`, options: [{ count: 2, exactCount: true }], errors: [{ line: 1, @@ -842,10 +830,9 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { message: REQUIRE_ERROR_MESSAGE_MULTIPLE(2), }], parserOptions: { ecmaVersion: 2015 }, - }, - { + }), + withoutAutofixOutput({ code: `const foo = require('foo');\n\n\n\n// some random comment\nconst bar = function() {};`, - output: `const foo = require('foo');\n\n\n\n// some random comment\nconst bar = function() {};`, options: [{ count: 2, exactCount: true }], errors: [{ line: 1, @@ -853,7 +840,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { message: REQUIRE_ERROR_MESSAGE_MULTIPLE(2), }], parserOptions: { ecmaVersion: 2015 }, - }, + }), { code: `import foo from 'foo';// some random comment\nexport default function() {};`, output: `import foo from 'foo';\n\n// some random comment\nexport default function() {};`, diff --git a/tests/src/rules/no-commonjs.js b/tests/src/rules/no-commonjs.js index b7c0aa803..9950608a7 100644 --- a/tests/src/rules/no-commonjs.js +++ b/tests/src/rules/no-commonjs.js @@ -1,4 +1,5 @@ import { RuleTester } from 'eslint'; +import { withoutAutofixOutput } from '../rule-tester'; import eslintPkg from 'eslint/package.json'; import semver from 'semver'; @@ -69,47 +70,41 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { // imports ...semver.satisfies(eslintPkg.version, '< 4.0.0') ? [] : [ - { code: 'var x = require("x")', output: 'var x = require("x")', errors: [{ message: IMPORT_MESSAGE }] }, - { code: 'x = require("x")', output: 'x = require("x")', errors: [{ message: IMPORT_MESSAGE }] }, - { code: 'require("x")', output: 'require("x")', errors: [{ message: IMPORT_MESSAGE }] }, - { code: 'require(`x`)', + withoutAutofixOutput({ code: 'var x = require("x")', errors: [{ message: IMPORT_MESSAGE }] }), + withoutAutofixOutput({ code: 'x = require("x")', errors: [{ message: IMPORT_MESSAGE }] }), + withoutAutofixOutput({ code: 'require("x")', errors: [{ message: IMPORT_MESSAGE }] }), + withoutAutofixOutput({ code: 'require(`x`)', parserOptions: { ecmaVersion: 2015 }, - output: 'require(`x`)', errors: [{ message: IMPORT_MESSAGE }], - }, + }), - { code: 'if (typeof window !== "undefined") require("x")', + withoutAutofixOutput({ code: 'if (typeof window !== "undefined") require("x")', options: [{ allowConditionalRequire: false }], - output: 'if (typeof window !== "undefined") require("x")', errors: [{ message: IMPORT_MESSAGE }], - }, - { code: 'if (typeof window !== "undefined") { require("x") }', + }), + withoutAutofixOutput({ code: 'if (typeof window !== "undefined") { require("x") }', options: [{ allowConditionalRequire: false }], - output: 'if (typeof window !== "undefined") { require("x") }', errors: [{ message: IMPORT_MESSAGE }], - }, - { code: 'try { require("x") } catch (error) {}', + }), + withoutAutofixOutput({ code: 'try { require("x") } catch (error) {}', options: [{ allowConditionalRequire: false }], - output: 'try { require("x") } catch (error) {}', errors: [{ message: IMPORT_MESSAGE }], - }, + }), ], // exports - { code: 'exports.face = "palm"', output: 'exports.face = "palm"', errors: [{ message: EXPORT_MESSAGE }] }, - { code: 'module.exports.face = "palm"', output: 'module.exports.face = "palm"', errors: [{ message: EXPORT_MESSAGE }] }, - { code: 'module.exports = face', output: 'module.exports = face', errors: [{ message: EXPORT_MESSAGE }] }, - { code: 'exports = module.exports = {}', output: 'exports = module.exports = {}', errors: [{ message: EXPORT_MESSAGE }] }, - { code: 'var x = module.exports = {}', output: 'var x = module.exports = {}', errors: [{ message: EXPORT_MESSAGE }] }, - { code: 'module.exports = {}', + withoutAutofixOutput({ code: 'exports.face = "palm"', errors: [{ message: EXPORT_MESSAGE }] }), + withoutAutofixOutput({ code: 'module.exports.face = "palm"', errors: [{ message: EXPORT_MESSAGE }] }), + withoutAutofixOutput({ code: 'module.exports = face', errors: [{ message: EXPORT_MESSAGE }] }), + withoutAutofixOutput({ code: 'exports = module.exports = {}', errors: [{ message: EXPORT_MESSAGE }] }), + withoutAutofixOutput({ code: 'var x = module.exports = {}', errors: [{ message: EXPORT_MESSAGE }] }), + withoutAutofixOutput({ code: 'module.exports = {}', options: ['allow-primitive-modules'], - output: 'module.exports = {}', errors: [{ message: EXPORT_MESSAGE }], - }, - { code: 'var x = module.exports', + }), + withoutAutofixOutput({ code: 'var x = module.exports', options: ['allow-primitive-modules'], - output: 'var x = module.exports', errors: [{ message: EXPORT_MESSAGE }], - }, + }), ], }); diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index c46f9df85..a48e4d3f7 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -3,6 +3,7 @@ import { test as testUtil, getNonDefaultParsers, parsers, tsVersionSatisfies, ty import jsxConfig from '../../../config/react'; import { RuleTester } from 'eslint'; +import { withoutAutofixOutput } from '../rule-tester'; import eslintPkg from 'eslint/package.json'; import semver from 'semver'; import flatMap from 'array.prototype.flatmap'; @@ -96,15 +97,14 @@ ruleTester.run('no-duplicates', rule, { }), // #86: duplicate unresolved modules should be flagged - test({ + // Autofix bail because of different default import names. + test(withoutAutofixOutput({ code: "import foo from 'non-existent'; import bar from 'non-existent';", - // Autofix bail because of different default import names. - output: "import foo from 'non-existent'; import bar from 'non-existent';", errors: [ "'non-existent' imported multiple times.", "'non-existent' imported multiple times.", ], - }), + })), test({ code: "import type { x } from './foo'; import type { y } from './foo'", @@ -227,12 +227,11 @@ ruleTester.run('no-duplicates', rule, { errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), - test({ + // Autofix bail because cannot merge namespace imports. + test(withoutAutofixOutput({ code: "import * as ns1 from './foo'; import * as ns2 from './foo'", - // Autofix bail because cannot merge namespace imports. - output: "import * as ns1 from './foo'; import * as ns2 from './foo'", errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), + })), test({ code: "import * as ns from './foo'; import {x} from './foo'; import {y} from './foo'", @@ -248,89 +247,57 @@ ruleTester.run('no-duplicates', rule, { errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), - test({ + // Autofix bail because of comment. + test(withoutAutofixOutput({ code: ` // some-tool-disable-next-line import {x} from './foo' import {//y\ny} from './foo' `, - // Autofix bail because of comment. - output: ` - // some-tool-disable-next-line - import {x} from './foo' - import {//y\ny} from './foo' - `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), - - test({ + })), + // Autofix bail because of comment. + test(withoutAutofixOutput({ code: ` import {x} from './foo' // some-tool-disable-next-line import {y} from './foo' `, - // Autofix bail because of comment. - output: ` - import {x} from './foo' - // some-tool-disable-next-line - import {y} from './foo' - `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), - - test({ + })), + // Autofix bail because of comment. + test(withoutAutofixOutput({ code: ` import {x} from './foo' // some-tool-disable-line import {y} from './foo' `, - // Autofix bail because of comment. - output: ` - import {x} from './foo' // some-tool-disable-line - import {y} from './foo' - `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), - - test({ + })), + // Autofix bail because of comment. + test(withoutAutofixOutput({ code: ` import {x} from './foo' import {y} from './foo' // some-tool-disable-line `, - // Autofix bail because of comment. - output: ` - import {x} from './foo' - import {y} from './foo' // some-tool-disable-line - `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), - - test({ + })), + // Autofix bail because of comment. + test(withoutAutofixOutput({ code: ` import {x} from './foo' /* comment */ import {y} from './foo' `, - // Autofix bail because of comment. - output: ` - import {x} from './foo' - /* comment */ import {y} from './foo' - `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), - - test({ + })), + // Autofix bail because of comment. + test(withoutAutofixOutput({ code: ` import {x} from './foo' import {y} from './foo' /* comment multiline */ `, - // Autofix bail because of comment. - output: ` - import {x} from './foo' - import {y} from './foo' /* comment - multiline */ - `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), + })), test({ code: ` @@ -361,75 +328,48 @@ import {x,y} from './foo' `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), - - test({ + // Autofix bail because of comment. + test(withoutAutofixOutput({ code: ` import {x} from './foo' import/* comment */{y} from './foo' `, - // Autofix bail because of comment. - output: ` - import {x} from './foo' - import/* comment */{y} from './foo' - `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), - - test({ + })), + // Autofix bail because of comment. + test(withoutAutofixOutput({ code: ` import {x} from './foo' import/* comment */'./foo' `, - // Autofix bail because of comment. - output: ` - import {x} from './foo' - import/* comment */'./foo' - `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), - - test({ + })), + // Autofix bail because of comment. + test(withoutAutofixOutput({ code: ` import {x} from './foo' import{y}/* comment */from './foo' `, - // Autofix bail because of comment. - output: ` - import {x} from './foo' - import{y}/* comment */from './foo' - `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), - - test({ + })), + // Autofix bail because of comment. + test(withoutAutofixOutput({ code: ` import {x} from './foo' import{y}from/* comment */'./foo' `, - // Autofix bail because of comment. - output: ` - import {x} from './foo' - import{y}from/* comment */'./foo' - `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), - - test({ + })), + // Autofix bail because of comment. + test(withoutAutofixOutput({ code: ` import {x} from // some-tool-disable-next-line './foo' import {y} from './foo' `, - // Autofix bail because of comment. - output: ` - import {x} from - // some-tool-disable-next-line - './foo' - import {y} from './foo' - `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], - }), + })), // #2027 long import list generate empty lines test({ @@ -616,9 +556,8 @@ context('TypeScript', function () { ]); const invalid = [ - test({ + test(withoutAutofixOutput({ code: "import type x from './foo'; import type y from './foo'", - output: "import type x from './foo'; import type y from './foo'", ...parserConfig, errors: [ { @@ -632,7 +571,7 @@ context('TypeScript', function () { message: "'./foo' imported multiple times.", }, ], - }), + })), test({ code: "import type x from './foo'; import type x from './foo'", output: "import type x from './foo'; ", diff --git a/tests/src/rules/no-namespace.js b/tests/src/rules/no-namespace.js index 03a23e3dd..e297d953a 100644 --- a/tests/src/rules/no-namespace.js +++ b/tests/src/rules/no-namespace.js @@ -1,4 +1,5 @@ import { RuleTester } from 'eslint'; +import { withoutAutofixOutput } from '../rule-tester'; import eslintPkg from 'eslint/package.json'; import semver from 'semver'; import { test } from '../utils'; @@ -82,33 +83,30 @@ ruleTester.run('no-namespace', require('rules/no-namespace'), { ], invalid: [ - test({ + test(withoutAutofixOutput({ code: 'import * as foo from \'foo\';', - output: 'import * as foo from \'foo\';', errors: [{ line: 1, column: 8, message: ERROR_MESSAGE, }], - }), - test({ + })), + test(withoutAutofixOutput({ code: 'import defaultExport, * as foo from \'foo\';', - output: 'import defaultExport, * as foo from \'foo\';', errors: [{ line: 1, column: 23, message: ERROR_MESSAGE, }], - }), - test({ + })), + test(withoutAutofixOutput({ code: 'import * as foo from \'./foo\';', - output: 'import * as foo from \'./foo\';', errors: [{ line: 1, column: 8, message: ERROR_MESSAGE, }], - }), + })), ...FIX_TESTS, ], }); diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index 978c8e34d..ff6b65730 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1,6 +1,7 @@ import { test, getTSParsers, getNonDefaultParsers, testFilePath, parsers } from '../utils'; import { RuleTester } from 'eslint'; +import { withoutAutofixOutput } from '../rule-tester'; import eslintPkg from 'eslint/package.json'; import semver from 'semver'; import flatMap from 'array.prototype.flatmap'; @@ -21,10 +22,6 @@ const flowRuleTester = new RuleTester({ }); const rule = require('rules/order'); -function withoutAutofixOutput(test) { - return { ...test, output: test.code }; -} - ruleTester.run('order', rule, { valid: [ // Default order using require @@ -1906,19 +1903,13 @@ ruleTester.run('order', rule, { ], }), // Cannot fix newlines-between with multiline comment after - test({ + test(withoutAutofixOutput({ code: ` var fs = require('fs'); /* multiline comment */ var index = require('./'); `, - output: ` - var fs = require('fs'); /* multiline - comment */ - - var index = require('./'); - `, options: [ { groups: [['builtin'], ['index']], @@ -1931,7 +1922,7 @@ ruleTester.run('order', rule, { message: 'There should be no empty line between import groups', }, ], - }), + })), // Option newlines-between: 'always' - should report lack of newline between groups test({ code: ` @@ -2017,7 +2008,7 @@ ruleTester.run('order', rule, { }), // Option newlines-between: 'never' with unassigned imports and warnOnUnassignedImports disabled // newline is preserved to match existing behavior - test({ + test(withoutAutofixOutput({ code: ` import path from 'path'; import 'loud-rejection'; @@ -2025,13 +2016,6 @@ ruleTester.run('order', rule, { import 'something-else'; import _ from 'lodash'; `, - output: ` - import path from 'path'; - import 'loud-rejection'; - - import 'something-else'; - import _ from 'lodash'; - `, options: [{ 'newlines-between': 'never', warnOnUnassignedImports: false }], errors: [ { @@ -2039,7 +2023,7 @@ ruleTester.run('order', rule, { message: 'There should be no empty line between import groups', }, ], - }), + })), // Option newlines-between: 'never' with unassigned imports and warnOnUnassignedImports enabled test({ code: ` @@ -2064,7 +2048,7 @@ ruleTester.run('order', rule, { ], }), // Option newlines-between: 'never' cannot fix if there are other statements between imports - test({ + test(withoutAutofixOutput({ code: ` import path from 'path'; export const abc = 123; @@ -2072,13 +2056,6 @@ ruleTester.run('order', rule, { import 'something-else'; import _ from 'lodash'; `, - output: ` - import path from 'path'; - export const abc = 123; - - import 'something-else'; - import _ from 'lodash'; - `, options: [{ 'newlines-between': 'never' }], errors: [ { @@ -2086,7 +2063,7 @@ ruleTester.run('order', rule, { message: 'There should be no empty line between import groups', }, ], - }), + })), // Option newlines-between: 'always' should report missing empty lines when using not assigned imports test({ code: ` @@ -2170,7 +2147,7 @@ ruleTester.run('order', rule, { ], }), // reorder fix cannot cross function call on moving below #1 - test({ + test(withoutAutofixOutput({ code: ` const local = require('./local'); @@ -2181,22 +2158,12 @@ ruleTester.run('order', rule, { fn_call(); `, - output: ` - const local = require('./local'); - - fn_call(); - - const global1 = require('global1'); - const global2 = require('global2'); - - fn_call(); - `, errors: [{ message: '`./local` import should occur after import of `global2`', }], - }), + })), // reorder fix cannot cross function call on moving below #2 - test({ + test(withoutAutofixOutput({ code: ` const local = require('./local'); fn_call(); @@ -2205,20 +2172,12 @@ ruleTester.run('order', rule, { fn_call(); `, - output: ` - const local = require('./local'); - fn_call(); - const global1 = require('global1'); - const global2 = require('global2'); - - fn_call(); - `, errors: [{ message: '`./local` import should occur after import of `global2`', }], - }), + })), // reorder fix cannot cross function call on moving below #3 - test({ + test(withoutAutofixOutput({ code: ` const local1 = require('./local1'); const local2 = require('./local2'); @@ -2232,26 +2191,13 @@ ruleTester.run('order', rule, { const global5 = require('global5'); fn_call(); `, - output: ` - const local1 = require('./local1'); - const local2 = require('./local2'); - const local3 = require('./local3'); - const local4 = require('./local4'); - fn_call(); - const global1 = require('global1'); - const global2 = require('global2'); - const global3 = require('global3'); - const global4 = require('global4'); - const global5 = require('global5'); - fn_call(); - `, errors: [ '`./local1` import should occur after import of `global5`', '`./local2` import should occur after import of `global5`', '`./local3` import should occur after import of `global5`', '`./local4` import should occur after import of `global5`', ], - }), + })), // reorder fix cannot cross function call on moving below test(withoutAutofixOutput({ code: ` @@ -2561,7 +2507,7 @@ ruleTester.run('order', rule, { }], })), // reorder fix cannot cross function call on moving below (from #1252) - test({ + test(withoutAutofixOutput({ code: ` const env = require('./config'); @@ -2572,20 +2518,10 @@ ruleTester.run('order', rule, { http.createServer(express()); `, - output: ` - const env = require('./config'); - - Object.keys(env); - - const http = require('http'); - const express = require('express'); - - http.createServer(express()); - `, errors: [{ message: '`./config` import should occur after import of `express`', }], - }), + })), // reorder cannot cross non plain requires test(withoutAutofixOutput({ code: ` @@ -3497,19 +3433,13 @@ context('TypeScript', function () { ], }), // warns for out of order unassigned imports (warnOnUnassignedImports enabled) - test({ + test(withoutAutofixOutput({ code: ` import './local1'; import global from 'global1'; import local from './local2'; import 'global2'; `, - output: ` - import './local1'; - import global from 'global1'; - import local from './local2'; - import 'global2'; - `, errors: [ { message: '`global1` import should occur before import of `./local1`', @@ -3519,9 +3449,9 @@ context('TypeScript', function () { }, ], options: [{ warnOnUnassignedImports: true }], - }), + })), // fix cannot move below unassigned import (warnOnUnassignedImports enabled) - test({ + test(withoutAutofixOutput({ code: ` import local from './local'; @@ -3530,19 +3460,11 @@ context('TypeScript', function () { import global2 from 'global2'; import global3 from 'global3'; `, - output: ` - import local from './local'; - - import 'global1'; - - import global2 from 'global2'; - import global3 from 'global3'; - `, errors: [{ message: '`./local` import should occur after import of `global3`', }], options: [{ warnOnUnassignedImports: true }], - }), + })), // Imports inside module declaration test({ code: ` diff --git a/tests/src/rules/unambiguous.js b/tests/src/rules/unambiguous.js index 8cef69625..c103353e4 100644 --- a/tests/src/rules/unambiguous.js +++ b/tests/src/rules/unambiguous.js @@ -1,4 +1,5 @@ import { RuleTester } from 'eslint'; +import { withoutAutofixOutput } from '../rule-tester'; import { parsers } from '../utils'; const ruleTester = new RuleTester(); @@ -48,11 +49,10 @@ ruleTester.run('unambiguous', rule, { }, ], invalid: [ - { + withoutAutofixOutput({ code: 'function x() {}', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, - output: 'function x() {}', errors: ['This module could be parsed as a valid script.'], - }, + }), ], }); From 7633e67a878dd0d58494a4732fb34f655a7945c3 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 20 Sep 2024 07:23:53 +1200 Subject: [PATCH 30/50] [Tests] `no-cycle`: don't override the filename --- tests/src/rules/no-cycle.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js index d005727e3..e9b41c8fb 100644 --- a/tests/src/rules/no-cycle.js +++ b/tests/src/rules/no-cycle.js @@ -8,11 +8,13 @@ const rule = require('rules/no-cycle'); const error = (message) => ({ message }); -const test = (def) => _test(Object.assign(def, { +const test = (def) => _test({ filename: testFilePath('./cycles/depth-zero.js'), -})); -const testVersion = (specifier, t) => _testVersion(specifier, () => Object.assign(t(), { + ...def, +}); +const testVersion = (specifier, t) => _testVersion(specifier, () => ({ filename: testFilePath('./cycles/depth-zero.js'), + ...t(), })); const testDialects = ['es6']; From d0fe9cf62263b61b6e7f0e3957d1905e9457db34 Mon Sep 17 00:00:00 2001 From: Ivan Rubinson Date: Wed, 25 Sep 2024 18:14:32 +0300 Subject: [PATCH 31/50] [Docs] `no-cycle`: add `disableScc` to docs --- CHANGELOG.md | 2 ++ docs/rules/no-cycle.md | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e40edde5e..33b4eb4f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Changed - [Docs] [`no-relative-packages`]: fix typo ([#3066], thanks [@joshuaobrien]) - [Performance] [`no-cycle`]: dont scc for each linted file ([#3068], thanks [@soryy708]) +- [Docs] [`no-cycle`]: add `disableScc` to docs ([#3070], thanks [@soryy708]) ## [2.30.0] - 2024-09-02 @@ -1141,6 +1142,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#3070]: https://github.com/import-js/eslint-plugin-import/pull/3070 [#3068]: https://github.com/import-js/eslint-plugin-import/pull/3068 [#3066]: https://github.com/import-js/eslint-plugin-import/pull/3066 [#3065]: https://github.com/import-js/eslint-plugin-import/pull/3065 diff --git a/docs/rules/no-cycle.md b/docs/rules/no-cycle.md index 76e96f95f..898b75330 100644 --- a/docs/rules/no-cycle.md +++ b/docs/rules/no-cycle.md @@ -94,6 +94,14 @@ export function getBar() { return import('./bar'); } > Cyclic dependency are **always** a dangerous anti-pattern as discussed extensively in [#2265](https://github.com/import-js/eslint-plugin-import/issues/2265). Please be extra careful about using this option. +#### `disableScc` + +This option disables a pre-processing step that calculates [Strongly Connected Components](https://en.wikipedia.org/wiki/Strongly_connected_component), which are used for avoiding unnecessary work checking files in different SCCs for cycles. + +However, under some configurations, this pre-processing may be more expensive than the time it saves. + +When this option is `true`, we don't calculate any SCC graph, and check all files for cycles (leading to higher time-complexity). Default is `false`. + ## When Not To Use It This rule is comparatively computationally expensive. If you are pressed for lint From 30354de496b36cdaba08566c1eaa8d47048a3f58 Mon Sep 17 00:00:00 2001 From: michael faith Date: Sun, 15 Sep 2024 11:12:26 -0500 Subject: [PATCH 32/50] [Fix] `exportMap`: export map cache is tainted by unreliable parse results This change addresses and issue observed with the v9 upgrade, where the ExportMap Cache is being tainted with a bad export map, if the parse doesn't yield a `visitorKeys` (which can happen if an incompatible parser is used (e.g. og babel eslint)) for one run of the no-cycle rule. If a subsequent test is run with a compatible parser, then the bad export map will be found in the cache and used instead of attempting to proceed with the parse. I also updated the `getExports` test to use a valid parserPath, rather than a mocked one, so that the tests are acting on a valid parserPath. Otherwise the export map won't be cached following this change. --- CHANGELOG.md | 2 ++ src/exportMap/builder.js | 6 +++++- tests/src/core/getExports.js | 24 ++++++++++++++++++------ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33b4eb4f4..da66f7e87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - `ExportMap` / flat config: include `languageOptions` in context ([#3052], thanks [@michaelfaith]) - [`no-named-as-default`]: Allow using an identifier if the export is both a named and a default export ([#3032], thanks [@akwodkiewicz]) - [`export`]: False positive for exported overloaded functions in TS ([#3065], thanks [@liuxingbaoyu]) +- `exportMap`: export map cache is tainted by unreliable parse results ([#3062], thanks [@michaelfaith]) ### Changed - [Docs] [`no-relative-packages`]: fix typo ([#3066], thanks [@joshuaobrien]) @@ -1146,6 +1147,7 @@ for info on changes for earlier releases. [#3068]: https://github.com/import-js/eslint-plugin-import/pull/3068 [#3066]: https://github.com/import-js/eslint-plugin-import/pull/3066 [#3065]: https://github.com/import-js/eslint-plugin-import/pull/3065 +[#3062]: https://github.com/import-js/eslint-plugin-import/pull/3062 [#3052]: https://github.com/import-js/eslint-plugin-import/pull/3052 [#3043]: https://github.com/import-js/eslint-plugin-import/pull/3043 [#3036]: https://github.com/import-js/eslint-plugin-import/pull/3036 diff --git a/src/exportMap/builder.js b/src/exportMap/builder.js index 5348dba37..f7b9006ef 100644 --- a/src/exportMap/builder.js +++ b/src/exportMap/builder.js @@ -92,7 +92,11 @@ export default class ExportMapBuilder { exportMap.mtime = stats.mtime; - exportCache.set(cacheKey, exportMap); + // If the visitor keys were not populated, then we shouldn't save anything to the cache, + // since the parse results may not be reliable. + if (exportMap.visitorKeys) { + exportCache.set(cacheKey, exportMap); + } return exportMap; } diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 76003410d..f11a26131 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -1,15 +1,18 @@ import { expect } from 'chai'; +import fs from 'fs'; import semver from 'semver'; import sinon from 'sinon'; import eslintPkg from 'eslint/package.json'; +import { test as testUnambiguous } from 'eslint-module-utils/unambiguous'; import typescriptPkg from 'typescript/package.json'; import * as tsConfigLoader from 'tsconfig-paths/lib/tsconfig-loader'; -import ExportMapBuilder from '../../../src/exportMap/builder'; - -import * as fs from 'fs'; +import ExportMapBuilder from '../../../src/exportMap/builder'; import { getFilename } from '../utils'; -import { test as testUnambiguous } from 'eslint-module-utils/unambiguous'; + +const babelPath = require.resolve('babel-eslint'); +const hypotheticalLocation = babelPath.replace('index.js', 'visitor-keys.js'); +const isVisitorKeysSupported = fs.existsSync(hypotheticalLocation); describe('ExportMap', function () { const fakeContext = Object.assign( @@ -21,7 +24,7 @@ describe('ExportMap', function () { }, { settings: {}, - parserPath: 'babel-eslint', + parserPath: require.resolve('babel-eslint'), }, ); @@ -36,11 +39,20 @@ describe('ExportMap', function () { }); - it('returns a cached copy on subsequent requests', function () { + (isVisitorKeysSupported ? it : it.skip)('returns a cached copy on subsequent requests', function () { expect(ExportMapBuilder.get('./named-exports', fakeContext)) .to.exist.and.equal(ExportMapBuilder.get('./named-exports', fakeContext)); }); + it('does not return a cached copy if the parse does not yield a visitor keys', function () { + const mockContext = { + ...fakeContext, + parserPath: 'not-real', + }; + expect(ExportMapBuilder.get('./named-exports', mockContext)) + .to.exist.and.not.equal(ExportMapBuilder.get('./named-exports', mockContext)); + }); + it('does not return a cached copy after modification', (done) => { const firstAccess = ExportMapBuilder.get('./mutator', fakeContext); expect(firstAccess).to.exist; From 4818c25c4ac37f52a40d8f5918d3bca7e5caacf5 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Thu, 26 Sep 2024 08:48:38 +1200 Subject: [PATCH 33/50] [Tests] use re-exported `RuleTester` This reduces the diff in #2996. --- CHANGELOG.md | 2 ++ tests/src/rule-tester.js | 2 ++ tests/src/rules/consistent-type-specifier-style.js | 2 +- tests/src/rules/default.js | 2 +- tests/src/rules/dynamic-import-chunkname.js | 3 +-- tests/src/rules/export.js | 2 +- tests/src/rules/exports-last.js | 2 +- tests/src/rules/extensions.js | 2 +- tests/src/rules/first.js | 2 +- tests/src/rules/group-exports.js | 2 +- tests/src/rules/max-dependencies.js | 2 +- tests/src/rules/named.js | 2 +- tests/src/rules/namespace.js | 2 +- tests/src/rules/newline-after-import.js | 3 +-- tests/src/rules/no-absolute-path.js | 2 +- tests/src/rules/no-amd.js | 2 +- tests/src/rules/no-anonymous-default-export.js | 2 +- tests/src/rules/no-commonjs.js | 3 +-- tests/src/rules/no-cycle.js | 2 +- tests/src/rules/no-default-export.js | 2 +- tests/src/rules/no-deprecated.js | 2 +- tests/src/rules/no-duplicates.js | 3 +-- tests/src/rules/no-dynamic-require.js | 2 +- tests/src/rules/no-empty-named-blocks.js | 2 +- tests/src/rules/no-extraneous-dependencies.js | 2 +- tests/src/rules/no-import-module-exports.js | 2 +- tests/src/rules/no-internal-modules.js | 2 +- tests/src/rules/no-mutable-exports.js | 2 +- tests/src/rules/no-named-as-default-member.js | 2 +- tests/src/rules/no-named-as-default.js | 2 +- tests/src/rules/no-named-default.js | 2 +- tests/src/rules/no-named-export.js | 2 +- tests/src/rules/no-namespace.js | 3 +-- tests/src/rules/no-nodejs-modules.js | 2 +- tests/src/rules/no-relative-packages.js | 2 +- tests/src/rules/no-relative-parent-imports.js | 2 +- tests/src/rules/no-restricted-paths.js | 2 +- tests/src/rules/no-self-import.js | 2 +- tests/src/rules/no-unassigned-import.js | 2 +- tests/src/rules/no-unresolved.js | 2 +- tests/src/rules/no-unused-modules.js | 2 +- tests/src/rules/no-useless-path-segments.js | 2 +- tests/src/rules/no-webpack-loader-syntax.js | 2 +- tests/src/rules/order.js | 3 +-- tests/src/rules/prefer-default-export.js | 2 +- tests/src/rules/unambiguous.js | 3 +-- 46 files changed, 48 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da66f7e87..8dcb3a98d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - [Docs] [`no-relative-packages`]: fix typo ([#3066], thanks [@joshuaobrien]) - [Performance] [`no-cycle`]: dont scc for each linted file ([#3068], thanks [@soryy708]) - [Docs] [`no-cycle`]: add `disableScc` to docs ([#3070], thanks [@soryy708]) +- [Tests] use re-exported `RuleTester` ([#3071], thanks [@G-Rath]) ## [2.30.0] - 2024-09-02 @@ -1143,6 +1144,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#3071]: https://github.com/import-js/eslint-plugin-import/pull/3071 [#3070]: https://github.com/import-js/eslint-plugin-import/pull/3070 [#3068]: https://github.com/import-js/eslint-plugin-import/pull/3068 [#3066]: https://github.com/import-js/eslint-plugin-import/pull/3066 diff --git a/tests/src/rule-tester.js b/tests/src/rule-tester.js index 89c584612..f00b520d0 100644 --- a/tests/src/rule-tester.js +++ b/tests/src/rule-tester.js @@ -1,3 +1,5 @@ export function withoutAutofixOutput(test) { return { ...test, output: test.code }; } + +export { RuleTester } from 'eslint'; diff --git a/tests/src/rules/consistent-type-specifier-style.js b/tests/src/rules/consistent-type-specifier-style.js index 7799238c3..139457ff6 100644 --- a/tests/src/rules/consistent-type-specifier-style.js +++ b/tests/src/rules/consistent-type-specifier-style.js @@ -1,4 +1,4 @@ -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import { test, parsers, tsVersionSatisfies, eslintVersionSatisfies, typescriptEslintParserSatisfies } from '../utils'; const rule = require('rules/consistent-type-specifier-style'); diff --git a/tests/src/rules/default.js b/tests/src/rules/default.js index eb2028c71..1df57a23a 100644 --- a/tests/src/rules/default.js +++ b/tests/src/rules/default.js @@ -1,6 +1,6 @@ import path from 'path'; import { test, testVersion, SYNTAX_CASES, getTSParsers, parsers } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import semver from 'semver'; import { version as tsEslintVersion } from 'typescript-eslint-parser/package.json'; diff --git a/tests/src/rules/dynamic-import-chunkname.js b/tests/src/rules/dynamic-import-chunkname.js index 136219672..e8f97475d 100644 --- a/tests/src/rules/dynamic-import-chunkname.js +++ b/tests/src/rules/dynamic-import-chunkname.js @@ -1,6 +1,5 @@ import { SYNTAX_CASES, getTSParsers, parsers } from '../utils'; -import { RuleTester } from 'eslint'; -import { withoutAutofixOutput } from '../rule-tester'; +import { RuleTester, withoutAutofixOutput } from '../rule-tester'; import semver from 'semver'; const rule = require('rules/dynamic-import-chunkname'); diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index f16a25ecf..338501511 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -1,6 +1,6 @@ import { test, testFilePath, SYNTAX_CASES, getTSParsers, testVersion } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import eslintPkg from 'eslint/package.json'; import semver from 'semver'; import { version as tsEslintVersion } from 'typescript-eslint-parser/package.json'; diff --git a/tests/src/rules/exports-last.js b/tests/src/rules/exports-last.js index d7122e9a0..a676ae044 100644 --- a/tests/src/rules/exports-last.js +++ b/tests/src/rules/exports-last.js @@ -1,6 +1,6 @@ import { test } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import rule from 'rules/exports-last'; const ruleTester = new RuleTester(); diff --git a/tests/src/rules/extensions.js b/tests/src/rules/extensions.js index 14d84eaa6..97267832c 100644 --- a/tests/src/rules/extensions.js +++ b/tests/src/rules/extensions.js @@ -1,4 +1,4 @@ -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import rule from 'rules/extensions'; import { getTSParsers, test, testFilePath, parsers } from '../utils'; diff --git a/tests/src/rules/first.js b/tests/src/rules/first.js index f34f227b2..52b71db86 100644 --- a/tests/src/rules/first.js +++ b/tests/src/rules/first.js @@ -2,7 +2,7 @@ import { test, getTSParsers, testVersion } from '../utils'; import fs from 'fs'; import path from 'path'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/first'); diff --git a/tests/src/rules/group-exports.js b/tests/src/rules/group-exports.js index c3d07046f..6f05bc866 100644 --- a/tests/src/rules/group-exports.js +++ b/tests/src/rules/group-exports.js @@ -1,5 +1,5 @@ import { test } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import rule from 'rules/group-exports'; import { resolve } from 'path'; import { default as babelPresetFlow } from 'babel-preset-flow'; diff --git a/tests/src/rules/max-dependencies.js b/tests/src/rules/max-dependencies.js index 982a4b427..959ee68de 100644 --- a/tests/src/rules/max-dependencies.js +++ b/tests/src/rules/max-dependencies.js @@ -1,6 +1,6 @@ import { test, getTSParsers, parsers } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/max-dependencies'); diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 227bffc80..f506caeb6 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -1,5 +1,5 @@ import { test, SYNTAX_CASES, getTSParsers, testFilePath, testVersion, parsers } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import path from 'path'; import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'; diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index 3f768a571..60fcb93f6 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -1,5 +1,5 @@ import { test, SYNTAX_CASES, getTSParsers, testVersion, testFilePath, parsers } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import flatMap from 'array.prototype.flatmap'; const ruleTester = new RuleTester({ env: { es6: true } }); diff --git a/tests/src/rules/newline-after-import.js b/tests/src/rules/newline-after-import.js index 5c827c0d2..984e89855 100644 --- a/tests/src/rules/newline-after-import.js +++ b/tests/src/rules/newline-after-import.js @@ -1,5 +1,4 @@ -import { RuleTester } from 'eslint'; -import { withoutAutofixOutput } from '../rule-tester'; +import { RuleTester, withoutAutofixOutput } from '../rule-tester'; import flatMap from 'array.prototype.flatmap'; import semver from 'semver'; import { version as tsEslintVersion } from 'typescript-eslint-parser/package.json'; diff --git a/tests/src/rules/no-absolute-path.js b/tests/src/rules/no-absolute-path.js index bfa08465c..bcf215137 100644 --- a/tests/src/rules/no-absolute-path.js +++ b/tests/src/rules/no-absolute-path.js @@ -1,6 +1,6 @@ import { test } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/no-absolute-path'); diff --git a/tests/src/rules/no-amd.js b/tests/src/rules/no-amd.js index 5317aa8fd..6b66578df 100644 --- a/tests/src/rules/no-amd.js +++ b/tests/src/rules/no-amd.js @@ -1,4 +1,4 @@ -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import eslintPkg from 'eslint/package.json'; import semver from 'semver'; diff --git a/tests/src/rules/no-anonymous-default-export.js b/tests/src/rules/no-anonymous-default-export.js index 53b2fc6fb..37b3009f0 100644 --- a/tests/src/rules/no-anonymous-default-export.js +++ b/tests/src/rules/no-anonymous-default-export.js @@ -1,6 +1,6 @@ import { test, testVersion, SYNTAX_CASES } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/no-anonymous-default-export'); diff --git a/tests/src/rules/no-commonjs.js b/tests/src/rules/no-commonjs.js index 9950608a7..3211c085a 100644 --- a/tests/src/rules/no-commonjs.js +++ b/tests/src/rules/no-commonjs.js @@ -1,5 +1,4 @@ -import { RuleTester } from 'eslint'; -import { withoutAutofixOutput } from '../rule-tester'; +import { RuleTester, withoutAutofixOutput } from '../rule-tester'; import eslintPkg from 'eslint/package.json'; import semver from 'semver'; diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js index e9b41c8fb..ae4baab66 100644 --- a/tests/src/rules/no-cycle.js +++ b/tests/src/rules/no-cycle.js @@ -1,6 +1,6 @@ import { parsers, test as _test, testFilePath, testVersion as _testVersion } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import flatMap from 'array.prototype.flatmap'; const ruleTester = new RuleTester(); diff --git a/tests/src/rules/no-default-export.js b/tests/src/rules/no-default-export.js index 6c1a85a1d..29292427c 100644 --- a/tests/src/rules/no-default-export.js +++ b/tests/src/rules/no-default-export.js @@ -1,6 +1,6 @@ import { parsers, test, testVersion } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/no-default-export'); diff --git a/tests/src/rules/no-deprecated.js b/tests/src/rules/no-deprecated.js index 318ea7c36..ad51d23c2 100644 --- a/tests/src/rules/no-deprecated.js +++ b/tests/src/rules/no-deprecated.js @@ -1,6 +1,6 @@ import { test, SYNTAX_CASES, getTSParsers } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/no-deprecated'); diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index a48e4d3f7..cf57a3d59 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -2,8 +2,7 @@ import * as path from 'path'; import { test as testUtil, getNonDefaultParsers, parsers, tsVersionSatisfies, typescriptEslintParserSatisfies } from '../utils'; import jsxConfig from '../../../config/react'; -import { RuleTester } from 'eslint'; -import { withoutAutofixOutput } from '../rule-tester'; +import { RuleTester, withoutAutofixOutput } from '../rule-tester'; import eslintPkg from 'eslint/package.json'; import semver from 'semver'; import flatMap from 'array.prototype.flatmap'; diff --git a/tests/src/rules/no-dynamic-require.js b/tests/src/rules/no-dynamic-require.js index e316470ec..fc7cf2b06 100644 --- a/tests/src/rules/no-dynamic-require.js +++ b/tests/src/rules/no-dynamic-require.js @@ -1,6 +1,6 @@ import { parsers, test, testVersion } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import flatMap from 'array.prototype.flatmap'; const ruleTester = new RuleTester(); diff --git a/tests/src/rules/no-empty-named-blocks.js b/tests/src/rules/no-empty-named-blocks.js index 87965a140..d9514a845 100644 --- a/tests/src/rules/no-empty-named-blocks.js +++ b/tests/src/rules/no-empty-named-blocks.js @@ -1,6 +1,6 @@ import { parsers, test } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/no-empty-named-blocks'); diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index dd01c141d..4a465eb39 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -3,7 +3,7 @@ import typescriptConfig from '../../../config/typescript'; import path from 'path'; import fs from 'fs'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import flatMap from 'array.prototype.flatmap'; const ruleTester = new RuleTester(); diff --git a/tests/src/rules/no-import-module-exports.js b/tests/src/rules/no-import-module-exports.js index aa927857e..5738f8c52 100644 --- a/tests/src/rules/no-import-module-exports.js +++ b/tests/src/rules/no-import-module-exports.js @@ -1,5 +1,5 @@ import path from 'path'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import { eslintVersionSatisfies, test, testVersion } from '../utils'; diff --git a/tests/src/rules/no-internal-modules.js b/tests/src/rules/no-internal-modules.js index c1c301545..9fa91ea3d 100644 --- a/tests/src/rules/no-internal-modules.js +++ b/tests/src/rules/no-internal-modules.js @@ -1,4 +1,4 @@ -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import flatMap from 'array.prototype.flatmap'; import rule from 'rules/no-internal-modules'; diff --git a/tests/src/rules/no-mutable-exports.js b/tests/src/rules/no-mutable-exports.js index 1171443c4..ff9643b0d 100644 --- a/tests/src/rules/no-mutable-exports.js +++ b/tests/src/rules/no-mutable-exports.js @@ -1,5 +1,5 @@ import { parsers, test, testVersion } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import rule from 'rules/no-mutable-exports'; const ruleTester = new RuleTester(); diff --git a/tests/src/rules/no-named-as-default-member.js b/tests/src/rules/no-named-as-default-member.js index 1773176f4..5c00224ed 100644 --- a/tests/src/rules/no-named-as-default-member.js +++ b/tests/src/rules/no-named-as-default-member.js @@ -1,5 +1,5 @@ import { test, testVersion, SYNTAX_CASES } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import rule from 'rules/no-named-as-default-member'; const ruleTester = new RuleTester(); diff --git a/tests/src/rules/no-named-as-default.js b/tests/src/rules/no-named-as-default.js index 7fcd6e4f7..349372067 100644 --- a/tests/src/rules/no-named-as-default.js +++ b/tests/src/rules/no-named-as-default.js @@ -1,5 +1,5 @@ import { test, testVersion, SYNTAX_CASES, parsers } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/no-named-as-default'); diff --git a/tests/src/rules/no-named-default.js b/tests/src/rules/no-named-default.js index 191c9c6ce..d36e26c44 100644 --- a/tests/src/rules/no-named-default.js +++ b/tests/src/rules/no-named-default.js @@ -1,5 +1,5 @@ import { test, testVersion, SYNTAX_CASES, parsers } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/no-named-default'); diff --git a/tests/src/rules/no-named-export.js b/tests/src/rules/no-named-export.js index 58b5da2f8..83ea8571f 100644 --- a/tests/src/rules/no-named-export.js +++ b/tests/src/rules/no-named-export.js @@ -1,4 +1,4 @@ -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import { parsers, test, testVersion } from '../utils'; const ruleTester = new RuleTester(); diff --git a/tests/src/rules/no-namespace.js b/tests/src/rules/no-namespace.js index e297d953a..f5cd967a2 100644 --- a/tests/src/rules/no-namespace.js +++ b/tests/src/rules/no-namespace.js @@ -1,5 +1,4 @@ -import { RuleTester } from 'eslint'; -import { withoutAutofixOutput } from '../rule-tester'; +import { RuleTester, withoutAutofixOutput } from '../rule-tester'; import eslintPkg from 'eslint/package.json'; import semver from 'semver'; import { test } from '../utils'; diff --git a/tests/src/rules/no-nodejs-modules.js b/tests/src/rules/no-nodejs-modules.js index b25eb0ce8..cf131ffee 100644 --- a/tests/src/rules/no-nodejs-modules.js +++ b/tests/src/rules/no-nodejs-modules.js @@ -1,6 +1,6 @@ import { test } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const isCore = require('is-core-module'); const ruleTester = new RuleTester(); diff --git a/tests/src/rules/no-relative-packages.js b/tests/src/rules/no-relative-packages.js index 6104aeb9c..9b424506c 100644 --- a/tests/src/rules/no-relative-packages.js +++ b/tests/src/rules/no-relative-packages.js @@ -1,4 +1,4 @@ -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import rule from 'rules/no-relative-packages'; import { normalize } from 'path'; diff --git a/tests/src/rules/no-relative-parent-imports.js b/tests/src/rules/no-relative-parent-imports.js index 1af9b8cf8..93c8b97aa 100644 --- a/tests/src/rules/no-relative-parent-imports.js +++ b/tests/src/rules/no-relative-parent-imports.js @@ -1,4 +1,4 @@ -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import rule from 'rules/no-relative-parent-imports'; import { parsers, test as _test, testFilePath } from '../utils'; diff --git a/tests/src/rules/no-restricted-paths.js b/tests/src/rules/no-restricted-paths.js index a83a804a0..c3382ad08 100644 --- a/tests/src/rules/no-restricted-paths.js +++ b/tests/src/rules/no-restricted-paths.js @@ -1,4 +1,4 @@ -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import rule from 'rules/no-restricted-paths'; import { getTSParsers, test, testFilePath } from '../utils'; diff --git a/tests/src/rules/no-self-import.js b/tests/src/rules/no-self-import.js index ff1248b43..dd2ea1bf2 100644 --- a/tests/src/rules/no-self-import.js +++ b/tests/src/rules/no-self-import.js @@ -1,6 +1,6 @@ import { test, testFilePath } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/no-self-import'); diff --git a/tests/src/rules/no-unassigned-import.js b/tests/src/rules/no-unassigned-import.js index f96808cbc..b73246ac0 100644 --- a/tests/src/rules/no-unassigned-import.js +++ b/tests/src/rules/no-unassigned-import.js @@ -1,7 +1,7 @@ import { test } from '../utils'; import * as path from 'path'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/no-unassigned-import'); diff --git a/tests/src/rules/no-unresolved.js b/tests/src/rules/no-unresolved.js index 9bf1a42d4..c6e300c5d 100644 --- a/tests/src/rules/no-unresolved.js +++ b/tests/src/rules/no-unresolved.js @@ -4,7 +4,7 @@ import { getTSParsers, test, SYNTAX_CASES, testVersion, parsers } from '../utils import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/no-unresolved'); diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 80bd70227..22b54ce6f 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -2,7 +2,7 @@ import { test, testVersion, testFilePath, getTSParsers, parsers } from '../utils import jsxConfig from '../../../config/react'; import typescriptConfig from '../../../config/typescript'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import fs from 'fs'; import eslintPkg from 'eslint/package.json'; import semver from 'semver'; diff --git a/tests/src/rules/no-useless-path-segments.js b/tests/src/rules/no-useless-path-segments.js index d6d0395de..87f7a73e9 100644 --- a/tests/src/rules/no-useless-path-segments.js +++ b/tests/src/rules/no-useless-path-segments.js @@ -1,5 +1,5 @@ import { parsers, test } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; const ruleTester = new RuleTester(); const rule = require('rules/no-useless-path-segments'); diff --git a/tests/src/rules/no-webpack-loader-syntax.js b/tests/src/rules/no-webpack-loader-syntax.js index 05ad242f5..86114b36c 100644 --- a/tests/src/rules/no-webpack-loader-syntax.js +++ b/tests/src/rules/no-webpack-loader-syntax.js @@ -1,6 +1,6 @@ import { test, getTSParsers, parsers } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import semver from 'semver'; const ruleTester = new RuleTester(); diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index ff6b65730..ea62cec71 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1,7 +1,6 @@ import { test, getTSParsers, getNonDefaultParsers, testFilePath, parsers } from '../utils'; -import { RuleTester } from 'eslint'; -import { withoutAutofixOutput } from '../rule-tester'; +import { RuleTester, withoutAutofixOutput } from '../rule-tester'; import eslintPkg from 'eslint/package.json'; import semver from 'semver'; import flatMap from 'array.prototype.flatmap'; diff --git a/tests/src/rules/prefer-default-export.js b/tests/src/rules/prefer-default-export.js index a7310445b..8e459873f 100644 --- a/tests/src/rules/prefer-default-export.js +++ b/tests/src/rules/prefer-default-export.js @@ -1,6 +1,6 @@ import { test, testVersion, getNonDefaultParsers, parsers } from '../utils'; -import { RuleTester } from 'eslint'; +import { RuleTester } from '../rule-tester'; import semver from 'semver'; import { version as tsEslintVersion } from 'typescript-eslint-parser/package.json'; diff --git a/tests/src/rules/unambiguous.js b/tests/src/rules/unambiguous.js index c103353e4..15c67470e 100644 --- a/tests/src/rules/unambiguous.js +++ b/tests/src/rules/unambiguous.js @@ -1,5 +1,4 @@ -import { RuleTester } from 'eslint'; -import { withoutAutofixOutput } from '../rule-tester'; +import { RuleTester, withoutAutofixOutput } from '../rule-tester'; import { parsers } from '../utils'; const ruleTester = new RuleTester(); From 5a41c459310f3977a39e0b53d122bf0b2a240e4b Mon Sep 17 00:00:00 2001 From: michael faith Date: Thu, 26 Sep 2024 05:12:56 -0500 Subject: [PATCH 34/50] [utils] [new] `hash`: add support for hashing functions --- utils/CHANGELOG.md | 4 ++++ utils/hash.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index 6e71a26f7..e2a6e9139 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +### Added +- `hash`: add support for hashing functions ([#3072], thanks [@michaelfaith]) + ## v2.11.1 - 2024-09-23 ### Fixed @@ -172,6 +175,7 @@ Yanked due to critical issue with cache key resulting from #839. ### Fixed - `unambiguous.test()` regex is now properly in multiline mode +[#3072]: https://github.com/import-js/eslint-plugin-import/pull/3072 [#3061]: https://github.com/import-js/eslint-plugin-import/pull/3061 [#3057]: https://github.com/import-js/eslint-plugin-import/pull/3057 [#3049]: https://github.com/import-js/eslint-plugin-import/pull/3049 diff --git a/utils/hash.js b/utils/hash.js index b3ce618b5..21ed524a9 100644 --- a/utils/hash.js +++ b/utils/hash.js @@ -17,6 +17,8 @@ function hashify(value, hash) { if (Array.isArray(value)) { hashArray(value, hash); + } else if (typeof value === 'function') { + hash.update(String(value)); } else if (value instanceof Object) { hashObject(value, hash); } else { From c64bb4fd27a9d086a04c1b93663cf0a97f3f100a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 26 Sep 2024 12:32:24 -0700 Subject: [PATCH 35/50] [utils] v2.12.0 --- utils/CHANGELOG.md | 2 ++ utils/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index e2a6e9139..619d05063 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,8 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +## v2.12.0 - 2024-09-26 + ### Added - `hash`: add support for hashing functions ([#3072], thanks [@michaelfaith]) diff --git a/utils/package.json b/utils/package.json index 709142faf..017eb7192 100644 --- a/utils/package.json +++ b/utils/package.json @@ -1,6 +1,6 @@ { "name": "eslint-module-utils", - "version": "2.11.1", + "version": "2.12.0", "description": "Core utilities to support eslint-plugin-import and other module-related plugins.", "engines": { "node": ">=4" From 2390c2c4a5e7807c96b6e82c9f8ca45bd010ec98 Mon Sep 17 00:00:00 2001 From: michael faith Date: Wed, 25 Sep 2024 20:32:24 -0500 Subject: [PATCH 36/50] [Fix] `exportMap`: improve cacheKey when using flat config Discovered this issue in https://github.com/import-js/eslint-plugin-import/pull/2996#issuecomment-2372522774 This change improves the logic for generating the cache key used for both the exportMap cache and resolver cache when using flat config. Prior to this change, the cache key was a combination of the `parserPath`, a hash of the `parserOptions`, a hash of the plugin settings, and the path of the file. When using flat config, `parserPath` isn't provided. So, there's the possibility of incorrect cache objects being used if someone ran with this plugin using different parsers within the same lint execution, if parserOptions and settings are the same. The equivalent cacheKey construction when using flat config is to use `languageOptions` as a component of the key, rather than `parserPath` and `parserOptions`. One caveat is that the `parser` property of `languageOptions` is an object that oftentimes has the same two functions (`parse` and `parseForESLint`). This won't be reliably distinct when using the base JSON.stringify function to detect changes. So, this implementation uses a replace function along with `JSON.stringify` to stringify function properties along with other property types. To ensure that this will work properly with v9, I also tested this against #2996 with v9 installed. Not only does it pass all tests in that branch, but it also removes the need to add this exception: https://github.com/import-js/eslint-plugin-import/pull/2996#discussion_r1774135785 --- CHANGELOG.md | 2 + package.json | 2 +- src/exportMap/childContext.js | 35 +++++++++++--- tests/src/exportMap/childContext.js | 72 ++++++++++++++++++++++++++++- 4 files changed, 103 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dcb3a98d..90cabd4eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - [`no-named-as-default`]: Allow using an identifier if the export is both a named and a default export ([#3032], thanks [@akwodkiewicz]) - [`export`]: False positive for exported overloaded functions in TS ([#3065], thanks [@liuxingbaoyu]) - `exportMap`: export map cache is tainted by unreliable parse results ([#3062], thanks [@michaelfaith]) +- `exportMap`: improve cacheKey when using flat config ([#3072], thanks [@michaelfaith]) ### Changed - [Docs] [`no-relative-packages`]: fix typo ([#3066], thanks [@joshuaobrien]) @@ -1144,6 +1145,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#3072]: https://github.com/import-js/eslint-plugin-import/pull/3072 [#3071]: https://github.com/import-js/eslint-plugin-import/pull/3071 [#3070]: https://github.com/import-js/eslint-plugin-import/pull/3070 [#3068]: https://github.com/import-js/eslint-plugin-import/pull/3068 diff --git a/package.json b/package.json index 75384155b..eb1bbbc8f 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.11.1", + "eslint-module-utils": "^2.12.0", "hasown": "^2.0.2", "is-core-module": "^2.15.1", "is-glob": "^4.0.3", diff --git a/src/exportMap/childContext.js b/src/exportMap/childContext.js index 3534c5913..8994ac206 100644 --- a/src/exportMap/childContext.js +++ b/src/exportMap/childContext.js @@ -1,10 +1,18 @@ import { hashObject } from 'eslint-module-utils/hash'; -let parserOptionsHash = ''; -let prevParserOptions = ''; +let optionsHash = ''; +let prevOptions = ''; let settingsHash = ''; let prevSettings = ''; +// Replacer function helps us with serializing the parser nested within `languageOptions`. +function stringifyReplacerFn(_, value) { + if (typeof value === 'function') { + return String(value); + } + return value; +} + /** * don't hold full context object in memory, just grab what we need. * also calculate a cacheKey, where parts of the cacheKey hash are memoized @@ -17,13 +25,28 @@ export default function childContext(path, context) { prevSettings = JSON.stringify(settings); } - if (JSON.stringify(parserOptions) !== prevParserOptions) { - parserOptionsHash = hashObject({ parserOptions }).digest('hex'); - prevParserOptions = JSON.stringify(parserOptions); + // We'll use either a combination of `parserOptions` and `parserPath` or `languageOptions` + // to construct the cache key, depending on whether this is using a flat config or not. + let optionsToken; + if (!parserPath && languageOptions) { + if (JSON.stringify(languageOptions, stringifyReplacerFn) !== prevOptions) { + optionsHash = hashObject({ languageOptions }).digest('hex'); + prevOptions = JSON.stringify(languageOptions, stringifyReplacerFn); + } + // For languageOptions, we're just using the hashed options as the options token + optionsToken = optionsHash; + } else { + if (JSON.stringify(parserOptions) !== prevOptions) { + optionsHash = hashObject({ parserOptions }).digest('hex'); + prevOptions = JSON.stringify(parserOptions); + } + // When not using flat config, we use a combination of the hashed parserOptions + // and parserPath as the token + optionsToken = String(parserPath) + optionsHash; } return { - cacheKey: String(parserPath) + parserOptionsHash + settingsHash + String(path), + cacheKey: optionsToken + settingsHash + String(path), settings, parserOptions, parserPath, diff --git a/tests/src/exportMap/childContext.js b/tests/src/exportMap/childContext.js index 06fa04afe..5bc53fdb0 100644 --- a/tests/src/exportMap/childContext.js +++ b/tests/src/exportMap/childContext.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { hashObject } from 'eslint-module-utils/hash'; import childContext from '../../../src/exportMap/childContext'; @@ -16,8 +17,13 @@ describe('childContext', () => { const languageOptions = { ecmaVersion: 2024, sourceType: 'module', - parser: {}, + parser: { + parseForESLint() { return 'parser1'; }, + }, }; + const languageOptionsHash = hashObject({ languageOptions }).digest('hex'); + const parserOptionsHash = hashObject({ parserOptions }).digest('hex'); + const settingsHash = hashObject({ settings }).digest('hex'); // https://github.com/import-js/eslint-plugin-import/issues/3051 it('should pass context properties through, if present', () => { @@ -48,4 +54,68 @@ describe('childContext', () => { expect(result.path).to.equal(path); expect(result.cacheKey).to.be.a('string'); }); + + it('should construct cache key out of languageOptions if present', () => { + const mockContext = { + settings, + languageOptions, + }; + + const result = childContext(path, mockContext); + + expect(result.cacheKey).to.equal(languageOptionsHash + settingsHash + path); + }); + + it('should use the same cache key upon multiple calls', () => { + const mockContext = { + settings, + languageOptions, + }; + + let result = childContext(path, mockContext); + + const expectedCacheKey = languageOptionsHash + settingsHash + path; + expect(result.cacheKey).to.equal(expectedCacheKey); + + result = childContext(path, mockContext); + expect(result.cacheKey).to.equal(expectedCacheKey); + }); + + it('should update cacheKey if different languageOptions are passed in', () => { + const mockContext = { + settings, + languageOptions, + }; + + let result = childContext(path, mockContext); + + const firstCacheKey = languageOptionsHash + settingsHash + path; + expect(result.cacheKey).to.equal(firstCacheKey); + + // Second run with different parser function + mockContext.languageOptions = { + ...languageOptions, + parser: { + parseForESLint() { return 'parser2'; }, + }, + }; + + result = childContext(path, mockContext); + + const secondCacheKey = hashObject({ languageOptions: mockContext.languageOptions }).digest('hex') + settingsHash + path; + expect(result.cacheKey).to.not.equal(firstCacheKey); + expect(result.cacheKey).to.equal(secondCacheKey); + }); + + it('should construct cache key out of parserOptions and parserPath if no languageOptions', () => { + const mockContext = { + settings, + parserOptions, + parserPath, + }; + + const result = childContext(path, mockContext); + + expect(result.cacheKey).to.equal(String(parserPath) + parserOptionsHash + settingsHash + path); + }); }); From dc086eaa56ba60ff400ff3739a787f5e1e418ec1 Mon Sep 17 00:00:00 2001 From: Emin Yilmaz <70356757+unbeauvoyage@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:19:32 +0900 Subject: [PATCH 37/50] [Docs] `no-restricted-paths`: fix grammar --- CHANGELOG.md | 3 +++ docs/rules/no-restricted-paths.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90cabd4eb..699cd58e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - [Performance] [`no-cycle`]: dont scc for each linted file ([#3068], thanks [@soryy708]) - [Docs] [`no-cycle`]: add `disableScc` to docs ([#3070], thanks [@soryy708]) - [Tests] use re-exported `RuleTester` ([#3071], thanks [@G-Rath]) +- [Docs] `no-restricted-paths`: fix grammar ([#3073], thanks [@unbeauvoyage]) ## [2.30.0] - 2024-09-02 @@ -1145,6 +1146,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#3073]: https://github.com/import-js/eslint-plugin-import/pull/3073 [#3072]: https://github.com/import-js/eslint-plugin-import/pull/3072 [#3071]: https://github.com/import-js/eslint-plugin-import/pull/3071 [#3070]: https://github.com/import-js/eslint-plugin-import/pull/3070 @@ -1997,6 +1999,7 @@ for info on changes for earlier releases. [@tomprats]: https://github.com/tomprats [@TrevorBurnham]: https://github.com/TrevorBurnham [@ttmarek]: https://github.com/ttmarek +[@unbeauvoyage]: https://github.com/unbeauvoyage [@vikr01]: https://github.com/vikr01 [@wenfangdu]: https://github.com/wenfangdu [@wKich]: https://github.com/wKich diff --git a/docs/rules/no-restricted-paths.md b/docs/rules/no-restricted-paths.md index 293f3ba00..a905226c2 100644 --- a/docs/rules/no-restricted-paths.md +++ b/docs/rules/no-restricted-paths.md @@ -5,7 +5,7 @@ Some projects contain files which are not always meant to be executed in the same environment. For example consider a web application that contains specific code for the server and some specific code for the browser/client. In this case you don’t want to import server-only files in your client code. -In order to prevent such scenarios this rule allows you to define restricted zones where you can forbid files from imported if they match a specific path. +In order to prevent such scenarios this rule allows you to define restricted zones where you can forbid files from being imported if they match a specific path. ## Rule Details From 2a043ea4ff4a7197892f35fd868a64f21768e761 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 27 Sep 2024 11:37:41 +1200 Subject: [PATCH 38/50] [Tests] `no-default-export`, `no-named-export`: add test case --- CHANGELOG.md | 3 ++- tests/src/rules/no-default-export.js | 3 +++ tests/src/rules/no-named-export.js | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 699cd58e4..8c249f1df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,8 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - [Performance] [`no-cycle`]: dont scc for each linted file ([#3068], thanks [@soryy708]) - [Docs] [`no-cycle`]: add `disableScc` to docs ([#3070], thanks [@soryy708]) - [Tests] use re-exported `RuleTester` ([#3071], thanks [@G-Rath]) -- [Docs] `no-restricted-paths`: fix grammar ([#3073], thanks [@unbeauvoyage]) +- [Docs] [`no-restricted-paths`]: fix grammar ([#3073], thanks [@unbeauvoyage]) +- [Tests] [`no-default-export`], [`no-named-export`]: add test case (thanks [@G-Rath]) ## [2.30.0] - 2024-09-02 diff --git a/tests/src/rules/no-default-export.js b/tests/src/rules/no-default-export.js index 29292427c..eef8b1322 100644 --- a/tests/src/rules/no-default-export.js +++ b/tests/src/rules/no-default-export.js @@ -7,6 +7,9 @@ const rule = require('rules/no-default-export'); ruleTester.run('no-default-export', rule, { valid: [ + test({ + code: 'module.exports = function foo() {}', + }), test({ code: ` export const foo = 'foo'; diff --git a/tests/src/rules/no-named-export.js b/tests/src/rules/no-named-export.js index 83ea8571f..c592189f5 100644 --- a/tests/src/rules/no-named-export.js +++ b/tests/src/rules/no-named-export.js @@ -6,6 +6,9 @@ const rule = require('rules/no-named-export'); ruleTester.run('no-named-export', rule, { valid: [].concat( + test({ + code: 'module.export.foo = function () {}', + }), test({ code: 'export default function bar() {};', }), From 1fb0b819e18ee11ea28c96919543a75812ab4fa8 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 30 Sep 2024 13:43:45 +0700 Subject: [PATCH 39/50] [Tests] `no-default-export`, `no-named-export`: add test cases with non-module `sourceType` --- tests/src/rules/no-default-export.js | 6 ++++++ tests/src/rules/no-named-export.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/tests/src/rules/no-default-export.js b/tests/src/rules/no-default-export.js index eef8b1322..8434ee148 100644 --- a/tests/src/rules/no-default-export.js +++ b/tests/src/rules/no-default-export.js @@ -7,6 +7,12 @@ const rule = require('rules/no-default-export'); ruleTester.run('no-default-export', rule, { valid: [ + test({ + code: 'module.exports = function foo() {}', + parserOptions: { + sourceType: 'script', + }, + }), test({ code: 'module.exports = function foo() {}', }), diff --git a/tests/src/rules/no-named-export.js b/tests/src/rules/no-named-export.js index c592189f5..41f8e8f02 100644 --- a/tests/src/rules/no-named-export.js +++ b/tests/src/rules/no-named-export.js @@ -6,6 +6,12 @@ const rule = require('rules/no-named-export'); ruleTester.run('no-named-export', rule, { valid: [].concat( + test({ + code: 'module.export.foo = function () {}', + parserOptions: { + sourceType: 'script', + }, + }), test({ code: 'module.export.foo = function () {}', }), From 7cabcd861263c31a45d7191fe6e6644660c079b2 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 30 Sep 2024 13:59:51 +0700 Subject: [PATCH 40/50] [Refactor] create `sourceType` helper --- src/core/sourceType.js | 7 +++++++ src/rules/no-default-export.js | 3 ++- src/rules/no-named-export.js | 3 ++- src/rules/unambiguous.js | 3 ++- 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 src/core/sourceType.js diff --git a/src/core/sourceType.js b/src/core/sourceType.js new file mode 100644 index 000000000..4243be4cf --- /dev/null +++ b/src/core/sourceType.js @@ -0,0 +1,7 @@ +/** + * @param {import('eslint').Rule.RuleContext} context + * @returns 'module' | 'script' | undefined + */ +export default function sourceType(context) { + return context.parserOptions.sourceType; +} diff --git a/src/rules/no-default-export.js b/src/rules/no-default-export.js index fcb4f1b2f..d18f0c48f 100644 --- a/src/rules/no-default-export.js +++ b/src/rules/no-default-export.js @@ -1,6 +1,7 @@ import { getSourceCode } from 'eslint-module-utils/contextCompat'; import docsUrl from '../docsUrl'; +import sourceType from '../core/sourceType'; module.exports = { meta: { @@ -15,7 +16,7 @@ module.exports = { create(context) { // ignore non-modules - if (context.parserOptions.sourceType !== 'module') { + if (sourceType(context) !== 'module') { return {}; } diff --git a/src/rules/no-named-export.js b/src/rules/no-named-export.js index efaf9dc4c..fc9b2c48d 100644 --- a/src/rules/no-named-export.js +++ b/src/rules/no-named-export.js @@ -1,3 +1,4 @@ +import sourceType from '../core/sourceType'; import docsUrl from '../docsUrl'; module.exports = { @@ -13,7 +14,7 @@ module.exports = { create(context) { // ignore non-modules - if (context.parserOptions.sourceType !== 'module') { + if (sourceType(context) !== 'module') { return {}; } diff --git a/src/rules/unambiguous.js b/src/rules/unambiguous.js index 91152ea2a..2491fad3e 100644 --- a/src/rules/unambiguous.js +++ b/src/rules/unambiguous.js @@ -5,6 +5,7 @@ import { isModule } from 'eslint-module-utils/unambiguous'; import docsUrl from '../docsUrl'; +import sourceType from '../core/sourceType'; module.exports = { meta: { @@ -19,7 +20,7 @@ module.exports = { create(context) { // ignore non-modules - if (context.parserOptions.sourceType !== 'module') { + if (sourceType(context) !== 'module') { return {}; } From e3a99103190a530ca3ce21b0a22f2bb185445de7 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 16 Sep 2024 08:07:30 +1200 Subject: [PATCH 41/50] [Fix] adjust "is source type module" checks for flat config Also see https://github.com/jest-community/eslint-plugin-jest/pull/1639 --- CHANGELOG.md | 2 ++ src/core/sourceType.js | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c249f1df..9865e3b2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - [`export`]: False positive for exported overloaded functions in TS ([#3065], thanks [@liuxingbaoyu]) - `exportMap`: export map cache is tainted by unreliable parse results ([#3062], thanks [@michaelfaith]) - `exportMap`: improve cacheKey when using flat config ([#3072], thanks [@michaelfaith]) +- adjust "is source type module" checks for flat config ([#2996], thanks [@G-Rath]) ### Changed - [Docs] [`no-relative-packages`]: fix typo ([#3066], thanks [@joshuaobrien]) @@ -1165,6 +1166,7 @@ for info on changes for earlier releases. [#3011]: https://github.com/import-js/eslint-plugin-import/pull/3011 [#3004]: https://github.com/import-js/eslint-plugin-import/pull/3004 [#2998]: https://github.com/import-js/eslint-plugin-import/pull/2998 +[#2996]: https://github.com/import-js/eslint-plugin-import/pull/2996 [#2993]: https://github.com/import-js/eslint-plugin-import/pull/2993 [#2991]: https://github.com/import-js/eslint-plugin-import/pull/2991 [#2989]: https://github.com/import-js/eslint-plugin-import/pull/2989 diff --git a/src/core/sourceType.js b/src/core/sourceType.js index 4243be4cf..5ff92edc9 100644 --- a/src/core/sourceType.js +++ b/src/core/sourceType.js @@ -1,7 +1,12 @@ /** * @param {import('eslint').Rule.RuleContext} context - * @returns 'module' | 'script' | undefined + * @returns 'module' | 'script' | 'commonjs' | undefined */ export default function sourceType(context) { - return context.parserOptions.sourceType; + if ('sourceType' in context.parserOptions) { + return context.parserOptions.sourceType; + } + if ('languageOptions' in context && context.languageOptions) { + return context.languageOptions.sourceType; + } } From db26bb87847de3dfbd2f8c22152406aa84a25033 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sun, 7 Apr 2024 12:31:53 +1200 Subject: [PATCH 42/50] [New] support eslint v9 Co-authored-by: Gareth Jones Co-authored-by: michael faith --- .github/workflows/node-4+.yml | 27 ++++++++++ CHANGELOG.md | 1 + package.json | 4 +- tests/files/issue210.config.flat.js | 3 ++ tests/files/just-json-files/eslint.config.js | 28 +++++++++++ tests/src/cli.js | 53 +++++++++++++------- tests/src/rule-tester.js | 46 ++++++++++++++++- tests/src/rules/named.js | 4 +- tests/src/rules/namespace.js | 2 +- tests/src/rules/no-unused-modules.js | 16 +++--- 10 files changed, 152 insertions(+), 32 deletions(-) create mode 100644 tests/files/issue210.config.flat.js create mode 100644 tests/files/just-json-files/eslint.config.js diff --git a/.github/workflows/node-4+.yml b/.github/workflows/node-4+.yml index f2dad098c..323c2ad54 100644 --- a/.github/workflows/node-4+.yml +++ b/.github/workflows/node-4+.yml @@ -36,6 +36,7 @@ jobs: - macos-latest node-version: ${{ fromJson(needs.matrix.outputs.latest) }} eslint: + - 9 - 8 - 7 - 6 @@ -63,34 +64,58 @@ jobs: env: TS_PARSER: 2 exclude: + - node-version: 16 + eslint: 9 + - node-version: 15 + eslint: 9 - node-version: 15 eslint: 8 + - node-version: 14 + eslint: 9 + - node-version: 13 + eslint: 9 - node-version: 13 eslint: 8 + - node-version: 12 + eslint: 9 + - node-version: 11 + eslint: 9 - node-version: 11 eslint: 8 + - node-version: 10 + eslint: 9 - node-version: 10 eslint: 8 + - node-version: 9 + eslint: 9 - node-version: 9 eslint: 8 - node-version: 9 eslint: 7 + - node-version: 8 + eslint: 9 - node-version: 8 eslint: 8 - node-version: 8 eslint: 7 + - node-version: 7 + eslint: 9 - node-version: 7 eslint: 8 - node-version: 7 eslint: 7 - node-version: 7 eslint: 6 + - node-version: 6 + eslint: 9 - node-version: 6 eslint: 8 - node-version: 6 eslint: 7 - node-version: 6 eslint: 6 + - node-version: 5 + eslint: 9 - node-version: 5 eslint: 8 - node-version: 5 @@ -99,6 +124,8 @@ jobs: eslint: 6 - node-version: 5 eslint: 5 + - node-version: 4 + eslint: 9 - node-version: 4 eslint: 8 - node-version: 4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9865e3b2b..795fa5bc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## [Unreleased] ### Added +- support eslint v9 ([#2996], thanks [@G-Rath] [@michaelfaith]) - [`order`]: allow validating named imports ([#3043], thanks [@manuth]) ### Fixed diff --git a/package.json b/package.json index eb1bbbc8f..a366c5c5e 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "chai": "^4.3.10", "cross-env": "^4.0.0", "escope": "^3.6.0", - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9", "eslint-doc-generator": "^1.6.1", "eslint-import-resolver-node": "file:./resolvers/node", "eslint-import-resolver-typescript": "^1.0.2 || ^1.1.1", @@ -106,7 +106,7 @@ "typescript-eslint-parser": "^15 || ^20 || ^22" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" }, "dependencies": { "@rtsao/scc": "^1.1.0", diff --git a/tests/files/issue210.config.flat.js b/tests/files/issue210.config.flat.js new file mode 100644 index 000000000..c894376f4 --- /dev/null +++ b/tests/files/issue210.config.flat.js @@ -0,0 +1,3 @@ +exports.languageOptions = { + sourceType: 'module', +} diff --git a/tests/files/just-json-files/eslint.config.js b/tests/files/just-json-files/eslint.config.js new file mode 100644 index 000000000..b1bf2070b --- /dev/null +++ b/tests/files/just-json-files/eslint.config.js @@ -0,0 +1,28 @@ +var jsonPlugin = require('eslint-plugin-json'); + +if (!jsonPlugin.processors.json) { + jsonPlugin.processors.json = jsonPlugin.processors['.json']; +} + +module.exports = [ + { + files: ['tests/files/just-json-files/*.json'], + plugins:{ + json: jsonPlugin, + }, + processor: 'json/json', + rules: Object.assign( + {}, + { + 'import/no-unused-modules': [ + 'error', + { + 'missingExports': false, + 'unusedExports': true, + }, + ], + }, + jsonPlugin.configs.recommended.rules + ) + }, +]; diff --git a/tests/src/cli.js b/tests/src/cli.js index 8a7345487..60b8382d0 100644 --- a/tests/src/cli.js +++ b/tests/src/cli.js @@ -15,17 +15,29 @@ describe('CLI regression tests', function () { let cli; before(function () { if (ESLint) { - eslint = new ESLint({ - useEslintrc: false, - overrideConfigFile: './tests/files/issue210.config.js', - rulePaths: ['./src/rules'], - overrideConfig: { - rules: { - named: 2, + if (semver.satisfies(eslintPkg.version, '>= 9')) { + eslint = new ESLint({ + overrideConfigFile: './tests/files/issue210.config.flat.js', + overrideConfig: { + rules: { + 'import/named': 2, + }, }, - }, - plugins: { 'eslint-plugin-import': importPlugin }, - }); + plugins: { 'eslint-plugin-import': importPlugin }, + }); + } else { + eslint = new ESLint({ + useEslintrc: false, + overrideConfigFile: './tests/files/issue210.config.js', + rulePaths: ['./src/rules'], + overrideConfig: { + rules: { + named: 2, + }, + }, + plugins: { 'eslint-plugin-import': importPlugin }, + }); + } } else { cli = new CLIEngine({ useEslintrc: false, @@ -56,13 +68,20 @@ describe('CLI regression tests', function () { this.skip(); } else { if (ESLint) { - eslint = new ESLint({ - useEslintrc: false, - overrideConfigFile: './tests/files/just-json-files/.eslintrc.json', - rulePaths: ['./src/rules'], - ignore: false, - plugins: { 'eslint-plugin-import': importPlugin }, - }); + if (semver.satisfies(eslintPkg.version, '>= 9')) { + eslint = new ESLint({ + overrideConfigFile: './tests/files/just-json-files/eslint.config.js', + plugins: { 'eslint-plugin-import': importPlugin }, + }); + } else { + eslint = new ESLint({ + useEslintrc: false, + overrideConfigFile: './tests/files/just-json-files/.eslintrc.json', + rulePaths: ['./src/rules'], + ignore: false, + plugins: { 'eslint-plugin-import': importPlugin }, + }); + } } else { cli = new CLIEngine({ useEslintrc: false, diff --git a/tests/src/rule-tester.js b/tests/src/rule-tester.js index f00b520d0..390c6cd7f 100644 --- a/tests/src/rule-tester.js +++ b/tests/src/rule-tester.js @@ -1,5 +1,47 @@ +import { RuleTester } from 'eslint'; +import { version as eslintVersion } from 'eslint/package.json'; +import semver from 'semver'; + +export const usingFlatConfig = semver.major(eslintVersion) >= 9; + export function withoutAutofixOutput(test) { - return { ...test, output: test.code }; + return { ...test, ...usingFlatConfig || { output: test.code } }; +} + +class FlatCompatRuleTester extends RuleTester { + constructor(testerConfig = { parserOptions: { sourceType: 'script' } }) { + super(FlatCompatRuleTester._flatCompat(testerConfig)); + } + + run(ruleName, rule, tests) { + super.run(ruleName, rule, { + valid: tests.valid.map((t) => FlatCompatRuleTester._flatCompat(t)), + invalid: tests.invalid.map((t) => FlatCompatRuleTester._flatCompat(t)), + }); + } + + static _flatCompat(config) { + if (!config || !usingFlatConfig || typeof config !== 'object') { + return config; + } + + const { parser, parserOptions = {}, languageOptions = {}, ...remainingConfig } = config; + const { ecmaVersion, sourceType, ...remainingParserOptions } = parserOptions; + const parserObj = typeof parser === 'string' ? require(parser) : parser; + + return { + ...remainingConfig, + languageOptions: { + ...languageOptions, + ...parserObj ? { parser: parserObj } : {}, + ...ecmaVersion ? { ecmaVersion } : {}, + ...sourceType ? { sourceType } : {}, + parserOptions: { + ...remainingParserOptions, + }, + }, + }; + } } -export { RuleTester } from 'eslint'; +export { FlatCompatRuleTester as RuleTester }; diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index f506caeb6..51a76c129 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -1,5 +1,5 @@ import { test, SYNTAX_CASES, getTSParsers, testFilePath, testVersion, parsers } from '../utils'; -import { RuleTester } from '../rule-tester'; +import { RuleTester, usingFlatConfig } from '../rule-tester'; import path from 'path'; import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'; @@ -32,7 +32,7 @@ ruleTester.run('named', rule, { settings: { 'import/resolve': { extensions: ['.js', '.jsx'] } } }), // validate that eslint-disable-line silences this properly - test({ code: 'import {a, b, d} from "./common"; // eslint-disable-line named' }), + test({ code: `import {a, b, d} from "./common"; // eslint-disable-line ${usingFlatConfig ? 'rule-to-test/' : ''}named` }), test({ code: 'import { foo, bar } from "./re-export-names"' }), diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index 60fcb93f6..2a31d57e1 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -2,7 +2,7 @@ import { test, SYNTAX_CASES, getTSParsers, testVersion, testFilePath, parsers } import { RuleTester } from '../rule-tester'; import flatMap from 'array.prototype.flatmap'; -const ruleTester = new RuleTester({ env: { es6: true } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); const rule = require('rules/namespace'); function error(name, namespace) { diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 22b54ce6f..d86f40622 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -288,8 +288,8 @@ describe('dynamic imports', function () { // test for unused exports with `import()` ruleTester.run('no-unused-modules', rule, { - valid: [ - test({ + valid: [].concat( + testVersion('< 9', () => ({ options: unusedExportsOptions, code: ` export const a = 10 @@ -300,10 +300,10 @@ describe('dynamic imports', function () { `, parser: parsers.BABEL_OLD, filename: testFilePath('./no-unused-modules/exports-for-dynamic-js.js'), - }), - ], - invalid: [ - test({ + })), + ), + invalid: [].concat( + testVersion('< 9', () => ({ options: unusedExportsOptions, code: ` export const a = 10 @@ -319,8 +319,8 @@ describe('dynamic imports', function () { error(`exported declaration 'b' not used within other modules`), error(`exported declaration 'c' not used within other modules`), error(`exported declaration 'default' not used within other modules`), - ] }), - ], + ] })), + ), }); typescriptRuleTester.run('no-unused-modules', rule, { valid: [ From fc2d8d9613e038c6348933b0e048a9ce08e30f4e Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 8 Apr 2024 17:11:39 +1200 Subject: [PATCH 43/50] [Tests] `rule-tester`: try this babel class workaround --- tests/src/rule-tester.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/rule-tester.js b/tests/src/rule-tester.js index 390c6cd7f..103f2fd6f 100644 --- a/tests/src/rule-tester.js +++ b/tests/src/rule-tester.js @@ -8,13 +8,13 @@ export function withoutAutofixOutput(test) { return { ...test, ...usingFlatConfig || { output: test.code } }; } -class FlatCompatRuleTester extends RuleTester { +class FlatCompatRuleTester { constructor(testerConfig = { parserOptions: { sourceType: 'script' } }) { - super(FlatCompatRuleTester._flatCompat(testerConfig)); + this._tester = new RuleTester(FlatCompatRuleTester._flatCompat(testerConfig)); } run(ruleName, rule, tests) { - super.run(ruleName, rule, { + this._tester.run(ruleName, rule, { valid: tests.valid.map((t) => FlatCompatRuleTester._flatCompat(t)), invalid: tests.invalid.map((t) => FlatCompatRuleTester._flatCompat(t)), }); From 44212a87ffd4b357ed02c55195fba2e1c934327e Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Wed, 5 Jul 2023 12:20:03 +0200 Subject: [PATCH 44/50] [New] `extensions`: add the `checkTypeImports` option --- CHANGELOG.md | 3 ++ docs/rules/extensions.md | 18 ++++++++++ src/rules/extensions.js | 9 +++-- tests/src/rules/extensions.js | 67 +++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 795fa5bc4..d1dc35e79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Added - support eslint v9 ([#2996], thanks [@G-Rath] [@michaelfaith]) - [`order`]: allow validating named imports ([#3043], thanks [@manuth]) +- [`extensions`]: add the `checkTypeImports` option ([#2817], thanks [@phryneas]) ### Fixed - `ExportMap` / flat config: include `languageOptions` in context ([#3052], thanks [@michaelfaith]) @@ -1187,6 +1188,7 @@ for info on changes for earlier releases. [#2842]: https://github.com/import-js/eslint-plugin-import/pull/2842 [#2835]: https://github.com/import-js/eslint-plugin-import/pull/2835 [#2832]: https://github.com/import-js/eslint-plugin-import/pull/2832 +[#2817]: https://github.com/import-js/eslint-plugin-import/pull/2817 [#2778]: https://github.com/import-js/eslint-plugin-import/pull/2778 [#2756]: https://github.com/import-js/eslint-plugin-import/pull/2756 [#2754]: https://github.com/import-js/eslint-plugin-import/pull/2754 @@ -1942,6 +1944,7 @@ for info on changes for earlier releases. [@pcorpet]: https://github.com/pcorpet [@Pearce-Ropion]: https://github.com/Pearce-Ropion [@Pessimistress]: https://github.com/Pessimistress +[@phryneas]: https://github.com/phryneas [@pmcelhaney]: https://github.com/pmcelhaney [@preco21]: https://github.com/preco21 [@pri1311]: https://github.com/pri1311 diff --git a/docs/rules/extensions.md b/docs/rules/extensions.md index 946ccb7bf..5d15e93f1 100644 --- a/docs/rules/extensions.md +++ b/docs/rules/extensions.md @@ -56,6 +56,8 @@ For example, `["error", "never", { "svg": "always" }]` would require that all ex In that case, if you still want to specify extensions, you can do so inside the **pattern** property. Default value of `ignorePackages` is `false`. +By default, `import type` and `export type` style imports/exports are ignored. If you want to check them as well, you can set the `checkTypeImports` option to `true`. + ### Exception When disallowing the use of certain extensions this rule makes an exception and allows the use of extension when the file would not be resolvable without extension. @@ -104,6 +106,14 @@ import express from 'express/index'; import * as path from 'path'; ``` +The following patterns are considered problems when the configuration is set to "never" and the option "checkTypeImports" is set to `true`: + +```js +import type { Foo } from './foo.ts'; + +export type { Foo } from './foo.ts'; +``` + The following patterns are considered problems when configuration set to "always": ```js @@ -167,6 +177,14 @@ import express from 'express'; import foo from '@/foo'; ``` +The following patterns are considered problems when the configuration is set to "always" and the option "checkTypeImports" is set to `true`: + +```js +import type { Foo } from './foo'; + +export type { Foo } from './foo'; +``` + ## When Not To Use It If you are not concerned about a consistent usage of file extension. diff --git a/src/rules/extensions.js b/src/rules/extensions.js index b1e5c6d9f..c2c03a2b1 100644 --- a/src/rules/extensions.js +++ b/src/rules/extensions.js @@ -14,6 +14,7 @@ const properties = { type: 'object', properties: { pattern: patternProperties, + checkTypeImports: { type: 'boolean' }, ignorePackages: { type: 'boolean' }, }, }; @@ -35,7 +36,7 @@ function buildProperties(context) { } // If this is not the new structure, transfer all props to result.pattern - if (obj.pattern === undefined && obj.ignorePackages === undefined) { + if (obj.pattern === undefined && obj.ignorePackages === undefined && obj.checkTypeImports === undefined) { Object.assign(result.pattern, obj); return; } @@ -49,6 +50,10 @@ function buildProperties(context) { if (obj.ignorePackages !== undefined) { result.ignorePackages = obj.ignorePackages; } + + if (obj.checkTypeImports !== undefined) { + result.checkTypeImports = obj.checkTypeImports; + } }); if (result.defaultConfig === 'ignorePackages') { @@ -168,7 +173,7 @@ module.exports = { if (!extension || !importPath.endsWith(`.${extension}`)) { // ignore type-only imports and exports - if (node.importKind === 'type' || node.exportKind === 'type') { return; } + if (!props.checkTypeImports && (node.importKind === 'type' || node.exportKind === 'type')) { return; } const extensionRequired = isUseOfExtensionRequired(extension, isPackage); const extensionForbidden = isUseOfExtensionForbidden(extension); if (extensionRequired && !extensionForbidden) { diff --git a/tests/src/rules/extensions.js b/tests/src/rules/extensions.js index 97267832c..883dfab65 100644 --- a/tests/src/rules/extensions.js +++ b/tests/src/rules/extensions.js @@ -3,6 +3,15 @@ import rule from 'rules/extensions'; import { getTSParsers, test, testFilePath, parsers } from '../utils'; const ruleTester = new RuleTester(); +const ruleTesterWithTypeScriptImports = new RuleTester({ + settings: { + 'import/resolver': { + typescript: { + alwaysTryTypes: true, + }, + }, + }, +}); ruleTester.run('extensions', rule, { valid: [ @@ -689,6 +698,64 @@ describe('TypeScript', () => { ], parser, }), + test({ + code: 'import type T from "./typescript-declare";', + errors: ['Missing file extension for "./typescript-declare"'], + options: [ + 'always', + { ts: 'never', tsx: 'never', js: 'never', jsx: 'never', checkTypeImports: true }, + ], + parser, + }), + test({ + code: 'export type { MyType } from "./typescript-declare";', + errors: ['Missing file extension for "./typescript-declare"'], + options: [ + 'always', + { ts: 'never', tsx: 'never', js: 'never', jsx: 'never', checkTypeImports: true }, + ], + parser, + }), + ], + }); + ruleTesterWithTypeScriptImports.run(`${parser}: (with TS resolver) extensions are enforced for type imports/export when checkTypeImports is set`, rule, { + valid: [ + test({ + code: 'import type { MyType } from "./typescript-declare.ts";', + options: [ + 'always', + { checkTypeImports: true }, + ], + parser, + }), + test({ + code: 'export type { MyType } from "./typescript-declare.ts";', + options: [ + 'always', + { checkTypeImports: true }, + ], + parser, + }), + ], + invalid: [ + test({ + code: 'import type { MyType } from "./typescript-declare";', + errors: ['Missing file extension "ts" for "./typescript-declare"'], + options: [ + 'always', + { checkTypeImports: true }, + ], + parser, + }), + test({ + code: 'export type { MyType } from "./typescript-declare";', + errors: ['Missing file extension "ts" for "./typescript-declare"'], + options: [ + 'always', + { checkTypeImports: true }, + ], + parser, + }), ], }); }); From b5c806135597eb5fd161d1688a2ccb8d84f85b5b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 3 Oct 2024 13:15:10 +0700 Subject: [PATCH 45/50] [utils] [refactor] `parse`: avoid using a regex here --- utils/CHANGELOG.md | 3 +++ utils/parse.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index 619d05063..bb9372512 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +### Changed +- [refactor] `parse`: avoid using a regex here (thanks [@ljharb]) + ## v2.12.0 - 2024-09-26 ### Added diff --git a/utils/parse.js b/utils/parse.js index 03022ac40..793e37152 100644 --- a/utils/parse.js +++ b/utils/parse.js @@ -33,7 +33,7 @@ function keysFromParser(parserPath, parserInstance, parsedResult) { // up with a `parsedResult` here. It also doesn't expose the visitor keys on the parser itself, // so we have to try and infer the visitor-keys module from the parserPath. // This is NOT supported in flat config! - if (typeof parserPath === 'string' && (/.*babel-eslint.*/).test(parserPath)) { + if (typeof parserPath === 'string' && parserPath.indexOf('babel-eslint') > -1) { return getBabelEslintVisitorKeys(parserPath); } // The espree parser doesn't have the `parseForESLint` function, so we don't end up with a From be23a2aba907c407cdffe1f93401024f71514262 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 3 Oct 2024 13:18:14 +0700 Subject: [PATCH 46/50] v2.31.0 --- CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1dc35e79..cbf393750 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## [Unreleased] +## [2.31.0] - 2024-10-03 + ### Added - support eslint v9 ([#2996], thanks [@G-Rath] [@michaelfaith]) - [`order`]: allow validating named imports ([#3043], thanks [@manuth]) @@ -1646,7 +1648,8 @@ for info on changes for earlier releases. [#119]: https://github.com/import-js/eslint-plugin-import/issues/119 [#89]: https://github.com/import-js/eslint-plugin-import/issues/89 -[Unreleased]: https://github.com/import-js/eslint-plugin-import/compare/v2.30.0...HEAD +[Unreleased]: https://github.com/import-js/eslint-plugin-import/compare/v2.31.0...HEAD +[2.31.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.30.0...v2.31.0 [2.30.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.29.1...v2.30.0 [2.29.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.29.0...v2.29.1 [2.29.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.28.1...v2.29.0 diff --git a/package.json b/package.json index a366c5c5e..a513514e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@exodus/eslint-plugin-import", - "version": "2.30.0", + "version": "2.31.0", "description": "Import with sanity.", "engines": { "node": ">=4" From 99b2e3b692cef02e8f4ed3729678ac5bceaf71fd Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 3 Oct 2024 17:36:09 +0700 Subject: [PATCH 47/50] [meta] add missing changelog links --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbf393750..ab57069c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,11 +45,11 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange - [`order`]: do not compare first path segment for relative paths ([#2682]) ([#2885], thanks [@mihkeleidast]) ### Changed -- [Docs] `no-extraneous-dependencies`: Make glob pattern description more explicit ([#2944], thanks [@mulztob]) +- [Docs] [`no-extraneous-dependencies`]: Make glob pattern description more explicit ([#2944], thanks [@mulztob]) - [`no-unused-modules`]: add console message to help debug [#2866] - [Refactor] `ExportMap`: make procedures static instead of monkeypatching exportmap ([#2982], thanks [@soryy708]) - [Refactor] `ExportMap`: separate ExportMap instance from its builder logic ([#2985], thanks [@soryy708]) -- [Docs] `order`: Add a quick note on how unbound imports and --fix ([#2640], thanks [@minervabot]) +- [Docs] [`order`]: Add a quick note on how unbound imports and --fix ([#2640], thanks [@minervabot]) - [Tests] appveyor -> GHA (run tests on Windows in both pwsh and WSL + Ubuntu) ([#2987], thanks [@joeyguerra]) - [actions] migrate OSX tests to GHA ([ljharb#37], thanks [@aks-]) - [Refactor] `exportMapBuilder`: avoid hoisting ([#2989], thanks [@soryy708]) @@ -1831,6 +1831,7 @@ for info on changes for earlier releases. [@fsmaia]: https://github.com/fsmaia [@fson]: https://github.com/fson [@futpib]: https://github.com/futpib +[@G-Rath]: https://github.com/G-Rath [@gajus]: https://github.com/gajus [@gausie]: https://github.com/gausie [@gavriguy]: https://github.com/gavriguy From 095c16bc91a48d83e181fa4d97b0a8ba5fefe336 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Tue, 12 Nov 2024 22:24:47 +0000 Subject: [PATCH 48/50] [resolvers/webpack] [new] add cache option --- resolvers/webpack/CHANGELOG.md | 3 ++ resolvers/webpack/README.md | 11 +++++ resolvers/webpack/index.js | 21 +++++++- resolvers/webpack/test/cache.js | 48 +++++++++++++++++++ .../files/webpack.function.config.multiple.js | 1 + 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 resolvers/webpack/test/cache.js diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index 79b2837e3..1d23bf6fd 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](https://keepachangelog.com). ## Unreleased +- [new] add cache option ([#3100], thanks [@seiyab]) ## 0.13.9 - 2024-09-02 - [refactor] simplify loop ([#3029], thanks [@fregante]) @@ -182,6 +183,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Added - `interpret` configs (such as `.babel.js`). Thanks to [@gausie] for the initial PR ([#164], ages ago! 😅) and [@jquense] for tests ([#278]). +[#3100]: https://github.com/import-js/eslint-plugin-import/pull/3100 [#3029]: https://github.com/import-js/eslint-plugin-import/pull/3029 [#2287]: https://github.com/import-js/eslint-plugin-import/pull/2287 [#2023]: https://github.com/import-js/eslint-plugin-import/pull/2023 @@ -247,6 +249,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange [@Rogeres]: https://github.com/Rogeres [@Satyam]: https://github.com/Satyam [@Schweinepriester]: https://github.com/Schweinepriester +[@seiyab]: https://github.com/seiyab [@SkeLLLa]: https://github.com/SkeLLLa [@taion]: https://github.com/taion [@toshafed]: https://github.com/toshafed diff --git a/resolvers/webpack/README.md b/resolvers/webpack/README.md index 06513ba14..9b0139689 100644 --- a/resolvers/webpack/README.md +++ b/resolvers/webpack/README.md @@ -94,6 +94,17 @@ settings: production: true ``` +If your config is set as a function, it will be evaluated at every resolution. You have an option to prevent this by caching it using the `cache` parameter: + +```yaml +--- +settings: + import/resolver: + webpack: + config: 'webpack.config.js' + cache: true +``` + ## Support [Get supported eslint-import-resolver-webpack with the Tidelift Subscription](https://tidelift.com/subscription/pkg/npm-eslint-import-resolver-webpack?utm_source=npm-eslint-import-resolver-webpack&utm_medium=referral&utm_campaign=readme) diff --git a/resolvers/webpack/index.js b/resolvers/webpack/index.js index 83297cd18..ae736abe7 100644 --- a/resolvers/webpack/index.js +++ b/resolvers/webpack/index.js @@ -311,6 +311,22 @@ function getResolveSync(configPath, webpackConfig, cwd) { return cached.value; } +const _evalCache = new Map(); +function evaluateFunctionConfigCached(configPath, webpackConfig, env, argv) { + const cacheKey = JSON.stringify({ configPath, args: [env, argv] }); + if (_evalCache.has(cacheKey)) { + return _evalCache.get(cacheKey); + } + const cached = webpackConfig(env, argv); + _evalCache.set(cacheKey, cached); + + while (_evalCache.size > MAX_CACHE) { + // remove oldest item + _evalCache.delete(_evalCache.keys().next().value); + } + return cached; +} + /** * Find the full path to 'source', given 'file' as a full reference path. * @@ -354,6 +370,7 @@ exports.resolve = function (source, file, settings) { const configIndex = settings && settings['config-index']; const env = settings && settings.env; const argv = settings && typeof settings.argv !== 'undefined' ? settings.argv : {}; + const shouldCacheFunctionConfig = settings && settings.cache; let packageDir; let configPath = typeof _configPath === 'string' && _configPath.startsWith('.') @@ -398,7 +415,9 @@ exports.resolve = function (source, file, settings) { } if (typeof webpackConfig === 'function') { - webpackConfig = webpackConfig(env, argv); + webpackConfig = shouldCacheFunctionConfig + ? evaluateFunctionConfigCached(configPath, webpackConfig, env, argv) + : webpackConfig(env, argv); } if (isArray(webpackConfig)) { diff --git a/resolvers/webpack/test/cache.js b/resolvers/webpack/test/cache.js new file mode 100644 index 000000000..04d6de057 --- /dev/null +++ b/resolvers/webpack/test/cache.js @@ -0,0 +1,48 @@ +'use strict'; + +const chai = require('chai'); +const expect = chai.expect; +const path = require('path'); + +const resolve = require('../index').resolve; + +const file = path.join(__dirname, 'files', 'src', 'jsx', 'dummy.js'); + +describe('cache', function () { + it('can distinguish different config files', function () { + const setting1 = { + config: require(path.join(__dirname, './files/webpack.function.config.js')), + argv: { + mode: 'test', + }, + cache: true, + }; + expect(resolve('baz', file, setting1)).to.have.property('path') + .and.equal(path.join(__dirname, 'files', 'some', 'bar', 'bar.js')); + const setting2 = { + config: require(path.join(__dirname, './files/webpack.function.config.multiple.js')), + cache: true, + }; + expect(resolve('baz', file, setting2)).to.have.property('path') + .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js')); + }); + + it('can distinguish different config', function () { + const setting1 = { + config: require(path.join(__dirname, './files/webpack.function.config.js')), + env: { + dummy: true, + }, + cache: true, + }; + expect(resolve('bar', file, setting1)).to.have.property('path') + .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js')); + const setting2 = { + config: require(path.join(__dirname, './files/webpack.function.config.multiple.js')), + cache: true, + }; + const result = resolve('bar', file, setting2); + expect(result).not.to.have.property('path'); + expect(result).to.have.property('found').to.be.false; + }); +}); diff --git a/resolvers/webpack/test/files/webpack.function.config.multiple.js b/resolvers/webpack/test/files/webpack.function.config.multiple.js index 4dbc94bbc..8ab982bbc 100644 --- a/resolvers/webpack/test/files/webpack.function.config.multiple.js +++ b/resolvers/webpack/test/files/webpack.function.config.multiple.js @@ -7,6 +7,7 @@ module.exports = [function(env) { alias: { 'foo': path.join(__dirname, 'some', 'goofy', 'path', 'foo.js'), 'bar': env ? path.join(__dirname, 'some', 'goofy', 'path', 'bar.js') : undefined, + 'baz': path.join(__dirname, 'some', 'goofy', 'path', 'foo.js'), 'some-alias': path.join(__dirname, 'some'), }, modules: [ From 444716b5eba6f5056d65adc30b17fd606efec4af Mon Sep 17 00:00:00 2001 From: Yuri Mikushov Date: Fri, 15 Nov 2024 09:38:43 +0100 Subject: [PATCH 49/50] [Docs] `dynamic-import-chunkname`: fix typo in usage of the rule --- docs/rules/dynamic-import-chunkname.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/dynamic-import-chunkname.md b/docs/rules/dynamic-import-chunkname.md index de554148e..d9ee8d15e 100644 --- a/docs/rules/dynamic-import-chunkname.md +++ b/docs/rules/dynamic-import-chunkname.md @@ -15,7 +15,7 @@ You can also configure the regex format you'd like to accept for the webpackChun ```javascript { - "dynamic-import-chunkname": [2, { + "import/dynamic-import-chunkname": [2, { importFunctions: ["dynamicImport"], webpackChunknameFormat: "[a-zA-Z0-57-9-/_]+", allowEmpty: false From 412768134ee6c3bbba912f75c96063c3516cae22 Mon Sep 17 00:00:00 2001 From: Jan W Date: Thu, 21 Nov 2024 10:43:21 +0100 Subject: [PATCH 50/50] test: fix --- tests/src/rules/no-relative-packages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/rules/no-relative-packages.js b/tests/src/rules/no-relative-packages.js index 9b424506c..f47c6816b 100644 --- a/tests/src/rules/no-relative-packages.js +++ b/tests/src/rules/no-relative-packages.js @@ -73,11 +73,11 @@ ruleTester.run('no-relative-packages', rule, { code: 'import bar from "../bar"', filename: testFilePath('./package-named/index.js'), errors: [{ - message: `Relative import from another package is not allowed. Use \`${normalize('eslint-plugin-import/tests/files/bar')}\` instead of \`../bar\``, + message: `Relative import from another package is not allowed. Use \`${normalize('@exodus/eslint-plugin-import/tests/files/bar')}\` instead of \`../bar\``, line: 1, column: 17, }], - output: `import bar from "eslint-plugin-import/tests/files/bar"`, + output: `import bar from "@exodus/eslint-plugin-import/tests/files/bar"`, }), ], });