From 15ba863914fc041dcdc399df1a79784b0ba8354f Mon Sep 17 00:00:00 2001 From: Chiawen Chen Date: Fri, 18 Oct 2019 18:03:49 +0800 Subject: [PATCH 01/31] [Fix] false positive for prefer-default-export with type export --- src/rules/prefer-default-export.js | 8 ++++---- tests/src/rules/prefer-default-export.js | 9 ++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/rules/prefer-default-export.js b/src/rules/prefer-default-export.js index f0af00d02b..17a07688c3 100644 --- a/src/rules/prefer-default-export.js +++ b/src/rules/prefer-default-export.js @@ -14,6 +14,7 @@ module.exports = { let specifierExportCount = 0 let hasDefaultExport = false let hasStarExport = false + let hasTypeExport = false let namedExportNode = null function captureDeclaration(identifierOrPattern) { @@ -50,9 +51,6 @@ module.exports = { // if there are specifiers, node.declaration should be null if (!node.declaration) return - // don't warn on single type aliases, declarations, or interfaces - if (node.exportKind === 'type') return - const { type } = node.declaration if ( @@ -61,6 +59,8 @@ module.exports = { type === 'TSInterfaceDeclaration' || type === 'InterfaceDeclaration' ) { + specifierExportCount++ + hasTypeExport = true return } @@ -86,7 +86,7 @@ module.exports = { }, 'Program:exit': function() { - if (specifierExportCount === 1 && !hasDefaultExport && !hasStarExport) { + if (specifierExportCount === 1 && !hasDefaultExport && !hasStarExport && !hasTypeExport) { context.report(namedExportNode, 'Prefer default export.') } }, diff --git a/tests/src/rules/prefer-default-export.js b/tests/src/rules/prefer-default-export.js index 452192737f..19aef41e0b 100644 --- a/tests/src/rules/prefer-default-export.js +++ b/tests/src/rules/prefer-default-export.js @@ -3,7 +3,7 @@ import { test, getNonDefaultParsers } from '../utils' import { RuleTester } from 'eslint' const ruleTester = new RuleTester() - , rule = require('rules/prefer-default-export') + , rule = require('../../../src/rules/prefer-default-export') ruleTester.run('prefer-default-export', rule, { valid: [ @@ -194,6 +194,13 @@ context('TypeScript', function() { }, parserConfig, ), + test ( + { + code: 'export interface foo { bar: string; }; export function goo() {}', + parser, + }, + parserConfig, + ), ], invalid: [], }) From a0614a7871682b33915a83e2885b5c8fc85eb1a1 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 4 Dec 2019 23:12:09 -0800 Subject: [PATCH 02/31] [meta] add missing changelog entry from #1506 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6591aa52..ff5aeb3e05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - `default`: make error message less confusing ([#1470], thanks [@golopot]) - Support export of a merged TypeScript namespace declaration ([#1495], thanks [@benmunro]) - [`import/order`]: fix autofix to not move imports across fn calls ([#1253], thanks [@tihonove]) +- [`prefer-default-export`]: fix false positive with type export ([#1506], thanks [@golopot]) ## [2.18.2] - 2019-07-19 - Skip warning on type interfaces ([#1425], thanks [@lencioni]) @@ -609,6 +610,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1506]: https://github.com/benmosher/eslint-plugin-import/pull/1506 [#1495]: https://github.com/benmosher/eslint-plugin-import/pull/1495 [#1472]: https://github.com/benmosher/eslint-plugin-import/pull/1472 [#1470]: https://github.com/benmosher/eslint-plugin-import/pull/1470 From 1b96580940dd21c6e05c343496412e0e6df192c2 Mon Sep 17 00:00:00 2001 From: Brendan Abbott Date: Mon, 28 Oct 2019 12:47:46 +1000 Subject: [PATCH 03/31] [meta] Fix eslint comma-dangle violations --- src/rules/no-extraneous-dependencies.js | 2 +- tests/src/core/getExports.js | 2 +- tests/src/core/resolve.js | 32 ++++++++++++------------- tests/src/rules/no-duplicates.js | 2 +- tests/src/rules/no-unresolved.js | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 1351029cc8..003e7a044f 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -23,7 +23,7 @@ function extractDepFields(pkg) { peerDependencies: pkg.peerDependencies || {}, // BundledDeps should be in the form of an array, but object notation is also supported by // `npm`, so we convert it to an array if it is an object - bundledDependencies: arrayOrKeys(pkg.bundleDependencies || pkg.bundledDependencies || []) + bundledDependencies: arrayOrKeys(pkg.bundleDependencies || pkg.bundledDependencies || []), } } diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 44edcf6292..d61544e7a9 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -85,7 +85,7 @@ describe('ExportMap', function () { var imports = ExportMap.parse( path, contents, - { parserPath: 'babel-eslint', settings: {} } + { parserPath: 'babel-eslint', settings: {} }, ) expect(imports, 'imports').to.exist diff --git a/tests/src/core/resolve.js b/tests/src/core/resolve.js index 1664f8d90c..5d5bd3a206 100644 --- a/tests/src/core/resolve.js +++ b/tests/src/core/resolve.js @@ -16,15 +16,15 @@ describe('resolve', function () { const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v1' }) expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), )).to.equal(utils.testFilePath('./bar.jsx')) expect(resolve( '../files/exception' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }), )).to.equal(undefined) expect(resolve( '../files/not-found' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }), )).to.equal(undefined) }) @@ -32,15 +32,15 @@ describe('resolve', function () { const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-no-version' }) expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), )).to.equal(utils.testFilePath('./bar.jsx')) expect(resolve( '../files/exception' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }), )).to.equal(undefined) expect(resolve( '../files/not-found' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }), )).to.equal(undefined) }) @@ -52,12 +52,12 @@ describe('resolve', function () { } expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), )).to.equal(utils.testFilePath('./bar.jsx')) testContextReports.length = 0 expect(resolve( '../files/exception' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }), )).to.equal(undefined) expect(testContextReports[0]).to.be.an('object') expect(testContextReports[0].message).to.equal('Resolve error: foo-bar-resolver-v2 resolve test exception') @@ -65,7 +65,7 @@ describe('resolve', function () { testContextReports.length = 0 expect(resolve( '../files/not-found' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }), )).to.equal(undefined) expect(testContextReports.length).to.equal(0) }) @@ -74,7 +74,7 @@ describe('resolve', function () { const testContext = utils.testContext({ 'import/resolver': [ './foo-bar-resolver-v2', './foo-bar-resolver-v1' ] }) expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), )).to.equal(utils.testFilePath('./bar.jsx')) }) @@ -82,7 +82,7 @@ describe('resolve', function () { const testContext = utils.testContext({ 'import/resolver': { './foo-bar-resolver-v2': {} } }) expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), )).to.equal(utils.testFilePath('./bar.jsx')) }) @@ -90,7 +90,7 @@ describe('resolve', function () { const testContext = utils.testContext({ 'import/resolver': [ { './foo-bar-resolver-v2': {} }, { './foo-bar-resolver-v1': {} } ] }) expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), )).to.equal(utils.testFilePath('./bar.jsx')) }) @@ -103,7 +103,7 @@ describe('resolve', function () { testContextReports.length = 0 expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), )).to.equal(undefined) expect(testContextReports[0]).to.be.an('object') expect(testContextReports[0].message).to.equal('Resolve error: invalid resolver config') @@ -119,7 +119,7 @@ describe('resolve', function () { } testContextReports.length = 0 expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), )).to.equal(undefined) expect(testContextReports[0]).to.be.an('object') expect(testContextReports[0].message).to.equal(`Resolve error: ${resolverName} with invalid interface loaded as resolver`) @@ -130,7 +130,7 @@ describe('resolve', function () { const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] }}) expect(resolve( './jsx/MyCoolComponent' - , testContext + , testContext, )).to.equal(utils.testFilePath('./jsx/MyCoolComponent.jsx')) }) @@ -142,7 +142,7 @@ describe('resolve', function () { } expect(resolve( '../files/exception' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }), )).to.equal(undefined) expect(testContextReports[0]).to.be.an('object') expect(testContextReports[0].message).to.equal('Resolve error: TEST ERROR') diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index 29b080466f..a93fdfa925 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -45,7 +45,7 @@ ruleTester.run('no-duplicates', rule, { output: "import { x , y } from './bar'; ", settings: { 'import/resolve': { paths: [path.join( process.cwd() - , 'tests', 'files' + , 'tests', 'files', )] }}, errors: 2, // path ends up hardcoded }), diff --git a/tests/src/rules/no-unresolved.js b/tests/src/rules/no-unresolved.js index 0c3a9008cb..cae6e8468b 100644 --- a/tests/src/rules/no-unresolved.js +++ b/tests/src/rules/no-unresolved.js @@ -15,7 +15,7 @@ function runResolverTests(resolver) { function rest(specs) { specs.settings = Object.assign({}, specs.settings, - { 'import/resolver': resolver } + { 'import/resolver': resolver }, ) return test(specs) From fb8ae719e0a8e28ef0776545ae02350a0e5e17f5 Mon Sep 17 00:00:00 2001 From: Brendan Abbott Date: Mon, 28 Oct 2019 12:05:44 +1000 Subject: [PATCH 04/31] When populating ExportMap, only load file if it's not ignored --- CHANGELOG.md | 3 +++ src/ExportMap.js | 13 ++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff5aeb3e05..0c893274e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Fixed - `default`: make error message less confusing ([#1470], thanks [@golopot]) +- Improve performance of `ExportMap.for` by only loading paths when necessary. ([#1519], thanks [@brendo]) - Support export of a merged TypeScript namespace declaration ([#1495], thanks [@benmunro]) - [`import/order`]: fix autofix to not move imports across fn calls ([#1253], thanks [@tihonove]) - [`prefer-default-export`]: fix false positive with type export ([#1506], thanks [@golopot]) @@ -610,6 +611,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1519]: https://github.com/benmosher/eslint-plugin-import/pull/1519 [#1506]: https://github.com/benmosher/eslint-plugin-import/pull/1506 [#1495]: https://github.com/benmosher/eslint-plugin-import/pull/1495 [#1472]: https://github.com/benmosher/eslint-plugin-import/pull/1472 @@ -998,3 +1000,4 @@ for info on changes for earlier releases. [@TrevorBurnham]: https://github.com/TrevorBurnham [@benmunro]: https://github.com/benmunro [@tihonove]: https://github.com/tihonove +[@brendo]: https://github.com/brendo diff --git a/src/ExportMap.js b/src/ExportMap.js index c9544c9c87..8009f8b831 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -310,11 +310,18 @@ ExportMap.for = function (context) { return null } + // check for and cache ignore + if (isIgnored(path, context)) { + log('ignored path due to ignore settings:', path) + exportCache.set(cacheKey, null) + return null + } + const content = fs.readFileSync(path, { encoding: 'utf8' }) - // check for and cache ignore - if (isIgnored(path, context) || !unambiguous.test(content)) { - log('ignored path due to unambiguous regex or ignore settings:', path) + // check for and cache unambigious modules + if (!unambiguous.test(content)) { + log('ignored path due to unambiguous regex:', path) exportCache.set(cacheKey, null) return null } From 568ca430e3114c582e0ae49509ce294347f6e722 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Mon, 28 Oct 2019 16:57:56 +0900 Subject: [PATCH 05/31] [Fix] `extensions`: Fix `ignorePackages` to produce errors --- CHANGELOG.md | 5 ++++- src/rules/extensions.js | 5 +++++ tests/src/rules/extensions.js | 24 +++++++++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c893274e0..33f9267854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,12 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-extraneous-dependencies`]: Implement support for [bundledDependencies](https://npm.github.io/using-pkgs-docs/package-json/types/bundleddependencies.html) ([#1436], thanks [@schmidsi])) ### Fixed -- `default`: make error message less confusing ([#1470], thanks [@golopot]) +- [`default`]: make error message less confusing ([#1470], thanks [@golopot]) - Improve performance of `ExportMap.for` by only loading paths when necessary. ([#1519], thanks [@brendo]) - Support export of a merged TypeScript namespace declaration ([#1495], thanks [@benmunro]) - [`import/order`]: fix autofix to not move imports across fn calls ([#1253], thanks [@tihonove]) - [`prefer-default-export`]: fix false positive with type export ([#1506], thanks [@golopot]) +- [`extensions`]: Fix `ignorePackages` to produce errors ([#1521], thanks [@saschanaz]) ## [2.18.2] - 2019-07-19 - Skip warning on type interfaces ([#1425], thanks [@lencioni]) @@ -611,6 +612,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1521]: https://github.com/benmosher/eslint-plugin-import/pull/1521 [#1519]: https://github.com/benmosher/eslint-plugin-import/pull/1519 [#1506]: https://github.com/benmosher/eslint-plugin-import/pull/1506 [#1495]: https://github.com/benmosher/eslint-plugin-import/pull/1495 @@ -1001,3 +1003,4 @@ for info on changes for earlier releases. [@benmunro]: https://github.com/benmunro [@tihonove]: https://github.com/tihonove [@brendo]: https://github.com/brendo +[@saschanaz]: https://github.com/saschanaz diff --git a/src/rules/extensions.js b/src/rules/extensions.js index b72c91bad0..0fe605adcb 100644 --- a/src/rules/extensions.js +++ b/src/rules/extensions.js @@ -50,6 +50,11 @@ function buildProperties(context) { } }) + if (result.defaultConfig === 'ignorePackages') { + result.defaultConfig = 'always' + result.ignorePackages = true + } + return result } diff --git a/tests/src/rules/extensions.js b/tests/src/rules/extensions.js index d7b97bea0b..a1629335c6 100644 --- a/tests/src/rules/extensions.js +++ b/tests/src/rules/extensions.js @@ -63,7 +63,7 @@ ruleTester.run('extensions', rule, { code: ` import foo from './foo.js' import bar from './bar.json' - import Component from './Component' + import Component from './Component.jsx' import express from 'express' `, options: [ 'ignorePackages' ], @@ -309,6 +309,28 @@ ruleTester.run('extensions', rule, { ], }), + test({ + code: ` + import foo from './foo.js' + import bar from './bar.json' + import Component from './Component' + import baz from 'foo/baz' + import express from 'express' + `, + options: [ 'ignorePackages' ], + errors: [ + { + message: 'Missing file extension for "./Component"', + line: 4, + column: 31, + }, { + message: 'Missing file extension for "foo/baz"', + line: 5, + column: 25, + }, + ], + }), + test({ code: ` import foo from './foo.js' From 2cdfc19d44cbe4a7faa22dc00a91291340bacca5 Mon Sep 17 00:00:00 2001 From: Chiawen Chen Date: Fri, 18 Oct 2019 18:25:16 +0800 Subject: [PATCH 06/31] [Docs] `no-useless-path-segments`: add docs for option `commonjs` --- CHANGELOG.md | 4 ++++ docs/rules/no-useless-path-segments.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33f9267854..81ca852140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`prefer-default-export`]: fix false positive with type export ([#1506], thanks [@golopot]) - [`extensions`]: Fix `ignorePackages` to produce errors ([#1521], thanks [@saschanaz]) +### Docs +- [`no-useless-path-segments`]: add docs for option `commonjs` ([#1507], thanks [@golopot]) + ## [2.18.2] - 2019-07-19 - Skip warning on type interfaces ([#1425], thanks [@lencioni]) @@ -614,6 +617,7 @@ for info on changes for earlier releases. [#1521]: https://github.com/benmosher/eslint-plugin-import/pull/1521 [#1519]: https://github.com/benmosher/eslint-plugin-import/pull/1519 +[#1507]: https://github.com/benmosher/eslint-plugin-import/pull/1507 [#1506]: https://github.com/benmosher/eslint-plugin-import/pull/1506 [#1495]: https://github.com/benmosher/eslint-plugin-import/pull/1495 [#1472]: https://github.com/benmosher/eslint-plugin-import/pull/1472 diff --git a/docs/rules/no-useless-path-segments.md b/docs/rules/no-useless-path-segments.md index 6a02eab9fa..19b7725855 100644 --- a/docs/rules/no-useless-path-segments.md +++ b/docs/rules/no-useless-path-segments.md @@ -73,3 +73,7 @@ import "./pages/index.js"; // should be "./pages" (auto-fixable) ``` Note: `noUselessIndex` only avoids ambiguous imports for `.js` files if you haven't specified other resolved file extensions. See [Settings: import/extensions](https://github.com/benmosher/eslint-plugin-import#importextensions) for details. + +### commonjs + +When set to `true`, this rule checks CommonJS imports. Default to `false`. From c37e42f3cb9c7dc7739e052aa32b570059469362 Mon Sep 17 00:00:00 2001 From: Liqueur Librazy Date: Sun, 29 Sep 2019 15:36:07 +0800 Subject: [PATCH 07/31] [new] `core`: add internal-regex setting for marking packages as internal --- CHANGELOG.md | 3 +++ README.md | 14 ++++++++++++++ docs/rules/order.md | 4 ++++ src/core/importType.js | 3 ++- tests/src/core/importType.js | 12 +++++++++++- 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81ca852140..d075a16e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +### Added +- [`internal-regex`]: regex pattern for marking packages "internal" ([#1491], thanks [@Librazy]) + ### Added - [`group-exports`]: make aggregate module exports valid ([#1472], thanks [@atikenny]) - [`no-namespace`]: Make rule fixable ([#1401], thanks [@TrevorBurnham]) diff --git a/README.md b/README.md index 76d50f44cf..814d5fc28d 100644 --- a/README.md +++ b/README.md @@ -402,6 +402,20 @@ settings: [`eslint_d`]: https://www.npmjs.com/package/eslint_d [`eslint-loader`]: https://www.npmjs.com/package/eslint-loader +#### `import/internal-regex` + +A regex for packages should be treated as internal. Useful when you are utilizing a monorepo setup or developing a set of packages that depend on each other. + +By default, any package referenced from [`import/external-module-folders`](#importexternal-module-folders) will be considered as "external", including packages in a monorepo like yarn workspace or lerna emvironentment. If you want to mark these packages as "internal" this will be useful. + +For example, if you pacakges in a monorepo are all in `@scope`, you can configure `import/internal-regex` like this + +```yaml +# .eslintrc.yml +settings: + import/internal-regex: ^@scope/ +``` + ## SublimeLinter-eslint diff --git a/docs/rules/order.md b/docs/rules/order.md index 88ddca46fb..d716430481 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -168,4 +168,8 @@ import sibling from './foo'; - [`import/external-module-folders`] setting +- [`import/internal-regex`] setting + [`import/external-module-folders`]: ../../README.md#importexternal-module-folders + +[`import/internal-regex`]: ../../README.md#importinternal-regex diff --git a/src/core/importType.js b/src/core/importType.js index b948ea2bb9..a3480ae12a 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -54,8 +54,9 @@ export function isScopedMain(name) { } function isInternalModule(name, settings, path) { + const internalScope = (settings && settings['import/internal-regex']) const matchesScopedOrExternalRegExp = scopedRegExp.test(name) || externalModuleRegExp.test(name) - return (matchesScopedOrExternalRegExp && !isExternalPath(path, name, settings)) + return (matchesScopedOrExternalRegExp && (internalScope && new RegExp(internalScope).test(name) || !isExternalPath(path, name, settings))) } function isRelativeToParent(name) { diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js index 07466bfa92..c85124a1f4 100644 --- a/tests/src/core/importType.js +++ b/tests/src/core/importType.js @@ -51,7 +51,7 @@ describe('importType(name)', function () { const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }) expect(importType('@importType/index', pathContext)).to.equal('internal') }) - + it("should return 'internal' for internal modules that are referenced by aliases", function () { const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }) expect(importType('@my-alias/fn', pathContext)).to.equal('internal') @@ -130,6 +130,16 @@ describe('importType(name)', function () { expect(importType('resolve', foldersContext)).to.equal('internal') }) + it("should return 'internal' for module from 'node_modules' if its name matched 'internal-regex'", function() { + const foldersContext = testContext({ 'import/internal-regex': '^@org' }) + expect(importType('@org/foobar', foldersContext)).to.equal('internal') + }) + + it("should return 'external' for module from 'node_modules' if its name did not match 'internal-regex'", function() { + const foldersContext = testContext({ 'import/internal-regex': '^@bar' }) + expect(importType('@org/foobar', foldersContext)).to.equal('external') + }) + it("should return 'external' for module from 'node_modules' if 'node_modules' contained in 'external-module-folders'", function() { const foldersContext = testContext({ 'import/external-module-folders': ['node_modules'] }) expect(importType('resolve', foldersContext)).to.equal('external') From 14c71a3fde4611226320c6fc1eb6ebbb6115f61d Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Tue, 3 Dec 2019 12:29:23 +0800 Subject: [PATCH 08/31] [Refactor] `no-unused-modules`/`es-modules-utils`: Avoid superfluous calls and code --- CHANGELOG.md | 7 +++++-- src/rules/no-unused-modules.js | 2 +- src/rules/order.js | 2 +- utils/CHANGELOG.md | 5 +++++ utils/resolve.js | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d075a16e3c..6639a77c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,6 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Added - [`internal-regex`]: regex pattern for marking packages "internal" ([#1491], thanks [@Librazy]) - -### Added - [`group-exports`]: make aggregate module exports valid ([#1472], thanks [@atikenny]) - [`no-namespace`]: Make rule fixable ([#1401], thanks [@TrevorBurnham]) - support `parseForESLint` from custom parser ([#1435], thanks [@JounQin]) @@ -25,6 +23,9 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Docs - [`no-useless-path-segments`]: add docs for option `commonjs` ([#1507], thanks [@golopot]) +### Changed +- [`no-unused-modules`]/`eslint-module-utils`: Avoid superfluous calls and code ([#1551], thanks [@brettz9]) + ## [2.18.2] - 2019-07-19 - Skip warning on type interfaces ([#1425], thanks [@lencioni]) @@ -618,6 +619,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1551]: https://github.com/benmosher/eslint-plugin-import/pull/1551 [#1521]: https://github.com/benmosher/eslint-plugin-import/pull/1521 [#1519]: https://github.com/benmosher/eslint-plugin-import/pull/1519 [#1507]: https://github.com/benmosher/eslint-plugin-import/pull/1507 @@ -1011,3 +1013,4 @@ for info on changes for earlier releases. [@tihonove]: https://github.com/tihonove [@brendo]: https://github.com/brendo [@saschanaz]: https://github.com/saschanaz +[@brettz9]: https://github.com/brettz9 diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 9cd7814754..860a73d625 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -355,7 +355,7 @@ module.exports = { exportCount.delete(EXPORT_ALL_DECLARATION) exportCount.delete(IMPORT_NAMESPACE_SPECIFIER) - if (missingExports && exportCount.size < 1) { + if (exportCount.size < 1) { // node.body[0] === 'undefined' only happens, if everything is commented out in the file // being linted context.report(node.body[0] ? node.body[0] : node, 'No exports found') diff --git a/src/rules/order.js b/src/rules/order.js index cd3db89453..920345ff2c 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -340,7 +340,7 @@ function makeNewlinesBetweenReport (context, imported, newlinesBetweenImports) { context.report({ node: previousImport.node, message: 'There should be at least one empty line between import groups', - fix: fixNewLineAfterImport(context, previousImport, currentImport), + fix: fixNewLineAfterImport(context, previousImport), }) } else if (currentImport.rank === previousImport.rank && emptyLinesBetween > 0 diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index 34bae11215..c42cbced40 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,9 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +### Changed + - Avoid superfluous calls and code ([#1551], thanks [@brettz9]) + ## v2.4.1 - 2019-07-19 ### Fixed @@ -52,6 +55,7 @@ Yanked due to critical issue with cache key resulting from #839. +[#1551]: https://github.com/benmosher/eslint-plugin-import/pull/1551 [#1409]: https://github.com/benmosher/eslint-plugin-import/pull/1409 [#1356]: https://github.com/benmosher/eslint-plugin-import/pull/1356 [#1290]: https://github.com/benmosher/eslint-plugin-import/pull/1290 @@ -65,3 +69,4 @@ Yanked due to critical issue with cache key resulting from #839. [@vikr01]: https://github.com/vikr01 [@bradzacher]: https://github.com/bradzacher [@christophercurrie]: https://github.com/christophercurrie +[@brettz9]: https://github.com/brettz9 diff --git a/utils/resolve.js b/utils/resolve.js index fdd6f1ee54..92c8f35002 100644 --- a/utils/resolve.js +++ b/utils/resolve.js @@ -64,7 +64,7 @@ function relative(modulePath, sourceFile, settings) { function fullResolve(modulePath, sourceFile, settings) { // check if this is a bonus core module const coreSet = new Set(settings['import/core-modules']) - if (coreSet != null && coreSet.has(modulePath)) return { found: true, path: null } + if (coreSet.has(modulePath)) return { found: true, path: null } const sourceDir = path.dirname(sourceFile) , cacheKey = sourceDir + hashObject(settings).digest('hex') + modulePath From 05085bbdafa624d8cf6a765b9e078c41c931679b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sun, 17 Nov 2019 10:47:01 +0100 Subject: [PATCH 09/31] [flow] `no-unused-modules`: add flow type support --- CHANGELOG.md | 2 ++ src/rules/no-unused-modules.js | 7 ++++-- tests/files/no-unused-modules/flow-0.js | 1 + tests/files/no-unused-modules/flow-1.js | 2 ++ tests/files/no-unused-modules/flow-2.js | 2 ++ tests/src/rules/no-unused-modules.js | 32 +++++++++++++++++++++++++ 6 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/files/no-unused-modules/flow-0.js create mode 100644 tests/files/no-unused-modules/flow-1.js create mode 100644 tests/files/no-unused-modules/flow-2.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 6639a77c13..fc51048f3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-namespace`]: Make rule fixable ([#1401], thanks [@TrevorBurnham]) - support `parseForESLint` from custom parser ([#1435], thanks [@JounQin]) - [`no-extraneous-dependencies`]: Implement support for [bundledDependencies](https://npm.github.io/using-pkgs-docs/package-json/types/bundleddependencies.html) ([#1436], thanks [@schmidsi])) +- [`no-unused-modules`]: add flow type support ([#1542], thanks [@rfermann]) ### Fixed - [`default`]: make error message less confusing ([#1470], thanks [@golopot]) @@ -620,6 +621,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md [#1551]: https://github.com/benmosher/eslint-plugin-import/pull/1551 +[#1542]: https://github.com/benmosher/eslint-plugin-import/pull/1542 [#1521]: https://github.com/benmosher/eslint-plugin-import/pull/1521 [#1519]: https://github.com/benmosher/eslint-plugin-import/pull/1519 [#1507]: https://github.com/benmosher/eslint-plugin-import/pull/1507 diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 860a73d625..ccb16fd95a 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -42,6 +42,7 @@ const VARIABLE_DECLARATION = 'VariableDeclaration' const FUNCTION_DECLARATION = 'FunctionDeclaration' const CLASS_DECLARATION = 'ClassDeclaration' const DEFAULT = 'default' +const TYPE_ALIAS = 'TypeAlias' let preparationDone = false const importList = new Map() @@ -463,7 +464,8 @@ module.exports = { if (declaration) { if ( declaration.type === FUNCTION_DECLARATION || - declaration.type === CLASS_DECLARATION + declaration.type === CLASS_DECLARATION || + declaration.type === TYPE_ALIAS ) { newExportIdentifiers.add(declaration.id.name) } @@ -788,7 +790,8 @@ module.exports = { if (node.declaration) { if ( node.declaration.type === FUNCTION_DECLARATION || - node.declaration.type === CLASS_DECLARATION + node.declaration.type === CLASS_DECLARATION || + node.declaration.type === TYPE_ALIAS ) { checkUsage(node, node.declaration.id.name) } diff --git a/tests/files/no-unused-modules/flow-0.js b/tests/files/no-unused-modules/flow-0.js new file mode 100644 index 0000000000..46bda68794 --- /dev/null +++ b/tests/files/no-unused-modules/flow-0.js @@ -0,0 +1 @@ +import { type FooType } from './flow-2'; diff --git a/tests/files/no-unused-modules/flow-1.js b/tests/files/no-unused-modules/flow-1.js new file mode 100644 index 0000000000..bb7266d3ce --- /dev/null +++ b/tests/files/no-unused-modules/flow-1.js @@ -0,0 +1,2 @@ +// @flow strict +export type Bar = number; diff --git a/tests/files/no-unused-modules/flow-2.js b/tests/files/no-unused-modules/flow-2.js new file mode 100644 index 0000000000..0cbb836a6d --- /dev/null +++ b/tests/files/no-unused-modules/flow-2.js @@ -0,0 +1,2 @@ +// @flow strict +export type FooType = string; diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 792748cd86..fed0f39bb5 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -638,3 +638,35 @@ describe('do not report unused export for files mentioned in package.json', () = ], }) }) + +describe('correctly report flow types', () => { + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ + options: unusedExportsOptions, + code: 'import { type FooType } from "./flow-2";', + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/flow-0.js'), + }), + test({ + options: unusedExportsOptions, + code: `// @flow strict + export type FooType = string;`, + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/flow-2.js'), + }), + ], + invalid: [ + test({ + options: unusedExportsOptions, + code: `// @flow strict + export type Bar = string;`, + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/flow-1.js'), + errors: [ + error(`exported declaration 'Bar' not used within other modules`), + ], + }), + ], + }) +}) From 0cd5e438728a44b15899e0029865dfa6752170be Mon Sep 17 00:00:00 2001 From: Yoann Prot Date: Tue, 8 Oct 2019 13:26:28 +0200 Subject: [PATCH 10/31] [Fix] `no-unused-modules`: fix crash due to `export *` --- CHANGELOG.md | 3 +++ src/rules/no-unused-modules.js | 9 +++++++-- tests/files/no-unused-modules/cjs.js | 7 +++++++ tests/files/no-unused-modules/filte-r.js | 1 + tests/src/rules/no-unused-modules.js | 16 ++++++++++++++++ 5 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/files/no-unused-modules/cjs.js create mode 100644 tests/files/no-unused-modules/filte-r.js diff --git a/CHANGELOG.md b/CHANGELOG.md index fc51048f3a..dbbc991db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`import/order`]: fix autofix to not move imports across fn calls ([#1253], thanks [@tihonove]) - [`prefer-default-export`]: fix false positive with type export ([#1506], thanks [@golopot]) - [`extensions`]: Fix `ignorePackages` to produce errors ([#1521], thanks [@saschanaz]) +- [`no-unused-modules`]: fix crash due to `export *` ([#1496], thanks [@Taranys]) ### Docs - [`no-useless-path-segments`]: add docs for option `commonjs` ([#1507], thanks [@golopot]) @@ -626,6 +627,7 @@ for info on changes for earlier releases. [#1519]: https://github.com/benmosher/eslint-plugin-import/pull/1519 [#1507]: https://github.com/benmosher/eslint-plugin-import/pull/1507 [#1506]: https://github.com/benmosher/eslint-plugin-import/pull/1506 +[#1496]: https://github.com/benmosher/eslint-plugin-import/pull/1496 [#1495]: https://github.com/benmosher/eslint-plugin-import/pull/1495 [#1472]: https://github.com/benmosher/eslint-plugin-import/pull/1472 [#1470]: https://github.com/benmosher/eslint-plugin-import/pull/1470 @@ -1016,3 +1018,4 @@ for info on changes for earlier releases. [@brendo]: https://github.com/brendo [@saschanaz]: https://github.com/saschanaz [@brettz9]: https://github.com/brettz9 +[@Taranys]: https://github.com/Taranys diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index ccb16fd95a..9bbafe99db 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -88,8 +88,13 @@ const prepareImportsAndExports = (srcFiles, context) => { // dependencies === export * from const currentExportAll = new Set() - dependencies.forEach(value => { - currentExportAll.add(value().path) + dependencies.forEach(getDependency => { + const dependency = getDependency() + if (dependency === null) { + return + } + + currentExportAll.add(dependency.path) }) exportAll.set(file, currentExportAll) diff --git a/tests/files/no-unused-modules/cjs.js b/tests/files/no-unused-modules/cjs.js new file mode 100644 index 0000000000..d5d7fbb98d --- /dev/null +++ b/tests/files/no-unused-modules/cjs.js @@ -0,0 +1,7 @@ +// Simple import extracted from 'redux-starter-kit' compiled file + +function isPlain(val) { + return true; +} + +exports.isPlain = isPlain; diff --git a/tests/files/no-unused-modules/filte-r.js b/tests/files/no-unused-modules/filte-r.js new file mode 100644 index 0000000000..c5b0dbbfeb --- /dev/null +++ b/tests/files/no-unused-modules/filte-r.js @@ -0,0 +1 @@ +export * from './cjs' diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index fed0f39bb5..5afae4dfac 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -453,6 +453,11 @@ describe('test behaviour for new file', () => { test({ options: unusedExportsOptions, code: `export * from '${testFilePath('./no-unused-modules/file-added-0.js')}'`, filename: testFilePath('./no-unused-modules/file-0.js')}), + // Test export * from 'external-compiled-library' + test({ options: unusedExportsOptions, + code: `export * from 'external-compiled-library'`, + filename: testFilePath('./no-unused-modules/file-r.js'), + }), ], invalid: [ test({ options: unusedExportsOptions, @@ -670,3 +675,14 @@ describe('correctly report flow types', () => { ], }) }) + +describe('Avoid errors if re-export all from umd compiled library', () => { + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `export * from '${testFilePath('./no-unused-modules/bin.js')}'`, + filename: testFilePath('./no-unused-modules/main/index.js')}), + ], + invalid: [], + }) +}) From 21bf8c665f15647f8fa9651b61a5332b8c26cd83 Mon Sep 17 00:00:00 2001 From: Maxim Malov Date: Sat, 5 Oct 2019 14:08:07 +0600 Subject: [PATCH 11/31] [Fix] `no-cycle`: should not warn for Flow imports --- CHANGELOG.md | 3 +++ src/ExportMap.js | 12 ++++++++++-- tests/files/cycles/flow-types-depth-one.js | 6 ++++++ tests/files/cycles/flow-types-depth-two.js | 1 + tests/files/cycles/flow-types.js | 6 ++++++ tests/src/rules/no-cycle.js | 9 +++++++++ 6 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 tests/files/cycles/flow-types-depth-one.js create mode 100644 tests/files/cycles/flow-types-depth-two.js create mode 100644 tests/files/cycles/flow-types.js diff --git a/CHANGELOG.md b/CHANGELOG.md index dbbc991db8..e6995d1737 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`prefer-default-export`]: fix false positive with type export ([#1506], thanks [@golopot]) - [`extensions`]: Fix `ignorePackages` to produce errors ([#1521], thanks [@saschanaz]) - [`no-unused-modules`]: fix crash due to `export *` ([#1496], thanks [@Taranys]) +- [`no-cycle`]: should not warn for Flow imports ([#1494], thanks [@maxmalov]) ### Docs - [`no-useless-path-segments`]: add docs for option `commonjs` ([#1507], thanks [@golopot]) @@ -629,6 +630,7 @@ for info on changes for earlier releases. [#1506]: https://github.com/benmosher/eslint-plugin-import/pull/1506 [#1496]: https://github.com/benmosher/eslint-plugin-import/pull/1496 [#1495]: https://github.com/benmosher/eslint-plugin-import/pull/1495 +[#1494]: https://github.com/benmosher/eslint-plugin-import/pull/1494 [#1472]: https://github.com/benmosher/eslint-plugin-import/pull/1472 [#1470]: https://github.com/benmosher/eslint-plugin-import/pull/1470 [#1436]: https://github.com/benmosher/eslint-plugin-import/pull/1436 @@ -1019,3 +1021,4 @@ for info on changes for earlier releases. [@saschanaz]: https://github.com/saschanaz [@brettz9]: https://github.com/brettz9 [@Taranys]: https://github.com/Taranys +[@maxmalov]: https://github.com/maxmalov diff --git a/src/ExportMap.js b/src/ExportMap.js index 8009f8b831..ba455e3685 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -404,19 +404,27 @@ ExportMap.parse = function (path, content, context) { function captureDependency(declaration) { if (declaration.source == null) return null + if (declaration.importKind === 'type') return null // skip Flow type imports const importedSpecifiers = new Set() const supportedTypes = new Set(['ImportDefaultSpecifier', 'ImportNamespaceSpecifier']) + let hasImportedType = false if (declaration.specifiers) { declaration.specifiers.forEach(specifier => { - if (supportedTypes.has(specifier.type)) { + const isType = specifier.importKind === 'type' + hasImportedType = hasImportedType || isType + + if (supportedTypes.has(specifier.type) && !isType) { importedSpecifiers.add(specifier.type) } - if (specifier.type === 'ImportSpecifier') { + if (specifier.type === 'ImportSpecifier' && !isType) { importedSpecifiers.add(specifier.imported.name) } }) } + // only Flow types were imported + if (hasImportedType && importedSpecifiers.size === 0) return null + const p = remotePath(declaration.source.value) if (p == null) return null const existing = m.imports.get(p) diff --git a/tests/files/cycles/flow-types-depth-one.js b/tests/files/cycles/flow-types-depth-one.js new file mode 100644 index 0000000000..f8a7a4b47c --- /dev/null +++ b/tests/files/cycles/flow-types-depth-one.js @@ -0,0 +1,6 @@ +// @flow + +import type { FooType } from './flow-types-depth-two'; +import { type BarType, bar } from './flow-types-depth-two'; + +export { bar } diff --git a/tests/files/cycles/flow-types-depth-two.js b/tests/files/cycles/flow-types-depth-two.js new file mode 100644 index 0000000000..9058840ac6 --- /dev/null +++ b/tests/files/cycles/flow-types-depth-two.js @@ -0,0 +1 @@ +import { foo } from './depth-one' diff --git a/tests/files/cycles/flow-types.js b/tests/files/cycles/flow-types.js new file mode 100644 index 0000000000..fbfb69f309 --- /dev/null +++ b/tests/files/cycles/flow-types.js @@ -0,0 +1,6 @@ +// @flow + +import type { FooType } from './flow-types-depth-two'; +import { type BarType } from './flow-types-depth-two'; + +export const bar = 1; diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js index 18fe88af18..df1e6d1433 100644 --- a/tests/src/rules/no-cycle.js +++ b/tests/src/rules/no-cycle.js @@ -53,6 +53,10 @@ ruleTester.run('no-cycle', rule, { code: 'import type { FooType, BarType } from "./depth-one"', parser: require.resolve('babel-eslint'), }), + test({ + code: 'import { bar } from "./flow-types"', + parser: require.resolve('babel-eslint'), + }), ], invalid: [ test({ @@ -120,6 +124,11 @@ ruleTester.run('no-cycle', rule, { errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)], parser: require.resolve('babel-eslint'), }), + test({ + code: 'import { bar } from "./flow-types-depth-one"', + parser: require.resolve('babel-eslint'), + errors: [error(`Dependency cycle via ./flow-types-depth-two:4=>./depth-one:1`)], + }), ], }) // }) From 99b3fbf8f25686aa86ba80661e5ba94d85e8f3d3 Mon Sep 17 00:00:00 2001 From: Marcus Armstrong Date: Mon, 2 Dec 2019 17:41:14 -0500 Subject: [PATCH 12/31] [Fix] `no-extraneous-dependencies`: Add support for `export from` Fixes #1049. --- CHANGELOG.md | 3 +++ src/rules/no-extraneous-dependencies.js | 6 ++++++ tests/src/rules/no-extraneous-dependencies.js | 16 ++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6995d1737..ba7f308459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] +- [`no-extraneous-dependencies`]: Check `export from` ([#1049], thanks [@marcusdarmstrong]) ### Added - [`internal-regex`]: regex pattern for marking packages "internal" ([#1491], thanks [@Librazy]) @@ -691,6 +692,7 @@ for info on changes for earlier releases. [#1093]: https://github.com/benmosher/eslint-plugin-import/pull/1093 [#1085]: https://github.com/benmosher/eslint-plugin-import/pull/1085 [#1068]: https://github.com/benmosher/eslint-plugin-import/pull/1068 +[#1049]: https://github.com/benmosher/eslint-plugin-import/pull/1049 [#1046]: https://github.com/benmosher/eslint-plugin-import/pull/1046 [#944]: https://github.com/benmosher/eslint-plugin-import/pull/944 [#912]: https://github.com/benmosher/eslint-plugin-import/pull/912 @@ -1022,3 +1024,4 @@ for info on changes for earlier releases. [@brettz9]: https://github.com/brettz9 [@Taranys]: https://github.com/Taranys [@maxmalov]: https://github.com/maxmalov +[@marcusdarmstrong]: https://github.com/marcusdarmstrong diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 003e7a044f..ced0f44b60 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -205,6 +205,12 @@ module.exports = { ImportDeclaration: function (node) { reportIfMissing(context, deps, depsOptions, node, node.source.value) }, + ExportNamedDeclaration: function (node) { + reportIfMissing(context, deps, depsOptions, node, node.source.value) + }, + ExportAllDeclaration: function (node) { + reportIfMissing(context, deps, depsOptions, node, node.source.value) + }, CallExpression: function handleRequires(node) { if (isStaticRequire(node)) { reportIfMissing(context, deps, depsOptions, node, node.arguments[0].value) diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index b50f9923b5..d29f23857f 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -122,6 +122,8 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import foo from "@generated/foo"', options: [{packageDir: packageDirBundledDepsRaceCondition}], }), + test({ code: 'export { foo } from "lodash.cond"' }), + test({ code: 'export * from "lodash.cond"' }), ], invalid: [ test({ @@ -319,5 +321,19 @@ ruleTester.run('no-extraneous-dependencies', rule, { options: [{packageDir: packageDirBundledDepsRaceCondition}], errors: ["'@generated/bar' should be listed in the project's dependencies. Run 'npm i -S @generated/bar' to add it"], }), + test({ + code: 'export { foo } from "not-a-dependency";', + errors: [{ + ruleId: 'no-extraneous-dependencies', + message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', + }], + }), + test({ + code: 'export * from "not-a-dependency";', + errors: [{ + ruleId: 'no-extraneous-dependencies', + message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', + }], + }), ], }) From 0426f164304d0acb2c0e241409025e63aa877e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Gro=C3=9Fe?= Date: Thu, 20 Jun 2019 08:11:42 +0200 Subject: [PATCH 13/31] [New] `order`: add pathGroups option to add support to order by paths Co-Authored-By: Matt Seccafien --- CHANGELOG.md | 4 + docs/rules/order.md | 26 +++++ src/rules/order.js | 98 ++++++++++++++++- tests/src/rules/order.js | 228 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 349 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba7f308459..a6143e9159 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - support `parseForESLint` from custom parser ([#1435], thanks [@JounQin]) - [`no-extraneous-dependencies`]: Implement support for [bundledDependencies](https://npm.github.io/using-pkgs-docs/package-json/types/bundleddependencies.html) ([#1436], thanks [@schmidsi])) - [`no-unused-modules`]: add flow type support ([#1542], thanks [@rfermann]) +- [`order`]: Adds support for pathGroups to allow ordering by defined patterns ([#795], [#1386], thanks [@Mairu]) ### Fixed - [`default`]: make error message less confusing ([#1470], thanks [@golopot]) @@ -644,6 +645,7 @@ for info on changes for earlier releases. [#1401]: https://github.com/benmosher/eslint-plugin-import/pull/1401 [#1393]: https://github.com/benmosher/eslint-plugin-import/pull/1393 [#1389]: https://github.com/benmosher/eslint-plugin-import/pull/1389 +[#1386]: https://github.com/benmosher/eslint-plugin-import/pull/1386 [#1377]: https://github.com/benmosher/eslint-plugin-import/pull/1377 [#1375]: https://github.com/benmosher/eslint-plugin-import/pull/1375 [#1372]: https://github.com/benmosher/eslint-plugin-import/pull/1372 @@ -788,6 +790,7 @@ for info on changes for earlier releases. [#863]: https://github.com/benmosher/eslint-plugin-import/issues/863 [#842]: https://github.com/benmosher/eslint-plugin-import/issues/842 [#839]: https://github.com/benmosher/eslint-plugin-import/issues/839 +[#795]: https://github.com/benmosher/eslint-plugin-import/issues/795 [#793]: https://github.com/benmosher/eslint-plugin-import/issues/793 [#720]: https://github.com/benmosher/eslint-plugin-import/issues/720 [#717]: https://github.com/benmosher/eslint-plugin-import/issues/717 @@ -1025,3 +1028,4 @@ for info on changes for earlier releases. [@Taranys]: https://github.com/Taranys [@maxmalov]: https://github.com/maxmalov [@marcusdarmstrong]: https://github.com/marcusdarmstrong +[@Mairu]: https://github.com/Mairu diff --git a/docs/rules/order.md b/docs/rules/order.md index d716430481..94c0115e15 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -94,6 +94,32 @@ You can set the options like this: "import/order": ["error", {"groups": ["index", "sibling", "parent", "internal", "external", "builtin"]}] ``` +### `pathGroups: [array of objects]`: + +To be able so group by paths mostly needed with aliases pathGroups can be defined. + +Properties of the objects + +| property | required | type | description | +|----------------|:--------:|--------|---------------| +| pattern | x | string | minimatch pattern for the paths to be in this group (will not be used for builtins or externals) | +| patternOptions | | object | options for minimatch, default: { nocomment: true } | +| group | x | string | one of the allowed groups, the pathGroup will be positioned relative to this group | +| position | | string | defines where around the group the pathGroup will be positioned, can be 'after' or 'before', if not provided pathGroup will be positioned like the group | + +```json +{ + "import/order": ["error", { + "pathGroups": [ + { + "pattern": "~/**", + "group": "external" + } + ] + }] +} +``` + ### `newlines-between: [ignore|always|always-and-inside-groups|never]`: diff --git a/src/rules/order.js b/src/rules/order.js index 920345ff2c..9daeb5e8a2 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -1,5 +1,6 @@ 'use strict' +import minimatch from 'minimatch' import importType from '../core/importType' import isStaticRequire from '../core/staticRequire' import docsUrl from '../docsUrl' @@ -244,9 +245,29 @@ function makeOutOfOrderReport(context, imported) { // DETECTING +function computePathRank(ranks, pathGroups, path, maxPosition) { + for (let i = 0, l = pathGroups.length; i < l; i++) { + const { pattern, patternOptions, group, position = 1 } = pathGroups[i] + if (minimatch(path, pattern, patternOptions || { nocomment: true })) { + return ranks[group] + (position / maxPosition) + } + } +} + function computeRank(context, ranks, name, type) { - return ranks[importType(name, context)] + - (type === 'import' ? 0 : 100) + const impType = importType(name, context) + let rank + if (impType !== 'builtin' && impType !== 'external') { + rank = computePathRank(ranks.groups, ranks.pathGroups, name, ranks.maxPosition) + } + if (!rank) { + rank = ranks.groups[impType] + } + if (type !== 'import') { + rank += 100 + } + + return rank } function registerNode(context, node, name, type, ranks, imported) { @@ -294,6 +315,49 @@ function convertGroupsToRanks(groups) { }, rankObject) } +function convertPathGroupsForRanks(pathGroups) { + const after = {} + const before = {} + + const transformed = pathGroups.map((pathGroup, index) => { + const { group, position: positionString } = pathGroup + let position = 0 + if (positionString === 'after') { + if (!after[group]) { + after[group] = 1 + } + position = after[group]++ + } else if (positionString === 'before') { + if (!before[group]) { + before[group] = [] + } + before[group].push(index) + } + + return Object.assign({}, pathGroup, { position }) + }) + + let maxPosition = 1 + + Object.keys(before).forEach((group) => { + const groupLength = before[group].length + before[group].forEach((groupIndex, index) => { + transformed[groupIndex].position = -1 * (groupLength - index) + }) + maxPosition = Math.max(maxPosition, groupLength) + }) + + Object.keys(after).forEach((key) => { + const groupNextPosition = after[key] + maxPosition = Math.max(maxPosition, groupNextPosition - 1) + }) + + return { + pathGroups: transformed, + maxPosition: maxPosition > 10 ? Math.pow(10, Math.ceil(Math.log10(maxPosition))) : 10, + } +} + function fixNewLineAfterImport(context, previousImport) { const prevRoot = findRootNode(previousImport.node) const tokensToEndOfLine = takeTokensAfterWhile( @@ -378,6 +442,29 @@ module.exports = { groups: { type: 'array', }, + pathGroups: { + type: 'array', + items: { + type: 'object', + properties: { + pattern: { + type: 'string', + }, + patternOptions: { + type: 'object', + }, + group: { + type: 'string', + enum: types, + }, + position: { + type: 'string', + enum: ['after', 'before'], + }, + }, + required: ['pattern', 'group'], + }, + }, 'newlines-between': { enum: [ 'ignore', @@ -398,7 +485,12 @@ module.exports = { let ranks try { - ranks = convertGroupsToRanks(options.groups || defaultGroups) + const { pathGroups, maxPosition } = convertPathGroupsForRanks(options.pathGroups || []) + ranks = { + groups: convertGroupsToRanks(options.groups || defaultGroups), + pathGroups, + maxPosition, + } } catch (error) { // Malformed configuration return { diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index ff71bbed25..669dc2dd02 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -204,6 +204,79 @@ ruleTester.run('order', rule, { }, ], }), + + // Using pathGroups to customize ordering, position 'after' + test({ + code: ` + import fs from 'fs'; + import _ from 'lodash'; + import { Input } from '~/components/Input'; + import { Button } from '#/components/Button'; + import { add } from './helper';`, + options: [{ + pathGroups: [ + { pattern: '~/**', group: 'external', position: 'after' }, + { pattern: '#/**', group: 'external', position: 'after' }, + ], + }], + }), + // pathGroup without position means "equal" with group + test({ + code: ` + import fs from 'fs'; + import { Input } from '~/components/Input'; + import async from 'async'; + import { Button } from '#/components/Button'; + import _ from 'lodash'; + import { add } from './helper';`, + options: [{ + pathGroups: [ + { pattern: '~/**', group: 'external' }, + { pattern: '#/**', group: 'external' }, + ], + }], + }), + // Using pathGroups to customize ordering, position 'before' + test({ + code: ` + import fs from 'fs'; + + import { Input } from '~/components/Input'; + + import { Button } from '#/components/Button'; + + import _ from 'lodash'; + + import { add } from './helper';`, + options: [{ + 'newlines-between': 'always', + pathGroups: [ + { pattern: '~/**', group: 'external', position: 'before' }, + { pattern: '#/**', group: 'external', position: 'before' }, + ], + }], + }), + // Using pathGroups to customize ordering, with patternOptions + test({ + code: ` + import fs from 'fs'; + + import _ from 'lodash'; + + import { Input } from '~/components/Input'; + + import { Button } from '!/components/Button'; + + import { add } from './helper';`, + options: [{ + 'newlines-between': 'always', + pathGroups: [ + { pattern: '~/**', group: 'external', position: 'after' }, + { pattern: '!/**', patternOptions: { nonegate: true }, group: 'external', position: 'after' }, + ], + }], + }), + // Option: newlines-between: 'always' test({ code: ` @@ -573,7 +646,7 @@ ruleTester.run('order', rule, { message: '`fs` import should occur before import of `async`', }], }), - // fix order of multile import + // fix order of multiline import test({ code: ` var async = require('async'); @@ -1396,6 +1469,153 @@ ruleTester.run('order', rule, { '`./local2` import should occur after import of `global4`', ], }), + + // pathGroup with position 'after' + test({ + code: ` + import fs from 'fs'; + import _ from 'lodash'; + import { add } from './helper'; + import { Input } from '~/components/Input'; + `, + output: ` + import fs from 'fs'; + import _ from 'lodash'; + import { Input } from '~/components/Input'; + import { add } from './helper'; + `, + options: [{ + pathGroups: [ + { pattern: '~/**', group: 'external', position: 'after' }, + ], + }], + errors: [{ + ruleId: 'order', + message: '`~/components/Input` import should occur before import of `./helper`', + }], + }), + // pathGroup without position + test({ + code: ` + import fs from 'fs'; + import _ from 'lodash'; + import { add } from './helper'; + import { Input } from '~/components/Input'; + import async from 'async'; + `, + output: ` + import fs from 'fs'; + import _ from 'lodash'; + import { Input } from '~/components/Input'; + import async from 'async'; + import { add } from './helper'; + `, + options: [{ + pathGroups: [ + { pattern: '~/**', group: 'external' }, + ], + }], + errors: [{ + ruleId: 'order', + message: '`./helper` import should occur after import of `async`', + }], + }), + // pathGroup with position 'before' + test({ + code: ` + import fs from 'fs'; + import _ from 'lodash'; + import { add } from './helper'; + import { Input } from '~/components/Input'; + `, + output: ` + import fs from 'fs'; + import { Input } from '~/components/Input'; + import _ from 'lodash'; + import { add } from './helper'; + `, + options: [{ + pathGroups: [ + { pattern: '~/**', group: 'external', position: 'before' }, + ], + }], + errors: [{ + ruleId: 'order', + message: '`~/components/Input` import should occur before import of `lodash`', + }], + }), + // multiple pathGroup with different positions for same group, fix for 'after' + test({ + code: ` + import fs from 'fs'; + import { Import } from '$/components/Import'; + import _ from 'lodash'; + import { Output } from '~/components/Output'; + import { Input } from '#/components/Input'; + import { add } from './helper'; + import { Export } from '-/components/Export'; + `, + output: ` + import fs from 'fs'; + import { Export } from '-/components/Export'; + import { Import } from '$/components/Import'; + import _ from 'lodash'; + import { Output } from '~/components/Output'; + import { Input } from '#/components/Input'; + import { add } from './helper'; + `, + options: [{ + pathGroups: [ + { pattern: '~/**', group: 'external', position: 'after' }, + { pattern: '#/**', group: 'external', position: 'after' }, + { pattern: '-/**', group: 'external', position: 'before' }, + { pattern: '$/**', group: 'external', position: 'before' }, + ], + }], + errors: [ + { + ruleId: 'order', + message: '`-/components/Export` import should occur before import of `$/components/Import`', + }, + ], + }), + + // multiple pathGroup with different positions for same group, fix for 'before' + test({ + code: ` + import fs from 'fs'; + import { Export } from '-/components/Export'; + import { Import } from '$/components/Import'; + import _ from 'lodash'; + import { Input } from '#/components/Input'; + import { add } from './helper'; + import { Output } from '~/components/Output'; + `, + output: ` + import fs from 'fs'; + import { Export } from '-/components/Export'; + import { Import } from '$/components/Import'; + import _ from 'lodash'; + import { Output } from '~/components/Output'; + import { Input } from '#/components/Input'; + import { add } from './helper'; + `, + options: [{ + pathGroups: [ + { pattern: '~/**', group: 'external', position: 'after' }, + { pattern: '#/**', group: 'external', position: 'after' }, + { pattern: '-/**', group: 'external', position: 'before' }, + { pattern: '$/**', group: 'external', position: 'before' }, + ], + }], + errors: [ + { + ruleId: 'order', + message: '`~/components/Output` import should occur before import of `#/components/Input`', + }, + ], + }), + // reorder fix cannot cross non import or require test(withoutAutofixOutput({ code: ` @@ -1469,7 +1689,7 @@ ruleTester.run('order', rule, { message: '`fs` import should occur before import of `async`', }], })), - // cannot require in case of not assignement require + // cannot require in case of not assignment require test(withoutAutofixOutput({ code: ` var async = require('async'); @@ -1493,7 +1713,7 @@ ruleTester.run('order', rule, { message: '`fs` import should occur before import of `async`', }], })), - // reorder cannot cross variable assignemet (import statement) + // reorder cannot cross variable assignment (import statement) test(withoutAutofixOutput({ code: ` import async from 'async'; @@ -1517,7 +1737,7 @@ ruleTester.run('order', rule, { message: '`fs` import should occur before import of `async`', }], })), - // cannot reorder in case of not assignement import + // cannot reorder in case of not assignment import test(withoutAutofixOutput({ code: ` import async from 'async'; From 2d3d045de9c1c2ee32872076f103934014e25fad Mon Sep 17 00:00:00 2001 From: AamuLumi Date: Tue, 1 Oct 2019 19:22:43 +0200 Subject: [PATCH 14/31] [fix] `importType`: Accept '@example' as internal Fixes #1379 --- CHANGELOG.md | 7 +++++-- src/core/importType.js | 2 +- tests/src/core/importType.js | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6143e9159..2769c55447 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,12 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`default`]: make error message less confusing ([#1470], thanks [@golopot]) - Improve performance of `ExportMap.for` by only loading paths when necessary. ([#1519], thanks [@brendo]) - Support export of a merged TypeScript namespace declaration ([#1495], thanks [@benmunro]) -- [`import/order`]: fix autofix to not move imports across fn calls ([#1253], thanks [@tihonove]) +- [`order`]: fix autofix to not move imports across fn calls ([#1253], thanks [@tihonove]) - [`prefer-default-export`]: fix false positive with type export ([#1506], thanks [@golopot]) - [`extensions`]: Fix `ignorePackages` to produce errors ([#1521], thanks [@saschanaz]) - [`no-unused-modules`]: fix crash due to `export *` ([#1496], thanks [@Taranys]) - [`no-cycle`]: should not warn for Flow imports ([#1494], thanks [@maxmalov]) +- [`order`]: fix `@someModule` considered as `unknown` instead of `internal` ([#1493], thanks [@aamulumi]) ### Docs - [`no-useless-path-segments`]: add docs for option `commonjs` ([#1507], thanks [@golopot]) @@ -146,7 +147,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-cycle`]: ignore Flow imports ([#1126], thanks [@gajus]) - fix Flow type imports ([#1106], thanks [@syymza]) - [`no-relative-parent-imports`]: resolve paths ([#1135], thanks [@chrislloyd]) -- [`import/order`]: fix autofixer when using typescript-eslint-parser ([#1137], thanks [@justinanastos]) +- [`order`]: fix autofixer when using typescript-eslint-parser ([#1137], thanks [@justinanastos]) - repeat fix from [#797] for [#717], in another place (thanks [@ljharb]) ### Refactors @@ -633,6 +634,7 @@ for info on changes for earlier releases. [#1496]: https://github.com/benmosher/eslint-plugin-import/pull/1496 [#1495]: https://github.com/benmosher/eslint-plugin-import/pull/1495 [#1494]: https://github.com/benmosher/eslint-plugin-import/pull/1494 +[#1493]: https://github.com/benmosher/eslint-plugin-import/pull/1493 [#1472]: https://github.com/benmosher/eslint-plugin-import/pull/1472 [#1470]: https://github.com/benmosher/eslint-plugin-import/pull/1470 [#1436]: https://github.com/benmosher/eslint-plugin-import/pull/1436 @@ -1029,3 +1031,4 @@ for info on changes for earlier releases. [@maxmalov]: https://github.com/maxmalov [@marcusdarmstrong]: https://github.com/marcusdarmstrong [@Mairu]: https://github.com/Mairu +[@aamulumi]: https://github.com/aamulumi diff --git a/src/core/importType.js b/src/core/importType.js index a3480ae12a..722ce7b063 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -43,7 +43,7 @@ export function isExternalModuleMain(name, settings, path) { return externalModuleMainRegExp.test(name) && isExternalPath(path, name, settings) } -const scopedRegExp = /^@[^/]+\/[^/]+/ +const scopedRegExp = /^@[^/]+\/?[^/]+/ function isScoped(name) { return scopedRegExp.test(name) } diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js index c85124a1f4..034b3cbbcf 100644 --- a/tests/src/core/importType.js +++ b/tests/src/core/importType.js @@ -30,6 +30,7 @@ describe('importType(name)', function () { }) it("should return 'external' for scopes packages", function() { + expect(importType('@cycle/', context)).to.equal('external') expect(importType('@cycle/core', context)).to.equal('external') expect(importType('@cycle/dom', context)).to.equal('external') expect(importType('@some-thing/something', context)).to.equal('external') @@ -55,6 +56,7 @@ describe('importType(name)', function () { it("should return 'internal' for internal modules that are referenced by aliases", function () { const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }) expect(importType('@my-alias/fn', pathContext)).to.equal('internal') + expect(importType('@importType', pathContext)).to.equal('internal') }) it("should return 'internal' for aliased internal modules that look like core modules (node resolver)", function () { @@ -96,7 +98,6 @@ describe('importType(name)', function () { }) it("should return 'unknown' for any unhandled cases", function() { - expect(importType('@malformed', context)).to.equal('unknown') expect(importType(' /malformed', context)).to.equal('unknown') expect(importType(' foo', context)).to.equal('unknown') }) From f12ae59b9edfc5260f88b9335ff5b47f6eb958c7 Mon Sep 17 00:00:00 2001 From: Pascal Corpet Date: Mon, 28 May 2018 21:51:36 +0200 Subject: [PATCH 15/31] [New] `no-duplicates`: add a considerQueryString option to handle false positives when using some webpack loaders. Fixes #1107. --- CHANGELOG.md | 5 ++++- docs/rules/no-duplicates.md | 25 +++++++++++++++++++++++++ src/rules/no-duplicates.js | 25 ++++++++++++++++++++++++- tests/src/rules/no-duplicates.js | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2769c55447..0ea8a264b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] -- [`no-extraneous-dependencies`]: Check `export from` ([#1049], thanks [@marcusdarmstrong]) ### Added - [`internal-regex`]: regex pattern for marking packages "internal" ([#1491], thanks [@Librazy]) @@ -14,6 +13,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-extraneous-dependencies`]: Implement support for [bundledDependencies](https://npm.github.io/using-pkgs-docs/package-json/types/bundleddependencies.html) ([#1436], thanks [@schmidsi])) - [`no-unused-modules`]: add flow type support ([#1542], thanks [@rfermann]) - [`order`]: Adds support for pathGroups to allow ordering by defined patterns ([#795], [#1386], thanks [@Mairu]) +- [`no-duplicates`]: Add `considerQueryString` option : allow duplicate imports with different query strings ([#1107], thanks [@pcorpet]). ### Fixed - [`default`]: make error message less confusing ([#1470], thanks [@golopot]) @@ -25,6 +25,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-unused-modules`]: fix crash due to `export *` ([#1496], thanks [@Taranys]) - [`no-cycle`]: should not warn for Flow imports ([#1494], thanks [@maxmalov]) - [`order`]: fix `@someModule` considered as `unknown` instead of `internal` ([#1493], thanks [@aamulumi]) +- [`no-extraneous-dependencies`]: Check `export from` ([#1049], thanks [@marcusdarmstrong]) ### Docs - [`no-useless-path-segments`]: add docs for option `commonjs` ([#1507], thanks [@golopot]) @@ -692,6 +693,7 @@ for info on changes for earlier releases. [#1126]: https://github.com/benmosher/eslint-plugin-import/pull/1126 [#1122]: https://github.com/benmosher/eslint-plugin-import/pull/1122 [#1112]: https://github.com/benmosher/eslint-plugin-import/pull/1112 +[#1107]: https://github.com/benmosher/eslint-plugin-import/pull/1107 [#1106]: https://github.com/benmosher/eslint-plugin-import/pull/1106 [#1093]: https://github.com/benmosher/eslint-plugin-import/pull/1093 [#1085]: https://github.com/benmosher/eslint-plugin-import/pull/1085 @@ -1032,3 +1034,4 @@ for info on changes for earlier releases. [@marcusdarmstrong]: https://github.com/marcusdarmstrong [@Mairu]: https://github.com/Mairu [@aamulumi]: https://github.com/aamulumi +[@pcorpet]: https://github.com/pcorpet diff --git a/docs/rules/no-duplicates.md b/docs/rules/no-duplicates.md index 0641e44186..f59b14d9cc 100644 --- a/docs/rules/no-duplicates.md +++ b/docs/rules/no-duplicates.md @@ -36,6 +36,31 @@ The motivation is that this is likely a result of two developers importing diffe names from the same module at different times (and potentially largely different locations in the file.) This rule brings both (or n-many) to attention. +### Query Strings + +By default, this rule ignores query strings (i.e. paths followed by a question mark), and thus imports from `./mod?a` and `./mod?b` will be considered as duplicates. However you can use the option `considerQueryString` to handle them as different (primarily because browsers will resolve those imports differently). + +Config: + +```json +"import/no-duplicates": ["error", {"considerQueryString": true}] +``` + +And then the following code becomes valid: +```js +import minifiedMod from './mod?minify' +import noCommentsMod from './mod?comments=0' +import originalMod from './mod' +``` + +It will still catch duplicates when using the same module and the exact same query string: +```js +import SomeDefaultClass from './mod?minify' + +// This is invalid, assuming `./mod` and `./mod.js` are the same target: +import * from './mod.js?minify' +``` + ## When Not To Use It If the core ESLint version is good enough (i.e. you're _not_ using Flow and you _are_ using [`import/extensions`](./extensions.md)), keep it and don't use this. diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index 33e3357482..1334a12582 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -230,15 +230,38 @@ module.exports = { url: docsUrl('no-duplicates'), }, fixable: 'code', + schema: [ + { + type: 'object', + properties: { + considerQueryString: { + type: 'boolean', + }, + }, + additionalProperties: false, + }, + ], }, create: function (context) { + // Prepare the resolver from options. + const considerQueryStringOption = context.options[0] && + context.options[0]['considerQueryString'] + const defaultResolver = sourcePath => resolve(sourcePath, context) || sourcePath + const resolver = considerQueryStringOption ? (sourcePath => { + const parts = sourcePath.match(/^([^?]*)\?(.*)$/) + if (!parts) { + return defaultResolver(sourcePath) + } + return defaultResolver(parts[1]) + '?' + parts[2] + }) : defaultResolver + const imported = new Map() const typesImported = new Map() return { 'ImportDeclaration': function (n) { // resolved path will cover aliased duplicates - const resolvedPath = resolve(n.source.value, context) || n.source.value + const resolvedPath = resolver(n.source.value) const importMap = n.importKind === 'type' ? typesImported : imported if (importMap.has(resolvedPath)) { diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index a93fdfa925..a4c41f677a 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -25,6 +25,18 @@ ruleTester.run('no-duplicates', rule, { code: "import { x } from './foo'; import type { y } from './foo'", parser: require.resolve('babel-eslint'), }), + + // #1107: Using different query strings that trigger different webpack loaders. + test({ + code: "import x from './bar?optionX'; import y from './bar?optionY';", + options: [{'considerQueryString': true}], + settings: { 'import/resolver': 'webpack' }, + }), + test({ + code: "import x from './foo'; import y from './bar';", + options: [{'considerQueryString': true}], + settings: { 'import/resolver': 'webpack' }, + }), ], invalid: [ test({ @@ -50,6 +62,26 @@ ruleTester.run('no-duplicates', rule, { errors: 2, // path ends up hardcoded }), + // #1107: Using different query strings that trigger different webpack loaders. + test({ + code: "import x from './bar.js?optionX'; import y from './bar?optionX';", + settings: { 'import/resolver': 'webpack' }, + errors: 2, // path ends up hardcoded + }), + test({ + code: "import x from './bar?optionX'; import y from './bar?optionY';", + settings: { 'import/resolver': 'webpack' }, + errors: 2, // path ends up hardcoded + }), + + // #1107: Using same query strings that trigger the same loader. + test({ + code: "import x from './bar?optionX'; import y from './bar.js?optionX';", + options: [{'considerQueryString': true}], + settings: { 'import/resolver': 'webpack' }, + errors: 2, // path ends up hardcoded + }), + // #86: duplicate unresolved modules should be flagged test({ code: "import foo from 'non-existent'; import bar from 'non-existent';", From 8224e51670c636b4b2be8bb2895cd6fed79cd3d2 Mon Sep 17 00:00:00 2001 From: Duncan Beevers Date: Thu, 17 May 2018 16:42:15 -0700 Subject: [PATCH 16/31] [New] `order`/`no-extraneous-dependencies`: Alphabetize imports within groups Fixes #1406. Fixes #389. Closes #629. Closes #1105. Closes #1360. Co-Authored-By: dannysindra Co-Authored-By: Radim Svoboda Co-Authored-By: Soma Lucz Co-Authored-By: Randall Reed, Jr Co-Authored-By: Jordan Harband --- CHANGELOG.md | 57 +++++++++++-------------- docs/rules/order.md | 34 ++++++++++++++- src/rules/order.js | 85 +++++++++++++++++++++++++++++++++++-- tests/src/rules/order.js | 91 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 230 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ea8a264b9..c5562da2ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Change Log + All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] - ### Added - [`internal-regex`]: regex pattern for marking packages "internal" ([#1491], thanks [@Librazy]) - [`group-exports`]: make aggregate module exports valid ([#1472], thanks [@atikenny]) @@ -14,6 +14,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-unused-modules`]: add flow type support ([#1542], thanks [@rfermann]) - [`order`]: Adds support for pathGroups to allow ordering by defined patterns ([#795], [#1386], thanks [@Mairu]) - [`no-duplicates`]: Add `considerQueryString` option : allow duplicate imports with different query strings ([#1107], thanks [@pcorpet]). +- [`order`]: Add support for alphabetical sorting of import paths within import groups ([#1360], [#1105], [#629], thanks [@duncanbeevers], [@stropho], [@luczsoma], [@randallreedjr]) ### Fixed - [`default`]: make error message less confusing ([#1470], thanks [@golopot]) @@ -34,10 +35,10 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-unused-modules`]/`eslint-module-utils`: Avoid superfluous calls and code ([#1551], thanks [@brettz9]) ## [2.18.2] - 2019-07-19 +### Fixed - Skip warning on type interfaces ([#1425], thanks [@lencioni]) ## [2.18.1] - 2019-07-18 - ### Fixed - Improve parse perf when using `@typescript-eslint/parser` ([#1409], thanks [@bradzacher]) - [`prefer-default-export`]: don't warn on TypeAlias & TSTypeAliasDeclaration ([#1377], thanks [@sharmilajesupaul]) @@ -45,10 +46,9 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`export`]: false positive for TypeScript overloads ([#1412], thanks [@golopot]) ### Refactors - - [`no-extraneous-dependencies`], `importType`: remove lodash ([#1419], thanks [@ljharb]) +- [`no-extraneous-dependencies`], `importType`: remove lodash ([#1419], thanks [@ljharb]) ## [2.18.0] - 2019-06-24 - ### Added - Support eslint v6 ([#1393], thanks [@sheepsteak]) - [`order`]: Adds support for correctly sorting unknown types into a single group ([#1375], thanks [@swernerx]) @@ -63,7 +63,6 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-named-as-default-member`]: update broken link ([#1389], thanks [@fooloomanzoo]) ## [2.17.3] - 2019-05-23 - ### Fixed - [`no-common-js`]: Also throw an error when assigning ([#1354], thanks [@charlessuh]) - [`no-unused-modules`]: don't crash when lint file outside src-folder ([#1347], thanks [@rfermann]) @@ -76,22 +75,18 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Docs - add missing `no-unused-modules` in README ([#1358], thanks [@golopot]) - [`no-unused-modules`]: Indicates usage, plugin defaults to no-op, and add description to main README.md ([#1352], thanks [@johndevedu]) -[@christophercurrie]: https://github.com/christophercurrie - Document `env` option for `eslint-import-resolver-webpack` ([#1363], thanks [@kgregory]) ## [2.17.2] - 2019-04-16 - ### Fixed - [`no-unused-modules`]: avoid crash when using `ignoreExports`-option ([#1331], [#1323], thanks [@rfermann]) - [`no-unused-modules`]: make sure that rule with no options will not fail ([#1330], [#1334], thanks [@kiwka]) ## [2.17.1] - 2019-04-13 - ### Fixed - require v2.4 of `eslint-module-utils` ([#1322]) ## [2.17.0] - 2019-04-13 - ### Added - [`no-useless-path-segments`]: Add `noUselessIndex` option ([#1290], thanks [@timkraut]) - [`no-duplicates`]: Add autofix ([#1312], thanks [@lydell]) @@ -116,7 +111,6 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - fix broken tests on master ([#1295], thanks [@jeffshaver] and [@ljharb]) - [`no-commonjs`]: add tests that show corner cases ([#1308], thanks [@TakeScoop]) - ## [2.16.0] - 2019-01-29 ### Added - `typescript` config ([#1257], thanks [@kirill-konshin]) @@ -133,13 +127,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`dynamic-import-chunkname`]: Add proper webpack comment parsing ([#1163], thanks [@st-sloth]) - [`named`]: fix destructuring assignment ([#1232], thanks [@ljqx]) - ## [2.14.0] - 2018-08-13 -* 69e0187 (HEAD -> master, source/master, origin/master, origin/HEAD) Merge pull request #1151 from jf248/jsx -|\ -| * e30a757 (source/pr/1151, fork/jsx) Add JSX check to namespace rule -|/ -* 8252344 (source/pr/1148) Add error to output when module loaded as resolver has invalid API ### Added - [`no-useless-path-segments`]: add commonJS (CJS) support ([#1128], thanks [@1pete]) - [`namespace`]: add JSX check ([#1151], thanks [@jf248]) @@ -497,11 +485,10 @@ I'm seeing 62% improvement over my normal test codebase when executing only ## [1.1.0] - 2016-03-15 ### Added -- Added an [`ignore`](./docs/rules/no-unresolved.md#ignore) option to [`no-unresolved`] for those pesky files that no -resolver can find. (still prefer enhancing the Webpack and Node resolvers to -using it, though). See [#89] for details. +- Added an [`ignore`](./docs/rules/no-unresolved.md#ignore) option to [`no-unresolved`] for those pesky files that no resolver can find. (still prefer enhancing the Webpack and Node resolvers to using it, though). See [#89] for details. ## [1.0.4] - 2016-03-11 + ### Changed - respect hoisting for deep namespaces ([`namespace`]/[`no-deprecated`]) ([#211]) @@ -510,39 +497,41 @@ using it, though). See [#89] for details. - correct cache behavior in `eslint_d` for deep namespaces ([#200]) ## [1.0.3] - 2016-02-26 + ### Changed - no-deprecated follows deep namespaces ([#191]) ### Fixed -- [`namespace`] no longer flags modules with only a default export as having no -names. (ns.default is valid ES6) +- [`namespace`] no longer flags modules with only a default export as having no names. (ns.default is valid ES6) ## [1.0.2] - 2016-02-26 + ### Fixed - don't parse imports with no specifiers ([#192]) ## [1.0.1] - 2016-02-25 + ### Fixed - export `stage-0` shared config - documented [`no-deprecated`] - deep namespaces are traversed regardless of how they get imported ([#189]) ## [1.0.0] - 2016-02-24 + ### Added -- [`no-deprecated`]: WIP rule to let you know at lint time if you're using -deprecated functions, constants, classes, or modules. +- [`no-deprecated`]: WIP rule to let you know at lint time if you're using deprecated functions, constants, classes, or modules. ### Changed - [`namespace`]: support deep namespaces ([#119] via [#157]) ## [1.0.0-beta.0] - 2016-02-13 + ### Changed - support for (only) ESLint 2.x -- no longer needs/refers to `import/parser` or `import/parse-options`. Instead, -ESLint provides the configured parser + options to the rules, and they use that -to parse dependencies. +- no longer needs/refers to `import/parser` or `import/parse-options`. Instead, ESLint provides the configured parser + options to the rules, and they use that to parse dependencies. ### Removed + - `babylon` as default import parser (see Breaking) ## [0.13.0] - 2016-02-08 @@ -562,14 +551,11 @@ Unpublished from npm and re-released as 0.13.0. See [#170]. ## [0.12.0] - 2015-12-14 ### Changed -- Ignore [`import/ignore` setting] if exports are actually found in the parsed module. Does -this to support use of `jsnext:main` in `node_modules` without the pain of -managing an allow list or a nuanced deny list. +- Ignore [`import/ignore` setting] if exports are actually found in the parsed module. Does this to support use of `jsnext:main` in `node_modules` without the pain of managing an allow list or a nuanced deny list. ## [0.11.0] - 2015-11-27 ### Added -- Resolver plugins. Now the linter can read Webpack config, properly follow -aliases and ignore externals, dismisses inline loaders, etc. etc.! +- Resolver plugins. Now the linter can read Webpack config, properly follow aliases and ignore externals, dismisses inline loaders, etc. etc.! ## Earlier releases (0.10.1 and younger) See [GitHub release notes](https://github.com/benmosher/eslint-plugin-import/releases?after=v0.11.0) @@ -655,6 +641,7 @@ for info on changes for earlier releases. [#1371]: https://github.com/benmosher/eslint-plugin-import/pull/1371 [#1370]: https://github.com/benmosher/eslint-plugin-import/pull/1370 [#1363]: https://github.com/benmosher/eslint-plugin-import/pull/1363 +[#1360]: https://github.com/benmosher/eslint-plugin-import/pull/1360 [#1358]: https://github.com/benmosher/eslint-plugin-import/pull/1358 [#1356]: https://github.com/benmosher/eslint-plugin-import/pull/1356 [#1354]: https://github.com/benmosher/eslint-plugin-import/pull/1354 @@ -695,6 +682,7 @@ for info on changes for earlier releases. [#1112]: https://github.com/benmosher/eslint-plugin-import/pull/1112 [#1107]: https://github.com/benmosher/eslint-plugin-import/pull/1107 [#1106]: https://github.com/benmosher/eslint-plugin-import/pull/1106 +[#1105]: https://github.com/benmosher/eslint-plugin-import/pull/1105 [#1093]: https://github.com/benmosher/eslint-plugin-import/pull/1093 [#1085]: https://github.com/benmosher/eslint-plugin-import/pull/1085 [#1068]: https://github.com/benmosher/eslint-plugin-import/pull/1068 @@ -724,6 +712,7 @@ for info on changes for earlier releases. [#639]: https://github.com/benmosher/eslint-plugin-import/pull/639 [#632]: https://github.com/benmosher/eslint-plugin-import/pull/632 [#630]: https://github.com/benmosher/eslint-plugin-import/pull/630 +[#629]: https://github.com/benmosher/eslint-plugin-import/pull/629 [#628]: https://github.com/benmosher/eslint-plugin-import/pull/628 [#596]: https://github.com/benmosher/eslint-plugin-import/pull/596 [#586]: https://github.com/benmosher/eslint-plugin-import/pull/586 @@ -774,7 +763,6 @@ for info on changes for earlier releases. [#211]: https://github.com/benmosher/eslint-plugin-import/pull/211 [#164]: https://github.com/benmosher/eslint-plugin-import/pull/164 [#157]: https://github.com/benmosher/eslint-plugin-import/pull/157 - [#1366]: https://github.com/benmosher/eslint-plugin-import/issues/1366 [#1334]: https://github.com/benmosher/eslint-plugin-import/issues/1334 [#1323]: https://github.com/benmosher/eslint-plugin-import/issues/1323 @@ -921,7 +909,6 @@ for info on changes for earlier releases. [0.12.1]: https://github.com/benmosher/eslint-plugin-import/compare/v0.12.0...v0.12.1 [0.12.0]: https://github.com/benmosher/eslint-plugin-import/compare/v0.11.0...v0.12.0 [0.11.0]: https://github.com/benmosher/eslint-plugin-import/compare/v0.10.1...v0.11.0 - [@mathieudutour]: https://github.com/mathieudutour [@gausie]: https://github.com/gausie [@singles]: https://github.com/singles @@ -1035,3 +1022,7 @@ for info on changes for earlier releases. [@Mairu]: https://github.com/Mairu [@aamulumi]: https://github.com/aamulumi [@pcorpet]: https://github.com/pcorpet +[@stropho]: https://github.com/stropho +[@luczsoma]: https://github.com/luczsoma +[@christophercurrie]: https://github.com/christophercurrie +[@randallreedjr]: https://github.com/randallreedjr diff --git a/docs/rules/order.md b/docs/rules/order.md index 94c0115e15..b5c4902ac8 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -122,7 +122,6 @@ Properties of the objects ### `newlines-between: [ignore|always|always-and-inside-groups|never]`: - Enforces or forbids new lines between import groups: - If set to `ignore`, no errors related to new lines between import groups will be reported (default). @@ -190,6 +189,39 @@ import index from './'; import sibling from './foo'; ``` +### `alphabetize: {order: asc|desc|ignore}`: + +Sort the order within each group in alphabetical manner based on **import path**: + +- `order`: use `asc` to sort in ascending order, and `desc` to sort in descending order (default: `ignore`). + +Example setting: +```js +alphabetize: { + order: 'asc', /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */ +} +``` + +This will fail the rule check: + +```js +/* eslint import/order: ["error", {"alphabetize": true}] */ +import React, { PureComponent } from 'react'; +import aTypes from 'prop-types'; +import { compose, apply } from 'xcompose'; +import * as classnames from 'classnames'; +``` + +While this will pass: + +```js +/* eslint import/order: ["error", {"alphabetize": true}] */ +import * as classnames from 'classnames'; +import aTypes from 'prop-types'; +import React, { PureComponent } from 'react'; +import { compose, apply } from 'xcompose'; +``` + ## Related - [`import/external-module-folders`] setting diff --git a/src/rules/order.js b/src/rules/order.js index 9daeb5e8a2..b3ea8207e9 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -180,12 +180,12 @@ function fixOutOfOrder(context, firstNode, secondNode, order) { const sourceCode = context.getSourceCode() const firstRoot = findRootNode(firstNode.node) - let firstRootStart = findStartOfLineWithComments(sourceCode, firstRoot) + const firstRootStart = findStartOfLineWithComments(sourceCode, firstRoot) const firstRootEnd = findEndOfLineWithComments(sourceCode, firstRoot) const secondRoot = findRootNode(secondNode.node) - let secondRootStart = findStartOfLineWithComments(sourceCode, secondRoot) - let secondRootEnd = findEndOfLineWithComments(sourceCode, secondRoot) + const secondRootStart = findStartOfLineWithComments(sourceCode, secondRoot) + const secondRootEnd = findEndOfLineWithComments(sourceCode, secondRoot) const canFix = canReorderItems(firstRoot, secondRoot) let newCode = sourceCode.text.substring(secondRootStart, secondRootEnd) @@ -243,6 +243,63 @@ function makeOutOfOrderReport(context, imported) { reportOutOfOrder(context, imported, outOfOrder, 'before') } +function importsSorterAsc(importA, importB) { + if (importA < importB) { + return -1 + } + + if (importA > importB) { + return 1 + } + + return 0 +} + +function importsSorterDesc(importA, importB) { + if (importA < importB) { + return 1 + } + + if (importA > importB) { + return -1 + } + + return 0 +} + +function mutateRanksToAlphabetize(imported, order) { + const groupedByRanks = imported.reduce(function(acc, importedItem) { + if (!Array.isArray(acc[importedItem.rank])) { + acc[importedItem.rank] = [] + } + acc[importedItem.rank].push(importedItem.name) + return acc + }, {}) + + const groupRanks = Object.keys(groupedByRanks) + + const sorterFn = order === 'asc' ? importsSorterAsc : importsSorterDesc + // sort imports locally within their group + groupRanks.forEach(function(groupRank) { + groupedByRanks[groupRank].sort(sorterFn) + }) + + // assign globally unique rank to each import + let newRank = 0 + const alphabetizedRanks = groupRanks.sort().reduce(function(acc, groupRank) { + groupedByRanks[groupRank].forEach(function(importedItemName) { + acc[importedItemName] = newRank + newRank += 1 + }) + return acc + }, {}) + + // mutate the original group-rank with alphabetized-rank + imported.forEach(function(importedItem) { + importedItem.rank = alphabetizedRanks[importedItem.name] + }) +} + // DETECTING function computePathRank(ranks, pathGroups, path, maxPosition) { @@ -427,6 +484,13 @@ function makeNewlinesBetweenReport (context, imported, newlinesBetweenImports) { }) } +function getAlphabetizeConfig(options) { + const alphabetize = options.alphabetize || {} + const order = alphabetize.order || 'ignore' + + return {order} +} + module.exports = { meta: { type: 'suggestion', @@ -473,6 +537,16 @@ module.exports = { 'never', ], }, + alphabetize: { + type: 'object', + properties: { + order: { + enum: ['ignore', 'asc', 'desc'], + default: 'ignore', + }, + }, + additionalProperties: false, + }, }, additionalProperties: false, }, @@ -482,6 +556,7 @@ module.exports = { create: function importOrderRule (context) { const options = context.options[0] || {} const newlinesBetweenImports = options['newlines-between'] || 'ignore' + const alphabetize = getAlphabetizeConfig(options) let ranks try { @@ -524,6 +599,10 @@ module.exports = { registerNode(context, node, name, 'require', ranks, imported) }, 'Program:exit': function reportAndReset() { + if (alphabetize.order !== 'ignore') { + mutateRanksToAlphabetize(imported, alphabetize.order) + } + makeOutOfOrderReport(context, imported) if (newlinesBetweenImports !== 'ignore') { diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index 669dc2dd02..2f67a89777 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -519,6 +519,47 @@ ruleTester.run('order', rule, { }, ], }), + // Option alphabetize: {order: 'ignore'} + test({ + code: ` + import a from 'foo'; + import b from 'bar'; + + import index from './'; + `, + options: [{ + groups: ['external', 'index'], + alphabetize: {order: 'ignore'}, + }], + }), + // Option alphabetize: {order: 'asc'} + test({ + code: ` + import c from 'Bar'; + import b from 'bar'; + import a from 'foo'; + + import index from './'; + `, + options: [{ + groups: ['external', 'index'], + alphabetize: {order: 'asc'}, + }], + }), + // Option alphabetize: {order: 'desc'} + test({ + code: ` + import a from 'foo'; + import b from 'bar'; + import c from 'Bar'; + + import index from './'; + `, + options: [{ + groups: ['external', 'index'], + alphabetize: {order: 'desc'}, + }], + }), ], invalid: [ // builtin before external module (require) @@ -1764,5 +1805,55 @@ ruleTester.run('order', rule, { message: '`fs` import should occur before import of `async`', }], })), + // Option alphabetize: {order: 'asc'} + test({ + code: ` + import b from 'bar'; + import c from 'Bar'; + import a from 'foo'; + + import index from './'; + `, + output: ` + import c from 'Bar'; + import b from 'bar'; + import a from 'foo'; + + import index from './'; + `, + options: [{ + groups: ['external', 'index'], + alphabetize: {order: 'asc'}, + }], + errors: [{ + ruleID: 'order', + message: '`Bar` import should occur before import of `bar`', + }], + }), + // Option alphabetize: {order: 'desc'} + test({ + code: ` + import a from 'foo'; + import c from 'Bar'; + import b from 'bar'; + + import index from './'; + `, + output: ` + import a from 'foo'; + import b from 'bar'; + import c from 'Bar'; + + import index from './'; + `, + options: [{ + groups: ['external', 'index'], + alphabetize: {order: 'desc'}, + }], + errors: [{ + ruleID: 'order', + message: '`bar` import should occur before import of `Bar`', + }], + }), ].filter((t) => !!t), }) From 414c9233386e5a4e525364f8a24a59b84dbae877 Mon Sep 17 00:00:00 2001 From: David Aghassi <3680126+Aghassi@users.noreply.github.com> Date: Sat, 12 Oct 2019 00:40:20 -0700 Subject: [PATCH 17/31] [New] enable passing cwd as an option to `eslint-import-resolver-webpack` This enables users to change the lookup of the webpack module for the resolve functionality should it not be in the user's local node_modules. This pertains to the case of a CLI where the CLI may be in charge of webpack, and the user's repo doesn't have it. Co-Authored-By: Jordan Harband --- resolvers/webpack/CHANGELOG.md | 5 +++++ resolvers/webpack/index.js | 28 ++++++++++++++++++++++------ resolvers/webpack/test/example.js | 9 +++++++++ resolvers/webpack/test/root.js | 11 ++++++++++- 4 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 resolvers/webpack/test/example.js diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index 204e0224ab..45a89bf0aa 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -5,6 +5,9 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +### Added +- [New] enable passing cwd as an option to `eslint-import-resolver-webpack` ([#1503], thanks [@Aghassi]) + ## 0.11.1 - 2019-04-13 ### Fixed @@ -117,6 +120,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - `interpret` configs (such as `.babel.js`). Thanks to [@gausie] for the initial PR ([#164], ages ago! 😅) and [@jquense] for tests ([#278]). +[#1503]: https://github.com/benmosher/eslint-plugin-import/pull/1503 [#1297]: https://github.com/benmosher/eslint-plugin-import/pull/1297 [#1261]: https://github.com/benmosher/eslint-plugin-import/pull/1261 [#1220]: https://github.com/benmosher/eslint-plugin-import/pull/1220 @@ -166,3 +170,4 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [@mattkrick]: https://github.com/mattkrick [@idudinov]: https://github.com/idudinov [@keann]: https://github.com/keann +[@Aghassi]: https://github.com/Aghassi diff --git a/resolvers/webpack/index.js b/resolvers/webpack/index.js index 0f75a28400..dd3fc7a38a 100644 --- a/resolvers/webpack/index.js +++ b/resolvers/webpack/index.js @@ -20,7 +20,15 @@ exports.interfaceVersion = 2 * resolveImport('./foo', '/Users/ben/bar.js') => '/Users/ben/foo.js' * @param {string} source - the module to resolve; i.e './some-module' * @param {string} file - the importing file's full path; i.e. '/usr/local/bin/file.js' - * TODO: take options as a third param, with webpack config file name + * @param {object} settings - the webpack config file name, as well as cwd + * @example + * options: { + * // Path to the webpack config + * config: 'webpack.config.js', + * // Path to be used to determine where to resolve webpack from + * // (may differ from the cwd in some cases) + * cwd: process.cwd() + * } * @return {string?} the resolved path to source, undefined if not resolved, or null * if resolved to a non-FS resource (i.e. script tag at page load) */ @@ -41,6 +49,11 @@ exports.resolve = function (source, file, settings) { var webpackConfig var configPath = get(settings, 'config') + /** + * Attempt to set the current working directory. + * If none is passed, default to the `cwd` where the config is located. + */ + , cwd = get(settings, 'cwd') , configIndex = get(settings, 'config-index') , env = get(settings, 'env') , argv = get(settings, 'argv', {}) @@ -114,7 +127,7 @@ exports.resolve = function (source, file, settings) { } // otherwise, resolve "normally" - var resolveSync = getResolveSync(configPath, webpackConfig) + var resolveSync = getResolveSync(configPath, webpackConfig, cwd) try { return { found: true, path: resolveSync(path.dirname(file), source) } @@ -130,13 +143,13 @@ exports.resolve = function (source, file, settings) { var MAX_CACHE = 10 var _cache = [] -function getResolveSync(configPath, webpackConfig) { +function getResolveSync(configPath, webpackConfig, cwd) { var cacheKey = { configPath: configPath, webpackConfig: webpackConfig } var cached = find(_cache, function (entry) { return isEqual(entry.key, cacheKey) }) if (!cached) { cached = { key: cacheKey, - value: createResolveSync(configPath, webpackConfig), + value: createResolveSync(configPath, webpackConfig, cwd), } // put in front and pop last item if (_cache.unshift(cached) > MAX_CACHE) { @@ -146,15 +159,18 @@ function getResolveSync(configPath, webpackConfig) { return cached.value } -function createResolveSync(configPath, webpackConfig) { +function createResolveSync(configPath, webpackConfig, cwd) { var webpackRequire , basedir = null if (typeof configPath === 'string') { - basedir = path.dirname(configPath) + // This can be changed via the settings passed in when defining the resolver + basedir = cwd || configPath + log(`Attempting to load webpack path from ${basedir}`) } try { + // Attempt to resolve webpack from the given `basedir` var webpackFilename = resolve.sync('webpack', { basedir, preserveSymlinks: false }) var webpackResolveOpts = { basedir: path.dirname(webpackFilename), preserveSymlinks: false } diff --git a/resolvers/webpack/test/example.js b/resolvers/webpack/test/example.js new file mode 100644 index 0000000000..375f6b5a1e --- /dev/null +++ b/resolvers/webpack/test/example.js @@ -0,0 +1,9 @@ +var path = require('path') + +var resolve = require('../index').resolve + +var file = path.join(__dirname, 'files', 'src', 'dummy.js') + +var webpackDir = path.join(__dirname, "different-package-location") + +console.log(resolve('main-module', file, { config: "webpack.config.js", cwd: webpackDir})) diff --git a/resolvers/webpack/test/root.js b/resolvers/webpack/test/root.js index 4839f3b894..4365720091 100644 --- a/resolvers/webpack/test/root.js +++ b/resolvers/webpack/test/root.js @@ -6,6 +6,7 @@ var resolve = require('../index').resolve var file = path.join(__dirname, 'files', 'src', 'dummy.js') +var webpackDir = path.join(__dirname, "different-package-location") describe("root", function () { it("works", function () { @@ -32,5 +33,13 @@ describe("root", function () { .property('path') .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js')) }) - + it("supports passing a different directory to load webpack from", function () { + // Webpack should still be able to resolve the config here + expect(resolve('main-module', file, { config: "webpack.config.js", cwd: webpackDir})) + .property('path') + .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) + expect(resolve('typeahead', file, { config: "webpack.config.js", cwd: webpackDir})) + .property('path') + .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js')) + }) }) From a60e5c64541610df6d82eeddce6468067f770a18 Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Sat, 3 Aug 2019 19:16:26 -0700 Subject: [PATCH 18/31] [New] `no-commonjs`: add `allowConditionalRequire` option Fixes #1437 --- CHANGELOG.md | 3 +++ docs/rules/no-commonjs.md | 19 +++++++++++++++++-- src/rules/no-commonjs.js | 30 ++++++++++++++++++++++++------ tests/src/rules/no-commonjs.js | 20 ++++++++++++++++++++ 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5562da2ed..c7c96bcd43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`order`]: Adds support for pathGroups to allow ordering by defined patterns ([#795], [#1386], thanks [@Mairu]) - [`no-duplicates`]: Add `considerQueryString` option : allow duplicate imports with different query strings ([#1107], thanks [@pcorpet]). - [`order`]: Add support for alphabetical sorting of import paths within import groups ([#1360], [#1105], [#629], thanks [@duncanbeevers], [@stropho], [@luczsoma], [@randallreedjr]) +- [`no-commonjs`]: add `allowConditionalRequire` option ([#1439], thanks [@Pessimistress]) ### Fixed - [`default`]: make error message less confusing ([#1470], thanks [@golopot]) @@ -624,6 +625,7 @@ for info on changes for earlier releases. [#1493]: https://github.com/benmosher/eslint-plugin-import/pull/1493 [#1472]: https://github.com/benmosher/eslint-plugin-import/pull/1472 [#1470]: https://github.com/benmosher/eslint-plugin-import/pull/1470 +[#1439]: https://github.com/benmosher/eslint-plugin-import/pull/1439 [#1436]: https://github.com/benmosher/eslint-plugin-import/pull/1436 [#1435]: https://github.com/benmosher/eslint-plugin-import/pull/1435 [#1425]: https://github.com/benmosher/eslint-plugin-import/pull/1425 @@ -1026,3 +1028,4 @@ for info on changes for earlier releases. [@luczsoma]: https://github.com/luczsoma [@christophercurrie]: https://github.com/christophercurrie [@randallreedjr]: https://github.com/randallreedjr +[@Pessimistress]: https://github.com/Pessimistress diff --git a/docs/rules/no-commonjs.md b/docs/rules/no-commonjs.md index 4353886bf7..7be4bb3993 100644 --- a/docs/rules/no-commonjs.md +++ b/docs/rules/no-commonjs.md @@ -27,15 +27,30 @@ If `allowRequire` option is set to `true`, `require` calls are valid: ```js /*eslint no-commonjs: [2, { allowRequire: true }]*/ +var mod = require('./mod'); +``` + +but `module.exports` is reported as usual. + +### Allow conditional require + +By default, conditional requires are allowed: + +```js +var a = b && require("c") if (typeof window !== "undefined") { require('that-ugly-thing'); } + +var fs = null; +try { + fs = require("fs") +} catch (error) {} ``` -but `module.exports` is reported as usual. +If the `allowConditionalRequire` option is set to `false`, they will be reported. -This is useful for conditional requires. If you don't rely on synchronous module loading, check out [dynamic import](https://github.com/airbnb/babel-plugin-dynamic-import-node). ### Allow primitive modules diff --git a/src/rules/no-commonjs.js b/src/rules/no-commonjs.js index 261654bbf2..456f030f42 100644 --- a/src/rules/no-commonjs.js +++ b/src/rules/no-commonjs.js @@ -25,6 +25,26 @@ function allowRequire(node, options) { return options.allowRequire } +function allowConditionalRequire(node, options) { + return options.allowConditionalRequire !== false +} + +function validateScope(scope) { + return scope.variableScope.type === 'module' +} + +// https://github.com/estree/estree/blob/master/es5.md +function isConditional(node) { + if ( + node.type === 'IfStatement' + || node.type === 'TryStatement' + || node.type === 'LogicalExpression' + || node.type === 'ConditionalExpression' + ) return true + if (node.parent) return isConditional(node.parent) + return false +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -35,6 +55,7 @@ const schemaObject = { properties: { allowPrimitiveModules: { 'type': 'boolean' }, allowRequire: { 'type': 'boolean' }, + allowConditionalRequire: { 'type': 'boolean' }, }, additionalProperties: false, } @@ -87,12 +108,7 @@ module.exports = { }, 'CallExpression': function (call) { - if (context.getScope().type !== 'module') return - if ( - call.parent.type !== 'ExpressionStatement' - && call.parent.type !== 'VariableDeclarator' - && call.parent.type !== 'AssignmentExpression' - ) return + if (!validateScope(context.getScope())) return if (call.callee.type !== 'Identifier') return if (call.callee.name !== 'require') return @@ -105,6 +121,8 @@ module.exports = { if (allowRequire(call, options)) return + if (allowConditionalRequire(call, options) && isConditional(call.parent)) return + // keeping it simple: all 1-string-arg `require` calls are reported context.report({ node: call.callee, diff --git a/tests/src/rules/no-commonjs.js b/tests/src/rules/no-commonjs.js index 8ca8fde509..1bcbc65ab3 100644 --- a/tests/src/rules/no-commonjs.js +++ b/tests/src/rules/no-commonjs.js @@ -56,6 +56,13 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { { code: 'module.exports = function () {}', options: [{ allowPrimitiveModules: true }] }, { code: 'module.exports = "foo"', options: ['allow-primitive-modules'] }, { code: 'module.exports = "foo"', options: [{ allowPrimitiveModules: true }] }, + + { code: 'if (typeof window !== "undefined") require("x")', options: [{ allowRequire: true }] }, + { code: 'if (typeof window !== "undefined") require("x")', options: [{ allowRequire: false }] }, + { code: 'if (typeof window !== "undefined") { require("x") }', options: [{ allowRequire: true }] }, + { code: 'if (typeof window !== "undefined") { require("x") }', options: [{ allowRequire: false }] }, + + { code: 'try { require("x") } catch (error) {}' }, ], invalid: [ @@ -65,6 +72,19 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { { code: 'var x = require("x")', errors: [ { message: IMPORT_MESSAGE }] }, { code: 'x = require("x")', errors: [ { message: IMPORT_MESSAGE }] }, { code: 'require("x")', errors: [ { message: IMPORT_MESSAGE }] }, + + { code: 'if (typeof window !== "undefined") require("x")', + options: [{ allowConditionalRequire: false }], + errors: [ { message: IMPORT_MESSAGE }], + }, + { code: 'if (typeof window !== "undefined") { require("x") }', + options: [{ allowConditionalRequire: false }], + errors: [ { message: IMPORT_MESSAGE }], + }, + { code: 'try { require("x") } catch (error) {}', + options: [{ allowConditionalRequire: false }], + errors: [ { message: IMPORT_MESSAGE }], + }, ]), // exports From 7190c3e927550e48d8e4e7d5383f4e1bff9253ea Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 7 Dec 2019 22:51:26 -0800 Subject: [PATCH 19/31] bump utils to v2.5.0 --- utils/.npmrc | 1 + utils/CHANGELOG.md | 7 +++++++ utils/package.json | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 utils/.npmrc diff --git a/utils/.npmrc b/utils/.npmrc new file mode 100644 index 0000000000..43c97e719a --- /dev/null +++ b/utils/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index c42cbced40..8165447b7d 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,11 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +## v2.5.0 - 2019-12-07 + +### Added +- support `parseForESLint` from custom parser ([#1435], thanks [@JounQin]) + ### Changed - Avoid superfluous calls and code ([#1551], thanks [@brettz9]) @@ -56,6 +61,7 @@ Yanked due to critical issue with cache key resulting from #839. [#1551]: https://github.com/benmosher/eslint-plugin-import/pull/1551 +[#1435]: https://github.com/benmosher/eslint-plugin-import/pull/1435 [#1409]: https://github.com/benmosher/eslint-plugin-import/pull/1409 [#1356]: https://github.com/benmosher/eslint-plugin-import/pull/1356 [#1290]: https://github.com/benmosher/eslint-plugin-import/pull/1290 @@ -70,3 +76,4 @@ Yanked due to critical issue with cache key resulting from #839. [@bradzacher]: https://github.com/bradzacher [@christophercurrie]: https://github.com/christophercurrie [@brettz9]: https://github.com/brettz9 +[@JounQin]: https://github.com/JounQin diff --git a/utils/package.json b/utils/package.json index eaad9b2544..459f7c4388 100644 --- a/utils/package.json +++ b/utils/package.json @@ -1,6 +1,6 @@ { "name": "eslint-module-utils", - "version": "2.4.1", + "version": "2.5.0", "description": "Core utilities to support eslint-plugin-import and other module-related plugins.", "engines": { "node": ">=4" @@ -25,7 +25,7 @@ }, "homepage": "https://github.com/benmosher/eslint-plugin-import#readme", "dependencies": { - "debug": "^2.6.8", + "debug": "^2.6.9", "pkg-dir": "^2.0.0" } } From 3f0e8f3553266fa6abf74efd822e41e1991a20a6 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 7 Dec 2019 23:19:15 -0800 Subject: [PATCH 20/31] [resolvers/node] [Deps] update `resolve` --- resolvers/node/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resolvers/node/package.json b/resolvers/node/package.json index 76529084d6..aa971afa30 100644 --- a/resolvers/node/package.json +++ b/resolvers/node/package.json @@ -29,7 +29,7 @@ "homepage": "https://github.com/benmosher/eslint-plugin-import", "dependencies": { "debug": "^2.6.9", - "resolve": "^1.10.0" + "resolve": "^1.13.1" }, "devDependencies": { "chai": "^3.5.0", From 26ad476cbc0da3469acd25a35ddac4d111fb565e Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 7 Dec 2019 23:23:08 -0800 Subject: [PATCH 21/31] [resolvers/webpack] [deps] update `debug`, `enhanced-resolve`, `has`, `interpret`, `lodash`, `resolve`, `semver` --- resolvers/webpack/package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/resolvers/webpack/package.json b/resolvers/webpack/package.json index 69a861c6ef..136d3b4dbf 100644 --- a/resolvers/webpack/package.json +++ b/resolvers/webpack/package.json @@ -31,15 +31,15 @@ "homepage": "https://github.com/benmosher/eslint-plugin-import/tree/master/resolvers/webpack", "dependencies": { "array-find": "^1.0.0", - "debug": "^2.6.8", - "enhanced-resolve": "~0.9.0", + "debug": "^2.6.9", + "enhanced-resolve": "^0.9.1", "find-root": "^1.1.0", - "has": "^1.0.1", - "interpret": "^1.0.0", - "lodash": "^4.17.4", + "has": "^1.0.3", + "interpret": "^1.2.0", + "lodash": "^4.17.15", "node-libs-browser": "^1.0.0 || ^2.0.0", - "resolve": "^1.10.0", - "semver": "^5.3.0" + "resolve": "^1.13.1", + "semver": "^5.7.1" }, "peerDependencies": { "eslint-plugin-import": ">=1.4.0", From 47a232e5a8e9b14484d48c7ad7c1879dee1dc6bc Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 7 Dec 2019 23:25:24 -0800 Subject: [PATCH 22/31] [resolvers/webpack] v0.12.0 --- resolvers/webpack/CHANGELOG.md | 2 ++ resolvers/webpack/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index 45a89bf0aa..78e01348e4 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -5,6 +5,8 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +## 0.12.0 - 2019-12-07 + ### Added - [New] enable passing cwd as an option to `eslint-import-resolver-webpack` ([#1503], thanks [@Aghassi]) diff --git a/resolvers/webpack/package.json b/resolvers/webpack/package.json index 136d3b4dbf..924f2bca6c 100644 --- a/resolvers/webpack/package.json +++ b/resolvers/webpack/package.json @@ -1,6 +1,6 @@ { "name": "eslint-import-resolver-webpack", - "version": "0.11.1", + "version": "0.12.0", "description": "Resolve paths to dependencies, given a webpack.config.js. Plugin for eslint-plugin-import.", "main": "index.js", "scripts": { From 9b76635160cdbf4e0f1f34b9757da81aeb1882f9 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 8 Dec 2019 15:12:53 -0800 Subject: [PATCH 23/31] Bump to v2.19.0 --- CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c96bcd43..f4b13c27c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] + +## [2.19.0] - 2019-12-08 ### Added - [`internal-regex`]: regex pattern for marking packages "internal" ([#1491], thanks [@Librazy]) - [`group-exports`]: make aggregate module exports valid ([#1472], thanks [@atikenny]) @@ -849,7 +851,8 @@ for info on changes for earlier releases. [#119]: https://github.com/benmosher/eslint-plugin-import/issues/119 [#89]: https://github.com/benmosher/eslint-plugin-import/issues/89 -[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.18.2...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.19.0...HEAD +[2.19.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.18.2...v2.19.0 [2.18.2]: https://github.com/benmosher/eslint-plugin-import/compare/v2.18.1...v2.18.2 [2.18.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.18.0...v2.18.1 [2.18.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.17.3...v2.18.0 diff --git a/package.json b/package.json index cb6a1dbdbe..728758b921 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.18.2", + "version": "2.19.0", "description": "Import with sanity.", "engines": { "node": ">=4" From be7efb14e44c201faae9af39737a17d037162b37 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 8 Dec 2019 23:15:06 -0800 Subject: [PATCH 24/31] [Fix] `no-extraneous-dependencies`: ensure `node.source` exists --- src/rules/no-extraneous-dependencies.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index ced0f44b60..41ccb2a318 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -206,7 +206,9 @@ module.exports = { reportIfMissing(context, deps, depsOptions, node, node.source.value) }, ExportNamedDeclaration: function (node) { - reportIfMissing(context, deps, depsOptions, node, node.source.value) + if (node.source) { + reportIfMissing(context, deps, depsOptions, node, node.source.value) + } }, ExportAllDeclaration: function (node) { reportIfMissing(context, deps, depsOptions, node, node.source.value) From bc3b034b59a034b4aa47b8a9e74f48fe0e14e997 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 8 Dec 2019 23:24:27 -0800 Subject: [PATCH 25/31] Bump to v2.19.1 --- CHANGELOG.md | 7 ++++++- package.json | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b13c27c2..da83f13868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +## [2.19.1] - 2019-12-08 +### Fixed +- [`no-extraneous-dependencies`]: ensure `node.source` exists + ## [2.19.0] - 2019-12-08 ### Added - [`internal-regex`]: regex pattern for marking packages "internal" ([#1491], thanks [@Librazy]) @@ -851,7 +855,8 @@ for info on changes for earlier releases. [#119]: https://github.com/benmosher/eslint-plugin-import/issues/119 [#89]: https://github.com/benmosher/eslint-plugin-import/issues/89 -[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.19.0...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.19.1...HEAD +[2.19.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.19.0...v2.19.1 [2.19.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.18.2...v2.19.0 [2.18.2]: https://github.com/benmosher/eslint-plugin-import/compare/v2.18.1...v2.18.2 [2.18.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.18.0...v2.18.1 diff --git a/package.json b/package.json index 728758b921..e196159744 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.19.0", + "version": "2.19.1", "description": "Import with sanity.", "engines": { "node": ">=4" From 977da57a21077b108b642d984a3be580cc9c3a7b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 8 Dec 2019 23:48:38 -0800 Subject: [PATCH 26/31] [Tests] `no-extraneous-dependencies`: add test case for 2.19.1 fix --- tests/src/rules/no-extraneous-dependencies.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index d29f23857f..f3f7448053 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -124,6 +124,7 @@ ruleTester.run('no-extraneous-dependencies', rule, { }), test({ code: 'export { foo } from "lodash.cond"' }), test({ code: 'export * from "lodash.cond"' }), + test({ code: 'export function getToken() {}' }), ], invalid: [ test({ From 2d669b19da62e8d0d0cf8bb40f95823f49c02a94 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 9 Dec 2019 09:00:57 -0800 Subject: [PATCH 27/31] [meta] fix changelog internal-regex link --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da83f13868..18409a9c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [2.19.0] - 2019-12-08 ### Added -- [`internal-regex`]: regex pattern for marking packages "internal" ([#1491], thanks [@Librazy]) +- [`internal-regex` setting]: regex pattern for marking packages "internal" ([#1491], thanks [@Librazy]) - [`group-exports`]: make aggregate module exports valid ([#1472], thanks [@atikenny]) - [`no-namespace`]: Make rule fixable ([#1401], thanks [@TrevorBurnham]) - support `parseForESLint` from custom parser ([#1435], thanks [@JounQin]) @@ -575,6 +575,7 @@ for info on changes for earlier releases. [`import/parsers` setting]: ./README.md#importparsers [`import/core-modules` setting]: ./README.md#importcore-modules [`import/external-module-folders` setting]: ./README.md#importexternal-module-folders +[`internal-regex` setting]: ./README.md#importinternal-regex [`default`]: ./docs/rules/default.md [`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md From 614e55f8306cddd55066babcae653af9fb9ead92 Mon Sep 17 00:00:00 2001 From: AamuLumi Date: Mon, 9 Dec 2019 15:25:28 +0100 Subject: [PATCH 28/31] [Fix] `order`: Fix `alphabetize` bug with `newlines-between` Fixes #1561. --- src/rules/order.js | 8 ++++---- tests/src/rules/order.js | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/rules/order.js b/src/rules/order.js index b3ea8207e9..7a7629bf7f 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -599,16 +599,16 @@ module.exports = { registerNode(context, node, name, 'require', ranks, imported) }, 'Program:exit': function reportAndReset() { + if (newlinesBetweenImports !== 'ignore') { + makeNewlinesBetweenReport(context, imported, newlinesBetweenImports) + } + if (alphabetize.order !== 'ignore') { mutateRanksToAlphabetize(imported, alphabetize.order) } makeOutOfOrderReport(context, imported) - if (newlinesBetweenImports !== 'ignore') { - makeNewlinesBetweenReport(context, imported, newlinesBetweenImports) - } - imported = [] }, FunctionDeclaration: incrementLevel, diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index 2f67a89777..153a923380 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -560,6 +560,21 @@ ruleTester.run('order', rule, { alphabetize: {order: 'desc'}, }], }), + // Option alphabetize with newlines-between: {order: 'asc', newlines-between: 'always'} + test({ + code: ` + import b from 'Bar'; + import c from 'bar'; + import a from 'foo'; + + import index from './'; + `, + options: [{ + groups: ['external', 'index'], + alphabetize: {order: 'asc'}, + 'newlines-between': 'always', + }], + }), ], invalid: [ // builtin before external module (require) From f507f38b5c51593e9528bbc1f54f02f747b5c966 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 9 Dec 2019 13:14:36 -0800 Subject: [PATCH 29/31] [Fix] `memo-parser`: add missing dependency --- memo-parser/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/memo-parser/package.json b/memo-parser/package.json index fa7d12973e..2c81bb88a3 100644 --- a/memo-parser/package.json +++ b/memo-parser/package.json @@ -26,5 +26,8 @@ "homepage": "https://github.com/benmosher/eslint-plugin-import#readme", "peerDependencies": { "eslint": ">=3.5.0" + }, + "dependencies": { + "eslint-module-utils": "^2.5.0" } } From e51773956a63a67eb510d34eb27d1d353b08bfd3 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Tue, 10 Dec 2019 12:51:07 +0900 Subject: [PATCH 30/31] [Fix] `import/extensions`: ignore non-main modules --- CHANGELOG.md | 4 ++++ src/core/importType.js | 4 ++-- src/rules/extensions.js | 12 ++++++------ tests/src/rules/extensions.js | 10 ++-------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18409a9c13..6c0bfec244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +### Fixed +- [`import/extensions`]: ignore non-main modules ([#1563], thanks [@saschanaz]) + ## [2.19.1] - 2019-12-08 ### Fixed - [`no-extraneous-dependencies`]: ensure `node.source` exists @@ -620,6 +623,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1563]: https://github.com/benmosher/eslint-plugin-import/pull/1563 [#1551]: https://github.com/benmosher/eslint-plugin-import/pull/1551 [#1542]: https://github.com/benmosher/eslint-plugin-import/pull/1542 [#1521]: https://github.com/benmosher/eslint-plugin-import/pull/1521 diff --git a/src/core/importType.js b/src/core/importType.js index 722ce7b063..57558cbd82 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -34,7 +34,7 @@ function isExternalPath(path, name, settings) { } const externalModuleRegExp = /^\w/ -function isExternalModule(name, settings, path) { +export function isExternalModule(name, settings, path) { return externalModuleRegExp.test(name) && isExternalPath(path, name, settings) } @@ -44,7 +44,7 @@ export function isExternalModuleMain(name, settings, path) { } const scopedRegExp = /^@[^/]+\/?[^/]+/ -function isScoped(name) { +export function isScoped(name) { return scopedRegExp.test(name) } diff --git a/src/rules/extensions.js b/src/rules/extensions.js index 0fe605adcb..c6077fb2c8 100644 --- a/src/rules/extensions.js +++ b/src/rules/extensions.js @@ -1,7 +1,7 @@ import path from 'path' import resolve from 'eslint-module-utils/resolve' -import { isBuiltIn, isExternalModuleMain, isScopedMain } from '../core/importType' +import { isBuiltIn, isExternalModule, isScoped } from '../core/importType' import docsUrl from '../docsUrl' const enumValues = { enum: [ 'always', 'ignorePackages', 'never' ] } @@ -110,8 +110,8 @@ module.exports = { return props.pattern[extension] || props.defaultConfig } - function isUseOfExtensionRequired(extension, isPackageMain) { - return getModifier(extension) === 'always' && (!props.ignorePackages || !isPackageMain) + function isUseOfExtensionRequired(extension, isPackage) { + return getModifier(extension) === 'always' && (!props.ignorePackages || !isPackage) } function isUseOfExtensionForbidden(extension) { @@ -144,11 +144,11 @@ module.exports = { const extension = path.extname(resolvedPath || importPath).substring(1) // determine if this is a module - const isPackageMain = isExternalModuleMain(importPath, context.settings) - || isScopedMain(importPath) + const isPackage = isExternalModule(importPath, context.settings) + || isScoped(importPath) if (!extension || !importPath.endsWith(`.${extension}`)) { - const extensionRequired = isUseOfExtensionRequired(extension, isPackageMain) + const extensionRequired = isUseOfExtensionRequired(extension, isPackage) const extensionForbidden = isUseOfExtensionForbidden(extension) if (extensionRequired && !extensionForbidden) { context.report({ diff --git a/tests/src/rules/extensions.js b/tests/src/rules/extensions.js index a1629335c6..720867c219 100644 --- a/tests/src/rules/extensions.js +++ b/tests/src/rules/extensions.js @@ -293,6 +293,7 @@ ruleTester.run('extensions', rule, { import bar from './bar.json' import Component from './Component' import baz from 'foo/baz' + import baw from '@scoped/baw/import' import express from 'express' `, options: [ 'always', {ignorePackages: true} ], @@ -301,10 +302,6 @@ ruleTester.run('extensions', rule, { message: 'Missing file extension for "./Component"', line: 4, column: 31, - }, { - message: 'Missing file extension for "foo/baz"', - line: 5, - column: 25, }, ], }), @@ -315,6 +312,7 @@ ruleTester.run('extensions', rule, { import bar from './bar.json' import Component from './Component' import baz from 'foo/baz' + import baw from '@scoped/baw/import' import express from 'express' `, options: [ 'ignorePackages' ], @@ -323,10 +321,6 @@ ruleTester.run('extensions', rule, { message: 'Missing file extension for "./Component"', line: 4, column: 31, - }, { - message: 'Missing file extension for "foo/baz"', - line: 5, - column: 25, }, ], }), From 4e8960dca0790388cde253bd2e016711b8dce21b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20S=CC=8Ctekl?= Date: Mon, 9 Dec 2019 11:55:34 +0100 Subject: [PATCH 31/31] [Fix] `no-unused-modules`: fix usage of `import/extensions` settings --- CHANGELOG.md | 4 ++ src/rules/no-unused-modules.js | 64 +++++++++++++++---- .../no-unused-modules/jsx/file-jsx-a.jsx | 3 + .../no-unused-modules/jsx/file-jsx-b.jsx | 1 + .../no-unused-modules/typescript/file-ts-a.ts | 3 + .../no-unused-modules/typescript/file-ts-b.ts | 1 + tests/src/rules/no-unused-modules.js | 64 +++++++++++++++++++ 7 files changed, 127 insertions(+), 13 deletions(-) create mode 100644 tests/files/no-unused-modules/jsx/file-jsx-a.jsx create mode 100644 tests/files/no-unused-modules/jsx/file-jsx-b.jsx create mode 100644 tests/files/no-unused-modules/typescript/file-ts-a.ts create mode 100644 tests/files/no-unused-modules/typescript/file-ts-b.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c0bfec244..b64f1d8240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] +### Fixed +- [`no-unused-modules`]: fix usage of `import/extensions` settings ([#1560], thanks [@stekycz]) ### Fixed - [`import/extensions`]: ignore non-main modules ([#1563], thanks [@saschanaz]) @@ -624,6 +626,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md [#1563]: https://github.com/benmosher/eslint-plugin-import/pull/1563 +[#1560]: https://github.com/benmosher/eslint-plugin-import/pull/1560 [#1551]: https://github.com/benmosher/eslint-plugin-import/pull/1551 [#1542]: https://github.com/benmosher/eslint-plugin-import/pull/1542 [#1521]: https://github.com/benmosher/eslint-plugin-import/pull/1521 @@ -1042,3 +1045,4 @@ for info on changes for earlier releases. [@christophercurrie]: https://github.com/christophercurrie [@randallreedjr]: https://github.com/randallreedjr [@Pessimistress]: https://github.com/Pessimistress +[@stekycz]: https://github.com/stekycz diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 9bbafe99db..5c6a73d828 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -5,6 +5,7 @@ */ import Exports from '../ExportMap' +import { getFileExtensions } from 'eslint-module-utils/ignore' import resolve from 'eslint-module-utils/resolve' import docsUrl from '../docsUrl' import { dirname, join } from 'path' @@ -16,19 +17,40 @@ import includes from 'array-includes' // and has been moved to eslint/lib/cli-engine/file-enumerator in version 6 let listFilesToProcess try { - var FileEnumerator = require('eslint/lib/cli-engine/file-enumerator').FileEnumerator - listFilesToProcess = function (src) { - var e = new FileEnumerator() + const FileEnumerator = require('eslint/lib/cli-engine/file-enumerator').FileEnumerator + listFilesToProcess = function (src, extensions) { + const e = new FileEnumerator({ + extensions: extensions, + }) return Array.from(e.iterateFiles(src), ({ filePath, ignored }) => ({ ignored, filename: filePath, })) } } catch (e1) { + // Prevent passing invalid options (extensions array) to old versions of the function. + // https://github.com/eslint/eslint/blob/v5.16.0/lib/util/glob-utils.js#L178-L280 + // https://github.com/eslint/eslint/blob/v5.2.0/lib/util/glob-util.js#L174-L269 + let originalListFilesToProcess try { - listFilesToProcess = require('eslint/lib/util/glob-utils').listFilesToProcess + originalListFilesToProcess = require('eslint/lib/util/glob-utils').listFilesToProcess + listFilesToProcess = function (src, extensions) { + return originalListFilesToProcess(src, { + extensions: extensions, + }) + } } catch (e2) { - listFilesToProcess = require('eslint/lib/util/glob-util').listFilesToProcess + originalListFilesToProcess = require('eslint/lib/util/glob-util').listFilesToProcess + + listFilesToProcess = function (src, extensions) { + const patterns = src.reduce((carry, pattern) => { + return carry.concat(extensions.map((extension) => { + return /\*\*|\*\./.test(pattern) ? pattern : `${pattern}/**/*${extension}` + })) + }, src.slice()) + + return originalListFilesToProcess(patterns) + } } } @@ -44,7 +66,6 @@ const CLASS_DECLARATION = 'ClassDeclaration' const DEFAULT = 'default' const TYPE_ALIAS = 'TypeAlias' -let preparationDone = false const importList = new Map() const exportList = new Map() const ignoredFiles = new Set() @@ -59,12 +80,14 @@ const isNodeModule = path => { * * return all files matching src pattern, which are not matching the ignoreExports pattern */ -const resolveFiles = (src, ignoreExports) => { +const resolveFiles = (src, ignoreExports, context) => { + const extensions = Array.from(getFileExtensions(context.settings)) + const srcFiles = new Set() - const srcFileList = listFilesToProcess(src) + const srcFileList = listFilesToProcess(src, extensions) // prepare list of ignored files - const ignoredFilesList = listFilesToProcess(ignoreExports) + const ignoredFilesList = listFilesToProcess(ignoreExports, extensions) ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename)) // prepare list of source files, don't consider files from node_modules @@ -200,11 +223,26 @@ const getSrc = src => { * the start of a new eslint run */ let srcFiles +let lastPrepareKey const doPreparation = (src, ignoreExports, context) => { - srcFiles = resolveFiles(getSrc(src), ignoreExports) + const prepareKey = JSON.stringify({ + src: (src || []).sort(), + ignoreExports: (ignoreExports || []).sort(), + extensions: Array.from(getFileExtensions(context.settings)).sort(), + }) + if (prepareKey === lastPrepareKey) { + return + } + + importList.clear() + exportList.clear() + ignoredFiles.clear() + filesOutsideSrc.clear() + + srcFiles = resolveFiles(getSrc(src), ignoreExports, context) prepareImportsAndExports(srcFiles, context) determineUsage() - preparationDone = true + lastPrepareKey = prepareKey } const newNamespaceImportExists = specifiers => @@ -340,7 +378,7 @@ module.exports = { unusedExports, } = context.options[0] || {} - if (unusedExports && !preparationDone) { + if (unusedExports) { doPreparation(src, ignoreExports, context) } @@ -389,7 +427,7 @@ module.exports = { // make sure file to be linted is included in source files if (!srcFiles.has(file)) { - srcFiles = resolveFiles(getSrc(src), ignoreExports) + srcFiles = resolveFiles(getSrc(src), ignoreExports, context) if (!srcFiles.has(file)) { filesOutsideSrc.add(file) return diff --git a/tests/files/no-unused-modules/jsx/file-jsx-a.jsx b/tests/files/no-unused-modules/jsx/file-jsx-a.jsx new file mode 100644 index 0000000000..1de6d020c8 --- /dev/null +++ b/tests/files/no-unused-modules/jsx/file-jsx-a.jsx @@ -0,0 +1,3 @@ +import {b} from './file-jsx-b'; + +export const a = b + 1; diff --git a/tests/files/no-unused-modules/jsx/file-jsx-b.jsx b/tests/files/no-unused-modules/jsx/file-jsx-b.jsx new file mode 100644 index 0000000000..202103085c --- /dev/null +++ b/tests/files/no-unused-modules/jsx/file-jsx-b.jsx @@ -0,0 +1 @@ +export const b = 2; diff --git a/tests/files/no-unused-modules/typescript/file-ts-a.ts b/tests/files/no-unused-modules/typescript/file-ts-a.ts new file mode 100644 index 0000000000..a4272256e6 --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-a.ts @@ -0,0 +1,3 @@ +import {b} from './file-ts-b'; + +export const a = b + 1; diff --git a/tests/files/no-unused-modules/typescript/file-ts-b.ts b/tests/files/no-unused-modules/typescript/file-ts-b.ts new file mode 100644 index 0000000000..202103085c --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-b.ts @@ -0,0 +1 @@ +export const b = 2; diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 5afae4dfac..cb3d4c103d 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -1,9 +1,13 @@ import { test, testFilePath } from '../utils' +import jsxConfig from '../../../config/react' +import typescriptConfig from '../../../config/typescript' import { RuleTester } from 'eslint' import fs from 'fs' const ruleTester = new RuleTester() + , typescriptRuleTester = new RuleTester(typescriptConfig) + , jsxRuleTester = new RuleTester(jsxConfig) , rule = require('rules/no-unused-modules') const error = message => ({ ruleId: 'no-unused-modules', message }) @@ -18,6 +22,18 @@ const unusedExportsOptions = [{ ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')], }] +const unusedExportsTypescriptOptions = [{ + unusedExports: true, + src: [testFilePath('./no-unused-modules/typescript')], + ignoreExports: undefined, +}] + +const unusedExportsJsxOptions = [{ + unusedExports: true, + src: [testFilePath('./no-unused-modules/jsx')], + ignoreExports: undefined, +}] + // tests for missing exports ruleTester.run('no-unused-modules', rule, { valid: [ @@ -686,3 +702,51 @@ describe('Avoid errors if re-export all from umd compiled library', () => { invalid: [], }) }) + +describe('correctly work with Typescript only files', () => { + typescriptRuleTester.run('no-unused-modules', rule, { + valid: [ + test({ + options: unusedExportsTypescriptOptions, + code: 'import a from "file-ts-a";', + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'), + }), + ], + invalid: [ + test({ + options: unusedExportsTypescriptOptions, + code: `export const b = 2;`, + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/typescript/file-ts-b.ts'), + errors: [ + error(`exported declaration 'b' not used within other modules`), + ], + }), + ], + }) +}) + +describe('correctly work with JSX only files', () => { + jsxRuleTester.run('no-unused-modules', rule, { + valid: [ + test({ + options: unusedExportsJsxOptions, + code: 'import a from "file-jsx-a";', + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/jsx/file-jsx-a.jsx'), + }), + ], + invalid: [ + test({ + options: unusedExportsJsxOptions, + code: `export const b = 2;`, + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/jsx/file-jsx-b.jsx'), + errors: [ + error(`exported declaration 'b' not used within other modules`), + ], + }), + ], + }) +})