From 7abb1e1c9ec95dcad44bf4882e05b094b7f67400 Mon Sep 17 00:00:00 2001 From: Gajus Kuizinas Date: Tue, 26 Jun 2018 18:33:44 +0100 Subject: [PATCH 001/151] feat: make no-cycle ignore Flow imports --- src/rules/no-cycle.js | 4 ++++ tests/src/rules/no-cycle.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index bbc251e388..ccf77dcf2e 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -30,6 +30,10 @@ module.exports = { function checkSourceValue(sourceNode, importer) { const imported = Exports.get(sourceNode.value, context) + if (sourceNode.parent.importKind === 'type') { + return // no Flow import resolution + } + if (imported == null) { return // no-unresolved territory } diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js index ae45ba36ec..4ee4daacb6 100644 --- a/tests/src/rules/no-cycle.js +++ b/tests/src/rules/no-cycle.js @@ -36,6 +36,10 @@ ruleTester.run('no-cycle', rule, { code: 'import { foo } from "./depth-two"', options: [{ maxDepth: 1 }], }), + test({ + code: 'import type { FooType } from "./depth-one"', + parser: 'babel-eslint', + }), ], invalid: [ test({ From 60f65979fae29fc38d11cd79ade6336037a789ea Mon Sep 17 00:00:00 2001 From: Gajus Kuizinas Date: Tue, 26 Jun 2018 19:30:36 +0100 Subject: [PATCH 002/151] fix: handly possible undefined parent --- src/rules/no-cycle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index ccf77dcf2e..a925f98713 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -30,7 +30,7 @@ module.exports = { function checkSourceValue(sourceNode, importer) { const imported = Exports.get(sourceNode.value, context) - if (sourceNode.parent.importKind === 'type') { + if (sourceNode.parent && sourceNode.parent.importKind === 'type') { return // no Flow import resolution } From 3feb54cfa20db3ed39b8a32e430b4ea3508eda8a Mon Sep 17 00:00:00 2001 From: Gajus Kuizinas Date: Sun, 29 Jul 2018 12:50:40 +0100 Subject: [PATCH 003/151] fix: add a workaround for ESLint < v5 --- src/rules/no-cycle.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index a925f98713..1a70db2c70 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -34,6 +34,10 @@ module.exports = { return // no Flow import resolution } + if (sourceNode._babelType === 'Literal') { + return // no Flow import resolution, workaround for ESLint < 5.x + } + if (imported == null) { return // no-unresolved territory } From 2d4f651d6e2759e56e2826d874c697e008deaa9c Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 1 Aug 2018 23:13:23 -0700 Subject: [PATCH 004/151] [eslint-module-utils]: when parser settings are not an array, throw a better error message Fixes #1149. --- utils/ignore.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/utils/ignore.js b/utils/ignore.js index 88e4080dda..91cc731a81 100644 --- a/utils/ignore.js +++ b/utils/ignore.js @@ -24,8 +24,11 @@ function makeValidExtensionSet(settings) { // all alternate parser extensions are also valid if ('import/parsers' in settings) { for (let parser in settings['import/parsers']) { - settings['import/parsers'][parser] - .forEach(ext => exts.add(ext)) + const parserSettings = settings['import/parsers'][parser] + if (!Array.isArray(parserSettings)) { + throw new TypeError('"settings" for ' + parser + ' must be an array') + } + parserSettings.forEach(ext => exts.add(ext)) } } From f5bff7b14c52fdd91afe76865ecec955b1f96539 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 9 Aug 2018 23:17:21 -0700 Subject: [PATCH 005/151] [fix] repeat fix from #797 for #717, in another place --- src/ExportMap.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ExportMap.js b/src/ExportMap.js index 1cb5dc3e9c..66b212a211 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -38,7 +38,12 @@ export default class ExportMap { get size() { let size = this.namespace.size + this.reexports.size - this.dependencies.forEach(dep => size += dep().size) + this.dependencies.forEach(dep => { + const d = dep() + // CJS / ignored dependencies won't exist (#717) + if (d == null) return + size += d.size + }) return size } From 825234402a3dbe58138781c2f44c3933c59babfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Czap=20Bal=C3=A1zs?= Date: Tue, 31 Jul 2018 10:47:36 +0200 Subject: [PATCH 006/151] Add error to output when module loaded as resolver has invalid API --- tests/files/foo-bar-resolver-invalid.js | 1 + tests/src/core/resolve.js | 17 ++++++++++++++++- utils/resolve.js | 13 ++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 tests/files/foo-bar-resolver-invalid.js diff --git a/tests/files/foo-bar-resolver-invalid.js b/tests/files/foo-bar-resolver-invalid.js new file mode 100644 index 0000000000..a6213d6678 --- /dev/null +++ b/tests/files/foo-bar-resolver-invalid.js @@ -0,0 +1 @@ +exports = {}; diff --git a/tests/src/core/resolve.js b/tests/src/core/resolve.js index 3c15303edf..b9a9063243 100644 --- a/tests/src/core/resolve.js +++ b/tests/src/core/resolve.js @@ -110,6 +110,22 @@ describe('resolve', function () { expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }) }) + it('reports loaded resolver with invalid interface', function () { + const resolverName = './foo-bar-resolver-invalid'; + const testContext = utils.testContext({ 'import/resolver': resolverName }); + const testContextReports = [] + testContext.report = function (reportInfo) { + testContextReports.push(reportInfo) + } + testContextReports.length = 0 + expect(resolve( '../files/foo' + , 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`) + expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }) + }) + it('respects import/resolve extensions', function () { const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] }}) @@ -119,7 +135,6 @@ describe('resolve', function () { }) it('reports load exception in a user resolver', function () { - const testContext = utils.testContext({ 'import/resolver': './load-error-resolver' }) const testContextReports = [] testContext.report = function (reportInfo) { diff --git a/utils/resolve.js b/utils/resolve.js index b280ca2cfa..87a1eaea81 100644 --- a/utils/resolve.js +++ b/utils/resolve.js @@ -160,8 +160,19 @@ function requireResolver(name, sourceFile) { if (!resolver) { throw new Error(`unable to load resolver "${name}".`) + } + if (!isResolverValid(resolver)) { + throw new Error(`${name} with invalid interface loaded as resolver`) + } + + return resolver +} + +function isResolverValid(resolver) { + if (resolver.interfaceVersion === 2) { + return resolver.resolve && typeof resolver.resolve === 'function' } else { - return resolver; + return resolver.resolveImport && typeof resolver.resolveImport === 'function' } } From e30a7577bf46f8c44bb12118563e833fd1b69d06 Mon Sep 17 00:00:00 2001 From: Joshua Freedman Date: Thu, 2 Aug 2018 09:00:36 -0700 Subject: [PATCH 007/151] Add JSX check to namespace rule --- src/rules/namespace.js | 19 +++++++++++++++---- tests/src/rules/namespace.js | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/rules/namespace.js b/src/rules/namespace.js index a1d5b6d813..bbba2ce2ef 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -44,7 +44,7 @@ module.exports = { return { // pick up all imports at body entry time, to properly respect hoisting - 'Program': function ({ body }) { + Program: function ({ body }) { function processBodyStatement(declaration) { if (declaration.type !== 'ImportDeclaration') return @@ -83,7 +83,7 @@ module.exports = { }, // same as above, but does not add names to local map - 'ExportNamespaceSpecifier': function (namespace) { + ExportNamespaceSpecifier: function (namespace) { var declaration = importDeclaration(context) var imports = Exports.get(declaration.source.value, context) @@ -102,7 +102,7 @@ module.exports = { // todo: check for possible redefinition - 'MemberExpression': function (dereference) { + MemberExpression: function (dereference) { if (dereference.object.type !== 'Identifier') return if (!namespaces.has(dereference.object.name)) return @@ -146,7 +146,7 @@ module.exports = { }, - 'VariableDeclarator': function ({ id, init }) { + VariableDeclarator: function ({ id, init }) { if (init == null) return if (init.type !== 'Identifier') return if (!namespaces.has(init.name)) return @@ -193,6 +193,17 @@ module.exports = { testKey(id, namespaces.get(init.name)) }, + + JSXMemberExpression: function({object, property}) { + if (!namespaces.has(object.name)) return + var namespace = namespaces.get(object.name) + if (!namespace.has(property.name)) { + context.report({ + node: property, + message: makeMessage(property, [object.name]), + }) + } + }, } }, } diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index 1cfee2b54d..7fa8cfcdb9 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -104,6 +104,16 @@ const valid = [ parser: 'babel-eslint', }), + // JSX + test({ + code: 'import * as Names from "./named-exports"; const Foo = ', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }), + ...SYNTAX_CASES, ] @@ -185,6 +195,17 @@ const invalid = [ errors: [`'default' not found in imported namespace 'ree'.`], }), + // JSX + test({ + code: 'import * as Names from "./named-exports"; const Foo = ', + errors: [error('e', 'Names')], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }), + ] /////////////////////// From 59311419f0546a1f14929a6889a86ec4859486ae Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 13 Aug 2018 00:28:41 -0700 Subject: [PATCH 008/151] Changelog/package bumps --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7254bc70..3969ea3aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +## [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]) + +### Fixed +- [`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]) +- repeat fix from [#797] for [#717], in another place (thanks [@ljharb]) + +### Refactors +- add explicit support for RestElement alongside ExperimentalRestProperty (thanks [@ljharb]) + ## [2.13.0] - 2018-06-24 ### Added - Add ESLint 5 support ([#1122], thanks [@ai] and [@ljharb]) @@ -473,7 +493,13 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1151]: https://github.com/benmosher/eslint-plugin-import/pull/1151 +[#1137]: https://github.com/benmosher/eslint-plugin-import/pull/1137 +[#1135]: https://github.com/benmosher/eslint-plugin-import/pull/1135 +[#1128]: https://github.com/benmosher/eslint-plugin-import/pull/1128 +[#1126]: https://github.com/benmosher/eslint-plugin-import/pull/1126 [#1122]: https://github.com/benmosher/eslint-plugin-import/pull/1122 +[#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 [#1068]: https://github.com/benmosher/eslint-plugin-import/pull/1068 @@ -486,6 +512,7 @@ for info on changes for earlier releases. [#858]: https://github.com/benmosher/eslint-plugin-import/pull/858 [#843]: https://github.com/benmosher/eslint-plugin-import/pull/843 [#871]: https://github.com/benmosher/eslint-plugin-import/pull/871 +[#797]: https://github.com/benmosher/eslint-plugin-import/pull/797 [#744]: https://github.com/benmosher/eslint-plugin-import/pull/744 [#742]: https://github.com/benmosher/eslint-plugin-import/pull/742 [#737]: https://github.com/benmosher/eslint-plugin-import/pull/737 @@ -558,6 +585,7 @@ for info on changes for earlier releases. [#842]: https://github.com/benmosher/eslint-plugin-import/issues/842 [#839]: https://github.com/benmosher/eslint-plugin-import/issues/839 [#720]: https://github.com/benmosher/eslint-plugin-import/issues/720 +[#717]: https://github.com/benmosher/eslint-plugin-import/issues/717 [#686]: https://github.com/benmosher/eslint-plugin-import/issues/686 [#671]: https://github.com/benmosher/eslint-plugin-import/issues/671 [#660]: https://github.com/benmosher/eslint-plugin-import/issues/660 @@ -734,3 +762,8 @@ for info on changes for earlier releases. [@hulkish]: https://github.com/hulkish [@chrislloyd]: https://github.com/chrislloyd [@ai]: https://github.com/ai +[@syymza]: https://github.com/syymza +[@justinanastos]: https://github.com/justinanastos +[@1pete]: https://github.com/1pete +[@gajus]: https://github.com/gajus +[@jf248]: https://github.com/jf248 diff --git a/package.json b/package.json index 7d39395a76..810084436e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.13.0", + "version": "2.14.0", "description": "Import with sanity.", "engines": { "node": ">=4" From b0b6125844e28e2b63752b590c50cd8fad262c74 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 17 Aug 2018 13:30:00 -0700 Subject: [PATCH 009/151] [Fix] detect extraneous deps even when there are none in the first place Fixes #1161 --- src/rules/no-extraneous-dependencies.js | 6 +----- tests/src/rules/no-extraneous-dependencies.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 9d51018e9a..ec9eedaa2d 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -176,11 +176,7 @@ module.exports = { create: function (context) { const options = context.options[0] || {} const filename = context.getFilename() - const deps = getDependencies(context, options.packageDir) - - if (!deps) { - return {} - } + const deps = getDependencies(context, options.packageDir) || extractDepFields({}) const depsOptions = { allowDevDeps: testConfig(options.devDependencies, filename) !== false, diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index 381b392cbf..10012f6b47 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -17,6 +17,7 @@ const packageFileWithSyntaxErrorMessage = (() => { const packageDirWithFlowTyped = path.join(__dirname, '../../files/with-flow-typed') const packageDirMonoRepoRoot = path.join(__dirname, '../../files/monorepo') const packageDirMonoRepoWithNested = path.join(__dirname, '../../files/monorepo/packages/nested-package') +const packageDirWithEmpty = path.join(__dirname, '../../files/empty') ruleTester.run('no-extraneous-dependencies', rule, { valid: [ @@ -263,5 +264,14 @@ ruleTester.run('no-extraneous-dependencies', rule, { message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it", }], }), + test({ + code: 'import "react";', + filename: path.join(packageDirWithEmpty, 'index.js'), + options: [{packageDir: packageDirWithEmpty}], + errors: [{ + ruleId: 'no-extraneous-dependencies', + message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it", + }], + }), ] }) From f7bd328f7b86c9f6d95c58c261b0b513df14bbd5 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 17 Aug 2018 13:30:00 -0700 Subject: [PATCH 010/151] [Fix] detect extraneous deps even when there are none in the first place Fixes #1161 --- tests/files/empty/package.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 tests/files/empty/package.json diff --git a/tests/files/empty/package.json b/tests/files/empty/package.json new file mode 100644 index 0000000000..da86787ad3 --- /dev/null +++ b/tests/files/empty/package.json @@ -0,0 +1,4 @@ +{ + "name": "foo", + "version": "1.0.0" +} From 8d8c20a92d1255a0701f9f69d88309a5579cc228 Mon Sep 17 00:00:00 2001 From: st-sloth Date: Wed, 22 Aug 2018 23:55:21 +0500 Subject: [PATCH 011/151] fix(dynamic-import-chunkname): Add proper webpack comment parsing --- docs/rules/dynamic-import-chunkname.md | 19 +++ src/rules/dynamic-import-chunkname.js | 65 +++++++-- tests/src/rules/dynamic-import-chunkname.js | 141 ++++++++++++++++++-- 3 files changed, 204 insertions(+), 21 deletions(-) diff --git a/docs/rules/dynamic-import-chunkname.md b/docs/rules/dynamic-import-chunkname.md index 98b98871e8..b1757b4e52 100644 --- a/docs/rules/dynamic-import-chunkname.md +++ b/docs/rules/dynamic-import-chunkname.md @@ -28,6 +28,10 @@ import( /*webpackChunkName:"someModule"*/ 'someModule', ); +import( + /* webpackChunkName : "someModule" */ + 'someModule', +); // chunkname contains a 6 (forbidden by rule config) import( @@ -41,6 +45,12 @@ import( 'someModule', ); +// invalid syntax for webpack comment +import( + /* totally not webpackChunkName: "someModule" */ + 'someModule', +); + // single-line comment, not a block-style comment import( // webpackChunkName: "someModule" @@ -59,6 +69,15 @@ The following patterns are valid: /* webpackChunkName: "someOtherModule12345789" */ 'someModule', ); + import( + /* webpackChunkName: "someModule" */ + /* webpackPrefetch: true */ + 'someModule', + ); + import( + /* webpackChunkName: "someModule", webpackPrefetch: true */ + 'someModule', + ); ``` ## When Not To Use It diff --git a/src/rules/dynamic-import-chunkname.js b/src/rules/dynamic-import-chunkname.js index 867808f0b0..6f51ebbbd5 100644 --- a/src/rules/dynamic-import-chunkname.js +++ b/src/rules/dynamic-import-chunkname.js @@ -1,3 +1,4 @@ +import vm from 'vm' import docsUrl from '../docsUrl' module.exports = { @@ -27,8 +28,10 @@ module.exports = { const { importFunctions = [] } = config || {} const { webpackChunknameFormat = '[0-9a-zA-Z-_/.]+' } = config || {} - const commentFormat = ` webpackChunkName: "${webpackChunknameFormat}" ` - const commentRegex = new RegExp(commentFormat) + const paddedCommentRegex = /^ (\S[\s\S]+\S) $/ + const commentStyleRegex = /^( \w+: ("[^"]*"|\d+|false|true),?)+ $/ + const chunkSubstrFormat = ` webpackChunkName: "${webpackChunknameFormat}",? ` + const chunkSubstrRegex = new RegExp(chunkSubstrFormat) return { CallExpression(node) { @@ -40,7 +43,7 @@ module.exports = { const arg = node.arguments[0] const leadingComments = sourceCode.getComments(arg).leading - if (!leadingComments || leadingComments.length !== 1) { + if (!leadingComments || leadingComments.length === 0) { context.report({ node, message: 'dynamic imports require a leading comment with the webpack chunkname', @@ -48,20 +51,56 @@ module.exports = { return } - const comment = leadingComments[0] - if (comment.type !== 'Block') { - context.report({ - node, - message: 'dynamic imports require a /* foo */ style comment, not a // foo comment', - }) - return + let isChunknamePresent = false + + for (const comment of leadingComments) { + if (comment.type !== 'Block') { + context.report({ + node, + message: 'dynamic imports require a /* foo */ style comment, not a // foo comment', + }) + return + } + + if (!paddedCommentRegex.test(comment.value)) { + context.report({ + node, + message: `dynamic imports require a block comment padded with spaces - /* foo */`, + }) + return + } + + try { + // just like webpack itself does + vm.runInNewContext(`(function(){return {${comment.value}}})()`) + } + catch (error) { + context.report({ + node, + message: `dynamic imports require a "webpack" comment with valid syntax`, + }) + return + } + + if (!commentStyleRegex.test(comment.value)) { + context.report({ + node, + message: + `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`, + }) + return + } + + if (chunkSubstrRegex.test(comment.value)) { + isChunknamePresent = true + } } - const webpackChunkDefinition = comment.value - if (!webpackChunkDefinition.match(commentRegex)) { + if (!isChunknamePresent) { context.report({ node, - message: `dynamic imports require a leading comment in the form /*${commentFormat}*/`, + message: + `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`, }) } }, diff --git a/tests/src/rules/dynamic-import-chunkname.js b/tests/src/rules/dynamic-import-chunkname.js index 329401106a..6b0d448da9 100644 --- a/tests/src/rules/dynamic-import-chunkname.js +++ b/tests/src/rules/dynamic-import-chunkname.js @@ -18,8 +18,10 @@ const parser = 'babel-eslint' const noLeadingCommentError = 'dynamic imports require a leading comment with the webpack chunkname' const nonBlockCommentError = 'dynamic imports require a /* foo */ style comment, not a // foo comment' -const commentFormatError = `dynamic imports require a leading comment in the form /* webpackChunkName: "${commentFormat}" */` -const pickyCommentFormatError = `dynamic imports require a leading comment in the form /* webpackChunkName: "${pickyCommentFormat}" */` +const noPaddingCommentError = 'dynamic imports require a block comment padded with spaces - /* foo */' +const invalidSyntaxCommentError = 'dynamic imports require a "webpack" comment with valid syntax' +const commentFormatError = `dynamic imports require a leading comment in the form /* webpackChunkName: "${commentFormat}",? */` +const pickyCommentFormatError = `dynamic imports require a leading comment in the form /* webpackChunkName: "${pickyCommentFormat}",? */` ruleTester.run('dynamic-import-chunkname', rule, { valid: [ @@ -79,6 +81,56 @@ ruleTester.run('dynamic-import-chunkname', rule, { options, parser, }, + { + code: `import( + /* webpackChunkName: "someModule", webpackPrefetch: true */ + 'test' + )`, + options, + parser, + }, + { + code: `import( + /* webpackChunkName: "someModule", webpackPrefetch: true, */ + 'test' + )`, + options, + parser, + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunkName: "someModule" */ + 'test' + )`, + options, + parser, + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunkName: "someModule", */ + 'test' + )`, + options, + parser, + }, + { + code: `import( + /* webpackPrefetch: true */ + /* webpackChunkName: "someModule" */ + 'test' + )`, + options, + parser, + }, + { + code: `import( + /* webpackChunkName: "someModule" */ + /* webpackPrefetch: true */ + 'test' + )`, + options, + parser, + }, { code: `import( /* webpackChunkName: "someModule" */ @@ -124,7 +176,7 @@ ruleTester.run('dynamic-import-chunkname', rule, { options, parser, errors: [{ - message: commentFormatError, + message: invalidSyntaxCommentError, type: 'CallExpression', }], }, @@ -148,7 +200,7 @@ ruleTester.run('dynamic-import-chunkname', rule, { options, parser, errors: [{ - message: commentFormatError, + message: invalidSyntaxCommentError, type: 'CallExpression', }], }, @@ -164,6 +216,79 @@ ruleTester.run('dynamic-import-chunkname', rule, { type: 'CallExpression', }], }, + { + code: `import( + /*webpackChunkName: "someModule"*/ + 'someModule' + )`, + options, + parser, + errors: [{ + message: noPaddingCommentError, + type: 'CallExpression', + }], + }, + { + code: `import( + /* webpackChunkName : "someModule" */ + 'someModule' + )`, + options, + parser, + errors: [{ + message: commentFormatError, + type: 'CallExpression', + }], + }, + { + code: `import( + /* webpackChunkName: "someModule" ; */ + 'someModule' + )`, + options, + parser, + errors: [{ + message: invalidSyntaxCommentError, + type: 'CallExpression', + }], + }, + { + code: `import( + /* totally not webpackChunkName: "someModule" */ + 'someModule' + )`, + options, + parser, + errors: [{ + message: invalidSyntaxCommentError, + type: 'CallExpression', + }], + }, + { + code: `import( + /* webpackPrefetch: true */ + /* webpackChunk: "someModule" */ + 'someModule' + )`, + options, + parser, + errors: [{ + message: commentFormatError, + type: 'CallExpression', + }], + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunk: "someModule" */ + 'someModule' + )`, + options, + parser, + errors: [{ + message: commentFormatError, + type: 'CallExpression', + }], + }, { code: `import( /* webpackChunkName: "someModule123" */ @@ -183,7 +308,7 @@ ruleTester.run('dynamic-import-chunkname', rule, { )`, options: multipleImportFunctionOptions, errors: [{ - message: commentFormatError, + message: invalidSyntaxCommentError, type: 'CallExpression', }], }, @@ -194,7 +319,7 @@ ruleTester.run('dynamic-import-chunkname', rule, { )`, options: multipleImportFunctionOptions, errors: [{ - message: commentFormatError, + message: invalidSyntaxCommentError, type: 'CallExpression', }], }, @@ -224,7 +349,7 @@ ruleTester.run('dynamic-import-chunkname', rule, { )`, options, errors: [{ - message: commentFormatError, + message: invalidSyntaxCommentError, type: 'CallExpression', }], }, @@ -246,7 +371,7 @@ ruleTester.run('dynamic-import-chunkname', rule, { )`, options, errors: [{ - message: commentFormatError, + message: invalidSyntaxCommentError, type: 'CallExpression', }], }, From 183aa0767ba74f48ab08f70fa2cadec4e2c3f0d2 Mon Sep 17 00:00:00 2001 From: Sandro Miguel Marques Date: Mon, 27 Aug 2018 19:42:29 +0100 Subject: [PATCH 012/151] Typo Changed segemnts to segments --- docs/rules/no-useless-path-segments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/no-useless-path-segments.md b/docs/rules/no-useless-path-segments.md index d0891ee187..b2ae82a3a7 100644 --- a/docs/rules/no-useless-path-segments.md +++ b/docs/rules/no-useless-path-segments.md @@ -1,6 +1,6 @@ # import/no-useless-path-segments -Use this rule to prevent unnecessary path segemnts in import and require statements. +Use this rule to prevent unnecessary path segments in import and require statements. ## Rule Details From 0764acd8ae31a25ea7679b77259c6051ec87c54f Mon Sep 17 00:00:00 2001 From: Steven Hargrove Date: Sun, 9 Sep 2018 15:21:10 -0400 Subject: [PATCH 013/151] use process.hrtime instead of Date.now (#1160) --- utils/ModuleCache.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/ModuleCache.js b/utils/ModuleCache.js index 19e6a21226..eba86d2585 100644 --- a/utils/ModuleCache.js +++ b/utils/ModuleCache.js @@ -14,7 +14,7 @@ class ModuleCache { * @param {[type]} result [description] */ set(cacheKey, result) { - this.map.set(cacheKey, { result, lastSeen: Date.now() }) + this.map.set(cacheKey, { result, lastSeen: process.hrtime() }) log('setting entry for', cacheKey) return result } @@ -23,7 +23,7 @@ class ModuleCache { if (this.map.has(cacheKey)) { const f = this.map.get(cacheKey) // check fresness - if (Date.now() - f.lastSeen < (settings.lifetime * 1000)) return f.result + if (process.hrtime(f.lastSeen)[0] < settings.lifetime) return f.result } else log('cache miss for', cacheKey) // cache miss return undefined From f04b7b6b0368f794f8dc0779d06f057aef4b8ff5 Mon Sep 17 00:00:00 2001 From: Fernando Maia Date: Sun, 9 Sep 2018 16:25:02 -0300 Subject: [PATCH 014/151] Add `no-named-export` + docs/tests (#1157) * Add `no-named-export` + docs/tests * Fix no-named-export docs missing quotes * Fix ruleId in no-named-export case test * Tighten no-named-export error message --- README.md | 2 + docs/rules/no-named-export.md | 77 +++++++++++++ src/index.js | 1 + src/rules/no-named-export.js | 33 ++++++ tests/src/rules/no-named-export.js | 179 +++++++++++++++++++++++++++++ 5 files changed, 292 insertions(+) create mode 100644 docs/rules/no-named-export.md create mode 100644 src/rules/no-named-export.js create mode 100644 tests/src/rules/no-named-export.js diff --git a/README.md b/README.md index 53b2640627..6f826a85b7 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a * Forbid unassigned imports ([`no-unassigned-import`]) * Forbid named default exports ([`no-named-default`]) * Forbid default exports ([`no-default-export`]) +* Forbid named exports ([`no-named-export`]) * Forbid anonymous values as default exports ([`no-anonymous-default-export`]) * Prefer named exports to be grouped together in a single export declaration ([`group-exports`]) * Enforce a leading comment with the webpackChunkName for dynamic imports ([`dynamic-import-chunkname`]) @@ -104,6 +105,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a [`no-anonymous-default-export`]: ./docs/rules/no-anonymous-default-export.md [`group-exports`]: ./docs/rules/group-exports.md [`no-default-export`]: ./docs/rules/no-default-export.md +[`no-named-export`]: ./docs/rules/no-named-export.md [`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md ## Installation diff --git a/docs/rules/no-named-export.md b/docs/rules/no-named-export.md new file mode 100644 index 0000000000..b3a0ef8d63 --- /dev/null +++ b/docs/rules/no-named-export.md @@ -0,0 +1,77 @@ +# no-named-export + +Prohibit named exports. Mostly an inverse of [`no-default-export`]. + +[`no-default-export`]: ./no-default-export.md + +## Rule Details + +The following patterns are considered warnings: + +```javascript +// bad1.js + +// There is only a single module export and it's a named export. +export const foo = 'foo'; +``` + +```javascript +// bad2.js + +// There is more than one named export in the module. +export const foo = 'foo'; +export const bar = 'bar'; +``` + +```javascript +// bad3.js + +// There is more than one named export in the module. +const foo = 'foo'; +const bar = 'bar'; +export { foo, bar } +``` + +```javascript +// bad4.js + +// There is more than one named export in the module. +export * from './other-module' +``` + +```javascript +// bad5.js + +// There is a default and a named export. +export const foo = 'foo'; +const bar = 'bar'; +export default 'bar'; +``` + +The following patterns are not warnings: + +```javascript +// good1.js + +// There is only a single module export and it's a default export. +export default 'bar'; +``` + +```javascript +// good2.js + +// There is only a single module export and it's a default export. +const foo = 'foo'; +export { foo as default } +``` + +```javascript +// good3.js + +// There is only a single module export and it's a default export. +export default from './other-module'; +``` + +## When Not To Use It + +If you don't care if named imports are used, or if you prefer named imports over default imports. diff --git a/src/index.js b/src/index.js index 7df67867f5..f5794595d6 100644 --- a/src/index.js +++ b/src/index.js @@ -32,6 +32,7 @@ export const rules = { 'newline-after-import': require('./rules/newline-after-import'), 'prefer-default-export': require('./rules/prefer-default-export'), 'no-default-export': require('./rules/no-default-export'), + 'no-named-export': require('./rules/no-named-export'), 'no-dynamic-require': require('./rules/no-dynamic-require'), 'unambiguous': require('./rules/unambiguous'), 'no-unassigned-import': require('./rules/no-unassigned-import'), diff --git a/src/rules/no-named-export.js b/src/rules/no-named-export.js new file mode 100644 index 0000000000..2c9f68599c --- /dev/null +++ b/src/rules/no-named-export.js @@ -0,0 +1,33 @@ +import docsUrl from '../docsUrl' + +module.exports = { + meta: { + docs: { url: docsUrl('no-named-export') }, + }, + + create(context) { + // ignore non-modules + if (context.parserOptions.sourceType !== 'module') { + return {} + } + + const message = 'Named exports are not allowed.' + + return { + ExportAllDeclaration(node) { + context.report({node, message}) + }, + + ExportNamedDeclaration(node) { + if (node.specifiers.length === 0) { + return context.report({node, message}) + } + + const someNamed = node.specifiers.some(specifier => specifier.exported.name !== 'default') + if (someNamed) { + context.report({node, message}) + } + }, + } + }, +} diff --git a/tests/src/rules/no-named-export.js b/tests/src/rules/no-named-export.js new file mode 100644 index 0000000000..d8748d3f6b --- /dev/null +++ b/tests/src/rules/no-named-export.js @@ -0,0 +1,179 @@ +import { RuleTester } from 'eslint' +import { test } from '../utils' + +const ruleTester = new RuleTester() + , rule = require('rules/no-named-export') + +ruleTester.run('no-named-export', rule, { + valid: [ + test({ + code: 'export default function bar() {};', + }), + test({ + code: 'export { foo as default }', + }), + test({ + code: 'export default from "foo.js"', + parser: 'babel-eslint', + }), + + // no exports at all + test({ + code: `import * as foo from './foo';`, + }), + test({ + code: `import foo from './foo';`, + }), + test({ + code: `import {default as foo} from './foo';`, + }), + ], + invalid: [ + test({ + code: ` + export const foo = 'foo'; + export const bar = 'bar'; + `, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }, { + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: ` + export const foo = 'foo'; + export default bar;`, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: ` + export const foo = 'foo'; + export function bar() {}; + `, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }, { + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: `export const foo = 'foo';`, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: ` + const foo = 'foo'; + export { foo }; + `, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: `export { foo, bar }`, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: `export const { foo, bar } = item;`, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: `export const { foo, bar: baz } = item;`, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: `export const { foo: { bar, baz } } = item;`, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: ` + export const foo = item; + export { item }; + `, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }, { + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: `export * from './foo';`, + errors: [{ + ruleId: 'ExportAllDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: `export const { foo } = { foo: "bar" };`, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: `export const { foo: { bar } } = { foo: { bar: "baz" } };`, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: 'export { a, b } from "foo.js"', + parser: 'babel-eslint', + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: `export type UserId = number;`, + parser: 'babel-eslint', + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: 'export foo from "foo.js"', + parser: 'babel-eslint', + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + test({ + code: `export Memory, { MemoryValue } from './Memory'`, + parser: 'babel-eslint', + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Named exports are not allowed.', + }], + }), + ], +}) From e3a03deca3b78129b897ad53b4054eb09678258a Mon Sep 17 00:00:00 2001 From: wtgtybhertgeghgtwtg Date: Sun, 9 Sep 2018 12:34:23 -0700 Subject: [PATCH 015/151] Bump `pkg-dir`. (#1111) --- utils/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/package.json b/utils/package.json index d955c53680..360cc76132 100644 --- a/utils/package.json +++ b/utils/package.json @@ -26,6 +26,6 @@ "homepage": "https://github.com/benmosher/eslint-plugin-import#readme", "dependencies": { "debug": "^2.6.8", - "pkg-dir": "^1.0.0" + "pkg-dir": "^2.0.0" } } From e8954dbaacd9590a8c46e3fc8ba31056576302cd Mon Sep 17 00:00:00 2001 From: Pirasis Leelatanon <1pete@users.noreply.github.com> Date: Mon, 10 Sep 2018 02:43:44 +0700 Subject: [PATCH 016/151] make rule `no-relative-parent-imports` support windows (#1141) --- src/core/importType.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/importType.js b/src/core/importType.js index 62519d175d..89b162aad3 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -57,7 +57,7 @@ function isInternalModule(name, settings, path) { } function isRelativeToParent(name) { - return name.indexOf('../') === 0 + return /^\.\.[\\/]/.test(name) } const indexFiles = ['.', './', './index', './index.js'] @@ -66,7 +66,7 @@ function isIndex(name) { } function isRelativeToSibling(name) { - return name.indexOf('./') === 0 + return /^\.[\\/]/.test(name) } const typeTest = cond([ From d3a58f879bb5b1576ffee1054b4c99286999424c Mon Sep 17 00:00:00 2001 From: Paul Hine Date: Wed, 10 Oct 2018 13:58:06 +0200 Subject: [PATCH 017/151] Fix packageDir array support (#1176) --- CHANGELOG.md | 7 +++++++ src/rules/no-extraneous-dependencies.js | 7 +++++-- tests/files/monorepo/package.json | 3 +++ tests/src/rules/no-extraneous-dependencies.js | 16 ++++++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3969ea3aac..a0b8a590d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ 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-extraneous-dependencies`]: `packageDir` option with array value was clobbering package deps instead of merging them ([#1175]/[#1176], thanks [@aravindet] & [@pzhine]) + ## [2.14.0] - 2018-08-13 * 69e0187 (HEAD -> master, source/master, origin/master, origin/HEAD) Merge pull request #1151 from jf248/jsx @@ -493,6 +496,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1176]: https://github.com/benmosher/eslint-plugin-import/pull/1176 [#1151]: https://github.com/benmosher/eslint-plugin-import/pull/1151 [#1137]: https://github.com/benmosher/eslint-plugin-import/pull/1137 [#1135]: https://github.com/benmosher/eslint-plugin-import/pull/1135 @@ -578,6 +582,7 @@ for info on changes for earlier releases. [#314]: https://github.com/benmosher/eslint-plugin-import/pull/314 [#912]: https://github.com/benmosher/eslint-plugin-import/pull/912 +[#1175]: https://github.com/benmosher/eslint-plugin-import/issues/1175 [#1058]: https://github.com/benmosher/eslint-plugin-import/issues/1058 [#931]: https://github.com/benmosher/eslint-plugin-import/issues/931 [#886]: https://github.com/benmosher/eslint-plugin-import/issues/886 @@ -767,3 +772,5 @@ for info on changes for earlier releases. [@1pete]: https://github.com/1pete [@gajus]: https://github.com/gajus [@jf248]: https://github.com/jf248 +[@aravindet]: https://github.com/aravindet +[@pzhine]: https://github.com/pzhine diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index ec9eedaa2d..528bb827ba 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -42,9 +42,12 @@ function getDependencies(context, packageDir) { if (!isEmpty(paths)) { // use rule config to find package.json paths.forEach(dir => { - Object.assign(packageContent, extractDepFields( + const _packageContent = extractDepFields( JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')) - )) + ) + Object.keys(packageContent).forEach(depsKey => + Object.assign(packageContent[depsKey], _packageContent[depsKey]) + ) }) } else { // use closest package.json diff --git a/tests/files/monorepo/package.json b/tests/files/monorepo/package.json index 3ed889ddf5..cf0b87ffa6 100644 --- a/tests/files/monorepo/package.json +++ b/tests/files/monorepo/package.json @@ -1,5 +1,8 @@ { "private": true, + "dependencies": { + "right-pad": "^1.0.1" + }, "devDependencies": { "left-pad": "^1.2.0" } diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index 10012f6b47..c29c8e5bd0 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -90,6 +90,22 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import leftpad from "left-pad";', options: [{packageDir: packageDirMonoRepoRoot}], }), + test({ + code: 'import react from "react";', + options: [{packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested]}], + }), + test({ + code: 'import leftpad from "left-pad";', + options: [{packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested]}], + }), + test({ + code: 'import leftpad from "left-pad";', + options: [{packageDir: [packageDirMonoRepoWithNested, packageDirMonoRepoRoot]}], + }), + test({ + code: 'import rightpad from "right-pad";', + options: [{packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested]}], + }), ], invalid: [ test({ From 78244fcc5dfa1d1d7987e26e7c560453bae328c0 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Thu, 11 Oct 2018 07:25:10 -0400 Subject: [PATCH 018/151] note `__dirname` as a way to define `packageDir` fixes #1061 --- docs/rules/no-extraneous-dependencies.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/rules/no-extraneous-dependencies.md b/docs/rules/no-extraneous-dependencies.md index 5c3542ebd4..2b66aa25c0 100644 --- a/docs/rules/no-extraneous-dependencies.md +++ b/docs/rules/no-extraneous-dependencies.md @@ -29,10 +29,14 @@ You can also use an array of globs instead of literal booleans: When using an array of globs, the setting will be set to `true` (no errors reported) if the name of the file being linted matches a single glob in the array, and `false` otherwise. -Also there is one more option called `packageDir`, this option is to specify the path to the folder containing package.json and is relative to the current working directory. +Also there is one more option called `packageDir`, this option is to specify the path to the folder containing package.json. + +If provided as a relative path string, will be computed relative to the current working directory at linter execution time. If this is not ideal (does not work with some editor integrations), consider using `__dirname` to provide a path relative to your configuration. ```js "import/no-extraneous-dependencies": ["error", {"packageDir": './some-dir/'}] +// or +"import/no-extraneous-dependencies": ["error", {"packageDir": path.join(__dirname, 'some-dir')}] ``` It may also be an array of multiple paths, to support monorepos or other novel project From b4a2f11fcacc6b2f048da4b29cfc896e682f17d1 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Thu, 11 Oct 2018 13:46:51 -0400 Subject: [PATCH 019/151] fix typescript build issue (#1180) * found: typescript resolver 1.1 requires tsconfig --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 810084436e..4f3722a950 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "cross-env": "^4.0.0", "eslint": "2.x - 5.x", "eslint-import-resolver-node": "file:./resolvers/node", - "eslint-import-resolver-typescript": "^1.0.2", + "eslint-import-resolver-typescript": "1.0.2", "eslint-import-resolver-webpack": "file:./resolvers/webpack", "eslint-module-utils": "file:./utils", "eslint-plugin-import": "2.x", @@ -66,7 +66,7 @@ "redux": "^3.0.4", "rimraf": "^2.6.2", "sinon": "^2.3.2", - "typescript": "^2.6.2", + "typescript": "~2.8.1", "typescript-eslint-parser": "^15.0.0" }, "peerDependencies": { From db471a85573a88e0bb9f4a1d53f40fed603651d1 Mon Sep 17 00:00:00 2001 From: Ivan Dudinov Date: Thu, 25 Oct 2018 13:57:33 +0300 Subject: [PATCH 020/151] Webpack Resolver fix for case config is an array of functions (#1220) * Added test for plugin-webpack-resolver: webpack config is an array of functions * plugin-webpack-resolver: handle case when webpack config is an array of functions * Updated Changelog * Updated call of webpack config function --- resolvers/webpack/CHANGELOG.md | 5 +++ resolvers/webpack/index.js | 10 ++++- resolvers/webpack/test/config.js | 11 +++++ .../files/webpack.function.config.multiple.js | 43 +++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 resolvers/webpack/test/files/webpack.function.config.multiple.js diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index 93246bf0b7..8ea232a3bd 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -4,6 +4,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 +- crash when webpack config is an array of functions ([#1219]/[#1220] by [@idudinov]) ## 0.10.1 - 2018-06-24 ### Fixed @@ -104,6 +106,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]). +[#1220]: https://github.com/benmosher/eslint-plugin-import/pull/1220 [#1091]: https://github.com/benmosher/eslint-plugin-import/pull/1091 [#969]: https://github.com/benmosher/eslint-plugin-import/pull/969 [#968]: https://github.com/benmosher/eslint-plugin-import/pull/968 @@ -121,6 +124,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [#181]: https://github.com/benmosher/eslint-plugin-import/pull/181 [#164]: https://github.com/benmosher/eslint-plugin-import/pull/164 +[#1219]: https://github.com/benmosher/eslint-plugin-import/issues/1219 [#788]: https://github.com/benmosher/eslint-plugin-import/issues/788 [#767]: https://github.com/benmosher/eslint-plugin-import/issues/767 [#681]: https://github.com/benmosher/eslint-plugin-import/issues/681 @@ -147,3 +151,4 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [@SkeLLLa]: https://github.com/SkeLLLa [@graingert]: https://github.com/graingert [@mattkrick]: https://github.com/mattkrick +[@idudinov]: https://github.com/idudinov diff --git a/resolvers/webpack/index.js b/resolvers/webpack/index.js index 4c9abc61d8..b1b0e45f67 100644 --- a/resolvers/webpack/index.js +++ b/resolvers/webpack/index.js @@ -87,10 +87,18 @@ exports.resolve = function (source, file, settings) { } if (typeof webpackConfig === 'function') { - webpackConfig = webpackConfig(env) + webpackConfig = webpackConfig(env, {}) } if (Array.isArray(webpackConfig)) { + webpackConfig = webpackConfig.map(cfg => { + if (typeof cfg === 'function') { + return cfg(env, {}) + } + + return cfg + }) + if (typeof configIndex !== 'undefined' && webpackConfig.length > configIndex) { webpackConfig = webpackConfig[configIndex] } diff --git a/resolvers/webpack/test/config.js b/resolvers/webpack/test/config.js index 2519daf8a8..16a4a6dda3 100644 --- a/resolvers/webpack/test/config.js +++ b/resolvers/webpack/test/config.js @@ -103,4 +103,15 @@ describe("config", function () { .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js')) }) + it('finds the config at option env when config is an array of functions', function() { + var settings = { + config: require(path.join(__dirname, './files/webpack.function.config.multiple.js')), + env: { + dummy: true, + }, + } + + expect(resolve('bar', file, settings)).to.have.property('path') + .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js')) + }) }) diff --git a/resolvers/webpack/test/files/webpack.function.config.multiple.js b/resolvers/webpack/test/files/webpack.function.config.multiple.js new file mode 100644 index 0000000000..4dbc94bbc9 --- /dev/null +++ b/resolvers/webpack/test/files/webpack.function.config.multiple.js @@ -0,0 +1,43 @@ +var path = require('path') +var pluginsTest = require('webpack-resolver-plugin-test') + +module.exports = [function(env) { + return { + resolve: { + alias: { + 'foo': path.join(__dirname, 'some', 'goofy', 'path', 'foo.js'), + 'bar': env ? path.join(__dirname, 'some', 'goofy', 'path', 'bar.js') : undefined, + 'some-alias': path.join(__dirname, 'some'), + }, + modules: [ + path.join(__dirname, 'src'), + path.join(__dirname, 'fallback'), + 'node_modules', + 'bower_components', + ], + modulesDirectories: ['node_modules', 'bower_components'], + root: path.join(__dirname, 'src'), + fallback: path.join(__dirname, 'fallback'), + }, + + externals: [ + { 'jquery': 'jQuery' }, + 'bootstrap', + function (context, request, callback) { + if (request === 'underscore') { + return callback(null, 'underscore') + } + callback() + }, + ], + + plugins: [ + new pluginsTest.ResolverPlugin([ + new pluginsTest.SimpleResolver( + path.join(__dirname, 'some', 'bar', 'bar.js'), + path.join(__dirname, 'some', 'bar') + ), + ]), + ], + } +}] From 64baa91bd934ffb072dd91e8408c2ce05912a715 Mon Sep 17 00:00:00 2001 From: Zhibin Liu Date: Thu, 15 Nov 2018 19:15:59 +0800 Subject: [PATCH 021/151] [import/named] fix destructuring assignemnt --- src/ExportMap.js | 4 ++++ tests/files/named-exports.js | 2 ++ tests/src/rules/named.js | 2 ++ 3 files changed, 8 insertions(+) diff --git a/src/ExportMap.js b/src/ExportMap.js index 66b212a211..563ff9e8c5 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -512,6 +512,10 @@ export function recursivePatternCapture(pattern, callback) { recursivePatternCapture(element, callback) }) break + + case 'AssignmentPattern': + callback(pattern.left) + break } } diff --git a/tests/files/named-exports.js b/tests/files/named-exports.js index 752092e0ad..f2881c10c5 100644 --- a/tests/files/named-exports.js +++ b/tests/files/named-exports.js @@ -14,6 +14,8 @@ export class ExportedClass { // destructuring exports export var { destructuredProp } = {} + , { destructingAssign = null } = {} + , { destructingAssign: destructingRenamedAssign = null } = {} , [ arrayKeyProp ] = [] , [ { deepProp } ] = [] , { arr: [ ,, deepSparseElement ] } = {} diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index cb1a5b843b..ed6dc9e049 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -22,6 +22,8 @@ ruleTester.run('named', rule, { test({code: 'import bar, { foo } from "./bar.js"'}), test({code: 'import {a, b, d} from "./named-exports"'}), test({code: 'import {ExportedClass} from "./named-exports"'}), + test({code: 'import { destructingAssign } from "./named-exports"'}), + test({code: 'import { destructingRenamedAssign } from "./named-exports"'}), test({code: 'import { ActionTypes } from "./qc"'}), test({code: 'import {a, b, c, d} from "./re-export"'}), From 9a13f811acfc375010c5d45e5655cc1538986904 Mon Sep 17 00:00:00 2001 From: Zhibin Liu Date: Fri, 16 Nov 2018 09:09:45 +0800 Subject: [PATCH 022/151] fix test --- tests/src/core/getExports.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 3423fe3e11..3bda3d3db1 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -273,7 +273,7 @@ describe('ExportMap', function () { context('#size', function () { it('counts the names', () => expect(ExportMap.get('./named-exports', fakeContext)) - .to.have.property('size', 8)) + .to.have.property('size', 10)) it('includes exported namespace size', () => expect(ExportMap.get('./export-all', fakeContext)) .to.have.property('size', 1)) From 5101b73effbf3706495f62121a8719f0ea0e2c68 Mon Sep 17 00:00:00 2001 From: Zhibin Liu Date: Sun, 18 Nov 2018 23:09:52 +0800 Subject: [PATCH 023/151] [Rules] add meta.type for all rules --- src/rules/default.js | 1 + src/rules/dynamic-import-chunkname.js | 1 + src/rules/export.js | 1 + src/rules/exports-last.js | 1 + src/rules/extensions.js | 1 + src/rules/first.js | 3 ++- src/rules/group-exports.js | 1 + src/rules/max-dependencies.js | 1 + src/rules/named.js | 1 + src/rules/namespace.js | 1 + src/rules/newline-after-import.js | 1 + src/rules/no-absolute-path.js | 1 + src/rules/no-amd.js | 1 + src/rules/no-anonymous-default-export.js | 1 + src/rules/no-commonjs.js | 1 + src/rules/no-cycle.js | 1 + src/rules/no-default-export.js | 1 + src/rules/no-deprecated.js | 1 + src/rules/no-duplicates.js | 1 + src/rules/no-dynamic-require.js | 1 + src/rules/no-extraneous-dependencies.js | 1 + src/rules/no-internal-modules.js | 1 + src/rules/no-mutable-exports.js | 1 + src/rules/no-named-as-default-member.js | 1 + src/rules/no-named-as-default.js | 1 + src/rules/no-named-default.js | 1 + src/rules/no-named-export.js | 1 + src/rules/no-namespace.js | 1 + src/rules/no-nodejs-modules.js | 1 + src/rules/no-relative-parent-imports.js | 1 + src/rules/no-restricted-paths.js | 1 + src/rules/no-self-import.js | 1 + src/rules/no-unassigned-import.js | 1 + src/rules/no-unresolved.js | 1 + src/rules/no-useless-path-segments.js | 1 + src/rules/no-webpack-loader-syntax.js | 1 + src/rules/order.js | 1 + src/rules/prefer-default-export.js | 1 + src/rules/unambiguous.js | 1 + 39 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/rules/default.js b/src/rules/default.js index 83c0ea95e3..7e07800dae 100644 --- a/src/rules/default.js +++ b/src/rules/default.js @@ -3,6 +3,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'problem', docs: { url: docsUrl('default'), }, diff --git a/src/rules/dynamic-import-chunkname.js b/src/rules/dynamic-import-chunkname.js index 6f51ebbbd5..44fb5611cc 100644 --- a/src/rules/dynamic-import-chunkname.js +++ b/src/rules/dynamic-import-chunkname.js @@ -3,6 +3,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('dynamic-import-chunkname'), }, diff --git a/src/rules/export.js b/src/rules/export.js index f6adf0ae83..db5c8c3c1b 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -3,6 +3,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'problem', docs: { url: docsUrl('export'), }, diff --git a/src/rules/exports-last.js b/src/rules/exports-last.js index 2d74ab5f31..fc40cc8271 100644 --- a/src/rules/exports-last.js +++ b/src/rules/exports-last.js @@ -8,6 +8,7 @@ function isNonExportStatement({ type }) { module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('exports-last'), }, diff --git a/src/rules/extensions.js b/src/rules/extensions.js index d50bd0ce8b..b72c91bad0 100644 --- a/src/rules/extensions.js +++ b/src/rules/extensions.js @@ -55,6 +55,7 @@ function buildProperties(context) { module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('extensions'), }, diff --git a/src/rules/first.js b/src/rules/first.js index 7af7f330b3..7bcd1fa22e 100644 --- a/src/rules/first.js +++ b/src/rules/first.js @@ -2,6 +2,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('first'), }, @@ -105,7 +106,7 @@ module.exports = { insertSourceCode = insertSourceCode.trim() + insertSourceCode.match(/^(\s+)/)[0] } - insertFixer = lastLegalImp ? + insertFixer = lastLegalImp ? fixer.insertTextAfter(lastLegalImp, insertSourceCode) : fixer.insertTextBefore(body[0], insertSourceCode) const fixers = [insertFixer].concat(removeFixers) diff --git a/src/rules/group-exports.js b/src/rules/group-exports.js index 96fff24fed..d650fff877 100644 --- a/src/rules/group-exports.js +++ b/src/rules/group-exports.js @@ -1,6 +1,7 @@ import docsUrl from '../docsUrl' const meta = { + type: 'suggestion', docs: { url: docsUrl('group-exports'), }, diff --git a/src/rules/max-dependencies.js b/src/rules/max-dependencies.js index 9af8f7912e..7e1fdb1011 100644 --- a/src/rules/max-dependencies.js +++ b/src/rules/max-dependencies.js @@ -16,6 +16,7 @@ const countDependencies = (dependencies, lastNode, context) => { module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('max-dependencies'), }, diff --git a/src/rules/named.js b/src/rules/named.js index 57e4f1d9ef..b1f261f32b 100644 --- a/src/rules/named.js +++ b/src/rules/named.js @@ -4,6 +4,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'problem', docs: { url: docsUrl('named'), }, diff --git a/src/rules/namespace.js b/src/rules/namespace.js index bbba2ce2ef..598b530d02 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -5,6 +5,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'problem', docs: { url: docsUrl('namespace'), }, diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js index fda1bc7634..f5724ef4a3 100644 --- a/src/rules/newline-after-import.js +++ b/src/rules/newline-after-import.js @@ -45,6 +45,7 @@ function isClassWithDecorator(node) { module.exports = { meta: { + type: 'layout', docs: { url: docsUrl('newline-after-import'), }, diff --git a/src/rules/no-absolute-path.js b/src/rules/no-absolute-path.js index b66b8b203f..4b7a8fcc2a 100644 --- a/src/rules/no-absolute-path.js +++ b/src/rules/no-absolute-path.js @@ -4,6 +4,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-absolute-path'), }, diff --git a/src/rules/no-amd.js b/src/rules/no-amd.js index 3ccb2129de..df0d3aeb24 100644 --- a/src/rules/no-amd.js +++ b/src/rules/no-amd.js @@ -11,6 +11,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-amd'), }, diff --git a/src/rules/no-anonymous-default-export.js b/src/rules/no-anonymous-default-export.js index 34128a914a..1557404507 100644 --- a/src/rules/no-anonymous-default-export.js +++ b/src/rules/no-anonymous-default-export.js @@ -72,6 +72,7 @@ const defaults = Object.keys(defs) module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-anonymous-default-export'), }, diff --git a/src/rules/no-commonjs.js b/src/rules/no-commonjs.js index 22939aa7bf..b6f11a7f0b 100644 --- a/src/rules/no-commonjs.js +++ b/src/rules/no-commonjs.js @@ -41,6 +41,7 @@ const schemaObject = { module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-commonjs'), }, diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index 1a70db2c70..f769b862cc 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -10,6 +10,7 @@ import docsUrl from '../docsUrl' // todo: cache cycles / deep relationships for faster repeat evaluation module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-cycle') }, schema: [makeOptionsSchema({ maxDepth:{ diff --git a/src/rules/no-default-export.js b/src/rules/no-default-export.js index 8d240ed6a1..e1c687c9f7 100644 --- a/src/rules/no-default-export.js +++ b/src/rules/no-default-export.js @@ -1,5 +1,6 @@ module.exports = { meta: { + type: 'suggestion', docs: {}, }, diff --git a/src/rules/no-deprecated.js b/src/rules/no-deprecated.js index ef96f41633..7a3130b20c 100644 --- a/src/rules/no-deprecated.js +++ b/src/rules/no-deprecated.js @@ -17,6 +17,7 @@ function getDeprecation(metadata) { module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-deprecated'), }, diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index 72b305e677..4632ea0ec9 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -13,6 +13,7 @@ function checkImports(imported, context) { module.exports = { meta: { + type: 'problem', docs: { url: docsUrl('no-duplicates'), }, diff --git a/src/rules/no-dynamic-require.js b/src/rules/no-dynamic-require.js index 5726d72ca3..b9ccad27b3 100644 --- a/src/rules/no-dynamic-require.js +++ b/src/rules/no-dynamic-require.js @@ -15,6 +15,7 @@ function isStaticValue(arg) { module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-dynamic-require'), }, diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 528bb827ba..d2c7cac6ee 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -158,6 +158,7 @@ function testConfig(config, filename) { module.exports = { meta: { + type: 'problem', docs: { url: docsUrl('no-extraneous-dependencies'), }, diff --git a/src/rules/no-internal-modules.js b/src/rules/no-internal-modules.js index 3e28554faa..9987dfd5c5 100644 --- a/src/rules/no-internal-modules.js +++ b/src/rules/no-internal-modules.js @@ -7,6 +7,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-internal-modules'), }, diff --git a/src/rules/no-mutable-exports.js b/src/rules/no-mutable-exports.js index 6bd6941a79..0908162bd1 100644 --- a/src/rules/no-mutable-exports.js +++ b/src/rules/no-mutable-exports.js @@ -2,6 +2,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-mutable-exports'), }, diff --git a/src/rules/no-named-as-default-member.js b/src/rules/no-named-as-default-member.js index 17af25a6fe..b7c3c75827 100644 --- a/src/rules/no-named-as-default-member.js +++ b/src/rules/no-named-as-default-member.js @@ -14,6 +14,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-named-as-default-member'), }, diff --git a/src/rules/no-named-as-default.js b/src/rules/no-named-as-default.js index eb9769513f..ad6a8ee6d1 100644 --- a/src/rules/no-named-as-default.js +++ b/src/rules/no-named-as-default.js @@ -4,6 +4,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'problem', docs: { url: docsUrl('no-named-as-default'), }, diff --git a/src/rules/no-named-default.js b/src/rules/no-named-default.js index e25cd49509..86f24ef6d1 100644 --- a/src/rules/no-named-default.js +++ b/src/rules/no-named-default.js @@ -2,6 +2,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-named-default'), }, diff --git a/src/rules/no-named-export.js b/src/rules/no-named-export.js index 2c9f68599c..2fa6392014 100644 --- a/src/rules/no-named-export.js +++ b/src/rules/no-named-export.js @@ -2,6 +2,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-named-export') }, }, diff --git a/src/rules/no-namespace.js b/src/rules/no-namespace.js index 76a11f92dc..3dbedca500 100644 --- a/src/rules/no-namespace.js +++ b/src/rules/no-namespace.js @@ -12,6 +12,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-namespace'), }, diff --git a/src/rules/no-nodejs-modules.js b/src/rules/no-nodejs-modules.js index e73ed379d0..125bb5f3f1 100644 --- a/src/rules/no-nodejs-modules.js +++ b/src/rules/no-nodejs-modules.js @@ -10,6 +10,7 @@ function reportIfMissing(context, node, allowed, name) { module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-nodejs-modules'), }, diff --git a/src/rules/no-relative-parent-imports.js b/src/rules/no-relative-parent-imports.js index 6b58c97f5a..544525755e 100644 --- a/src/rules/no-relative-parent-imports.js +++ b/src/rules/no-relative-parent-imports.js @@ -7,6 +7,7 @@ import importType from '../core/importType' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-relative-parent-imports'), }, diff --git a/src/rules/no-restricted-paths.js b/src/rules/no-restricted-paths.js index 5b20c40d84..0d906f6318 100644 --- a/src/rules/no-restricted-paths.js +++ b/src/rules/no-restricted-paths.js @@ -7,6 +7,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'problem', docs: { url: docsUrl('no-restricted-paths'), }, diff --git a/src/rules/no-self-import.js b/src/rules/no-self-import.js index 8a8620c9ae..b869d46e06 100644 --- a/src/rules/no-self-import.js +++ b/src/rules/no-self-import.js @@ -21,6 +21,7 @@ function isImportingSelf(context, node, requireName) { module.exports = { meta: { + type: 'problem', docs: { description: 'Forbid a module from importing itself', recommended: true, diff --git a/src/rules/no-unassigned-import.js b/src/rules/no-unassigned-import.js index ad081bd1be..5ea637e67b 100644 --- a/src/rules/no-unassigned-import.js +++ b/src/rules/no-unassigned-import.js @@ -54,6 +54,7 @@ function create(context) { module.exports = { create, meta: { + type: 'suggestion', docs: { url: docsUrl('no-unassigned-import'), }, diff --git a/src/rules/no-unresolved.js b/src/rules/no-unresolved.js index 2a5232a1cd..8436e4c92b 100644 --- a/src/rules/no-unresolved.js +++ b/src/rules/no-unresolved.js @@ -10,6 +10,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'problem', docs: { url: docsUrl('no-unresolved'), }, diff --git a/src/rules/no-useless-path-segments.js b/src/rules/no-useless-path-segments.js index 5872b2d1c3..2ad207fada 100644 --- a/src/rules/no-useless-path-segments.js +++ b/src/rules/no-useless-path-segments.js @@ -35,6 +35,7 @@ const countRelParent = x => sumBy(x, v => v === '..') module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('no-useless-path-segments'), }, diff --git a/src/rules/no-webpack-loader-syntax.js b/src/rules/no-webpack-loader-syntax.js index e89fc9c35c..723f472692 100644 --- a/src/rules/no-webpack-loader-syntax.js +++ b/src/rules/no-webpack-loader-syntax.js @@ -11,6 +11,7 @@ function reportIfNonStandard(context, node, name) { module.exports = { meta: { + type: 'problem', docs: { url: docsUrl('no-webpack-loader-syntax'), }, diff --git a/src/rules/order.js b/src/rules/order.js index f925a20eb4..5c68f1b310 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -362,6 +362,7 @@ function makeNewlinesBetweenReport (context, imported, newlinesBetweenImports) { module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('order'), }, diff --git a/src/rules/prefer-default-export.js b/src/rules/prefer-default-export.js index f9cec8bf0b..0e31346f3b 100644 --- a/src/rules/prefer-default-export.js +++ b/src/rules/prefer-default-export.js @@ -4,6 +4,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('prefer-default-export'), }, diff --git a/src/rules/unambiguous.js b/src/rules/unambiguous.js index f89ebad9cf..7ec38c2cb2 100644 --- a/src/rules/unambiguous.js +++ b/src/rules/unambiguous.js @@ -8,6 +8,7 @@ import docsUrl from '../docsUrl' module.exports = { meta: { + type: 'suggestion', docs: { url: docsUrl('unambiguous'), }, From d290a87dd6e10227c5f352bd05dee0fb361cd5a6 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 3 Jan 2019 11:19:03 -0800 Subject: [PATCH 024/151] [Dev Deps] update `babylon`, `coveralls`, `eslint-import-resolver-typescript`, `gulp`, `linklocal`, `nyc`, `redux`, `rimraf`, `sinon`, `typescript-eslint-parser` --- package.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 4f3722a950..7dfe80350d 100644 --- a/package.json +++ b/package.json @@ -48,26 +48,26 @@ "babel-plugin-istanbul": "^4.1.6", "babel-preset-es2015-argon": "latest", "babel-register": "^6.26.0", - "babylon": "6.15.0", + "babylon": "^6.15.0", "chai": "^3.5.0", - "coveralls": "^3.0.0", + "coveralls": "^3.0.2", "cross-env": "^4.0.0", "eslint": "2.x - 5.x", "eslint-import-resolver-node": "file:./resolvers/node", - "eslint-import-resolver-typescript": "1.0.2", + "eslint-import-resolver-typescript": "^1.0.2", "eslint-import-resolver-webpack": "file:./resolvers/webpack", "eslint-module-utils": "file:./utils", "eslint-plugin-import": "2.x", - "gulp": "^3.9.0", + "gulp": "^3.9.1", "gulp-babel": "6.1.2", - "linklocal": "^2.6.0", + "linklocal": "^2.8.2", "mocha": "^3.5.3", - "nyc": "^11.7.1", - "redux": "^3.0.4", - "rimraf": "^2.6.2", - "sinon": "^2.3.2", + "nyc": "^11.9.0", + "redux": "^3.7.2", + "rimraf": "^2.6.3", + "sinon": "^2.4.1", "typescript": "~2.8.1", - "typescript-eslint-parser": "^15.0.0" + "typescript-eslint-parser": "^21.0.2" }, "peerDependencies": { "eslint": "2.x - 5.x" From cf1f6f46f7d0fd6a9532c51d44d12ae08447cffd Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 3 Jan 2019 11:22:23 -0800 Subject: [PATCH 025/151] [Deps] update `debug`, `eslint-import-resolver-node`, `has`, `lodash`, `minimatch`, `resolve` --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 7dfe80350d..dd54055335 100644 --- a/package.json +++ b/package.json @@ -74,15 +74,15 @@ }, "dependencies": { "contains-path": "^0.1.0", - "debug": "^2.6.8", + "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.1", + "eslint-import-resolver-node": "^0.3.2", "eslint-module-utils": "^2.2.0", - "has": "^1.0.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.3", + "has": "^1.0.3", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", "read-pkg-up": "^2.0.0", - "resolve": "^1.6.0" + "resolve": "^1.9.0" }, "nyc": { "require": [ From b686f9d823c417e98a824c4ff6bfd65c1582ec45 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Mon, 14 Jan 2019 21:10:11 -0500 Subject: [PATCH 026/151] drop ESLint 2/3 from Travis/Appveyor because Typescript parser can't deal --- .travis.yml | 19 +++++++++++-------- appveyor.yml | 9 +++++---- package.json | 2 +- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index ea8c60a593..03de81c6d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,8 @@ os: linux env: - ESLINT_VERSION=5 - ESLINT_VERSION=4 - - ESLINT_VERSION=3 - - ESLINT_VERSION=2 + # - ESLINT_VERSION=3 + # - ESLINT_VERSION=2 # osx backlog is often deep, so to be polite we can just hit these highlights matrix: @@ -38,12 +38,15 @@ matrix: - os: osx env: ESLINT_VERSION=4 node_js: 8 - - os: osx - env: ESLINT_VERSION=3 - node_js: 6 - - os: osx - env: ESLINT_VERSION=2 - node_js: 4 + + # the following combos fail TypeScript tests + # - os: osx + # env: ESLINT_VERSION=3 + # node_js: 6 + # - os: osx + # env: ESLINT_VERSION=2 + # node_js: 4 + exclude: - node_js: '4' env: ESLINT_VERSION=5 diff --git a/appveyor.yml b/appveyor.yml index 0176e12545..bb435695f5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,13 +3,14 @@ environment: matrix: - nodejs_version: "10" - nodejs_version: "8" - - nodejs_version: "6" - - nodejs_version: "4" + # - nodejs_version: "6" + # - nodejs_version: "4" matrix: fast_finish: true - allow_failures: - - nodejs_version: "4" # for eslint 5 + + # allow_failures: + # - nodejs_version: "4" # for eslint 5 # platform: # - x86 diff --git a/package.json b/package.json index dd54055335..58618e2335 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "redux": "^3.7.2", "rimraf": "^2.6.3", "sinon": "^2.4.1", - "typescript": "~2.8.1", + "typescript": "^3.2.2", "typescript-eslint-parser": "^21.0.2" }, "peerDependencies": { From acfb6e926f9324210d71ce1c8d453d17d707a9bd Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Mon, 14 Jan 2019 21:54:12 -0500 Subject: [PATCH 027/151] jk, test against eslint 2/3 but skip Typescript tests. also bumped babel-eslint --- .travis.yml | 18 +-- package.json | 2 +- src/rules/no-amd.js | 44 +++--- tests/src/core/getExports.js | 5 +- tests/src/rules/named.js | 190 ++++++++++++------------ tests/src/rules/newline-after-import.js | 3 +- tests/src/utils.js | 11 ++ 7 files changed, 146 insertions(+), 127 deletions(-) diff --git a/.travis.yml b/.travis.yml index 03de81c6d4..75f2e67ade 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,8 @@ os: linux env: - ESLINT_VERSION=5 - ESLINT_VERSION=4 - # - ESLINT_VERSION=3 - # - ESLINT_VERSION=2 + - ESLINT_VERSION=3 + - ESLINT_VERSION=2 # osx backlog is often deep, so to be polite we can just hit these highlights matrix: @@ -38,14 +38,12 @@ matrix: - os: osx env: ESLINT_VERSION=4 node_js: 8 - - # the following combos fail TypeScript tests - # - os: osx - # env: ESLINT_VERSION=3 - # node_js: 6 - # - os: osx - # env: ESLINT_VERSION=2 - # node_js: 4 + - os: osx + env: ESLINT_VERSION=3 + node_js: 6 + - os: osx + env: ESLINT_VERSION=2 + node_js: 4 exclude: - node_js: '4' diff --git a/package.json b/package.json index 58618e2335..fd0ead0c83 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ }, "homepage": "https://github.com/benmosher/eslint-plugin-import", "devDependencies": { - "babel-eslint": "8.0.x", + "babel-eslint": "^10.0.1", "babel-plugin-istanbul": "^4.1.6", "babel-preset-es2015-argon": "latest", "babel-register": "^6.26.0", diff --git a/src/rules/no-amd.js b/src/rules/no-amd.js index df0d3aeb24..394244341e 100644 --- a/src/rules/no-amd.js +++ b/src/rules/no-amd.js @@ -10,35 +10,35 @@ import docsUrl from '../docsUrl' //------------------------------------------------------------------------------ module.exports = { - meta: { - type: 'suggestion', - docs: { - url: docsUrl('no-amd'), - }, + meta: { + type: 'suggestion', + docs: { + url: docsUrl('no-amd'), }, + }, - create: function (context) { + create: function (context) { + return { + 'CallExpression': function (node) { + if (context.getScope().type !== 'module') return - return { + console.log("got scope", context.getScope().type) - 'CallExpression': function (node) { - if (context.getScope().type !== 'module') return + if (node.callee.type !== 'Identifier') return + if (node.callee.name !== 'require' && + node.callee.name !== 'define') return - if (node.callee.type !== 'Identifier') return - if (node.callee.name !== 'require' && - node.callee.name !== 'define') return + // todo: capture define((require, module, exports) => {}) form? + if (node.arguments.length !== 2) return - // todo: capture define((require, module, exports) => {}) form? - if (node.arguments.length !== 2) return + const modules = node.arguments[0] + if (modules.type !== 'ArrayExpression') return - const modules = node.arguments[0] - if (modules.type !== 'ArrayExpression') return + // todo: check second arg type? (identifier or callback) - // todo: check second arg type? (identifier or callback) + context.report(node, `Expected imports instead of AMD ${node.callee.name}().`) + }, + } - context.report(node, `Expected imports instead of AMD ${node.callee.name}().`) - }, - } - - }, + }, } diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 3bda3d3db1..b33d548f01 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -3,7 +3,7 @@ import ExportMap from '../../../src/ExportMap' import * as fs from 'fs' -import { getFilename } from '../utils' +import { getFilename, skipESLints } from '../utils' import * as unambiguous from 'eslint-module-utils/unambiguous' describe('ExportMap', function () { @@ -310,7 +310,7 @@ describe('ExportMap', function () { }) - context('alternate parsers', function () { + skipESLints([2, 3])('alternate parsers', function () { const configs = [ // ['string form', { 'typescript-eslint-parser': '.ts' }], @@ -318,6 +318,7 @@ describe('ExportMap', function () { ] configs.forEach(([description, parserConfig]) => { + describe(description, function () { const context = Object.assign({}, fakeContext, { settings: { diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index ed6dc9e049..7ffd929e1b 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -1,4 +1,4 @@ -import { test, SYNTAX_CASES } from '../utils' +import { test, SYNTAX_CASES, skipESLints } from '../utils' import { RuleTester } from 'eslint' import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve' @@ -83,70 +83,6 @@ ruleTester.run('named', rule, { parser: 'babel-eslint', }), - // TypeScript - test({ - code: 'import { MyType } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { Foo } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { Bar } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { getFoo } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { MyEnum } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: ` - import { MyModule } from "./typescript" - MyModule.ModuleFunction() - `, - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: ` - import { MyNamespace } from "./typescript" - MyNamespace.NSModule.NSModuleFunction() - `, - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - // jsnext test({ code: '/*jsnext*/ import { createStore } from "redux"', @@ -246,32 +182,6 @@ ruleTester.run('named', rule, { // }], // }), - // TypeScript - test({ - code: 'import { MissingType } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - errors: [{ - message: "MissingType not found in './typescript'", - type: 'Identifier', - }], - }), - test({ - code: 'import { NotExported } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - errors: [{ - message: "NotExported not found in './typescript'", - type: 'Identifier', - }], - }), - test({ code: 'import { type MyOpaqueType, MyMissingClass } from "./flowtypes"', parser: 'babel-eslint', @@ -342,3 +252,101 @@ ruleTester.run('named (export *)', rule, { }), ], }) + + +skipESLints([2, 3])("Typescript", function () { + // Typescript + ruleTester.run("named", rule, { + valid: [ + test({ + code: 'import { MyType } from "./typescript"', + parser: 'typescript-eslint-parser', + settings: { + 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: 'import { Foo } from "./typescript"', + parser: 'typescript-eslint-parser', + settings: { + 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: 'import { Bar } from "./typescript"', + parser: 'typescript-eslint-parser', + settings: { + 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: 'import { getFoo } from "./typescript"', + parser: 'typescript-eslint-parser', + settings: { + 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: 'import { MyEnum } from "./typescript"', + parser: 'typescript-eslint-parser', + settings: { + 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: ` + import { MyModule } from "./typescript" + MyModule.ModuleFunction() + `, + parser: 'typescript-eslint-parser', + settings: { + 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: ` + import { MyNamespace } from "./typescript" + MyNamespace.NSModule.NSModuleFunction() + `, + parser: 'typescript-eslint-parser', + settings: { + 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + ], + + invalid: [ + test({ + code: 'import { MissingType } from "./typescript"', + parser: 'typescript-eslint-parser', + settings: { + 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: [{ + message: "MissingType not found in './typescript'", + type: 'Identifier', + }], + }), + test({ + code: 'import { NotExported } from "./typescript"', + parser: 'typescript-eslint-parser', + settings: { + 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: [{ + message: "NotExported not found in './typescript'", + type: 'Identifier', + }], + }), + ] + }) +}) diff --git a/tests/src/rules/newline-after-import.js b/tests/src/rules/newline-after-import.js index 00ebfa432b..730cef3636 100644 --- a/tests/src/rules/newline-after-import.js +++ b/tests/src/rules/newline-after-import.js @@ -153,8 +153,9 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `//issue 592 + export default @SomeDecorator(require('./some-file')) - export default class App {} + class App {} `, parserOptions: { sourceType: 'module' }, parser: 'babel-eslint', diff --git a/tests/src/utils.js b/tests/src/utils.js index fe04d684e2..9ec1b3e48a 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -29,6 +29,17 @@ export function getFilename(file) { return path.join(__dirname, '..', 'files', file || 'foo.js') } +/** + * skip tests iff ESLINT_VERSION is in provided `versions` array + */ +export function skipESLints(versions) { + if (!versions.includes(+process.env.ESLINT_VERSION)) { + return describe + } else { + return describe.skip + } +} + /** * to be added as valid cases just to ensure no nullable fields are going * to crash at runtime From f16523728a32f185058e50c5f7348a9d0bf69d1f Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Mon, 14 Jan 2019 22:00:07 -0500 Subject: [PATCH 028/151] ah geez, bumping babel-eslint breaks no-amd/no-cjs also left my debug console in :-( --- package.json | 2 +- src/rules/no-amd.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index fd0ead0c83..f13598aa8a 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ }, "homepage": "https://github.com/benmosher/eslint-plugin-import", "devDependencies": { - "babel-eslint": "^10.0.1", + "babel-eslint": "^8.2.6", "babel-plugin-istanbul": "^4.1.6", "babel-preset-es2015-argon": "latest", "babel-register": "^6.26.0", diff --git a/src/rules/no-amd.js b/src/rules/no-amd.js index 394244341e..bb7c8ed826 100644 --- a/src/rules/no-amd.js +++ b/src/rules/no-amd.js @@ -22,8 +22,6 @@ module.exports = { 'CallExpression': function (node) { if (context.getScope().type !== 'module') return - console.log("got scope", context.getScope().type) - if (node.callee.type !== 'Identifier') return if (node.callee.name !== 'require' && node.callee.name !== 'define') return From 10c981163c3970b5633c1fd4d812b8f65790f6d8 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Mon, 14 Jan 2019 22:16:00 -0500 Subject: [PATCH 029/151] node 4 fix for test util --- tests/src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/utils.js b/tests/src/utils.js index 9ec1b3e48a..56e8ab72a2 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -33,7 +33,7 @@ export function getFilename(file) { * skip tests iff ESLINT_VERSION is in provided `versions` array */ export function skipESLints(versions) { - if (!versions.includes(+process.env.ESLINT_VERSION)) { + if (versions.indexOf(+process.env.ESLINT_VERSION) === -1) { return describe } else { return describe.skip From 05c3935048577bd7b025d6b833d8503807f02189 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Wed, 16 Jan 2019 07:18:43 -0500 Subject: [PATCH 030/151] repair `no-deprecated` for ESLint* 5 * technically espree v5 - also technically it doesn't support comma-first anymore but that seems reasonable --- src/ExportMap.js | 48 +++++++++++++++++++++++++++++---------- tests/files/deprecated.js | 6 ++--- tests/src/core/parse.js | 2 ++ utils/parse.js | 6 +++-- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/ExportMap.js b/src/ExportMap.js index 563ff9e8c5..c8335d9c8a 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -4,6 +4,8 @@ import doctrine from 'doctrine' import debug from 'debug' +import SourceCode from 'eslint/lib/util/source-code' + import parse from 'eslint-module-utils/parse' import resolve from 'eslint-module-utils/resolve' import isIgnored, { hasValidExtension } from 'eslint-module-utils/ignore' @@ -193,22 +195,28 @@ export default class ExportMap { * @param {...[type]} nodes [description] * @return {{doc: object}} */ -function captureDoc(docStyleParsers) { +function captureDoc(source, docStyleParsers) { const metadata = {} , nodes = Array.prototype.slice.call(arguments, 1) // 'some' short-circuits on first 'true' nodes.some(n => { - if (!n.leadingComments) return false - - for (let name in docStyleParsers) { - const doc = docStyleParsers[name](n.leadingComments) - if (doc) { - metadata.doc = doc + try { + // n.leadingComments is legacy `attachComments` behavior + let leadingComments = n.leadingComments || source.getCommentsBefore(n) + if (leadingComments.length === 0) return false + + for (let name in docStyleParsers) { + const doc = docStyleParsers[name](leadingComments) + if (doc) { + metadata.doc = doc + } } - } - return true + return true + } catch (err) { + return false + } }) return metadata @@ -338,6 +346,8 @@ ExportMap.parse = function (path, content, context) { docStyleParsers[style] = availableDocStyleParsers[style] }) + const source = makeSourceCode(content, ast) + // attempt to collect module doc if (ast.comments) { ast.comments.some(c => { @@ -405,7 +415,7 @@ ExportMap.parse = function (path, content, context) { ast.body.forEach(function (n) { if (n.type === 'ExportDefaultDeclaration') { - const exportMeta = captureDoc(docStyleParsers, n) + const exportMeta = captureDoc(source, docStyleParsers, n) if (n.declaration.type === 'Identifier') { addNamespace(exportMeta, n.declaration) } @@ -441,12 +451,12 @@ ExportMap.parse = function (path, content, context) { case 'TSInterfaceDeclaration': case 'TSAbstractClassDeclaration': case 'TSModuleDeclaration': - m.namespace.set(n.declaration.id.name, captureDoc(docStyleParsers, n)) + m.namespace.set(n.declaration.id.name, captureDoc(source, docStyleParsers, n)) break case 'VariableDeclaration': n.declaration.declarations.forEach((d) => recursivePatternCapture(d.id, - id => m.namespace.set(id.name, captureDoc(docStyleParsers, d, n)))) + id => m.namespace.set(id.name, captureDoc(source, docStyleParsers, d, n)))) break } } @@ -531,3 +541,17 @@ function childContext(path, context) { path, } } + + +/** + * sometimes legacy support isn't _that_ hard... right? + */ +function makeSourceCode(text, ast) { + if (SourceCode.length > 1) { + // ESLint 3 + return new SourceCode(text, ast) + } else { + // ESLint 4, 5 + return new SourceCode({ text, ast }) + } +} diff --git a/tests/files/deprecated.js b/tests/files/deprecated.js index 10e81dc912..f5229f59b8 100644 --- a/tests/files/deprecated.js +++ b/tests/files/deprecated.js @@ -27,18 +27,18 @@ export const MY_TERRIBLE_ACTION = "ugh" * @deprecated this chain is awful * @type {String} */ -export const CHAIN_A = "a" +export const CHAIN_A = "a", /** * @deprecated so awful * @type {String} */ - , CHAIN_B = "b" + CHAIN_B = "b", /** * @deprecated still terrible * @type {String} */ - , CHAIN_C = "C" + CHAIN_C = "C" /** * this one is fine diff --git a/tests/src/core/parse.js b/tests/src/core/parse.js index 9cc153ae3c..4b0f12c626 100644 --- a/tests/src/core/parse.js +++ b/tests/src/core/parse.js @@ -38,6 +38,8 @@ describe('parse(content, { settings, ecmaFeatures })', function () { .that.is.eql(parserOptions.ecmaFeatures) .and.is.not.equal(parserOptions.ecmaFeatures) expect(parseSpy.args[0][1], 'custom parser to get parserOptions.attachComment equal to true').to.have.property('attachComment', true) + expect(parseSpy.args[0][1], 'custom parser to get parserOptions.tokens equal to true').to.have.property('tokens', true) + expect(parseSpy.args[0][1], 'custom parser to get parserOptions.range equal to true').to.have.property('range', true) expect(parseSpy.args[0][1], 'custom parser to get parserOptions.filePath equal to the full path of the source file').to.have.property('filePath', path) }) diff --git a/utils/parse.js b/utils/parse.js index 5bafdba495..2946047ad5 100644 --- a/utils/parse.js +++ b/utils/parse.js @@ -19,12 +19,14 @@ exports.default = function parse(path, content, context) { parserOptions = Object.assign({}, parserOptions) parserOptions.ecmaFeatures = Object.assign({}, parserOptions.ecmaFeatures) - // always include and attach comments + // always include comments and tokens (for doc parsing) parserOptions.comment = true - parserOptions.attachComment = true + parserOptions.attachComment = true // keeping this for backward-compat with older parsers + parserOptions.tokens = true // attach node locations parserOptions.loc = true + parserOptions.range = true // provide the `filePath` like eslint itself does, in `parserOptions` // https://github.com/eslint/eslint/blob/3ec436ee/lib/linter.js#L637 From 73080d0ba88b55b0794d288f8ef4f476873c3367 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Thu, 17 Jan 2019 06:09:54 -0500 Subject: [PATCH 031/151] dep-time-travel: use older versions of dependencies for tests against older ESLint versions. --- .travis.yml | 3 ++- tests/dep-time-travel.sh | 20 ++++++++++++++++++++ tests/src/core/getExports.js | 4 ++-- tests/src/rules/named.js | 4 ++-- tests/src/utils.js | 11 ----------- 5 files changed, 26 insertions(+), 16 deletions(-) create mode 100755 tests/dep-time-travel.sh diff --git a/.travis.yml b/.travis.yml index 75f2e67ade..c359e73713 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,7 @@ matrix: node_js: 6 - env: PACKAGE=resolvers/webpack node_js: 4 + - os: osx env: ESLINT_VERSION=5 node_js: 10 @@ -54,7 +55,7 @@ before_install: - 'if [ -n "${PACKAGE-}" ]; then cd "${PACKAGE}"; fi' install: - npm install - - npm install --no-save eslint@$ESLINT_VERSION --ignore-scripts || true + - 'if [ -n "${ESLINT_VERSION}" ]; then ./tests/dep-time-travel.sh; fi' script: - 'npm test' diff --git a/tests/dep-time-travel.sh b/tests/dep-time-travel.sh new file mode 100755 index 0000000000..eae24998df --- /dev/null +++ b/tests/dep-time-travel.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# expected: ESLINT_VERSION numeric env var + +npm install --no-save eslint@$ESLINT_VERSION --ignore-scripts || true + +# use these alternate typescript dependencies for ESLint < v4 +if [[ "$ESLINT_VERSION" -lt "4" ]]; then + echo "Downgrading babel-eslint..." + npm i --no-save babel-eslint@8.0.3 + + echo "Downgrading TypeScript dependencies..." + npm i --no-save typescript-eslint-parser@15 typescript@2.8.1 +fi + +# typescript-eslint-parser 1.1.1+ is not compatible with node 6 +if [[ "$TRAVIS_NODE_VERSION" -lt "8" ]]; then + echo "Downgrading eslint-import-resolver-typescript..." + npm i --no-save eslint-import-resolver-typescript@1.0.2 +fi diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index b33d548f01..8e01f62acf 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -3,7 +3,7 @@ import ExportMap from '../../../src/ExportMap' import * as fs from 'fs' -import { getFilename, skipESLints } from '../utils' +import { getFilename } from '../utils' import * as unambiguous from 'eslint-module-utils/unambiguous' describe('ExportMap', function () { @@ -310,7 +310,7 @@ describe('ExportMap', function () { }) - skipESLints([2, 3])('alternate parsers', function () { + context('alternate parsers', function () { const configs = [ // ['string form', { 'typescript-eslint-parser': '.ts' }], diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 7ffd929e1b..92b3d9163e 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -1,4 +1,4 @@ -import { test, SYNTAX_CASES, skipESLints } from '../utils' +import { test, SYNTAX_CASES } from '../utils' import { RuleTester } from 'eslint' import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve' @@ -254,7 +254,7 @@ ruleTester.run('named (export *)', rule, { }) -skipESLints([2, 3])("Typescript", function () { +context("Typescript", function () { // Typescript ruleTester.run("named", rule, { valid: [ diff --git a/tests/src/utils.js b/tests/src/utils.js index 56e8ab72a2..fe04d684e2 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -29,17 +29,6 @@ export function getFilename(file) { return path.join(__dirname, '..', 'files', file || 'foo.js') } -/** - * skip tests iff ESLINT_VERSION is in provided `versions` array - */ -export function skipESLints(versions) { - if (versions.indexOf(+process.env.ESLINT_VERSION) === -1) { - return describe - } else { - return describe.skip - } -} - /** * to be added as valid cases just to ensure no nullable fields are going * to crash at runtime From 64d9be7d76aa7a901d846f08a3342b98b0c3d809 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Fri, 18 Jan 2019 20:03:36 -0500 Subject: [PATCH 032/151] allow_failures for dicey Node/ESLint intersection --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index c359e73713..e8eaf9d967 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,6 +49,12 @@ matrix: exclude: - node_js: '4' env: ESLINT_VERSION=5 + + fast_finish: true + allow_failures: + # issues with typescript deps in this version intersection + - node_js: '4' + env: ESLINT_VERSION=4 before_install: - 'nvm install-latest-npm' From 548ea0244b8717567975fa5d8325c83340521a15 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 22 Jan 2019 14:42:18 +0300 Subject: [PATCH 033/151] added support for argv parameter of webpack`s config-as-a-function (#1261) --- resolvers/webpack/index.js | 5 +++-- resolvers/webpack/test/config.js | 21 +++++++++++++++++++ .../test/files/webpack.function.config.js | 3 ++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/resolvers/webpack/index.js b/resolvers/webpack/index.js index b1b0e45f67..146213a0ca 100644 --- a/resolvers/webpack/index.js +++ b/resolvers/webpack/index.js @@ -47,6 +47,7 @@ exports.resolve = function (source, file, settings) { var configPath = get(settings, 'config') , configIndex = get(settings, 'config-index') , env = get(settings, 'env') + , argv = get(settings, 'argv', {}) , packageDir log('Config path from settings:', configPath) @@ -87,13 +88,13 @@ exports.resolve = function (source, file, settings) { } if (typeof webpackConfig === 'function') { - webpackConfig = webpackConfig(env, {}) + webpackConfig = webpackConfig(env, argv) } if (Array.isArray(webpackConfig)) { webpackConfig = webpackConfig.map(cfg => { if (typeof cfg === 'function') { - return cfg(env, {}) + return cfg(env, argv) } return cfg diff --git a/resolvers/webpack/test/config.js b/resolvers/webpack/test/config.js index 16a4a6dda3..07c6350c56 100644 --- a/resolvers/webpack/test/config.js +++ b/resolvers/webpack/test/config.js @@ -114,4 +114,25 @@ describe("config", function () { expect(resolve('bar', file, settings)).to.have.property('path') .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js')) }) + + it('passes argv to config when it is a function', function() { + var settings = { + config: require(path.join(__dirname, './files/webpack.function.config.js')), + argv: { + mode: 'test' + } + } + + expect(resolve('baz', file, settings)).to.have.property('path') + .and.equal(path.join(__dirname, 'files', 'some', 'bar', 'bar.js')) + }) + + it('passes a default empty argv object to config when it is a function', function() { + var settings = { + config: require(path.join(__dirname, './files/webpack.function.config.js')), + argv: undefined + } + + expect(function () { resolve('baz', file, settings) }).to.not.throw(Error) + }) }) diff --git a/resolvers/webpack/test/files/webpack.function.config.js b/resolvers/webpack/test/files/webpack.function.config.js index ce87dd1b11..0dad14e067 100644 --- a/resolvers/webpack/test/files/webpack.function.config.js +++ b/resolvers/webpack/test/files/webpack.function.config.js @@ -1,12 +1,13 @@ var path = require('path') var pluginsTest = require('webpack-resolver-plugin-test') -module.exports = function(env) { +module.exports = function(env, argv) { return { resolve: { alias: { 'foo': path.join(__dirname, 'some', 'goofy', 'path', 'foo.js'), 'bar': env ? path.join(__dirname, 'some', 'goofy', 'path', 'bar.js') : undefined, + 'baz': argv.mode === 'test' ? path.join(__dirname, 'some', 'bar', 'bar.js') : undefined, 'some-alias': path.join(__dirname, 'some'), }, modules: [ From 1e4100d8c8e16045933c361c15a7ab1fbad31148 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Tue, 22 Jan 2019 06:44:25 -0500 Subject: [PATCH 034/151] changelog note for #1261 --- resolvers/webpack/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index 8ea232a3bd..a1d0454302 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -4,6 +4,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## Unreleased + +### Added +- support for `argv` parameter when config is a function. ([#1261], thanks [@keann]) + ### Fixed - crash when webpack config is an array of functions ([#1219]/[#1220] by [@idudinov]) @@ -106,6 +110,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]). +[#1261]: https://github.com/benmosher/eslint-plugin-import/pull/1261 [#1220]: https://github.com/benmosher/eslint-plugin-import/pull/1220 [#1091]: https://github.com/benmosher/eslint-plugin-import/pull/1091 [#969]: https://github.com/benmosher/eslint-plugin-import/pull/969 @@ -152,3 +157,4 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [@graingert]: https://github.com/graingert [@mattkrick]: https://github.com/mattkrick [@idudinov]: https://github.com/idudinov +[@keann]: https://github.com/keann From 20a8f3b178377bb92e3310b21b3d91b8753fe3a3 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Tue, 22 Jan 2019 06:58:34 -0500 Subject: [PATCH 035/151] bump utils to v2.3.0 --- package.json | 2 +- utils/CHANGELOG.md | 10 ++++++++++ utils/package.json | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f13598aa8a..21c3757342 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "debug": "^2.6.9", "doctrine": "1.5.0", "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.2.0", + "eslint-module-utils": "^2.3.0", "has": "^1.0.3", "lodash": "^4.17.11", "minimatch": "^3.0.4", diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index 018fd30669..cb86dc2259 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,10 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +## v2.3.0 - 2019-01-22 +### Fixed +- use `process.hrtime()` for cache dates ([#1160], thanks [@hulkish]) + ## v2.2.0 - 2018-03-29 ### Changed - `parse`: attach node locations by default. @@ -30,3 +34,9 @@ Yanked due to critical issue with cache key resulting from #839. ### Fixed - `unambiguous.test()` regex is now properly in multiline mode + + + +[#1160]: https://github.com/benmosher/eslint-plugin-import/pull/1160 + +[@hulkish]: https://github.com/hulkish diff --git a/utils/package.json b/utils/package.json index 360cc76132..c380b6e2b1 100644 --- a/utils/package.json +++ b/utils/package.json @@ -1,6 +1,6 @@ { "name": "eslint-module-utils", - "version": "2.2.0", + "version": "2.3.0", "description": "Core utilities to support eslint-plugin-import and other module-related plugins.", "engines": { "node": ">=4" From 038d668b0b03e3ea06091bc744f082397cff200c Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Tue, 22 Jan 2019 06:58:50 -0500 Subject: [PATCH 036/151] bump webpack resolver to v0.11.0 --- resolvers/webpack/CHANGELOG.md | 3 +++ resolvers/webpack/package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index a1d0454302..5526ca5401 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 + +## 0.11.0 - 2018-01-22 + ### Added - support for `argv` parameter when config is a function. ([#1261], thanks [@keann]) diff --git a/resolvers/webpack/package.json b/resolvers/webpack/package.json index a36a78e474..5985babe6c 100644 --- a/resolvers/webpack/package.json +++ b/resolvers/webpack/package.json @@ -1,6 +1,6 @@ { "name": "eslint-import-resolver-webpack", - "version": "0.10.1", + "version": "0.11.0", "description": "Resolve paths to dependencies, given a webpack.config.js. Plugin for eslint-plugin-import.", "main": "index.js", "scripts": { From 767f01a2f34b77d118edf762809c2f2046abe1b7 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Tue, 22 Jan 2019 07:07:39 -0500 Subject: [PATCH 037/151] bump to v2.15.0 --- CHANGELOG.md | 23 ++++++++++++++++++++--- package.json | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0b8a590d9..1a0a3f5ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,23 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] + + +## [2.15.0] - 2019-01-22 +### Added +- new rule: [`no-named-export`] ([#1157], thanks [@fsmaia]) + ### Fixed - [`no-extraneous-dependencies`]: `packageDir` option with array value was clobbering package deps instead of merging them ([#1175]/[#1176], thanks [@aravindet] & [@pzhine]) +- [`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]) @@ -493,10 +501,15 @@ for info on changes for earlier releases. [`no-default-export`]: ./docs/rules/no-default-export.md [`no-useless-path-segments`]: ./docs/rules/no-useless-path-segments.md [`no-cycle`]: ./docs/rules/no-cycle.md +[`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md +[`no-named-export`]: ./docs/rules/no-named-export.md [`memo-parser`]: ./memo-parser/README.md +[#1232]: https://github.com/benmosher/eslint-plugin-import/pull/1232 [#1176]: https://github.com/benmosher/eslint-plugin-import/pull/1176 +[#1163]: https://github.com/benmosher/eslint-plugin-import/pull/1163 +[#1157]: https://github.com/benmosher/eslint-plugin-import/pull/1157 [#1151]: https://github.com/benmosher/eslint-plugin-import/pull/1151 [#1137]: https://github.com/benmosher/eslint-plugin-import/pull/1137 [#1135]: https://github.com/benmosher/eslint-plugin-import/pull/1135 @@ -652,7 +665,9 @@ 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.13.0...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.15.0...HEAD +[2.15.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.14.0...v2.15.0 +[2.14.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.13.0...v2.14.0 [2.13.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.12.0...v2.13.0 [2.12.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.11.0...v2.12.0 [2.11.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.10.0...v2.11.0 @@ -774,3 +789,5 @@ for info on changes for earlier releases. [@jf248]: https://github.com/jf248 [@aravindet]: https://github.com/aravindet [@pzhine]: https://github.com/pzhine +[@st-sloth]: https://github.com/st-sloth +[@ljqx]: https://github.com/ljqx diff --git a/package.json b/package.json index 21c3757342..39a8d5510f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.14.0", + "version": "2.15.0", "description": "Import with sanity.", "engines": { "node": ">=4" From 1ec80fa35fa1819e2d35a70e68fb6a149fb57c5e Mon Sep 17 00:00:00 2001 From: Kirill Konshin Date: Wed, 23 Jan 2019 04:29:47 -0800 Subject: [PATCH 038/151] Fix for #1256 (#1257) --- config/typescript.js | 22 ++++++++++++++++++++++ src/index.js | 1 + 2 files changed, 23 insertions(+) create mode 100644 config/typescript.js diff --git a/config/typescript.js b/config/typescript.js new file mode 100644 index 0000000000..d2d707c589 --- /dev/null +++ b/config/typescript.js @@ -0,0 +1,22 @@ +/** + * Adds `.jsx`, `.ts` and `.tsx` as an extension, and enables JSX/TSX parsing. + */ +var jsExtensions = ['.js', '.jsx']; +var tsExtensions = ['.ts', '.tsx']; +var allExtensions = jsExtensions.concat(tsExtensions); + +module.exports = { + + settings: { + 'import/extensions': allExtensions, + 'import/parsers': { + 'typescript-eslint-parser': tsExtensions + }, + 'import/resolver': { + 'node': { + 'extensions': allExtensions + } + } + } + +} diff --git a/src/index.js b/src/index.js index f5794595d6..6cbe0a6428 100644 --- a/src/index.js +++ b/src/index.js @@ -62,4 +62,5 @@ export const configs = { 'react': require('../config/react'), 'react-native': require('../config/react-native'), 'electron': require('../config/electron'), + 'typescript': require('../config/typescript'), } From e72a336e9b62174c77be79ff6252fb6d780dd238 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Tue, 29 Jan 2019 05:45:05 -0500 Subject: [PATCH 039/151] fix #1266 by moving closure creation out of parsing scope (#1275) --- CHANGELOG.md | 9 +++++++++ docs/rules/no-deprecated.md | 4 ---- src/ExportMap.js | 28 +++++++++++++++++++++------- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0a3f5ac7..f21cfeb2c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] +### Added +- `typescript` config ([#1257], thanks [@kirill-konshin]) +### Fixed +- Memory leak of `SourceCode` objects for all parsed dependencies, resolved. (issue [#1266], thanks [@asapach] and [@sergei-startsev] for digging in) ## [2.15.0] - 2019-01-22 ### Added @@ -506,6 +510,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1257]: https://github.com/benmosher/eslint-plugin-import/pull/1257 [#1232]: https://github.com/benmosher/eslint-plugin-import/pull/1232 [#1176]: https://github.com/benmosher/eslint-plugin-import/pull/1176 [#1163]: https://github.com/benmosher/eslint-plugin-import/pull/1163 @@ -595,6 +600,7 @@ for info on changes for earlier releases. [#314]: https://github.com/benmosher/eslint-plugin-import/pull/314 [#912]: https://github.com/benmosher/eslint-plugin-import/pull/912 +[#1266]: https://github.com/benmosher/eslint-plugin-import/issues/1266 [#1175]: https://github.com/benmosher/eslint-plugin-import/issues/1175 [#1058]: https://github.com/benmosher/eslint-plugin-import/issues/1058 [#931]: https://github.com/benmosher/eslint-plugin-import/issues/931 @@ -791,3 +797,6 @@ for info on changes for earlier releases. [@pzhine]: https://github.com/pzhine [@st-sloth]: https://github.com/st-sloth [@ljqx]: https://github.com/ljqx +[@kirill-konshin]: https://github.com/kirill-konshin +[@asapach]: https://github.com/asapach +[@sergei-startsev]: https://github.com/sergei-startsev diff --git a/docs/rules/no-deprecated.md b/docs/rules/no-deprecated.md index 7583651f31..fae7d8daf2 100644 --- a/docs/rules/no-deprecated.md +++ b/docs/rules/no-deprecated.md @@ -1,9 +1,5 @@ # import/no-deprecated -**Stage: 0** - -**NOTE**: this rule is currently a work in progress. There may be "breaking" changes: most likely, additional cases that are flagged. - Reports use of a deprecated name, as indicated by a JSDoc block with a `@deprecated` tag or TomDoc `Deprecated: ` comment. diff --git a/src/ExportMap.js b/src/ExportMap.js index c8335d9c8a..38aedc6d6c 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -192,8 +192,6 @@ export default class ExportMap { /** * parse docs from the first node that has leading comments - * @param {...[type]} nodes [description] - * @return {{doc: object}} */ function captureDoc(source, docStyleParsers) { const metadata = {} @@ -202,9 +200,17 @@ function captureDoc(source, docStyleParsers) { // 'some' short-circuits on first 'true' nodes.some(n => { try { + + let leadingComments + // n.leadingComments is legacy `attachComments` behavior - let leadingComments = n.leadingComments || source.getCommentsBefore(n) - if (leadingComments.length === 0) return false + if ('leadingComments' in n) { + leadingComments = n.leadingComments + } else if (n.range) { + leadingComments = source.getCommentsBefore(n) + } + + if (!leadingComments || leadingComments.length === 0) return false for (let name in docStyleParsers) { const doc = docStyleParsers[name](leadingComments) @@ -346,8 +352,6 @@ ExportMap.parse = function (path, content, context) { docStyleParsers[style] = availableDocStyleParsers[style] }) - const source = makeSourceCode(content, ast) - // attempt to collect module doc if (ast.comments) { ast.comments.some(c => { @@ -400,7 +404,7 @@ ExportMap.parse = function (path, content, context) { const existing = m.imports.get(p) if (existing != null) return existing.getter - const getter = () => ExportMap.for(childContext(p, context)) + const getter = thunkFor(p, context) m.imports.set(p, { getter, source: { // capturing actual node reference holds full AST in memory! @@ -411,6 +415,7 @@ ExportMap.parse = function (path, content, context) { return getter } + const source = makeSourceCode(content, ast) ast.body.forEach(function (n) { @@ -496,6 +501,15 @@ ExportMap.parse = function (path, content, context) { return m } +/** + * The creation of this closure is isolated from other scopes + * to avoid over-retention of unrelated variables, which has + * caused memory leaks. See #1266. + */ +function thunkFor(p, context) { + return () => ExportMap.for(childContext(p, context)) +} + /** * Traverse a pattern/identifier node, calling 'callback' From d305f6ab7c8869dce80928f6a4d7cd3de10ee3f5 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Tue, 29 Jan 2019 06:21:48 -0500 Subject: [PATCH 040/151] use proper rest arg instead of [].slice --- src/ExportMap.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ExportMap.js b/src/ExportMap.js index 38aedc6d6c..07120754c6 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -193,9 +193,8 @@ export default class ExportMap { /** * parse docs from the first node that has leading comments */ -function captureDoc(source, docStyleParsers) { +function captureDoc(source, docStyleParsers, ...nodes) { const metadata = {} - , nodes = Array.prototype.slice.call(arguments, 1) // 'some' short-circuits on first 'true' nodes.some(n => { From 9bac44e629105572ca78a532c968df202e5a18b8 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Tue, 29 Jan 2019 06:23:58 -0500 Subject: [PATCH 041/151] bump to v2.16.0 --- CHANGELOG.md | 6 +++++- package.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f21cfeb2c9..d93839d76b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] + + +## [2.16.0] - 2019-01-29 ### Added - `typescript` config ([#1257], thanks [@kirill-konshin]) @@ -671,7 +674,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.15.0...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.16.0...HEAD +[2.16.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.15.0...v2.16.0 [2.15.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.14.0...v2.15.0 [2.14.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.13.0...v2.14.0 [2.13.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.12.0...v2.13.0 diff --git a/package.json b/package.json index 39a8d5510f..79f52e5930 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.15.0", + "version": "2.16.0", "description": "Import with sanity.", "engines": { "node": ">=4" From bdc05aa1d029b70125ae415e5ca5dca22250858b Mon Sep 17 00:00:00 2001 From: Kirill Konshin Date: Wed, 30 Jan 2019 05:03:15 -0800 Subject: [PATCH 042/151] Update README.md for #1256 (#1277) --- README.md | 16 ++++++++++++++++ config/typescript.js | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f826a85b7..41bbff0a0d 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,22 @@ rules: # etc... ``` +# Typescript + +You may use the following shortcut or assemble your own config using the granular settings described below. + +Make sure you have installed the [`@typescript-eslint/parser`] which is used in the following configuration. Unfortunately NPM does not allow to list optional peer dependencies. + +```yaml +extends: + - eslint:recommended + - plugin:import/errors + - plugin:import/warnings + - plugin:import/typescript # this line does the trick +``` + +[`@typescript-eslint/parser`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser + # Resolvers With the advent of module bundlers and the current state of modules and module diff --git a/config/typescript.js b/config/typescript.js index d2d707c589..6a2a08618c 100644 --- a/config/typescript.js +++ b/config/typescript.js @@ -10,7 +10,7 @@ module.exports = { settings: { 'import/extensions': allExtensions, 'import/parsers': { - 'typescript-eslint-parser': tsExtensions + '@typescript-eslint/parser': tsExtensions }, 'import/resolver': { 'node': { From b4bad0ed3c6b040093b9f365a3d1c9a4cdcdf4da Mon Sep 17 00:00:00 2001 From: jeffshaver Date: Sun, 24 Feb 2019 18:05:06 -0500 Subject: [PATCH 043/151] [Tests] fix a bunch of tests that were failing --- tests/src/rules/export.js | 6 +++--- tests/src/rules/extensions.js | 8 ++++---- tests/src/rules/named.js | 2 +- tests/src/rules/no-default-export.js | 5 +++-- tests/src/rules/no-named-export.js | 5 +++-- tests/src/rules/no-unresolved.js | 2 +- tests/src/rules/prefer-default-export.js | 3 +++ tests/src/utils.js | 4 ++-- 8 files changed, 20 insertions(+), 15 deletions(-) diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index 84598677da..33f0121233 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -17,8 +17,8 @@ ruleTester.run('export', rule, { test({ code: 'export var [ foo, bar ] = array;' }), test({ code: 'export var { foo, bar } = object;' }), test({ code: 'export var [ foo, bar ] = array;' }), - test({ code: 'export { foo, foo as bar }' }), - test({ code: 'export { bar }; export * from "./export-all"' }), + test({ code: 'let foo; export { foo, foo as bar }' }), + test({ code: 'let bar; export { bar }; export * from "./export-all"' }), test({ code: 'export * from "./export-all"' }), test({ code: 'export * from "./does-not-exist"' }), @@ -62,7 +62,7 @@ ruleTester.run('export', rule, { // errors: ['Parsing error: Duplicate export \'foo\''], // }), test({ - code: 'export { foo }; export * from "./export-all"', + code: 'let foo; export { foo }; export * from "./export-all"', errors: ['Multiple exports of name \'foo\'.', 'Multiple exports of name \'foo\'.'], }), diff --git a/tests/src/rules/extensions.js b/tests/src/rules/extensions.js index 8b816daa9e..d7b97bea0b 100644 --- a/tests/src/rules/extensions.js +++ b/tests/src/rules/extensions.js @@ -105,14 +105,14 @@ ruleTester.run('extensions', rule, { test({ code: [ 'export { foo } from "./foo.js"', - 'export { bar }', + 'let bar; export { bar }', ].join('\n'), options: [ 'always' ], }), test({ code: [ 'export { foo } from "./foo"', - 'export { bar }', + 'let bar; export { bar }', ].join('\n'), options: [ 'never' ], }), @@ -334,7 +334,7 @@ ruleTester.run('extensions', rule, { test({ code: [ 'export { foo } from "./foo"', - 'export { bar }', + 'let bar; export { bar }', ].join('\n'), options: [ 'always' ], errors: [ @@ -348,7 +348,7 @@ ruleTester.run('extensions', rule, { test({ code: [ 'export { foo } from "./foo.js"', - 'export { bar }', + 'let bar; export { bar }', ].join('\n'), options: [ 'never' ], errors: [ diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 92b3d9163e..569f3158a5 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -61,7 +61,7 @@ ruleTester.run('named', rule, { }), // regression tests - test({ code: 'export { foo as bar }'}), + test({ code: 'let foo; export { foo as bar }'}), // destructured exports test({ code: 'import { destructuredProp } from "./named-exports"' }), diff --git a/tests/src/rules/no-default-export.js b/tests/src/rules/no-default-export.js index 6440bfa895..5977ef19b2 100644 --- a/tests/src/rules/no-default-export.js +++ b/tests/src/rules/no-default-export.js @@ -29,7 +29,7 @@ ruleTester.run('no-default-export', rule, { `, }), test({ - code: `export { foo, bar }`, + code: `let foo, bar; export { foo, bar }`, }), test({ code: `export const { foo, bar } = item;`, @@ -42,6 +42,7 @@ ruleTester.run('no-default-export', rule, { }), test({ code: ` + let item; export const foo = item; export { item }; `, @@ -102,7 +103,7 @@ ruleTester.run('no-default-export', rule, { }], }), test({ - code: 'export { foo as default }', + code: 'let foo; export { foo as default }', errors: [{ ruleId: 'ExportNamedDeclaration', message: 'Do not alias `foo` as `default`. Just export `foo` itself ' + diff --git a/tests/src/rules/no-named-export.js b/tests/src/rules/no-named-export.js index d8748d3f6b..c56a135421 100644 --- a/tests/src/rules/no-named-export.js +++ b/tests/src/rules/no-named-export.js @@ -10,7 +10,7 @@ ruleTester.run('no-named-export', rule, { code: 'export default function bar() {};', }), test({ - code: 'export { foo as default }', + code: 'let foo; export { foo as default }', }), test({ code: 'export default from "foo.js"', @@ -82,7 +82,7 @@ ruleTester.run('no-named-export', rule, { }], }), test({ - code: `export { foo, bar }`, + code: `let foo, bar; export { foo, bar }`, errors: [{ ruleId: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', @@ -111,6 +111,7 @@ ruleTester.run('no-named-export', rule, { }), test({ code: ` + let item; export const foo = item; export { item }; `, diff --git a/tests/src/rules/no-unresolved.js b/tests/src/rules/no-unresolved.js index 5b4f6ae53c..9db6e1a5c5 100644 --- a/tests/src/rules/no-unresolved.js +++ b/tests/src/rules/no-unresolved.js @@ -34,7 +34,7 @@ function runResolverTests(resolver) { rest({ code: 'export { foo } from "./bar"' }), rest({ code: 'export * from "./bar"' }), - rest({ code: 'export { foo }' }), + rest({ code: 'let foo; export { foo }' }), // stage 1 proposal for export symmetry, rest({ code: 'export * as bar from "./bar"' diff --git a/tests/src/rules/prefer-default-export.js b/tests/src/rules/prefer-default-export.js index 3a9145198d..3a89c6aa7f 100644 --- a/tests/src/rules/prefer-default-export.js +++ b/tests/src/rules/prefer-default-export.js @@ -28,6 +28,7 @@ ruleTester.run('prefer-default-export', rule, { }), test({ code: ` + let foo, bar; export { foo, bar }`, }), test({ @@ -44,11 +45,13 @@ ruleTester.run('prefer-default-export', rule, { }), test({ code: ` + let item; export const foo = item; export { item };`, }), test({ code: ` + let foo; export { foo as default }`, }), test({ diff --git a/tests/src/utils.js b/tests/src/utils.js index fe04d684e2..52e2d23cd5 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -43,8 +43,8 @@ export const SYNTAX_CASES = [ test({ code: 'const { x, y, ...z } = bar', parser: 'babel-eslint' }), // all the exports - test({ code: 'export { x }' }), - test({ code: 'export { x as y }' }), + test({ code: 'let x; export { x }' }), + test({ code: 'let x; export { x as y }' }), // not sure about these since they reference a file // test({ code: 'export { x } from "./y.js"'}), From 158cd8082b23a60264912fc6e557f168d3ec6b0f Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 2 Mar 2019 21:37:35 -0800 Subject: [PATCH 044/151] [Tests] use `resolve`, an actual dep, instead of `builtin-modules`, an erstwhile transitive dep --- tests/src/core/importType.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js index abf9b95228..28c3c4ff70 100644 --- a/tests/src/core/importType.js +++ b/tests/src/core/importType.js @@ -90,16 +90,16 @@ describe('importType(name)', function () { }) it("should return 'external' for module from 'node_modules' with default config", function() { - expect(importType('builtin-modules', context)).to.equal('external') + expect(importType('resolve', context)).to.equal('external') }) it("should return 'internal' for module from 'node_modules' if 'node_modules' missed in 'external-module-folders'", function() { const foldersContext = testContext({ 'import/external-module-folders': [] }) - expect(importType('builtin-modules', foldersContext)).to.equal('internal') + expect(importType('resolve', foldersContext)).to.equal('internal') }) 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('builtin-modules', foldersContext)).to.equal('external') + expect(importType('resolve', foldersContext)).to.equal('external') }) }) From e9544f83754bc74193f9caa6071c8c343b37c057 Mon Sep 17 00:00:00 2001 From: Guylian Cox Date: Thu, 6 Apr 2017 15:57:02 +0200 Subject: [PATCH 045/151] [Fix] Fix interpreting some external modules being interpreted as internal modules Fixes #793. - Add skipped test to expect scoped internal packages to be "internal" --- CHANGELOG.md | 5 +++++ package.json | 2 ++ src/core/importType.js | 6 +++++- tests/files/@importType/index.js | 1 + tests/files/order-redirect-scoped/module/package.json | 5 +++++ tests/files/order-redirect-scoped/other-module/file.js | 0 tests/files/order-redirect-scoped/package.json | 5 +++++ tests/files/order-redirect/module/package.json | 5 +++++ tests/files/order-redirect/other-module/file.js | 0 tests/files/order-redirect/package.json | 5 +++++ tests/src/core/importType.js | 10 ++++++++++ 11 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/files/@importType/index.js create mode 100644 tests/files/order-redirect-scoped/module/package.json create mode 100644 tests/files/order-redirect-scoped/other-module/file.js create mode 100644 tests/files/order-redirect-scoped/package.json create mode 100644 tests/files/order-redirect/module/package.json create mode 100644 tests/files/order-redirect/other-module/file.js create mode 100644 tests/files/order-redirect/package.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d93839d76b..9427a79ae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,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 +- [`order`]: Fix interpreting some external modules being interpreted as internal modules ([#793], [#794] thanks [@ephys]) ## [2.16.0] - 2019-01-29 @@ -538,6 +540,7 @@ for info on changes for earlier releases. [#843]: https://github.com/benmosher/eslint-plugin-import/pull/843 [#871]: https://github.com/benmosher/eslint-plugin-import/pull/871 [#797]: https://github.com/benmosher/eslint-plugin-import/pull/797 +[#794]: https://github.com/benmosher/eslint-plugin-import/pull/794 [#744]: https://github.com/benmosher/eslint-plugin-import/pull/744 [#742]: https://github.com/benmosher/eslint-plugin-import/pull/742 [#737]: https://github.com/benmosher/eslint-plugin-import/pull/737 @@ -615,6 +618,7 @@ for info on changes for earlier releases. [#717]: https://github.com/benmosher/eslint-plugin-import/issues/717 [#686]: https://github.com/benmosher/eslint-plugin-import/issues/686 [#671]: https://github.com/benmosher/eslint-plugin-import/issues/671 +[#793]: https://github.com/benmosher/eslint-plugin-import/issues/793 [#660]: https://github.com/benmosher/eslint-plugin-import/issues/660 [#653]: https://github.com/benmosher/eslint-plugin-import/issues/653 [#627]: https://github.com/benmosher/eslint-plugin-import/issues/627 @@ -804,3 +808,4 @@ for info on changes for earlier releases. [@kirill-konshin]: https://github.com/kirill-konshin [@asapach]: https://github.com/asapach [@sergei-startsev]: https://github.com/sergei-startsev +[@ephys]: https://github.com/ephys diff --git a/package.json b/package.json index 79f52e5930..b114f9294d 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,8 @@ "eslint-import-resolver-typescript": "^1.0.2", "eslint-import-resolver-webpack": "file:./resolvers/webpack", "eslint-module-utils": "file:./utils", + "eslint-import-test-order-redirect": "file:./tests/files/order-redirect", + "@eslint/import-test-order-redirect-scoped": "file:./tests/files/order-redirect-scoped", "eslint-plugin-import": "2.x", "gulp": "^3.9.1", "gulp-babel": "6.1.2", diff --git a/src/core/importType.js b/src/core/importType.js index 89b162aad3..f2d6bda86e 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -29,7 +29,11 @@ export function isBuiltIn(name, settings) { function isExternalPath(path, name, settings) { const folders = (settings && settings['import/external-module-folders']) || ['node_modules'] - return !path || folders.some(folder => -1 < path.indexOf(join(folder, name))) + + // extract the part before the first / (redux-saga/effects => redux-saga) + const packageName = name.match(/([^/]+)/)[0] + + return !path || folders.some(folder => -1 < path.indexOf(join(folder, packageName))) } const externalModuleRegExp = /^\w/ diff --git a/tests/files/@importType/index.js b/tests/files/@importType/index.js new file mode 100644 index 0000000000..bc4ffafc8c --- /dev/null +++ b/tests/files/@importType/index.js @@ -0,0 +1 @@ +/* for importType test, just needs to exist */ diff --git a/tests/files/order-redirect-scoped/module/package.json b/tests/files/order-redirect-scoped/module/package.json new file mode 100644 index 0000000000..6c4325e8e9 --- /dev/null +++ b/tests/files/order-redirect-scoped/module/package.json @@ -0,0 +1,5 @@ +{ + "name": "order-redirect-module", + "private": true, + "main": "../other-module/file.js" +} diff --git a/tests/files/order-redirect-scoped/other-module/file.js b/tests/files/order-redirect-scoped/other-module/file.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/files/order-redirect-scoped/package.json b/tests/files/order-redirect-scoped/package.json new file mode 100644 index 0000000000..8e1acfa874 --- /dev/null +++ b/tests/files/order-redirect-scoped/package.json @@ -0,0 +1,5 @@ +{ + "name": "@eslint/import-test-order-redirect-scoped", + "version": "1.0.0", + "private": true +} diff --git a/tests/files/order-redirect/module/package.json b/tests/files/order-redirect/module/package.json new file mode 100644 index 0000000000..6c4325e8e9 --- /dev/null +++ b/tests/files/order-redirect/module/package.json @@ -0,0 +1,5 @@ +{ + "name": "order-redirect-module", + "private": true, + "main": "../other-module/file.js" +} diff --git a/tests/files/order-redirect/other-module/file.js b/tests/files/order-redirect/other-module/file.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/files/order-redirect/package.json b/tests/files/order-redirect/package.json new file mode 100644 index 0000000000..1605a22035 --- /dev/null +++ b/tests/files/order-redirect/package.json @@ -0,0 +1,5 @@ +{ + "name": "eslint-import-test-order-redirect", + "version": "1.0.0", + "private": true +} diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js index 28c3c4ff70..67de1d8a85 100644 --- a/tests/src/core/importType.js +++ b/tests/src/core/importType.js @@ -36,11 +36,21 @@ describe('importType(name)', function () { expect(importType('@some-thing/something/some-directory/someModule.js', context)).to.equal('external') }) + it("should return 'external' for external modules that redirect to its parent module using package.json", function() { + expect(importType('eslint-import-test-order-redirect/module', context)).to.equal('external') + expect(importType('@eslint/import-test-order-redirect-scoped/module', context)).to.equal('external') + }) + it("should return 'internal' for non-builtins resolved outside of node_modules", function () { const pathContext = testContext({ "import/resolver": { node: { paths: [ path.join(__dirname, '..', '..', 'files') ] } } }) expect(importType('importType', pathContext)).to.equal('internal') }) + it.skip("should return 'internal' for scoped packages resolved outside of node_modules", function () { + const pathContext = testContext({ "import/resolver": { node: { paths: [ path.join(__dirname, '..', '..', 'files') ] } } }) + expect(importType('@importType/index', pathContext)).to.equal('internal') + }) + it("should return 'parent' for internal modules that go through the parent", function() { expect(importType('../foo', context)).to.equal('parent') expect(importType('../../foo', context)).to.equal('parent') From 513a488ea7b5fdbd6b5b42386307ef9110a1bc00 Mon Sep 17 00:00:00 2001 From: Daniele Zanni Date: Fri, 18 May 2018 12:32:33 +1000 Subject: [PATCH 046/151] Fixed flow types imports --- src/rules/named.js | 3 +++ tests/files/flowtypes.js | 3 +++ tests/src/rules/named.js | 8 ++++++++ 3 files changed, 14 insertions(+) diff --git a/src/rules/named.js b/src/rules/named.js index 8c2acd714e..f0151a89d8 100644 --- a/src/rules/named.js +++ b/src/rules/named.js @@ -30,6 +30,9 @@ module.exports = { node.specifiers.forEach(function (im) { if (im.type !== type) return + // ignore type imports + if(im.importKind === 'type') return + const deepLookup = imports.hasDeep(im[key].name) if (!deepLookup.found) { diff --git a/tests/files/flowtypes.js b/tests/files/flowtypes.js index 7ada3482b1..2df2471475 100644 --- a/tests/files/flowtypes.js +++ b/tests/files/flowtypes.js @@ -10,3 +10,6 @@ export type MyType = { export interface MyInterface {} export class MyClass {} + +export opaque type MyOpaqueType: string = string; + diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 4fdd3434f9..fce5230933 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -72,6 +72,14 @@ ruleTester.run('named', rule, { code: 'import type { MissingType } from "./flowtypes"', parser: 'babel-eslint', }), + test({ + code: 'import type { MyOpaqueType } from "./flowtypes"', + parser: 'babel-eslint', + }), + test({ + code: 'import { type MyOpaqueType, MyClass } from "./flowtypes"', + parser: 'babel-eslint', + }), // TypeScript test({ From 96500b84518f2e2802a79f673da35e5625bb8c3a Mon Sep 17 00:00:00 2001 From: Daniele Zanni Date: Fri, 18 May 2018 18:39:01 +1000 Subject: [PATCH 047/151] Added missing space --- src/rules/named.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/named.js b/src/rules/named.js index f0151a89d8..57e4f1d9ef 100644 --- a/src/rules/named.js +++ b/src/rules/named.js @@ -31,7 +31,7 @@ module.exports = { if (im.type !== type) return // ignore type imports - if(im.importKind === 'type') return + if (im.importKind === 'type') return const deepLookup = imports.hasDeep(im[key].name) From 1d9ae51f9f305ed54c9b36d1c6a883392ea94d8e Mon Sep 17 00:00:00 2001 From: syymza Date: Fri, 18 May 2018 22:34:05 +1000 Subject: [PATCH 048/151] Added test for mixed flow imports --- tests/src/rules/named.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index fce5230933..cb1a5b843b 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -270,6 +270,12 @@ ruleTester.run('named', rule, { }], }), + test({ + code: 'import { type MyOpaqueType, MyMissingClass } from "./flowtypes"', + parser: 'babel-eslint', + errors: ["MyMissingClass not found in './flowtypes'"], + }), + // jsnext test({ code: '/*jsnext*/ import { createSnorlax } from "redux"', From 3b04d5fab6c095e7f0f99488665d90e285872271 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 25 Jun 2018 14:33:43 -0700 Subject: [PATCH 049/151] [Refactor] add explicit support for RestElement alongside ExperimentalRestProperty --- src/rules/namespace.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/rules/namespace.js b/src/rules/namespace.js index 93e5891594..a1d5b6d813 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -58,7 +58,7 @@ module.exports = { return } - for (let specifier of declaration.specifiers) { + for (const specifier of declaration.specifiers) { switch (specifier.type) { case 'ImportNamespaceSpecifier': if (!imports.size) { @@ -160,8 +160,12 @@ module.exports = { if (pattern.type !== 'ObjectPattern') return - for (let property of pattern.properties) { - if (property.type === 'ExperimentalRestProperty' || !property.key) { + for (const property of pattern.properties) { + if ( + property.type === 'ExperimentalRestProperty' + || property.type === 'RestElement' + || !property.key + ) { continue } From 2c1886e0094159beefc7c6571db83872590b1590 Mon Sep 17 00:00:00 2001 From: Pirasis <1pete@users.noreply.github.com> Date: Fri, 29 Jun 2018 21:13:58 +0700 Subject: [PATCH 050/151] make rule `no-useless-path-segments` work with commonjs --- src/rules/no-useless-path-segments.js | 10 +++++ tests/src/rules/no-useless-path-segments.js | 47 +++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/rules/no-useless-path-segments.js b/src/rules/no-useless-path-segments.js index b9c4eedda0..5872b2d1c3 100644 --- a/src/rules/no-useless-path-segments.js +++ b/src/rules/no-useless-path-segments.js @@ -39,6 +39,16 @@ module.exports = { url: docsUrl('no-useless-path-segments'), }, + schema: [ + { + type: 'object', + properties: { + commonjs: { type: 'boolean' }, + }, + additionalProperties: false, + }, + ], + fixable: 'code', }, diff --git a/tests/src/rules/no-useless-path-segments.js b/tests/src/rules/no-useless-path-segments.js index 1f4229f5ee..ed20440012 100644 --- a/tests/src/rules/no-useless-path-segments.js +++ b/tests/src/rules/no-useless-path-segments.js @@ -7,6 +7,10 @@ const rule = require('rules/no-useless-path-segments') function runResolverTests(resolver) { ruleTester.run(`no-useless-path-segments (${resolver})`, rule, { valid: [ + // commonjs with default options + test({ code: 'require("./../files/malformed.js")' }), + + // esmodule test({ code: 'import "./malformed.js"' }), test({ code: 'import "./test-module"' }), test({ code: 'import "./bar/"' }), @@ -16,6 +20,49 @@ function runResolverTests(resolver) { ], invalid: [ + // commonjs + test({ + code: 'require("./../files/malformed.js")', + options: [{ commonjs: true }], + errors: [ 'Useless path segments for "./../files/malformed.js", should be "../files/malformed.js"'], + }), + test({ + code: 'require("./../files/malformed")', + options: [{ commonjs: true }], + errors: [ 'Useless path segments for "./../files/malformed", should be "../files/malformed"'], + }), + test({ + code: 'require("../files/malformed.js")', + options: [{ commonjs: true }], + errors: [ 'Useless path segments for "../files/malformed.js", should be "./malformed.js"'], + }), + test({ + code: 'require("../files/malformed")', + options: [{ commonjs: true }], + errors: [ 'Useless path segments for "../files/malformed", should be "./malformed"'], + }), + test({ + code: 'require("./test-module/")', + options: [{ commonjs: true }], + errors: [ 'Useless path segments for "./test-module/", should be "./test-module"'], + }), + test({ + code: 'require("./")', + options: [{ commonjs: true }], + errors: [ 'Useless path segments for "./", should be "."'], + }), + test({ + code: 'require("../")', + options: [{ commonjs: true }], + errors: [ 'Useless path segments for "../", should be ".."'], + }), + test({ + code: 'require("./deep//a")', + options: [{ commonjs: true }], + errors: [ 'Useless path segments for "./deep//a", should be "./deep/a"'], + }), + + // esmodule test({ code: 'import "./../files/malformed.js"', errors: [ 'Useless path segments for "./../files/malformed.js", should be "../files/malformed.js"'], From ce4b1af7c02aeb6097766786a1025d817663a54b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Berm=C3=BAdez=20Schettino?= Date: Wed, 11 Jul 2018 11:26:14 +0200 Subject: [PATCH 051/151] Fix format of changelog Updated version links. --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d384d5c390..2a7254bc70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -619,7 +619,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.12.0...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.13.0...HEAD +[2.13.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.12.0...v2.13.0 [2.12.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.11.0...v2.12.0 [2.11.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.10.0...v2.11.0 [2.10.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.9.0...v2.10.0 From 81bf977ffc47980ed597e8bcba1315ccc5968226 Mon Sep 17 00:00:00 2001 From: Chris Lloyd Date: Wed, 11 Jul 2018 09:50:27 -0700 Subject: [PATCH 052/151] [no-relative-parent-imports] Resolve paths This changes the rule to resolve paths before emitting an error. While this means the error will trigger less often (before we could report an error even if the file didn't exist on disk yet) I think it's a fine tradeoff so that it can be useful in more situations. --- src/rules/no-relative-parent-imports.js | 20 ++++++++++++--- tests/src/rules/no-relative-parent-imports.js | 25 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/rules/no-relative-parent-imports.js b/src/rules/no-relative-parent-imports.js index 3153eeb784..6b58c97f5a 100644 --- a/src/rules/no-relative-parent-imports.js +++ b/src/rules/no-relative-parent-imports.js @@ -1,6 +1,7 @@ import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor' import docsUrl from '../docsUrl' -import { basename } from 'path' +import { basename, dirname, relative } from 'path' +import resolve from 'eslint-module-utils/resolve' import importType from '../core/importType' @@ -14,11 +15,24 @@ module.exports = { create: function noRelativePackages(context) { const myPath = context.getFilename() - if (myPath === '') return {} // can't cycle-check a non-file + if (myPath === '') return {} // can't check a non-file function checkSourceValue(sourceNode) { const depPath = sourceNode.value - if (importType(depPath, context) === 'parent') { + + if (importType(depPath, context) === 'external') { // ignore packages + return + } + + const absDepPath = resolve(depPath, context) + + if (!absDepPath) { // unable to resolve path + return + } + + const relDepPath = relative(dirname(myPath), absDepPath) + + if (importType(relDepPath, context) === 'parent') { context.report({ node: sourceNode, message: 'Relative imports from parent directories are not allowed. ' + diff --git a/tests/src/rules/no-relative-parent-imports.js b/tests/src/rules/no-relative-parent-imports.js index 6d7a2c2fae..8978230904 100644 --- a/tests/src/rules/no-relative-parent-imports.js +++ b/tests/src/rules/no-relative-parent-imports.js @@ -38,9 +38,18 @@ ruleTester.run('no-relative-parent-imports', rule, { test({ code: 'import("./app/index.js")', }), + test({ + code: 'import(".")', + }), + test({ + code: 'import("path")', + }), test({ code: 'import("package")', }), + test({ + code: 'import("@scope/package")', + }), ], invalid: [ @@ -69,5 +78,21 @@ ruleTester.run('no-relative-parent-imports', rule, { column: 8, } ], }), + test({ + code: 'import foo from "./../plugin.js"', + errors: [ { + message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `./../plugin.js` or consider making `./../plugin.js` a package.', + line: 1, + column: 17 + }] + }), + test({ + code: 'import foo from "../../api/service"', + errors: [ { + message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `../../api/service` or consider making `../../api/service` a package.', + line: 1, + column: 17 + }] + }) ], }) From 3b1a806c066832e43c5e86d8b506451c358fb4c5 Mon Sep 17 00:00:00 2001 From: Justin Anastos Date: Thu, 12 Jul 2018 16:54:35 -0400 Subject: [PATCH 053/151] test(order): Add failing test for typescript-eslint-parser --- tests/src/rules/order.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index fb3b788448..23487dac91 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1260,5 +1260,21 @@ ruleTester.run('order', rule, { message: '`fs` import should occur before import of `async`', }], })), + // fix incorrect order with typescript-eslint-parser + test({ + code: ` + var async = require('async'); + var fs = require('fs'); + `, + output: ` + var fs = require('fs'); + var async = require('async'); + `, + parser: 'typescript-eslint-parser', + errors: [{ + ruleId: 'order', + message: '`fs` import should occur before import of `async`', + }], + }), ], }) From 8d02f323b6b828aed1e33fa303e9600c96b79d74 Mon Sep 17 00:00:00 2001 From: Justin Anastos Date: Thu, 12 Jul 2018 16:55:47 -0400 Subject: [PATCH 054/151] fix(rules/order): Use `.range` instead of `.start` and `.end` for autofixer `typescript-eslint-parser` does not add `.start` and `.end` to nodes like `babel-eslint`. They both include a `range` that can be used isntead. --- src/rules/order.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rules/order.js b/src/rules/order.js index 81babd7fde..f925a20eb4 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -97,8 +97,8 @@ function findRootNode(node) { function findEndOfLineWithComments(sourceCode, node) { const tokensToEndOfLine = takeTokensAfterWhile(sourceCode, node, commentOnSameLineAs(node)) let endOfTokens = tokensToEndOfLine.length > 0 - ? tokensToEndOfLine[tokensToEndOfLine.length - 1].end - : node.end + ? tokensToEndOfLine[tokensToEndOfLine.length - 1].range[1] + : node.range[1] let result = endOfTokens for (let i = endOfTokens; i < sourceCode.text.length; i++) { if (sourceCode.text[i] === '\n') { @@ -121,7 +121,7 @@ function commentOnSameLineAs(node) { function findStartOfLineWithComments(sourceCode, node) { const tokensToEndOfLine = takeTokensBeforeWhile(sourceCode, node, commentOnSameLineAs(node)) - let startOfTokens = tokensToEndOfLine.length > 0 ? tokensToEndOfLine[0].start : node.start + let startOfTokens = tokensToEndOfLine.length > 0 ? tokensToEndOfLine[0].range[0] : node.range[0] let result = startOfTokens for (let i = startOfTokens - 1; i > 0; i--) { if (sourceCode.text[i] !== ' ' && sourceCode.text[i] !== '\t') { @@ -296,11 +296,11 @@ function fixNewLineAfterImport(context, previousImport) { const tokensToEndOfLine = takeTokensAfterWhile( context.getSourceCode(), prevRoot, commentOnSameLineAs(prevRoot)) - let endOfLine = prevRoot.end + let endOfLine = prevRoot.range[1] if (tokensToEndOfLine.length > 0) { - endOfLine = tokensToEndOfLine[tokensToEndOfLine.length - 1].end + endOfLine = tokensToEndOfLine[tokensToEndOfLine.length - 1].range[1] } - return (fixer) => fixer.insertTextAfterRange([prevRoot.start, endOfLine], '\n') + return (fixer) => fixer.insertTextAfterRange([prevRoot.range[0], endOfLine], '\n') } function removeNewLineAfterImport(context, currentImport, previousImport) { From 75a9eab9f053f16ab31806cf086903bd2d7a0fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Mon, 23 Jul 2018 17:49:29 +0200 Subject: [PATCH 055/151] New: `no-unused-modules` rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Fermann --- docs/rules/no-unused-modules.md | 80 ++++++ src/ExportMap.js | 12 + src/index.js | 1 + src/rules/no-unused-modules.js | 419 ++++++++++++++++++++++++++++++++ 4 files changed, 512 insertions(+) create mode 100644 docs/rules/no-unused-modules.md create mode 100644 src/rules/no-unused-modules.js diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md new file mode 100644 index 0000000000..f185997599 --- /dev/null +++ b/docs/rules/no-unused-modules.md @@ -0,0 +1,80 @@ +# import/no-unused-modules + +Reports: + - modules without any or + - individual exports not being used within other modules + +## Rule Details + + +### Options + +This rule takes the following option: + +- `src`: an array with files/paths to be analyzed. It only for applies for unused exports +- `ignore`: an array with files/paths to be ignored. It only for applies for unused exports +- `missingExports`: if `true`, files without any exports are reported +- `unusedExports`: if `true`, exports without any usage are reported + + +### Example for missing exports +#### The following will be reported +```js +const class MyClass { /*...*/ } + +function makeClass() { return new MyClass(...arguments) } +``` + +#### The following will not be reported + +```js +export default function () { /*...*/ } +``` +```js +export const foo = function () { /*...*/ } +``` +```js +export { foo, bar } +``` +```js +export { foo as bar } +``` + +### Example for unused exports +given file-c: +```js +import { e } from 'file-a' +import { f } from 'file-b' + +export default 7 // will be reported +``` +and file-b: +```js +import two, { b, c, doAnything } from 'file-a' + +export const f = 6 // will not be reported +``` +and file-a: +```js +const b = 2 +const c = 3 +const d = 4 + +export const a = 1 // will be reported + +export { b, c } // will not be reported + +export { d as e } // will not be reported + +export function doAnything() { + // some code +} // will not be reported + +export default 5 // will not be reported +``` + + + +## When not to use + +If you don't mind having unused files or dead code within your codebase, you can disable this rule \ No newline at end of file diff --git a/src/ExportMap.js b/src/ExportMap.js index 1cb5dc3e9c..e7e1c4cfcb 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -379,6 +379,17 @@ ExportMap.parse = function (path, content, context) { function captureDependency(declaration) { if (declaration.source == null) return null + const importedSpecifiers = new Set() + if (declaration.specifiers) { + declaration.specifiers.forEach(specifier => { + if (specifier.type === 'ImportDefaultSpecifier') { + importedSpecifiers.add('ImportDefaultSpecifier') + } + if (specifier.type === 'ImportSpecifier') { + importedSpecifiers.add(specifier.local.name) + } + }) + } const p = remotePath(declaration.source.value) if (p == null) return null @@ -392,6 +403,7 @@ ExportMap.parse = function (path, content, context) { value: declaration.source.value, loc: declaration.source.loc, }, + importedSpecifiers, }) return getter } diff --git a/src/index.js b/src/index.js index 7df67867f5..7b5ef667a2 100644 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,7 @@ export const rules = { 'no-named-as-default': require('./rules/no-named-as-default'), 'no-named-as-default-member': require('./rules/no-named-as-default-member'), 'no-anonymous-default-export': require('./rules/no-anonymous-default-export'), + 'no-unused-modules': require('./rules/no-unused-modules'), 'no-commonjs': require('./rules/no-commonjs'), 'no-amd': require('./rules/no-amd'), diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js new file mode 100644 index 0000000000..c41d68a97a --- /dev/null +++ b/src/rules/no-unused-modules.js @@ -0,0 +1,419 @@ +/** + * @fileOverview Ensures that modules contain exports and/or all + * modules are consumed within other modules. + * @author René Fermann + */ + +import Exports from '../ExportMap' +import { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor' +import { listFilesToProcess } from 'eslint/lib/util/glob-util' +import resolve from 'eslint-module-utils/resolve' +import docsUrl from '../docsUrl' + +const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration' +const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration' +const IMPORT_DECLARATION = 'ImportDeclaration' +const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier' +const IMPORT_SPECIFIER = 'ImportSpecifier' +const VARIABLE_DECLARATION = 'VariableDeclaration' +const FUNCTION_DECLARATION = 'FunctionDeclaration' +const DEFAULT = 'default' +const UNDEFINED = 'undefined' + +let preparationDone = false +const importList = new Map() +const exportList = new Map() +const ignoredFiles = new Set() + +const isNodeModule = path => { + return path.indexOf('node_modules') > -1 +} + +/** + * read all files matching the patterns in src and ignore + * + * return all files matching src pattern, which are not matching the ignore pattern + */ +const resolveFiles = (src, ignore) => { + const srcFiles = new Set() + const srcFileList = listFilesToProcess(src) + + // prepare list of ignored files + const ignoredFilesList = listFilesToProcess(ignore) + ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename)) + + // prepare list of source files, don't consider files from node_modules + srcFileList.forEach(({ filename }) => { + if (isNodeModule(filename)) { + return + } + srcFiles.add(filename) + }) + return srcFiles +} + +/** + * parse all source files and build up 2 maps containing the existing imports and exports + */ +const prepareImportsAndExports = (srcFiles, context) => { + srcFiles.forEach(file => { + if (isNodeModule(file)) { + return + } + const exports = new Map() + const imports = new Map() + const currentExports = Exports.get(file, context) + if (!currentExports) { + return + } + const { imports: localImportList, namespace } = currentExports + localImportList.forEach((value, key) => { + imports.set(key, value.importedSpecifiers) + }) + importList.set(file, imports) + + // build up export list only, if file is not ignored + if (ignoredFiles.has(file)) { + return + } + namespace.forEach((value, key) => { + exports.set(key, { whereUsed: new Set() }) + }) + exportList.set(file, exports) + }) +} + +/** + * traverse through all imports and add the respective path to the whereUsed-list + * of the corresponding export + */ +const determineUsage = () => { + importList.forEach((listValue, listKey) => { + listValue.forEach((value, key) => { + const exports = exportList.get(key) + if (typeof exports !== UNDEFINED) { + value.forEach(currentImport => { + let specifier + if (currentImport === IMPORT_DEFAULT_SPECIFIER) { + specifier = DEFAULT + } else { + specifier = currentImport + } + if (typeof specifier !== UNDEFINED) { + const exportStatement = exports.get(specifier) + if (typeof exportStatement !== UNDEFINED) { + const {whereUsed} = exportStatement + whereUsed.add(listKey) + exports.set(specifier, { whereUsed }) + } + } + }) + } + }) + }) +} + +/** + * prepare the lists of existing imports and exports - should only be executed once at + * the start of a new eslint run + */ +const doPreparation = (src, ignore, context) => { + const { id } = context + + // do some sanity checks + if (!Array.isArray(src)) { + throw new Error(`Rule ${id}: src option must be an array`) + } + + if (!Array.isArray(ignore)) { + throw new Error(`Rule ${id}: ignore option must be an array`) + } + + if (src.length < 1) { + throw new Error(`Rule ${id}: src option must be defined`) + } + + // no empty patterns for paths, as this will cause issues during path resolution + src.forEach(file => { + if (file.length < 1) { + throw new Error(`Rule ${id}: src option must not contain empty strings`) + } + }) + + ignore.forEach(file => { + if (file.length < 1) { + throw new Error(`Rule ${id}: ignore option must not contain empty strings`) + } + }) + + const srcFiles = resolveFiles(src, ignore) + prepareImportsAndExports(srcFiles, context) + determineUsage() + preparationDone = true +} + +const oldDefaultImportExists = imports => { + return imports.has(IMPORT_DEFAULT_SPECIFIER) +} + +const newDefaultImportExists = specifiers => { + let hasNewDefaultImport = false + specifiers.forEach(specifier => { + if (specifier.type === IMPORT_DEFAULT_SPECIFIER) { + hasNewDefaultImport = true + } + }) + return hasNewDefaultImport +} + +module.exports = { + meta: { + docs: { url: docsUrl('no-unused-modules') }, + schema: [ + makeOptionsSchema({ + src: { + description: 'files/paths to be analyzed (only for unused exports)', + type: 'array', + }, + ignore: { + description: 'files/paths to be ignored (only for unused exports)', + type: 'array', + }, + missingExports: { + description: 'report modules without any exports', + type: 'boolean', + }, + unusedExports: { + description: 'report exports without any usage', + type: 'boolean', + }, + }), + ], + }, + + create: context => { + const { getFilename } = context + const { src, ignore, missingExports = false, unusedExports = false } = context.options[0] + + if (unusedExports && !preparationDone) { + doPreparation(src, ignore, context) + } + + const file = getFilename() + + const checkExportPresence = node => { + if (ignoredFiles.has(file)) { + return + } + + const exportCount = exportList.get(file) + if (missingExports && 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') + } + } + + const checkUsage = (node, exportedValue) => { + if (!unusedExports) { + return + } + + if (ignoredFiles.has(file)) { + return + } + const exports = exportList.get(file) + const exportStatement = exports.get(exportedValue) + if (typeof exportStatement !== UNDEFINED){ + const { whereUsed } = exportStatement + if ( whereUsed.size < 1) { + context.report( + node, + `exported declaration '${exportedValue}' not used within other modules` + ) + } + } + } + + /** + * only useful for tools like vscode-eslint + * + * update lists of existing exports during runtime + */ + const updateExportUsage = node => { + if (ignoredFiles.has(file)) { + return + } + + let exports = exportList.get(file) + + // new module has been created during runtime + // include it in further processing + if (typeof exports === UNDEFINED) { + exports = new Map() + } + + const newExports = new Map() + const newExportIdentifiers = new Set() + + node.body.forEach(({ type, declaration, specifiers }) => { + if (type === EXPORT_DEFAULT_DECLARATION) { + newExportIdentifiers.add(DEFAULT) + } + if (type === EXPORT_NAMED_DECLARATION) { + if (specifiers.length > 0) { + specifiers.forEach(specifier => { + newExportIdentifiers.add(specifier.local.name) + }) + } + if (declaration) { + if (declaration.type === FUNCTION_DECLARATION) { + newExportIdentifiers.add(declaration.id.name) + } + if (declaration.type === VARIABLE_DECLARATION) { + declaration.declarations.forEach(({ id }) => { + newExportIdentifiers.add(id.name) + }) + } + } + } + }) + + // old exports exist within list of new exports identifiers: add to map of new exports + exports.forEach((value, key) => { + if (newExportIdentifiers.has(key)) { + newExports.set(key, value) + } + }) + + // new export identifiers added: add to map of new exports + newExportIdentifiers.forEach(key => { + if (!exports.has(key)) { + newExports.set(key, {whereUsed: new Set()}) + } + }) + exportList.set(file, newExports) + } + + /** + * only useful for tools like vscode-eslint + * + * update lists of existing imports during runtime + */ + const updateImportUsage = node => { + if (!unusedExports) { + return + } + const oldImportPaths = importList.get(file) + + node.body.forEach(astNode => { + if (astNode.type === IMPORT_DECLARATION) { + const resolvedPath = resolve(astNode.source.value, context) + + if (!resolvedPath) { + return + } + + if (isNodeModule(resolvedPath)) { + return + } + const imports = oldImportPaths.get(resolvedPath) + const exports = exportList.get(resolvedPath) + + // unknown module + if (typeof exports === UNDEFINED) { + return + } + + const tmpImports = new Set() + imports.forEach((value, key) => { + if (key !== IMPORT_DEFAULT_SPECIFIER) { + tmpImports.add(key) + } + }) + + // update usage of default import/export + const defaultExport = exports.get(DEFAULT) + const hasOldDefaultImport = oldDefaultImportExists(imports) + const hasNewDefaultImport = newDefaultImportExists(astNode.specifiers) + + if (hasNewDefaultImport && !hasOldDefaultImport) { + imports.add(IMPORT_DEFAULT_SPECIFIER) + if (typeof defaultExport !== UNDEFINED) { + const { whereUsed } = defaultExport + whereUsed.add(file) + } + } + + if (!hasNewDefaultImport && hasOldDefaultImport) { + imports.delete(IMPORT_DEFAULT_SPECIFIER) + if (typeof defaultExport !== UNDEFINED) { + const { whereUsed } = defaultExport + whereUsed.delete(file) + } + } + + // update usage of named imports/export + astNode.specifiers.forEach(specifier => { + if (specifier.exported) { + imports.add(specifier.exported.name) + } else { + imports.add(specifier.local.name) + } + tmpImports.delete(specifier.local.name) + + const currentExport = exports.get(specifier.local.name) + if (specifier.type === IMPORT_SPECIFIER) { + if (typeof currentExport !== UNDEFINED) { + currentExport.whereUsed.add(file) + } + } + }) + + // one or more imports have been removed: update usage list of exports + if (tmpImports.size > 0) { + tmpImports.forEach(key => { + const currentExport = exports.get(key) + if (typeof currentExport !== UNDEFINED) { + const { whereUsed } = currentExport + whereUsed.delete(file) + } + }) + } + } + }) + } + + return { + 'Program:exit': node => { + updateExportUsage(node) + updateImportUsage(node) + checkExportPresence(node) + }, + 'ExportDefaultDeclaration': node => { + checkUsage(node, DEFAULT) + }, + 'ExportNamedDeclaration': node => { + if (node.specifiers) { + node.specifiers.forEach(specifier => { + if (specifier.exported) { + checkUsage(node, specifier.exported.name) + } else { + checkUsage(node, specifier.local.name) + } + }) + } + if (node.declaration) { + if (node.declaration.type === FUNCTION_DECLARATION) { + checkUsage(node, node.declaration.id.name) + } + if (node.declaration.type === VARIABLE_DECLARATION) { + node.declaration.declarations.forEach(declaration => { + checkUsage(node, declaration.id.name) + }) + } + } + }, + } + }, +} From 1d8ddf726f6e869d45cf78d32875216071c00076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Mon, 30 Jul 2018 18:26:17 +0200 Subject: [PATCH 056/151] New: `no-unused-modules` rule - minor refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Fermann --- src/rules/no-unused-modules.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index c41d68a97a..e46588b7e2 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -68,6 +68,9 @@ const prepareImportsAndExports = (srcFiles, context) => { } const { imports: localImportList, namespace } = currentExports localImportList.forEach((value, key) => { + if (isNodeModule(key)) { + return + } imports.set(key, value.importedSpecifiers) }) importList.set(file, imports) @@ -317,13 +320,16 @@ module.exports = { if (isNodeModule(resolvedPath)) { return } - const imports = oldImportPaths.get(resolvedPath) + let imports = oldImportPaths.get(resolvedPath) const exports = exportList.get(resolvedPath) // unknown module if (typeof exports === UNDEFINED) { return } + if (typeof imports === UNDEFINED) { + imports = new Set() + } const tmpImports = new Set() imports.forEach((value, key) => { @@ -355,11 +361,7 @@ module.exports = { // update usage of named imports/export astNode.specifiers.forEach(specifier => { - if (specifier.exported) { - imports.add(specifier.exported.name) - } else { - imports.add(specifier.local.name) - } + imports.add(specifier.local.name) tmpImports.delete(specifier.local.name) const currentExport = exports.get(specifier.local.name) @@ -398,8 +400,6 @@ module.exports = { node.specifiers.forEach(specifier => { if (specifier.exported) { checkUsage(node, specifier.exported.name) - } else { - checkUsage(node, specifier.local.name) } }) } From 153599d976476a3ba66f5716f6eb6b5975765306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Mon, 30 Jul 2018 18:27:14 +0200 Subject: [PATCH 057/151] New: `no-unused-modules` rule - added tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Fermann --- tests/files/no-unused-modules/file-0.js | 7 + tests/files/no-unused-modules/file-a.js | 1 + tests/files/no-unused-modules/file-b.js | 1 + tests/files/no-unused-modules/file-c.js | 7 + tests/files/no-unused-modules/file-d.js | 3 + tests/files/no-unused-modules/file-e.js | 3 + tests/files/no-unused-modules/file-f.js | 1 + tests/files/no-unused-modules/file-g.js | 1 + tests/files/no-unused-modules/file-h.js | 7 + tests/files/no-unused-modules/file-i.js | 7 + .../files/no-unused-modules/file-ignored-a.js | 1 + .../files/no-unused-modules/file-ignored-b.js | 1 + .../files/no-unused-modules/file-ignored-c.js | 7 + .../files/no-unused-modules/file-ignored-d.js | 3 + .../files/no-unused-modules/file-ignored-e.js | 3 + tests/files/no-unused-modules/file-j.js | 3 + tests/files/no-unused-modules/file-k.js | 3 + tests/src/rules/no-unused-modules.js | 206 ++++++++++++++++++ 18 files changed, 265 insertions(+) create mode 100644 tests/files/no-unused-modules/file-0.js create mode 100644 tests/files/no-unused-modules/file-a.js create mode 100644 tests/files/no-unused-modules/file-b.js create mode 100644 tests/files/no-unused-modules/file-c.js create mode 100644 tests/files/no-unused-modules/file-d.js create mode 100644 tests/files/no-unused-modules/file-e.js create mode 100644 tests/files/no-unused-modules/file-f.js create mode 100644 tests/files/no-unused-modules/file-g.js create mode 100644 tests/files/no-unused-modules/file-h.js create mode 100644 tests/files/no-unused-modules/file-i.js create mode 100644 tests/files/no-unused-modules/file-ignored-a.js create mode 100644 tests/files/no-unused-modules/file-ignored-b.js create mode 100644 tests/files/no-unused-modules/file-ignored-c.js create mode 100644 tests/files/no-unused-modules/file-ignored-d.js create mode 100644 tests/files/no-unused-modules/file-ignored-e.js create mode 100644 tests/files/no-unused-modules/file-j.js create mode 100644 tests/files/no-unused-modules/file-k.js create mode 100644 tests/src/rules/no-unused-modules.js diff --git a/tests/files/no-unused-modules/file-0.js b/tests/files/no-unused-modules/file-0.js new file mode 100644 index 0000000000..adabd7f75b --- /dev/null +++ b/tests/files/no-unused-modules/file-0.js @@ -0,0 +1,7 @@ +import eslint from 'eslint' +import fileA from './file-a' +import { b } from './file-b' +import { c1, c2 } from './file-c' +import { d } from './file-d' +import { e } from './file-e' +import { h2 } from './file-h' \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-a.js b/tests/files/no-unused-modules/file-a.js new file mode 100644 index 0000000000..a316dbbf82 --- /dev/null +++ b/tests/files/no-unused-modules/file-a.js @@ -0,0 +1 @@ +export default () => 1 \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-b.js b/tests/files/no-unused-modules/file-b.js new file mode 100644 index 0000000000..63a6208637 --- /dev/null +++ b/tests/files/no-unused-modules/file-b.js @@ -0,0 +1 @@ +export const b = 2 \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-c.js b/tests/files/no-unused-modules/file-c.js new file mode 100644 index 0000000000..0f47a8510f --- /dev/null +++ b/tests/files/no-unused-modules/file-c.js @@ -0,0 +1,7 @@ +const c1 = 3 + +function c2() { + return 3 +} + +export { c1, c2 } \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-d.js b/tests/files/no-unused-modules/file-d.js new file mode 100644 index 0000000000..90ae456580 --- /dev/null +++ b/tests/files/no-unused-modules/file-d.js @@ -0,0 +1,3 @@ +export function d() { + return 4 +} \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-e.js b/tests/files/no-unused-modules/file-e.js new file mode 100644 index 0000000000..d93c4eface --- /dev/null +++ b/tests/files/no-unused-modules/file-e.js @@ -0,0 +1,3 @@ +const e0 = 5 + +export { e0 as e } \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-f.js b/tests/files/no-unused-modules/file-f.js new file mode 100644 index 0000000000..a316dbbf82 --- /dev/null +++ b/tests/files/no-unused-modules/file-f.js @@ -0,0 +1 @@ +export default () => 1 \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-g.js b/tests/files/no-unused-modules/file-g.js new file mode 100644 index 0000000000..66e8462b10 --- /dev/null +++ b/tests/files/no-unused-modules/file-g.js @@ -0,0 +1 @@ +export const g = 2 \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-h.js b/tests/files/no-unused-modules/file-h.js new file mode 100644 index 0000000000..38ec42a3ba --- /dev/null +++ b/tests/files/no-unused-modules/file-h.js @@ -0,0 +1,7 @@ +const h1 = 3 + +function h2() { + return 3 +} + +export { h1, h2 } \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-i.js b/tests/files/no-unused-modules/file-i.js new file mode 100644 index 0000000000..7a63049efb --- /dev/null +++ b/tests/files/no-unused-modules/file-i.js @@ -0,0 +1,7 @@ +const i1 = 3 + +function i2() { + return 3 +} + +export { i1, i2 } \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-ignored-a.js b/tests/files/no-unused-modules/file-ignored-a.js new file mode 100644 index 0000000000..a316dbbf82 --- /dev/null +++ b/tests/files/no-unused-modules/file-ignored-a.js @@ -0,0 +1 @@ +export default () => 1 \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-ignored-b.js b/tests/files/no-unused-modules/file-ignored-b.js new file mode 100644 index 0000000000..63a6208637 --- /dev/null +++ b/tests/files/no-unused-modules/file-ignored-b.js @@ -0,0 +1 @@ +export const b = 2 \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-ignored-c.js b/tests/files/no-unused-modules/file-ignored-c.js new file mode 100644 index 0000000000..0f47a8510f --- /dev/null +++ b/tests/files/no-unused-modules/file-ignored-c.js @@ -0,0 +1,7 @@ +const c1 = 3 + +function c2() { + return 3 +} + +export { c1, c2 } \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-ignored-d.js b/tests/files/no-unused-modules/file-ignored-d.js new file mode 100644 index 0000000000..90ae456580 --- /dev/null +++ b/tests/files/no-unused-modules/file-ignored-d.js @@ -0,0 +1,3 @@ +export function d() { + return 4 +} \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-ignored-e.js b/tests/files/no-unused-modules/file-ignored-e.js new file mode 100644 index 0000000000..d93c4eface --- /dev/null +++ b/tests/files/no-unused-modules/file-ignored-e.js @@ -0,0 +1,3 @@ +const e0 = 5 + +export { e0 as e } \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-j.js b/tests/files/no-unused-modules/file-j.js new file mode 100644 index 0000000000..e5928f8598 --- /dev/null +++ b/tests/files/no-unused-modules/file-j.js @@ -0,0 +1,3 @@ +export function j() { + return 4 +} \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-k.js b/tests/files/no-unused-modules/file-k.js new file mode 100644 index 0000000000..cdf5333b56 --- /dev/null +++ b/tests/files/no-unused-modules/file-k.js @@ -0,0 +1,3 @@ +const k0 = 5 + +export { k0 as k } \ No newline at end of file diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js new file mode 100644 index 0000000000..af6fb5e612 --- /dev/null +++ b/tests/src/rules/no-unused-modules.js @@ -0,0 +1,206 @@ +import { test, testFilePath } from '../utils' + +import { RuleTester } from 'eslint' + +const ruleTester = new RuleTester() + , rule = require('rules/no-unused-modules') + +const error = message => ({ ruleId: 'no-unused-modules', message }) + +const missingExportsOptions = [{ + missingExports: true, +}] + +const unusedExportsOptions = [{ + unusedExports: true, + src: [testFilePath('./no-unused-modules/**/*.js')], + ignore: [testFilePath('./no-unused-modules/*ignored*.js')], +}] + +// tests for missing exports +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: missingExportsOptions, + code: 'export default () => 1'}), + test({ options: missingExportsOptions, + code: 'export const a = 1'}), + test({ options: missingExportsOptions, + code: 'const a = 1; export { a }'}), + test({ options: missingExportsOptions, + code: 'function a() { return true }; export { a }'}), + test({ options: missingExportsOptions, + code: 'const a = 1; const b = 2; export { a, b }'}), + test({ options: missingExportsOptions, + code: 'const a = 1; export default a'}), + ], + invalid: [ + test({ + options: missingExportsOptions, + code: 'const a = 1', + errors: [error(`No exports found`)], + }), + ], +}) + + +// tests for unused exports +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: 'export default () => 1', + filename: testFilePath('./no-unused-modules/file-a.js')}), + test({ options: unusedExportsOptions, + code: 'export const b = 2', + filename: testFilePath('./no-unused-modules/file-b.js')}), + test({ options: unusedExportsOptions, + code: 'const c1 = 3; function c2() { return 3 }; export { c1, c2 }', + filename: testFilePath('./no-unused-modules/file-c.js')}), + test({ options: unusedExportsOptions, + code: 'export function d() { return 4 }', + filename: testFilePath('./no-unused-modules/file-d.js')}), + test({ options: unusedExportsOptions, + code: 'const e0 = 5; export { e0 as e }', + filename: testFilePath('./no-unused-modules/file-e.js')}), + ], + invalid: [], +}) + +// test for unused exports +ruleTester.run('no-unused-modules', rule, { + valid: [], + invalid: [ + test({ options: unusedExportsOptions, + code: 'export default () => 1', + filename: testFilePath('./no-unused-modules/file-f.js'), + errors: [error(`exported declaration 'default' not used within other modules`)]}), + test({ options: unusedExportsOptions, + code: 'export const g = 2', + filename: testFilePath('./no-unused-modules/file-g.js'), + errors: [error(`exported declaration 'g' not used within other modules`)]}), + test({ options: unusedExportsOptions, + code: 'const h1 = 3; function h2() { return 3 }; export { h1, h2 }', + filename: testFilePath('./no-unused-modules/file-h.js'), + errors: [error(`exported declaration 'h1' not used within other modules`)]}), + test({ options: unusedExportsOptions, + code: 'const i1 = 3; function i2() { return 3 }; export { i1, i2 }', + filename: testFilePath('./no-unused-modules/file-i.js'), + errors: [ + error(`exported declaration 'i1' not used within other modules`), + error(`exported declaration 'i2' not used within other modules`), + ]}), + test({ options: unusedExportsOptions, + code: 'export function j() { return 4 }', + filename: testFilePath('./no-unused-modules/file-j.js'), + errors: [error(`exported declaration 'j' not used within other modules`)]}), + test({ options: unusedExportsOptions, + code: 'const k0 = 5; export { k0 as k }', + filename: testFilePath('./no-unused-modules/file-k.js'), + errors: [error(`exported declaration 'k' not used within other modules`)]}), + ], +}) + +// test for ignored files +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: 'export default () => 1', + filename: testFilePath('./no-unused-modules/file-ignored-a.js')}), + test({ options: unusedExportsOptions, + code: 'export const b = 2', + filename: testFilePath('./no-unused-modules/file-ignored-b.js')}), + test({ options: unusedExportsOptions, + code: 'const c1 = 3; function c2() { return 3 }; export { c1, c2 }', + filename: testFilePath('./no-unused-modules/file-ignored-c.js')}), + test({ options: unusedExportsOptions, + code: 'export function d() { return 4 }', + filename: testFilePath('./no-unused-modules/file-ignored-d.js')}), + test({ options: unusedExportsOptions, + code: 'const f = 5; export { f as e }', + filename: testFilePath('./no-unused-modules/file-ignored-e.js')})], + invalid: [], +}) + +// add named import for file with default export +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import { j } from '${testFilePath('./no-unused-modules/file-f.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: 'export default () => 1', + filename: testFilePath('./no-unused-modules/file-f.js'), + errors: [error(`exported declaration 'default' not used within other modules`)]}), + ], +}) + +// add default import for file with default export +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import f from '${testFilePath('./no-unused-modules/file-f.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + test({ options: unusedExportsOptions, + code: 'export default () => 1', + filename: testFilePath('./no-unused-modules/file-f.js')}), + ], + invalid: [], +}) + +// add default import for file with named export +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import g from '${testFilePath('./no-unused-modules/file-g.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: 'export const g = 2', + filename: testFilePath('./no-unused-modules/file-g.js'), + errors: [error(`exported declaration 'g' not used within other modules`)]})], +}) + +// add named import for file with named export +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import { g } from '${testFilePath('./no-unused-modules/file-g.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + test({ options: unusedExportsOptions, + code: 'export const g = 2', + filename: testFilePath('./no-unused-modules/file-g.js')}), + ], + invalid: [], +}) + +// add different named import for file with named export +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import { c } from '${testFilePath('./no-unused-modules/file-b.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: 'export const b = 2', + filename: testFilePath('./no-unused-modules/file-b.js'), + errors: [error(`exported declaration 'b' not used within other modules`)]}), + ], +}) + +// remove default import for file with default export +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import { a1, a2 } from '${testFilePath('./no-unused-modules/file-a.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: 'export default () => 1', + filename: testFilePath('./no-unused-modules/file-a.js'), + errors: [error(`exported declaration 'default' not used within other modules`)]}), + ], +}) \ No newline at end of file From fd1ccc040275cfd9791f148868a651cdbcdc7adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Tue, 31 Jul 2018 17:12:44 +0200 Subject: [PATCH 058/151] New: `no-unused-modules` rule - removed destructoring of context.getFilename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Fermann --- src/rules/no-unused-modules.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index e46588b7e2..50ddf5404f 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -195,14 +195,13 @@ module.exports = { }, create: context => { - const { getFilename } = context const { src, ignore, missingExports = false, unusedExports = false } = context.options[0] if (unusedExports && !preparationDone) { doPreparation(src, ignore, context) } - const file = getFilename() + const file = context.getFilename() const checkExportPresence = node => { if (ignoredFiles.has(file)) { From a40202316e9de676da552490c0762c96b947c7f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Tue, 31 Jul 2018 21:30:04 +0200 Subject: [PATCH 059/151] New: `no-unused-modules` rule - minor refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Fermann --- src/rules/no-unused-modules.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 50ddf5404f..dcc633f5fe 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -57,9 +57,6 @@ const resolveFiles = (src, ignore) => { */ const prepareImportsAndExports = (srcFiles, context) => { srcFiles.forEach(file => { - if (isNodeModule(file)) { - return - } const exports = new Map() const imports = new Map() const currentExports = Exports.get(file, context) @@ -170,6 +167,7 @@ const newDefaultImportExists = specifiers => { } module.exports = { + doPreparation, meta: { docs: { url: docsUrl('no-unused-modules') }, schema: [ From 230122be9b38971bef7a6f1f78ee95336e9f681c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Tue, 31 Jul 2018 21:32:04 +0200 Subject: [PATCH 060/151] New: `no-unused-modules` rule - added more tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Fermann --- tests/files/no-unused-modules/empty_file.js | 0 tests/files/no-unused-modules/node_modules.js | 0 tests/src/rules/no-unused-modules.js | 32 +++++++++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/files/no-unused-modules/empty_file.js create mode 100644 tests/files/no-unused-modules/node_modules.js diff --git a/tests/files/no-unused-modules/empty_file.js b/tests/files/no-unused-modules/empty_file.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/files/no-unused-modules/node_modules.js b/tests/files/no-unused-modules/node_modules.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index af6fb5e612..db4f86c732 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -1,6 +1,9 @@ import { test, testFilePath } from '../utils' import { RuleTester } from 'eslint' +import { expect } from 'chai'; + +const doPreparation = require( '../../../src/rules/no-unused-modules').doPreparation const ruleTester = new RuleTester() , rule = require('rules/no-unused-modules') @@ -17,6 +20,26 @@ const unusedExportsOptions = [{ ignore: [testFilePath('./no-unused-modules/*ignored*.js')], }] +describe('doPreparation throws correct errors', () => { + // const fn = doPreparation() + const context = {id: 'no-unused-modules' } + it('should throw an error, if src is not an array', () => { + expect(doPreparation.bind(doPreparation, null, null, context)).to.throw(`Rule ${context.id}: src option must be an array`) + }) + it('should throw an error, if ignore is not an array', () => { + expect(doPreparation.bind(doPreparation, [], null, context)).to.throw(`Rule ${context.id}: ignore option must be an array`) + }) + it('should throw an error, if src is empty', () => { + expect(doPreparation.bind(doPreparation, [], [], context)).to.throw(`Rule ${context.id}: src option must be defined`) + }) + it('should throw an error, if src is empty', () => { + expect(doPreparation.bind(doPreparation, [""], [], context)).to.throw(`Rule ${context.id}: src option must not contain empty strings`) + }) + it('should throw an error, if src is empty', () => { + expect(doPreparation.bind(doPreparation, ["src"], [""], context)).to.throw(`Rule ${context.id}: ignore option must not contain empty strings`) + }) +}) + // tests for missing exports ruleTester.run('no-unused-modules', rule, { valid: [ @@ -39,6 +62,11 @@ ruleTester.run('no-unused-modules', rule, { code: 'const a = 1', errors: [error(`No exports found`)], }), + test({ + options: missingExportsOptions, + code: '/* const a = 1 */', + errors: [error(`No exports found`)], + }), ], }) @@ -152,7 +180,7 @@ ruleTester.run('no-unused-modules', rule, { ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import g from '${testFilePath('./no-unused-modules/file-g.js')}'`, + code: `import g from '${testFilePath('./no-unused-modules/file-g.js')}';import {h} from '${testFilePath('./no-unused-modules/file-gg.js')}'`, filename: testFilePath('./no-unused-modules/file-0.js')}), ], invalid: [ @@ -166,7 +194,7 @@ ruleTester.run('no-unused-modules', rule, { ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import { g } from '${testFilePath('./no-unused-modules/file-g.js')}'`, + code: `import { g } from '${testFilePath('./no-unused-modules/file-g.js')}'; import eslint from 'eslint'`, filename: testFilePath('./no-unused-modules/file-0.js')}), test({ options: unusedExportsOptions, code: 'export const g = 2', From aa7253baf383f370b3dccefbb11b17cd5db2d8dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sun, 5 Aug 2018 16:28:54 +0200 Subject: [PATCH 061/151] New: `no-unused-modules` rule - added default src, more comprehensive sanity checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Fermann --- src/rules/no-unused-modules.js | 59 +++++++++++++++++++--------- tests/src/rules/no-unused-modules.js | 43 +++++++++++++++----- 2 files changed, 74 insertions(+), 28 deletions(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index dcc633f5fe..e601887ae4 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -6,10 +6,17 @@ import Exports from '../ExportMap' import { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor' -import { listFilesToProcess } from 'eslint/lib/util/glob-util' import resolve from 'eslint-module-utils/resolve' import docsUrl from '../docsUrl' +// eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3 +let listFilesToProcess +try { + listFilesToProcess = require('eslint/lib/util/glob-util').listFilesToProcess +} catch (err) { + listFilesToProcess = require('eslint/lib/util/glob-utils').listFilesToProcess +} + const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration' const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration' const IMPORT_DECLARATION = 'ImportDeclaration' @@ -113,6 +120,13 @@ const determineUsage = () => { }) } +const getSrc = src => { + if (src) { + return src + } + return [process.cwd()] +} + /** * prepare the lists of existing imports and exports - should only be executed once at * the start of a new eslint run @@ -121,32 +135,38 @@ const doPreparation = (src, ignore, context) => { const { id } = context // do some sanity checks - if (!Array.isArray(src)) { + if (typeof src !== UNDEFINED && !Array.isArray(src)) { throw new Error(`Rule ${id}: src option must be an array`) } - if (!Array.isArray(ignore)) { + if (typeof ignore !== UNDEFINED && !Array.isArray(ignore)) { throw new Error(`Rule ${id}: ignore option must be an array`) } - if (src.length < 1) { - throw new Error(`Rule ${id}: src option must be defined`) - } - // no empty patterns for paths, as this will cause issues during path resolution - src.forEach(file => { - if (file.length < 1) { - throw new Error(`Rule ${id}: src option must not contain empty strings`) - } - }) - - ignore.forEach(file => { - if (file.length < 1) { - throw new Error(`Rule ${id}: ignore option must not contain empty strings`) - } - }) + if (src) { + src.forEach(file => { + if (typeof file !== 'string') { + throw new Error(`Rule ${id}: src option must not contain values other than strings`) + } + if (file.length < 1) { + throw new Error(`Rule ${id}: src option must not contain empty strings`) + } + }) + } + + if (ignore) { + ignore.forEach(file => { + if (typeof file !== 'string') { + throw new Error(`Rule ${id}: ignore option must not contain values other than strings`) + } + if (file.length < 1) { + throw new Error(`Rule ${id}: ignore option must not contain empty strings`) + } + }) + } - const srcFiles = resolveFiles(src, ignore) + const srcFiles = resolveFiles(getSrc(src), ignore) prepareImportsAndExports(srcFiles, context) determineUsage() preparationDone = true @@ -168,6 +188,7 @@ const newDefaultImportExists = specifiers => { module.exports = { doPreparation, + getSrc, meta: { docs: { url: docsUrl('no-unused-modules') }, schema: [ diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index db4f86c732..7696d46d0e 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -1,9 +1,10 @@ import { test, testFilePath } from '../utils' import { RuleTester } from 'eslint' -import { expect } from 'chai'; +import { expect } from 'chai' const doPreparation = require( '../../../src/rules/no-unused-modules').doPreparation +const getSrc = require( '../../../src/rules/no-unused-modules').getSrc const ruleTester = new RuleTester() , rule = require('rules/no-unused-modules') @@ -21,22 +22,46 @@ const unusedExportsOptions = [{ }] describe('doPreparation throws correct errors', () => { - // const fn = doPreparation() - const context = {id: 'no-unused-modules' } + const context = { id: 'no-unused-modules' } it('should throw an error, if src is not an array', () => { expect(doPreparation.bind(doPreparation, null, null, context)).to.throw(`Rule ${context.id}: src option must be an array`) }) it('should throw an error, if ignore is not an array', () => { expect(doPreparation.bind(doPreparation, [], null, context)).to.throw(`Rule ${context.id}: ignore option must be an array`) }) - it('should throw an error, if src is empty', () => { - expect(doPreparation.bind(doPreparation, [], [], context)).to.throw(`Rule ${context.id}: src option must be defined`) + it('should throw an error, if src contains empty strings', () => { + expect(doPreparation.bind(doPreparation, [''], [], context)).to.throw(`Rule ${context.id}: src option must not contain empty strings`) }) - it('should throw an error, if src is empty', () => { - expect(doPreparation.bind(doPreparation, [""], [], context)).to.throw(`Rule ${context.id}: src option must not contain empty strings`) + it('should throw an error, if src contains values other than strings', () => { + expect(doPreparation.bind(doPreparation, [false], [], context)).to.throw(`Rule ${context.id}: src option must not contain values other than strings`) }) - it('should throw an error, if src is empty', () => { - expect(doPreparation.bind(doPreparation, ["src"], [""], context)).to.throw(`Rule ${context.id}: ignore option must not contain empty strings`) + it('should throw an error, if src contains values other than strings', () => { + expect(doPreparation.bind(doPreparation, [null], [], context)).to.throw(`Rule ${context.id}: src option must not contain values other than strings`) + }) + it('should throw an error, if src contains values other than strings', () => { + expect(doPreparation.bind(doPreparation, [undefined], [], context)).to.throw(`Rule ${context.id}: src option must not contain values other than strings`) + }) + it('should throw an error, if ignore contains empty strings', () => { + expect(doPreparation.bind(doPreparation, ['src'], [''], context)).to.throw(`Rule ${context.id}: ignore option must not contain empty strings`) + }) + it('should throw an error, if ignore contains values other than strings', () => { + expect(doPreparation.bind(doPreparation, ['src'], [false], context)).to.throw(`Rule ${context.id}: ignore option must not contain values other than strings`) + }) + it('should throw an error, if ignore contains values other than strings', () => { + expect(doPreparation.bind(doPreparation, ['src'], [null], context)).to.throw(`Rule ${context.id}: ignore option must not contain values other than strings`) + }) + it('should throw an error, if ignore contains values other than strings', () => { + expect(doPreparation.bind(doPreparation, ['src'], [undefined], context)).to.throw(`Rule ${context.id}: ignore option must not contain values other than strings`) + }) +}) + +describe('getSrc returns correct source', () => { + it('if src is provided', () => { + const src = ['file-a.js'] + expect(getSrc(src)).to.eq(src) + }) + it('if src is not provided', () => { + expect(getSrc()).to.eql([process.cwd()]) }) }) From d47ccdfcb1eae5506701302dc652548a2d2c535f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sat, 11 Aug 2018 16:12:23 +0200 Subject: [PATCH 062/151] New: `no-unused-modules` rule - add support for 'import *' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Fermann --- src/ExportMap.js | 3 + src/rules/no-unused-modules.js | 197 ++++++++++++++---- tests/files/no-unused-modules/file-0.js | 3 +- .../files/no-unused-modules/file-ignored-l.js | 6 + tests/files/no-unused-modules/file-l.js | 6 + tests/files/no-unused-modules/file-m.js | 6 + tests/src/rules/no-unused-modules.js | 107 +++++++++- 7 files changed, 284 insertions(+), 44 deletions(-) create mode 100644 tests/files/no-unused-modules/file-ignored-l.js create mode 100644 tests/files/no-unused-modules/file-l.js create mode 100644 tests/files/no-unused-modules/file-m.js diff --git a/src/ExportMap.js b/src/ExportMap.js index e7e1c4cfcb..f18c0bc1b2 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -385,6 +385,9 @@ ExportMap.parse = function (path, content, context) { if (specifier.type === 'ImportDefaultSpecifier') { importedSpecifiers.add('ImportDefaultSpecifier') } + if (specifier.type === 'ImportNamespaceSpecifier') { + importedSpecifiers.add('ImportNamespaceSpecifier') + } if (specifier.type === 'ImportSpecifier') { importedSpecifiers.add(specifier.local.name) } diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index e601887ae4..fa9379fe89 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -20,6 +20,7 @@ try { const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration' const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration' const IMPORT_DECLARATION = 'ImportDeclaration' +const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier' const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier' const IMPORT_SPECIFIER = 'ImportSpecifier' const VARIABLE_DECLARATION = 'VariableDeclaration' @@ -84,8 +85,13 @@ const prepareImportsAndExports = (srcFiles, context) => { return } namespace.forEach((value, key) => { - exports.set(key, { whereUsed: new Set() }) + if (key === DEFAULT) { + exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() }) + } else { + exports.set(key, { whereUsed: new Set() }) + } }) + exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed: new Set() }) exportList.set(file, exports) }) } @@ -101,8 +107,10 @@ const determineUsage = () => { if (typeof exports !== UNDEFINED) { value.forEach(currentImport => { let specifier - if (currentImport === IMPORT_DEFAULT_SPECIFIER) { - specifier = DEFAULT + if (currentImport === IMPORT_NAMESPACE_SPECIFIER) { + specifier = IMPORT_NAMESPACE_SPECIFIER + } else if (currentImport === IMPORT_DEFAULT_SPECIFIER) { + specifier = IMPORT_DEFAULT_SPECIFIER } else { specifier = currentImport } @@ -168,14 +176,22 @@ const doPreparation = (src, ignore, context) => { const srcFiles = resolveFiles(getSrc(src), ignore) prepareImportsAndExports(srcFiles, context) - determineUsage() + determineUsage() preparationDone = true } -const oldDefaultImportExists = imports => { - return imports.has(IMPORT_DEFAULT_SPECIFIER) + +const newNamespaceImportExists = specifiers => { + let hasNewNamespaceImport = false + specifiers.forEach(specifier => { + if (specifier.type === IMPORT_NAMESPACE_SPECIFIER) { + hasNewNamespaceImport = true + } + }) + return hasNewNamespaceImport } + const newDefaultImportExists = specifiers => { let hasNewDefaultImport = false specifiers.forEach(specifier => { @@ -223,11 +239,12 @@ module.exports = { const file = context.getFilename() const checkExportPresence = node => { - if (ignoredFiles.has(file)) { + if (!missingExports) { return } const exportCount = exportList.get(file) + exportCount.delete(IMPORT_NAMESPACE_SPECIFIER) if (missingExports && exportCount.size < 1) { // node.body[0] === 'undefined' only happens, if everything is commented out in the file // being linted @@ -243,14 +260,29 @@ module.exports = { if (ignoredFiles.has(file)) { return } + const exports = exportList.get(file) + + // special case: namespace import + const nameSpaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER) + if (typeof nameSpaceImports !== UNDEFINED) { + if (nameSpaceImports.whereUsed.size > 0) { + return + } + } + const exportStatement = exports.get(exportedValue) if (typeof exportStatement !== UNDEFINED){ - const { whereUsed } = exportStatement - if ( whereUsed.size < 1) { + if ( exportStatement.whereUsed.size < 1) { + let value = '' + if (exportedValue === IMPORT_DEFAULT_SPECIFIER) { + value = DEFAULT + } else { + value = exportedValue + } context.report( node, - `exported declaration '${exportedValue}' not used within other modules` + `exported declaration '${value}' not used within other modules` ) } } @@ -279,12 +311,14 @@ module.exports = { node.body.forEach(({ type, declaration, specifiers }) => { if (type === EXPORT_DEFAULT_DECLARATION) { - newExportIdentifiers.add(DEFAULT) + newExportIdentifiers.add(IMPORT_DEFAULT_SPECIFIER) } if (type === EXPORT_NAMED_DECLARATION) { if (specifiers.length > 0) { specifiers.forEach(specifier => { - newExportIdentifiers.add(specifier.local.name) + if (specifier.exported) { + newExportIdentifiers.add(specifier.exported.name) + } }) } if (declaration) { @@ -313,6 +347,9 @@ module.exports = { newExports.set(key, {whereUsed: new Set()}) } }) + + // preserve information about namespace imports + newExports.set(IMPORT_NAMESPACE_SPECIFIER, exports.get(IMPORT_NAMESPACE_SPECIFIER)) exportList.set(file, newExports) } @@ -325,8 +362,28 @@ module.exports = { if (!unusedExports) { return } - const oldImportPaths = importList.get(file) + + let oldImportPaths = importList.get(file) + if (typeof oldImportPaths === UNDEFINED) { + oldImportPaths = new Map() + } + const oldNamespaceImports = new Set() + const newNamespaceImports = new Set() + oldImportPaths.forEach((value, key) => { + if (value.has(IMPORT_NAMESPACE_SPECIFIER)) { + oldNamespaceImports.add(key) + } + }) + + const oldDefaultImports = new Set() + const newDefaultImports = new Set() + oldImportPaths.forEach((value, key) => { + if (value.has(IMPORT_DEFAULT_SPECIFIER)) { + oldDefaultImports.add(key) + } + }) + node.body.forEach(astNode => { if (astNode.type === IMPORT_DECLARATION) { const resolvedPath = resolve(astNode.source.value, context) @@ -338,47 +395,36 @@ module.exports = { if (isNodeModule(resolvedPath)) { return } + + if (newNamespaceImportExists(astNode.specifiers)) { + newNamespaceImports.add(resolvedPath) + } + + if (newDefaultImportExists(astNode.specifiers)) { + newDefaultImports.add(resolvedPath) + } + let imports = oldImportPaths.get(resolvedPath) const exports = exportList.get(resolvedPath) - // unknown module - if (typeof exports === UNDEFINED) { - return - } if (typeof imports === UNDEFINED) { imports = new Set() } const tmpImports = new Set() imports.forEach((value, key) => { - if (key !== IMPORT_DEFAULT_SPECIFIER) { + if (key !== IMPORT_DEFAULT_SPECIFIER && key !== IMPORT_NAMESPACE_SPECIFIER) { tmpImports.add(key) } }) - // update usage of default import/export - const defaultExport = exports.get(DEFAULT) - const hasOldDefaultImport = oldDefaultImportExists(imports) - const hasNewDefaultImport = newDefaultImportExists(astNode.specifiers) - - if (hasNewDefaultImport && !hasOldDefaultImport) { - imports.add(IMPORT_DEFAULT_SPECIFIER) - if (typeof defaultExport !== UNDEFINED) { - const { whereUsed } = defaultExport - whereUsed.add(file) - } - } - - if (!hasNewDefaultImport && hasOldDefaultImport) { - imports.delete(IMPORT_DEFAULT_SPECIFIER) - if (typeof defaultExport !== UNDEFINED) { - const { whereUsed } = defaultExport - whereUsed.delete(file) - } - } - // update usage of named imports/export astNode.specifiers.forEach(specifier => { + if (specifier.type === IMPORT_DEFAULT_SPECIFIER || + specifier.type === IMPORT_NAMESPACE_SPECIFIER) { + return + } + imports.add(specifier.local.name) tmpImports.delete(specifier.local.name) @@ -395,13 +441,80 @@ module.exports = { tmpImports.forEach(key => { const currentExport = exports.get(key) if (typeof currentExport !== UNDEFINED) { - const { whereUsed } = currentExport - whereUsed.delete(file) + currentExport.whereUsed.delete(file) } }) } } }) + + newDefaultImports.forEach(value => { + if (!oldDefaultImports.has(value)) { + let imports = oldImportPaths.get(value) + if (typeof imports === UNDEFINED) { + imports = new Set() + } + imports.add(IMPORT_DEFAULT_SPECIFIER) + oldImportPaths.set(value, imports) + + const exports = exportList.get(value) + if (typeof exports !== UNDEFINED) { + const currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER) + if (typeof currentExport !== UNDEFINED) { + currentExport.whereUsed.add(file) + } + } + } + }) + + oldDefaultImports.forEach(value => { + if (!newDefaultImports.has(value)) { + const imports = oldImportPaths.get(value) + imports.delete(IMPORT_DEFAULT_SPECIFIER) + + const exports = exportList.get(value) + if (typeof exports !== UNDEFINED) { + const currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER) + if (typeof currentExport !== UNDEFINED) { + currentExport.whereUsed.delete(file) + } + } + } + }) + + newNamespaceImports.forEach(value => { + if (!oldNamespaceImports.has(value)) { + let imports = oldImportPaths.get(value) + if (typeof imports === UNDEFINED) { + imports = new Set() + } + imports.add(IMPORT_NAMESPACE_SPECIFIER) + oldImportPaths.set(value, imports) + + const exports = exportList.get(value) + if (typeof exports !== UNDEFINED) { + const currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER) + if (typeof currentExport !== UNDEFINED) { + currentExport.whereUsed.add(file) + } + } + } + }) + + oldNamespaceImports.forEach(value => { + if (!newNamespaceImports.has(value)) { + const imports = oldImportPaths.get(value) + imports.delete(IMPORT_NAMESPACE_SPECIFIER) + + const exports = exportList.get(value) + if (typeof exports !== UNDEFINED) { + const currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER) + if (typeof currentExport !== UNDEFINED) { + currentExport.whereUsed.delete(file) + } + } + } + }) } return { @@ -411,7 +524,7 @@ module.exports = { checkExportPresence(node) }, 'ExportDefaultDeclaration': node => { - checkUsage(node, DEFAULT) + checkUsage(node, IMPORT_DEFAULT_SPECIFIER) }, 'ExportNamedDeclaration': node => { if (node.specifiers) { diff --git a/tests/files/no-unused-modules/file-0.js b/tests/files/no-unused-modules/file-0.js index adabd7f75b..1cf687d200 100644 --- a/tests/files/no-unused-modules/file-0.js +++ b/tests/files/no-unused-modules/file-0.js @@ -4,4 +4,5 @@ import { b } from './file-b' import { c1, c2 } from './file-c' import { d } from './file-d' import { e } from './file-e' -import { h2 } from './file-h' \ No newline at end of file +import { h2 } from './file-h' +import * as l from './file-l' \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-ignored-l.js b/tests/files/no-unused-modules/file-ignored-l.js new file mode 100644 index 0000000000..7ce44d4b9d --- /dev/null +++ b/tests/files/no-unused-modules/file-ignored-l.js @@ -0,0 +1,6 @@ +const l0 = 5 +const l = 10 + +export { l0 as l1, l } + +export default () => {} \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-l.js b/tests/files/no-unused-modules/file-l.js new file mode 100644 index 0000000000..7ce44d4b9d --- /dev/null +++ b/tests/files/no-unused-modules/file-l.js @@ -0,0 +1,6 @@ +const l0 = 5 +const l = 10 + +export { l0 as l1, l } + +export default () => {} \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-m.js b/tests/files/no-unused-modules/file-m.js new file mode 100644 index 0000000000..c11891f81d --- /dev/null +++ b/tests/files/no-unused-modules/file-m.js @@ -0,0 +1,6 @@ +const m0 = 5 +const m = 10 + +export { m0 as m1, m } + +export default () => {} \ No newline at end of file diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 7696d46d0e..36a35faef8 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -2,6 +2,7 @@ import { test, testFilePath } from '../utils' import { RuleTester } from 'eslint' import { expect } from 'chai' +import fs from 'fs' const doPreparation = require( '../../../src/rules/no-unused-modules').doPreparation const getSrc = require( '../../../src/rules/no-unused-modules').getSrc @@ -114,6 +115,9 @@ ruleTester.run('no-unused-modules', rule, { test({ options: unusedExportsOptions, code: 'const e0 = 5; export { e0 as e }', filename: testFilePath('./no-unused-modules/file-e.js')}), + test({ options: unusedExportsOptions, + code: 'const l0 = 5; const l = 10; export { l0 as l1, l }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-l.js')}), ], invalid: [], }) @@ -169,7 +173,11 @@ ruleTester.run('no-unused-modules', rule, { filename: testFilePath('./no-unused-modules/file-ignored-d.js')}), test({ options: unusedExportsOptions, code: 'const f = 5; export { f as e }', - filename: testFilePath('./no-unused-modules/file-ignored-e.js')})], + filename: testFilePath('./no-unused-modules/file-ignored-e.js')}), + test({ options: unusedExportsOptions, + code: 'const l0 = 5; const l = 10; export { l0 as l1, l }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-ignored-l.js')}), + ], invalid: [], }) @@ -256,4 +264,101 @@ ruleTester.run('no-unused-modules', rule, { filename: testFilePath('./no-unused-modules/file-a.js'), errors: [error(`exported declaration 'default' not used within other modules`)]}), ], +}) + +// add namespace import for file with unused exports +ruleTester.run('no-unused-modules', rule, { + valid: [], + invalid: [ + test({ options: unusedExportsOptions, + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js'), + errors: [ + error(`exported declaration 'm1' not used within other modules`), + error(`exported declaration 'm' not used within other modules`), + error(`exported declaration 'default' not used within other modules`), + ]}), + ], +}) +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'; import unknown from 'unknown-module'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + test({ options: unusedExportsOptions, + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js')}), + ], + invalid: [], +}) + +// remove all exports +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `/* import * as m from '${testFilePath('./no-unused-modules/file-m.js')}' */`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js'), + errors: [ + error(`exported declaration 'm1' not used within other modules`), + error(`exported declaration 'm' not used within other modules`), + error(`exported declaration 'default' not used within other modules`), + ]}), + ], +}) + +describe('test behaviour for new file', () => { + before(() => { + fs.writeFileSync(testFilePath('./no-unused-modules/file-added.js'), '', {encoding: 'utf8'}) + }) + + // add import in newly created file + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'`, + filename: testFilePath('./no-unused-modules/file-added.js')}), + test({ options: unusedExportsOptions, + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js')}), + ], + invalid: [], + }) + + // add export for newly created file + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added.js')}), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: `export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added.js'), + errors: [error(`exported declaration 'default' not used within other modules`)]}), + ], + }) + + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import def from '${testFilePath('./no-unused-modules/file-added.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + test({ options: unusedExportsOptions, + code: `export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added.js')}), + ], + invalid: [], + }) + + after(() => { + if (fs.existsSync(testFilePath('./no-unused-modules/file-added.js'))) { + fs.unlinkSync(testFilePath('./no-unused-modules/file-added.js')) + } + }) }) \ No newline at end of file From 06a09766a41b1b5c8cd77cd34dd88f5d2984009a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sun, 19 Aug 2018 18:49:52 +0200 Subject: [PATCH 063/151] New: rule - added support for 'export * from' and 'export { stuff } from ', revised docs --- docs/rules/no-unused-modules.md | 29 +- src/rules/no-unused-modules.js | 361 +++++++++++++++++------- tests/files/no-unused-modules/file-0.js | 6 +- tests/files/no-unused-modules/file-a.js | 3 +- tests/files/no-unused-modules/file-n.js | 6 + tests/files/no-unused-modules/file-o.js | 6 + tests/src/rules/no-unused-modules.js | 309 +++++++++++++++++--- 7 files changed, 580 insertions(+), 140 deletions(-) create mode 100644 tests/files/no-unused-modules/file-n.js create mode 100644 tests/files/no-unused-modules/file-o.js diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md index f185997599..ade208d3e7 100644 --- a/docs/rules/no-unused-modules.md +++ b/docs/rules/no-unused-modules.md @@ -4,6 +4,8 @@ Reports: - modules without any or - individual exports not being used within other modules +Note: dynamic imports are currently not supported. + ## Rule Details @@ -11,10 +13,10 @@ Reports: This rule takes the following option: -- `src`: an array with files/paths to be analyzed. It only for applies for unused exports +- `src`: an array with files/paths to be analyzed. It only for applies for unused exports. Defaults to `process.cwd()`, if not provided - `ignore`: an array with files/paths to be ignored. It only for applies for unused exports - `missingExports`: if `true`, files without any exports are reported -- `unusedExports`: if `true`, exports without any usage are reported +- `unusedExports`: if `true`, exports without any usage within other modules are reported. ### Example for missing exports @@ -41,12 +43,31 @@ export { foo as bar } ``` ### Example for unused exports -given file-c: +given file-f: ```js import { e } from 'file-a' import { f } from 'file-b' +import * from 'file-c' +export * from 'file-d' +export { default, i0 } from 'file-e' // both will be reported -export default 7 // will be reported +export const j = 99 // will be reported +``` +and file-e: +```js +export const i0 = 9 // will not be reported +export const i1 = 9 // will be reported +export default () => {} // will not be reported +``` +and file-d: +```js +export const h = 8 // will not be reported +export default () => {} // will be reported, as export * only considers named exports and ignores default exports +``` +and file-c: +```js +export const g = 7 // will not be reported +export default () => {} // will not be reported ``` and file-b: ```js diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index fa9379fe89..3d0d4cbc8b 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -19,10 +19,10 @@ try { const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration' const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration' +const EXPORT_ALL_DECLARATION = 'ExportAllDeclaration' const IMPORT_DECLARATION = 'ImportDeclaration' const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier' const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier' -const IMPORT_SPECIFIER = 'ImportSpecifier' const VARIABLE_DECLARATION = 'VariableDeclaration' const FUNCTION_DECLARATION = 'FunctionDeclaration' const DEFAULT = 'default' @@ -64,36 +64,77 @@ const resolveFiles = (src, ignore) => { * parse all source files and build up 2 maps containing the existing imports and exports */ const prepareImportsAndExports = (srcFiles, context) => { + const exportAll = new Map() srcFiles.forEach(file => { const exports = new Map() const imports = new Map() const currentExports = Exports.get(file, context) - if (!currentExports) { - return - } - const { imports: localImportList, namespace } = currentExports - localImportList.forEach((value, key) => { - if (isNodeModule(key)) { + if (currentExports) { + const { dependencies, reexports, imports: localImportList, namespace } = currentExports + + // dependencies === export * from + const currentExportAll = new Set() + dependencies.forEach(value => { + currentExportAll.add(value().path) + }) + exportAll.set(file, currentExportAll) + + reexports.forEach((value, key) => { + if (key === DEFAULT) { + exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() }) + } else { + exports.set(key, { whereUsed: new Set() }) + } + const reexport = value.getImport() + if (!reexport) { + return + } + let localImport = imports.get(reexport.path) + let currentValue + if (value.local === DEFAULT) { + currentValue = IMPORT_DEFAULT_SPECIFIER + } else { + currentValue = value.local + } + if (typeof localImport !== UNDEFINED) { + localImport = new Set([...localImport, currentValue]) + } else { + localImport = new Set([currentValue]) + } + imports.set(reexport.path, localImport) + }) + + localImportList.forEach((value, key) => { + if (isNodeModule(key)) { + return + } + imports.set(key, value.importedSpecifiers) + }) + importList.set(file, imports) + + // build up export list only, if file is not ignored + if (ignoredFiles.has(file)) { return } - imports.set(key, value.importedSpecifiers) - }) - importList.set(file, imports) - - // build up export list only, if file is not ignored - if (ignoredFiles.has(file)) { - return + namespace.forEach((value, key) => { + if (key === DEFAULT) { + exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() }) + } else { + exports.set(key, { whereUsed: new Set() }) + } + }) } - namespace.forEach((value, key) => { - if (key === DEFAULT) { - exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() }) - } else { - exports.set(key, { whereUsed: new Set() }) - } - }) + exports.set(EXPORT_ALL_DECLARATION, { whereUsed: new Set() }) exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed: new Set() }) exportList.set(file, exports) }) + exportAll.forEach((value, key) => { + value.forEach(val => { + const currentExports = exportList.get(val) + const currentExport = currentExports.get(EXPORT_ALL_DECLARATION) + currentExport.whereUsed.add(key) + }) + }) } /** @@ -117,7 +158,7 @@ const determineUsage = () => { if (typeof specifier !== UNDEFINED) { const exportStatement = exports.get(specifier) if (typeof exportStatement !== UNDEFINED) { - const {whereUsed} = exportStatement + const { whereUsed } = exportStatement whereUsed.add(listKey) exports.set(specifier, { whereUsed }) } @@ -180,7 +221,6 @@ const doPreparation = (src, ignore, context) => { preparationDone = true } - const newNamespaceImportExists = specifiers => { let hasNewNamespaceImport = false specifiers.forEach(specifier => { @@ -191,7 +231,6 @@ const newNamespaceImportExists = specifiers => { return hasNewNamespaceImport } - const newDefaultImportExists = specifiers => { let hasNewDefaultImport = false specifiers.forEach(specifier => { @@ -244,12 +283,18 @@ module.exports = { } const exportCount = exportList.get(file) + const exportAll = exportCount.get(EXPORT_ALL_DECLARATION) + const namespaceImports = exportCount.get(IMPORT_NAMESPACE_SPECIFIER) + + exportCount.delete(EXPORT_ALL_DECLARATION) exportCount.delete(IMPORT_NAMESPACE_SPECIFIER) if (missingExports && 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') } + exportCount.set(EXPORT_ALL_DECLARATION, exportAll) + exportCount.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports) } const checkUsage = (node, exportedValue) => { @@ -261,30 +306,45 @@ module.exports = { return } - const exports = exportList.get(file) + exports = exportList.get(file) + + // special case: export * from + const exportAll = exports.get(EXPORT_ALL_DECLARATION) + if (typeof exportAll !== UNDEFINED && exportedValue !== IMPORT_DEFAULT_SPECIFIER) { + if (exportAll.whereUsed.size > 0) { + return + } + } // special case: namespace import - const nameSpaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER) - if (typeof nameSpaceImports !== UNDEFINED) { - if (nameSpaceImports.whereUsed.size > 0) { + const namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER) + if (typeof namespaceImports !== UNDEFINED) { + if (namespaceImports.whereUsed.size > 0) { return } } const exportStatement = exports.get(exportedValue) + + let value = '' + if (exportedValue === IMPORT_DEFAULT_SPECIFIER) { + value = DEFAULT + } else { + value = exportedValue + } + if (typeof exportStatement !== UNDEFINED){ if ( exportStatement.whereUsed.size < 1) { - let value = '' - if (exportedValue === IMPORT_DEFAULT_SPECIFIER) { - value = DEFAULT - } else { - value = exportedValue - } context.report( node, `exported declaration '${value}' not used within other modules` ) } + } else { + context.report( + node, + `exported declaration '${value}' not used within other modules` + ) } } @@ -299,7 +359,7 @@ module.exports = { } let exports = exportList.get(file) - + // new module has been created during runtime // include it in further processing if (typeof exports === UNDEFINED) { @@ -344,12 +404,20 @@ module.exports = { // new export identifiers added: add to map of new exports newExportIdentifiers.forEach(key => { if (!exports.has(key)) { - newExports.set(key, {whereUsed: new Set()}) + newExports.set(key, { whereUsed: new Set() }) } }) // preserve information about namespace imports - newExports.set(IMPORT_NAMESPACE_SPECIFIER, exports.get(IMPORT_NAMESPACE_SPECIFIER)) + let exportAll = exports.get(EXPORT_ALL_DECLARATION) + let namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER) + + if (typeof namespaceImports === UNDEFINED) { + namespaceImports = { whereUsed: new Set() } + } + + newExports.set(EXPORT_ALL_DECLARATION, exportAll) + newExports.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports) exportList.set(file, newExports) } @@ -370,24 +438,59 @@ module.exports = { const oldNamespaceImports = new Set() const newNamespaceImports = new Set() - oldImportPaths.forEach((value, key) => { - if (value.has(IMPORT_NAMESPACE_SPECIFIER)) { - oldNamespaceImports.add(key) - } - }) + + const oldExportAll = new Set() + const newExportAll = new Set() const oldDefaultImports = new Set() const newDefaultImports = new Set() + + const oldImports = new Map() + const newImports = new Map() oldImportPaths.forEach((value, key) => { + if (value.has(EXPORT_ALL_DECLARATION)) { + oldExportAll.add(key) + } + if (value.has(IMPORT_NAMESPACE_SPECIFIER)) { + oldNamespaceImports.add(key) + } if (value.has(IMPORT_DEFAULT_SPECIFIER)) { oldDefaultImports.add(key) } + value.forEach(val => { + if (val !== IMPORT_NAMESPACE_SPECIFIER && + val !== IMPORT_DEFAULT_SPECIFIER) { + oldImports.set(val, key) + } + }) }) - + node.body.forEach(astNode => { + let resolvedPath + + // support for export { value } from 'module' + if (astNode.type === EXPORT_NAMED_DECLARATION) { + if (astNode.source) { + resolvedPath = resolve(astNode.source.value, context) + astNode.specifiers.forEach(specifier => { + let name + if (specifier.exported.name === DEFAULT) { + name = IMPORT_DEFAULT_SPECIFIER + } else { + name = specifier.local.name + } + newImports.set(name, resolvedPath) + }) + } + } + + if (astNode.type === EXPORT_ALL_DECLARATION) { + resolvedPath = resolve(astNode.source.value, context) + newExportAll.add(resolvedPath) + } + if (astNode.type === IMPORT_DECLARATION) { - const resolvedPath = resolve(astNode.source.value, context) - + resolvedPath = resolve(astNode.source.value, context) if (!resolvedPath) { return } @@ -404,46 +507,55 @@ module.exports = { newDefaultImports.add(resolvedPath) } - let imports = oldImportPaths.get(resolvedPath) - const exports = exportList.get(resolvedPath) - - if (typeof imports === UNDEFINED) { - imports = new Set() - } - - const tmpImports = new Set() - imports.forEach((value, key) => { - if (key !== IMPORT_DEFAULT_SPECIFIER && key !== IMPORT_NAMESPACE_SPECIFIER) { - tmpImports.add(key) - } - }) - - // update usage of named imports/export astNode.specifiers.forEach(specifier => { if (specifier.type === IMPORT_DEFAULT_SPECIFIER || specifier.type === IMPORT_NAMESPACE_SPECIFIER) { return } - - imports.add(specifier.local.name) - tmpImports.delete(specifier.local.name) - - const currentExport = exports.get(specifier.local.name) - if (specifier.type === IMPORT_SPECIFIER) { - if (typeof currentExport !== UNDEFINED) { - currentExport.whereUsed.add(file) - } - } + newImports.set(specifier.local.name, resolvedPath) }) + } + }) - // one or more imports have been removed: update usage list of exports - if (tmpImports.size > 0) { - tmpImports.forEach(key => { - const currentExport = exports.get(key) - if (typeof currentExport !== UNDEFINED) { - currentExport.whereUsed.delete(file) - } - }) + newExportAll.forEach(value => { + if (!oldExportAll.has(value)) { + let imports = oldImportPaths.get(value) + if (typeof imports === UNDEFINED) { + imports = new Set() + } + imports.add(EXPORT_ALL_DECLARATION) + oldImportPaths.set(value, imports) + + let exports = exportList.get(value) + let currentExport + if (typeof exports !== UNDEFINED) { + currentExport = exports.get(EXPORT_ALL_DECLARATION) + } else { + exports = new Map() + exportList.set(value, exports) + } + + if (typeof currentExport !== UNDEFINED) { + currentExport.whereUsed.add(file) + } else { + const whereUsed = new Set() + whereUsed.add(file) + exports.set(EXPORT_ALL_DECLARATION, { whereUsed }) + } + } + }) + + oldExportAll.forEach(value => { + if (!newExportAll.has(value)) { + const imports = oldImportPaths.get(value) + imports.delete(EXPORT_ALL_DECLARATION) + + const exports = exportList.get(value) + if (typeof exports !== UNDEFINED) { + const currentExport = exports.get(EXPORT_ALL_DECLARATION) + if (typeof currentExport !== UNDEFINED) { + currentExport.whereUsed.delete(file) + } } } }) @@ -457,12 +569,21 @@ module.exports = { imports.add(IMPORT_DEFAULT_SPECIFIER) oldImportPaths.set(value, imports) - const exports = exportList.get(value) + let exports = exportList.get(value) + let currentExport if (typeof exports !== UNDEFINED) { - const currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER) - if (typeof currentExport !== UNDEFINED) { - currentExport.whereUsed.add(file) - } + currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER) + } else { + exports = new Map() + exportList.set(value, exports) + } + + if (typeof currentExport !== UNDEFINED) { + currentExport.whereUsed.add(file) + } else { + const whereUsed = new Set() + whereUsed.add(file) + exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed }) } } }) @@ -491,12 +612,21 @@ module.exports = { imports.add(IMPORT_NAMESPACE_SPECIFIER) oldImportPaths.set(value, imports) - const exports = exportList.get(value) + let exports = exportList.get(value) + let currentExport if (typeof exports !== UNDEFINED) { - const currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER) - if (typeof currentExport !== UNDEFINED) { - currentExport.whereUsed.add(file) - } + currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER) + } else { + exports = new Map() + exportList.set(value, exports) + } + + if (typeof currentExport !== UNDEFINED) { + currentExport.whereUsed.add(file) + } else { + const whereUsed = new Set() + whereUsed.add(file) + exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed }) } } }) @@ -505,7 +635,7 @@ module.exports = { if (!newNamespaceImports.has(value)) { const imports = oldImportPaths.get(value) imports.delete(IMPORT_NAMESPACE_SPECIFIER) - + const exports = exportList.get(value) if (typeof exports !== UNDEFINED) { const currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER) @@ -515,6 +645,49 @@ module.exports = { } } }) + + newImports.forEach((value, key) => { + if (!oldImports.has(key)) { + let imports = oldImportPaths.get(value) + if (typeof imports === UNDEFINED) { + imports = new Set() + } + imports.add(key) + oldImportPaths.set(value, imports) + + let exports = exportList.get(value) + let currentExport + if (typeof exports !== UNDEFINED) { + currentExport = exports.get(key) + } else { + exports = new Map() + exportList.set(value, exports) + } + + if (typeof currentExport !== UNDEFINED) { + currentExport.whereUsed.add(file) + } else { + const whereUsed = new Set() + whereUsed.add(file) + exports.set(key, { whereUsed }) + } + } + }) + + oldImports.forEach((value, key) => { + if (!newImports.has(key)) { + const imports = oldImportPaths.get(value) + imports.delete(key) + + const exports = exportList.get(value) + if (typeof exports !== UNDEFINED) { + const currentExport = exports.get(key) + if (typeof currentExport !== UNDEFINED) { + currentExport.whereUsed.delete(file) + } + } + } + }) } return { @@ -527,13 +700,9 @@ module.exports = { checkUsage(node, IMPORT_DEFAULT_SPECIFIER) }, 'ExportNamedDeclaration': node => { - if (node.specifiers) { - node.specifiers.forEach(specifier => { - if (specifier.exported) { - checkUsage(node, specifier.exported.name) - } - }) - } + node.specifiers.forEach(specifier => { + checkUsage(node, specifier.exported.name) + }) if (node.declaration) { if (node.declaration.type === FUNCTION_DECLARATION) { checkUsage(node, node.declaration.id.name) diff --git a/tests/files/no-unused-modules/file-0.js b/tests/files/no-unused-modules/file-0.js index 1cf687d200..c90c2af6c7 100644 --- a/tests/files/no-unused-modules/file-0.js +++ b/tests/files/no-unused-modules/file-0.js @@ -4,5 +4,9 @@ import { b } from './file-b' import { c1, c2 } from './file-c' import { d } from './file-d' import { e } from './file-e' +import { e2 } from './file-e' import { h2 } from './file-h' -import * as l from './file-l' \ No newline at end of file +import * as l from './file-l' +export * from './file-n' +export { default, o0, o3 } from './file-o' +export { p } from './file-p' \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-a.js b/tests/files/no-unused-modules/file-a.js index a316dbbf82..7c16dd4dd0 100644 --- a/tests/files/no-unused-modules/file-a.js +++ b/tests/files/no-unused-modules/file-a.js @@ -1 +1,2 @@ -export default () => 1 \ No newline at end of file +import { o2 } from './file-o' +export default () => 1 diff --git a/tests/files/no-unused-modules/file-n.js b/tests/files/no-unused-modules/file-n.js new file mode 100644 index 0000000000..92c1b6feb3 --- /dev/null +++ b/tests/files/no-unused-modules/file-n.js @@ -0,0 +1,6 @@ +const n0 = 'n0' +const n1 = 42 + +export { n0, n1 } + +export default () => {} \ No newline at end of file diff --git a/tests/files/no-unused-modules/file-o.js b/tests/files/no-unused-modules/file-o.js new file mode 100644 index 0000000000..6f18f00c01 --- /dev/null +++ b/tests/files/no-unused-modules/file-o.js @@ -0,0 +1,6 @@ +const o0 = 0 +const o1 = 1 + +export { o0, o1 as o2 } + +export default () => {} \ No newline at end of file diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 36a35faef8..4cf8f4fec7 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -97,29 +97,58 @@ ruleTester.run('no-unused-modules', rule, { }) -// tests for unused exports +// tests for exports ruleTester.run('no-unused-modules', rule, { valid: [ + test({ options: unusedExportsOptions, - code: 'export default () => 1', - filename: testFilePath('./no-unused-modules/file-a.js')}), + code: 'import { o2 } from "./file-o";export default () => 12', + filename: testFilePath('./no-unused-modules/file-a.js')}), test({ options: unusedExportsOptions, - code: 'export const b = 2', - filename: testFilePath('./no-unused-modules/file-b.js')}), + code: 'export const b = 2', + filename: testFilePath('./no-unused-modules/file-b.js')}), test({ options: unusedExportsOptions, - code: 'const c1 = 3; function c2() { return 3 }; export { c1, c2 }', - filename: testFilePath('./no-unused-modules/file-c.js')}), + code: 'const c1 = 3; function c2() { return 3 }; export { c1, c2 }', + filename: testFilePath('./no-unused-modules/file-c.js')}), test({ options: unusedExportsOptions, - code: 'export function d() { return 4 }', - filename: testFilePath('./no-unused-modules/file-d.js')}), + code: 'export function d() { return 4 }', + filename: testFilePath('./no-unused-modules/file-d.js')}), test({ options: unusedExportsOptions, - code: 'const e0 = 5; export { e0 as e }', - filename: testFilePath('./no-unused-modules/file-e.js')}), + code: 'const e0 = 5; export { e0 as e }', + filename: testFilePath('./no-unused-modules/file-e.js')}), test({ options: unusedExportsOptions, - code: 'const l0 = 5; const l = 10; export { l0 as l1, l }; export default () => {}', - filename: testFilePath('./no-unused-modules/file-l.js')}), + code: 'const l0 = 5; const l = 10; export { l0 as l1, l }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-l.js')}), + test({ options: unusedExportsOptions, + code: 'const o0 = 0; const o1 = 1; export { o0, o1 as o2 }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-o.js')}), ], - invalid: [], + invalid: [ + test({ options: unusedExportsOptions, + code: `import eslint from 'eslint' + import fileA from './file-a' + import { b } from './file-b' + import { c1, c2 } from './file-c' + import { d } from './file-d' + import { e } from './file-e' + import { e2 } from './file-e' + import { h2 } from './file-h' + import * as l from './file-l' + export * from './file-n' + export { default, o0, o3 } from './file-o' + export { p } from './file-p'`, + filename: testFilePath('./no-unused-modules/file-0.js'), + errors: [ + error(`exported declaration 'default' not used within other modules`), + error(`exported declaration 'o0' not used within other modules`), + error(`exported declaration 'o3' not used within other modules`), + error(`exported declaration 'p' not used within other modules`), + ]}), + test({ options: unusedExportsOptions, + code: `const n0 = 'n0'; const n1 = 42; export { n0, n1 }; export default () => {}`, + filename: testFilePath('./no-unused-modules/file-n.js'), + errors: [error(`exported declaration 'default' not used within other modules`)]}), + ], }) // test for unused exports @@ -127,9 +156,9 @@ ruleTester.run('no-unused-modules', rule, { valid: [], invalid: [ test({ options: unusedExportsOptions, - code: 'export default () => 1', + code: 'export default () => 13', filename: testFilePath('./no-unused-modules/file-f.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), + errors: [error(`exported declaration 'default' not used within other modules`)]}), test({ options: unusedExportsOptions, code: 'export const g = 2', filename: testFilePath('./no-unused-modules/file-g.js'), @@ -156,11 +185,31 @@ ruleTester.run('no-unused-modules', rule, { ], }) +// // test for export from +ruleTester.run('no-unused-modules', rule, { + valid: [], + invalid: [ + test({ options: unusedExportsOptions, + code: `export { k } from '${testFilePath('./no-unused-modules/file-k.js')}'`, + filename: testFilePath('./no-unused-modules/file-j.js'), + errors: [error(`exported declaration 'k' not used within other modules`)]}), + ], +}) + +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: 'const k0 = 5; export { k0 as k }', + filename: testFilePath('./no-unused-modules/file-k.js')}), + ], + invalid: [], +}) + // test for ignored files ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: 'export default () => 1', + code: 'export default () => 14', filename: testFilePath('./no-unused-modules/file-ignored-a.js')}), test({ options: unusedExportsOptions, code: 'export const b = 2', @@ -185,14 +234,14 @@ ruleTester.run('no-unused-modules', rule, { ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import { j } from '${testFilePath('./no-unused-modules/file-f.js')}'`, + code: `import { f } from '${testFilePath('./no-unused-modules/file-f.js')}'`, filename: testFilePath('./no-unused-modules/file-0.js')}), ], invalid: [ test({ options: unusedExportsOptions, - code: 'export default () => 1', + code: 'export default () => 15', filename: testFilePath('./no-unused-modules/file-f.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), + errors: [error(`exported declaration 'default' not used within other modules`)]}), ], }) @@ -203,7 +252,7 @@ ruleTester.run('no-unused-modules', rule, { code: `import f from '${testFilePath('./no-unused-modules/file-f.js')}'`, filename: testFilePath('./no-unused-modules/file-0.js')}), test({ options: unusedExportsOptions, - code: 'export default () => 1', + code: 'export default () => 16', filename: testFilePath('./no-unused-modules/file-f.js')}), ], invalid: [], @@ -260,7 +309,7 @@ ruleTester.run('no-unused-modules', rule, { ], invalid: [ test({ options: unusedExportsOptions, - code: 'export default () => 1', + code: 'export default () => 17', filename: testFilePath('./no-unused-modules/file-a.js'), errors: [error(`exported declaration 'default' not used within other modules`)]}), ], @@ -311,54 +360,238 @@ ruleTester.run('no-unused-modules', rule, { ], }) +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `export * from '${testFilePath('./no-unused-modules/file-m.js')}';`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + ], + invalid: [], +}) +ruleTester.run('no-unused-modules', rule, { + valid: [], + invalid: [ + test({ options: unusedExportsOptions, + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js'), + errors: [error(`exported declaration 'default' not used within other modules`)]}), + ], +}) + +ruleTester.run('no-unused-modules', rule, { + valid: [], + invalid: [ + test({ options: unusedExportsOptions, + code: `export { m1, m} from '${testFilePath('./no-unused-modules/file-m.js')}';`, + filename: testFilePath('./no-unused-modules/file-0.js'), + errors: [ + error(`exported declaration 'm1' not used within other modules`), + error(`exported declaration 'm' not used within other modules`), + ]}), + test({ options: unusedExportsOptions, + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js'), + errors: [error(`exported declaration 'default' not used within other modules`)]}), + ], +}) + +ruleTester.run('no-unused-modules', rule, { + valid: [ + // test({ options: unusedExportsOptions, + // code: `export { default, m1 } from '${testFilePath('./no-unused-modules/file-m.js')}';`, + // filename: testFilePath('./no-unused-modules/file-0.js')}), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: `export { default, m1 } from '${testFilePath('./no-unused-modules/file-m.js')}';`, + filename: testFilePath('./no-unused-modules/file-0.js'), + errors: [ + error(`exported declaration 'default' not used within other modules`), + error(`exported declaration 'm1' not used within other modules`), + ]}), + test({ options: unusedExportsOptions, + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js'), + errors: [error(`exported declaration 'm' not used within other modules`)]}), + ], +}) + describe('test behaviour for new file', () => { before(() => { - fs.writeFileSync(testFilePath('./no-unused-modules/file-added.js'), '', {encoding: 'utf8'}) + fs.writeFileSync(testFilePath('./no-unused-modules/file-added-0.js'), '', {encoding: 'utf8'}) }) // add import in newly created file ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'`, - filename: testFilePath('./no-unused-modules/file-added.js')}), + code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'`, + filename: testFilePath('./no-unused-modules/file-added-0.js')}), test({ options: unusedExportsOptions, - code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', - filename: testFilePath('./no-unused-modules/file-m.js')}), + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js')}), ], invalid: [], }) // add export for newly created file + ruleTester.run('no-unused-modules', rule, { + valid: [], + invalid: [ + test({ options: unusedExportsOptions, + code: `export default () => {2}`, + filename: testFilePath('./no-unused-modules/file-added-0.js'), + errors: [error(`exported declaration 'default' not used within other modules`)]}), + ], + }) + + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import def from '${testFilePath('./no-unused-modules/file-added-0.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + test({ options: unusedExportsOptions, + code: `export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-0.js')}), + ], + invalid: [], + }) + + // export * only considers named imports. default imports still need to be reported ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `export default () => {}`, - filename: testFilePath('./no-unused-modules/file-added.js')}), + code: `export * from '${testFilePath('./no-unused-modules/file-added-0.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), ], invalid: [ test({ options: unusedExportsOptions, - code: `export default () => {}`, - filename: testFilePath('./no-unused-modules/file-added.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), + code: `export const z = 'z';export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-0.js'), + errors: [error(`exported declaration 'default' not used within other modules`)]}), + ], + }) + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `export const a = 2`, + filename: testFilePath('./no-unused-modules/file-added-0.js')}), + ], + invalid: [], + }) + + // remove export *. all exports need to be reported + ruleTester.run('no-unused-modules', rule, { + valid: [], + invalid: [ + test({ options: unusedExportsOptions, + code: `export { a } from '${testFilePath('./no-unused-modules/file-added-0.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js'), + errors: [error(`exported declaration 'a' not used within other modules`)]}), + test({ options: unusedExportsOptions, + code: `export const z = 'z';export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-0.js'), + errors: [ + error(`exported declaration 'z' not used within other modules`), + error(`exported declaration 'default' not used within other modules`), + ]}), + ], + }) + + + describe('test behaviour for new file', () => { + before(() => { + fs.writeFileSync(testFilePath('./no-unused-modules/file-added-1.js'), '', {encoding: 'utf8'}) + }) + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `export * from '${testFilePath('./no-unused-modules/file-added-1.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: `export const z = 'z';export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-1.js'), + errors: [error(`exported declaration 'default' not used within other modules`)]}), ], + }) + after(() => { + if (fs.existsSync(testFilePath('./no-unused-modules/file-added-1.js'))) { + fs.unlinkSync(testFilePath('./no-unused-modules/file-added-1.js')) + } + }) }) + after(() => { + if (fs.existsSync(testFilePath('./no-unused-modules/file-added-0.js'))) { + fs.unlinkSync(testFilePath('./no-unused-modules/file-added-0.js')) + } + }) +}) + +describe('test behaviour for new file', () => { + before(() => { + fs.writeFileSync(testFilePath('./no-unused-modules/file-added-2.js'), '', {encoding: 'utf8'}) + }) ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import def from '${testFilePath('./no-unused-modules/file-added.js')}'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `import added from '${testFilePath('./no-unused-modules/file-added-2.js')}'`, + filename: testFilePath('./no-unused-modules/file-added-1.js')}), test({ options: unusedExportsOptions, - code: `export default () => {}`, - filename: testFilePath('./no-unused-modules/file-added.js')}), + code: `export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-2.js')}), ], invalid: [], }) + after(() => { + if (fs.existsSync(testFilePath('./no-unused-modules/file-added-2.js'))) { + fs.unlinkSync(testFilePath('./no-unused-modules/file-added-2.js')) + } + }) +}) +describe('test behaviour for new file', () => { + before(() => { + fs.writeFileSync(testFilePath('./no-unused-modules/file-added-3.js'), '', {encoding: 'utf8'}) + }) + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import { added } from '${testFilePath('./no-unused-modules/file-added-3.js')}'`, + filename: testFilePath('./no-unused-modules/file-added-1.js')}), + test({ options: unusedExportsOptions, + code: `export const added = () => {}`, + filename: testFilePath('./no-unused-modules/file-added-3.js')}), + ], + invalid: [], + }) + after(() => { + if (fs.existsSync(testFilePath('./no-unused-modules/file-added-3.js'))) { + fs.unlinkSync(testFilePath('./no-unused-modules/file-added-3.js')) + } + }) +}) + +describe('test behaviour for new file', () => { + before(() => { + fs.writeFileSync(testFilePath('./no-unused-modules/file-added-4.js.js'), '', {encoding: 'utf8'}) + }) + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import * as added from '${testFilePath('./no-unused-modules/file-added-4.js.js')}'`, + filename: testFilePath('./no-unused-modules/file-added-1.js')}), + test({ options: unusedExportsOptions, + code: `export const added = () => {}; export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-4.js.js')}), + ], + invalid: [], + }) after(() => { - if (fs.existsSync(testFilePath('./no-unused-modules/file-added.js'))) { - fs.unlinkSync(testFilePath('./no-unused-modules/file-added.js')) + if (fs.existsSync(testFilePath('./no-unused-modules/file-added-4.js.js'))) { + fs.unlinkSync(testFilePath('./no-unused-modules/file-added-4.js.js')) } }) }) \ No newline at end of file From c48351499467deab8be12e15b68c01e0d07cd3db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Wed, 12 Sep 2018 13:17:08 +0200 Subject: [PATCH 064/151] New: `no-unused-modules` rule - renamed 'ignore' option to 'ignoreExports', revised docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Fermann --- docs/rules/no-unused-modules.md | 2 +- src/rules/no-unused-modules.js | 33 ++++++++++++++-------------- tests/src/rules/no-unused-modules.js | 22 +++++++++---------- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md index ade208d3e7..808e855cf6 100644 --- a/docs/rules/no-unused-modules.md +++ b/docs/rules/no-unused-modules.md @@ -14,7 +14,7 @@ Note: dynamic imports are currently not supported. This rule takes the following option: - `src`: an array with files/paths to be analyzed. It only for applies for unused exports. Defaults to `process.cwd()`, if not provided -- `ignore`: an array with files/paths to be ignored. It only for applies for unused exports +- `ignore`: an array with files/paths for which unused exports will not be reported (e.g module entry points) - `missingExports`: if `true`, files without any exports are reported - `unusedExports`: if `true`, exports without any usage within other modules are reported. diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 3d0d4cbc8b..519098e678 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -38,16 +38,16 @@ const isNodeModule = path => { } /** - * read all files matching the patterns in src and ignore + * read all files matching the patterns in src and ignoreExports * - * return all files matching src pattern, which are not matching the ignore pattern + * return all files matching src pattern, which are not matching the ignoreExports pattern */ -const resolveFiles = (src, ignore) => { +const resolveFiles = (src, ignoreExports) => { const srcFiles = new Set() const srcFileList = listFilesToProcess(src) // prepare list of ignored files - const ignoredFilesList = listFilesToProcess(ignore) + const ignoredFilesList = listFilesToProcess(ignoreExports) ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename)) // prepare list of source files, don't consider files from node_modules @@ -180,7 +180,7 @@ const getSrc = src => { * prepare the lists of existing imports and exports - should only be executed once at * the start of a new eslint run */ -const doPreparation = (src, ignore, context) => { +const doPreparation = (src, ignoreExports, context) => { const { id } = context // do some sanity checks @@ -188,8 +188,8 @@ const doPreparation = (src, ignore, context) => { throw new Error(`Rule ${id}: src option must be an array`) } - if (typeof ignore !== UNDEFINED && !Array.isArray(ignore)) { - throw new Error(`Rule ${id}: ignore option must be an array`) + if (typeof ignoreExports !== UNDEFINED && !Array.isArray(ignoreExports)) { + throw new Error(`Rule ${id}: ignoreExports option must be an array`) } // no empty patterns for paths, as this will cause issues during path resolution @@ -204,18 +204,18 @@ const doPreparation = (src, ignore, context) => { }) } - if (ignore) { - ignore.forEach(file => { + if (ignoreExports) { + ignoreExports.forEach(file => { if (typeof file !== 'string') { - throw new Error(`Rule ${id}: ignore option must not contain values other than strings`) + throw new Error(`Rule ${id}: ignoreExports option must not contain values other than strings`) } if (file.length < 1) { - throw new Error(`Rule ${id}: ignore option must not contain empty strings`) + throw new Error(`Rule ${id}: ignoreExports option must not contain empty strings`) } }) } - const srcFiles = resolveFiles(getSrc(src), ignore) + const srcFiles = resolveFiles(getSrc(src), ignoreExports) prepareImportsAndExports(srcFiles, context) determineUsage() preparationDone = true @@ -252,8 +252,9 @@ module.exports = { description: 'files/paths to be analyzed (only for unused exports)', type: 'array', }, - ignore: { - description: 'files/paths to be ignored (only for unused exports)', + ignoreExports: { + description: + 'files/paths for which unused exports will not be reported (e.g module entry points)', type: 'array', }, missingExports: { @@ -269,10 +270,10 @@ module.exports = { }, create: context => { - const { src, ignore, missingExports = false, unusedExports = false } = context.options[0] + const { src, ignoreExports, missingExports = false, unusedExports = false } = context.options[0] if (unusedExports && !preparationDone) { - doPreparation(src, ignore, context) + doPreparation(src, ignoreExports, context) } const file = context.getFilename() diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 4cf8f4fec7..428e834df5 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -19,7 +19,7 @@ const missingExportsOptions = [{ const unusedExportsOptions = [{ unusedExports: true, src: [testFilePath('./no-unused-modules/**/*.js')], - ignore: [testFilePath('./no-unused-modules/*ignored*.js')], + ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')], }] describe('doPreparation throws correct errors', () => { @@ -27,8 +27,8 @@ describe('doPreparation throws correct errors', () => { it('should throw an error, if src is not an array', () => { expect(doPreparation.bind(doPreparation, null, null, context)).to.throw(`Rule ${context.id}: src option must be an array`) }) - it('should throw an error, if ignore is not an array', () => { - expect(doPreparation.bind(doPreparation, [], null, context)).to.throw(`Rule ${context.id}: ignore option must be an array`) + it('should throw an error, if ignoreExports is not an array', () => { + expect(doPreparation.bind(doPreparation, [], null, context)).to.throw(`Rule ${context.id}: ignoreExports option must be an array`) }) it('should throw an error, if src contains empty strings', () => { expect(doPreparation.bind(doPreparation, [''], [], context)).to.throw(`Rule ${context.id}: src option must not contain empty strings`) @@ -42,17 +42,17 @@ describe('doPreparation throws correct errors', () => { it('should throw an error, if src contains values other than strings', () => { expect(doPreparation.bind(doPreparation, [undefined], [], context)).to.throw(`Rule ${context.id}: src option must not contain values other than strings`) }) - it('should throw an error, if ignore contains empty strings', () => { - expect(doPreparation.bind(doPreparation, ['src'], [''], context)).to.throw(`Rule ${context.id}: ignore option must not contain empty strings`) + it('should throw an error, if ignoreExports contains empty strings', () => { + expect(doPreparation.bind(doPreparation, ['src'], [''], context)).to.throw(`Rule ${context.id}: ignoreExports option must not contain empty strings`) }) - it('should throw an error, if ignore contains values other than strings', () => { - expect(doPreparation.bind(doPreparation, ['src'], [false], context)).to.throw(`Rule ${context.id}: ignore option must not contain values other than strings`) + it('should throw an error, if ignoreExports contains values other than strings', () => { + expect(doPreparation.bind(doPreparation, ['src'], [false], context)).to.throw(`Rule ${context.id}: ignoreExports option must not contain values other than strings`) }) - it('should throw an error, if ignore contains values other than strings', () => { - expect(doPreparation.bind(doPreparation, ['src'], [null], context)).to.throw(`Rule ${context.id}: ignore option must not contain values other than strings`) + it('should throw an error, if ignoreExports contains values other than strings', () => { + expect(doPreparation.bind(doPreparation, ['src'], [null], context)).to.throw(`Rule ${context.id}: ignoreExports option must not contain values other than strings`) }) - it('should throw an error, if ignore contains values other than strings', () => { - expect(doPreparation.bind(doPreparation, ['src'], [undefined], context)).to.throw(`Rule ${context.id}: ignore option must not contain values other than strings`) + it('should throw an error, if ignoreExports contains values other than strings', () => { + expect(doPreparation.bind(doPreparation, ['src'], [undefined], context)).to.throw(`Rule ${context.id}: ignoreExports option must not contain values other than strings`) }) }) From e04c87c4494cd3d451c7e36c8c84b8d5ec6bb88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Wed, 12 Sep 2018 13:21:05 +0200 Subject: [PATCH 065/151] New: `no-unused-modules` rule - corrected typo in docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René Fermann --- docs/rules/no-unused-modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md index 808e855cf6..a85c8f7ac0 100644 --- a/docs/rules/no-unused-modules.md +++ b/docs/rules/no-unused-modules.md @@ -14,7 +14,7 @@ Note: dynamic imports are currently not supported. This rule takes the following option: - `src`: an array with files/paths to be analyzed. It only for applies for unused exports. Defaults to `process.cwd()`, if not provided -- `ignore`: an array with files/paths for which unused exports will not be reported (e.g module entry points) +- `ignoreExports`: an array with files/paths for which unused exports will not be reported (e.g module entry points) - `missingExports`: if `true`, files without any exports are reported - `unusedExports`: if `true`, exports without any usage within other modules are reported. From 2922910fb75aaedec6ad27e260cadaaa3cee0083 Mon Sep 17 00:00:00 2001 From: Fermann Date: Thu, 18 Oct 2018 12:56:22 +0200 Subject: [PATCH 066/151] New: `no-unused-modules` rule - reworked schema, removed UNDEFINED variable, fixed documentation Signed-off-by: Fermann --- docs/rules/no-unused-modules.md | 2 +- src/rules/no-unused-modules.js | 152 ++++++++++++++------------- tests/src/rules/no-unused-modules.js | 34 ------ 3 files changed, 82 insertions(+), 106 deletions(-) diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md index a85c8f7ac0..fa8d375ecf 100644 --- a/docs/rules/no-unused-modules.md +++ b/docs/rules/no-unused-modules.md @@ -98,4 +98,4 @@ export default 5 // will not be reported ## When not to use -If you don't mind having unused files or dead code within your codebase, you can disable this rule \ No newline at end of file +If you don't mind having unused files or dead code within your codebase, you can disable this rule diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 519098e678..1c8d0b8ec2 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -5,7 +5,6 @@ */ import Exports from '../ExportMap' -import { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor' import resolve from 'eslint-module-utils/resolve' import docsUrl from '../docsUrl' @@ -26,7 +25,6 @@ const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier' const VARIABLE_DECLARATION = 'VariableDeclaration' const FUNCTION_DECLARATION = 'FunctionDeclaration' const DEFAULT = 'default' -const UNDEFINED = 'undefined' let preparationDone = false const importList = new Map() @@ -96,7 +94,7 @@ const prepareImportsAndExports = (srcFiles, context) => { } else { currentValue = value.local } - if (typeof localImport !== UNDEFINED) { + if (typeof localImport !== 'undefined') { localImport = new Set([...localImport, currentValue]) } else { localImport = new Set([currentValue]) @@ -145,7 +143,7 @@ const determineUsage = () => { importList.forEach((listValue, listKey) => { listValue.forEach((value, key) => { const exports = exportList.get(key) - if (typeof exports !== UNDEFINED) { + if (typeof exports !== 'undefined') { value.forEach(currentImport => { let specifier if (currentImport === IMPORT_NAMESPACE_SPECIFIER) { @@ -155,9 +153,9 @@ const determineUsage = () => { } else { specifier = currentImport } - if (typeof specifier !== UNDEFINED) { + if (typeof specifier !== 'undefined') { const exportStatement = exports.get(specifier) - if (typeof exportStatement !== UNDEFINED) { + if (typeof exportStatement !== 'undefined') { const { whereUsed } = exportStatement whereUsed.add(listKey) exports.set(specifier, { whereUsed }) @@ -181,40 +179,6 @@ const getSrc = src => { * the start of a new eslint run */ const doPreparation = (src, ignoreExports, context) => { - const { id } = context - - // do some sanity checks - if (typeof src !== UNDEFINED && !Array.isArray(src)) { - throw new Error(`Rule ${id}: src option must be an array`) - } - - if (typeof ignoreExports !== UNDEFINED && !Array.isArray(ignoreExports)) { - throw new Error(`Rule ${id}: ignoreExports option must be an array`) - } - - // no empty patterns for paths, as this will cause issues during path resolution - if (src) { - src.forEach(file => { - if (typeof file !== 'string') { - throw new Error(`Rule ${id}: src option must not contain values other than strings`) - } - if (file.length < 1) { - throw new Error(`Rule ${id}: src option must not contain empty strings`) - } - }) - } - - if (ignoreExports) { - ignoreExports.forEach(file => { - if (typeof file !== 'string') { - throw new Error(`Rule ${id}: ignoreExports option must not contain values other than strings`) - } - if (file.length < 1) { - throw new Error(`Rule ${id}: ignoreExports option must not contain empty strings`) - } - }) - } - const srcFiles = resolveFiles(getSrc(src), ignoreExports) prepareImportsAndExports(srcFiles, context) determineUsage() @@ -246,16 +210,26 @@ module.exports = { getSrc, meta: { docs: { url: docsUrl('no-unused-modules') }, - schema: [ - makeOptionsSchema({ + schema: [{ + properties: { src: { description: 'files/paths to be analyzed (only for unused exports)', type: 'array', + minItems: 1, + items: { + type: 'string', + minLength: 1, + }, }, ignoreExports: { description: 'files/paths for which unused exports will not be reported (e.g module entry points)', type: 'array', + minItems: 1, + items: { + type: 'string', + minLength: 1, + }, }, missingExports: { description: 'report modules without any exports', @@ -265,12 +239,48 @@ module.exports = { description: 'report exports without any usage', type: 'boolean', }, - }), - ], + }, + not: { + properties: { + unusedExports: { enum: [false] }, + missingExports: { enum: [false] }, + }, + }, + anyOf:[{ + not: { + properties: { + unusedExports: { enum: [true] }, + }, + }, + required: ['missingExports'], + }, { + not: { + properties: { + missingExports: { enum: [true] }, + }, + }, + required: ['unusedExports'], + }, { + properties: { + unusedExports: { enum: [true] }, + }, + required: ['unusedExports'], + }, { + properties: { + missingExports: { enum: [true] }, + }, + required: ['missingExports'], + }], + }], }, create: context => { - const { src, ignoreExports, missingExports = false, unusedExports = false } = context.options[0] + const { + src, + ignoreExports = [], + missingExports, + unusedExports, + } = context.options[0] if (unusedExports && !preparationDone) { doPreparation(src, ignoreExports, context) @@ -311,7 +321,7 @@ module.exports = { // special case: export * from const exportAll = exports.get(EXPORT_ALL_DECLARATION) - if (typeof exportAll !== UNDEFINED && exportedValue !== IMPORT_DEFAULT_SPECIFIER) { + if (typeof exportAll !== 'undefined' && exportedValue !== IMPORT_DEFAULT_SPECIFIER) { if (exportAll.whereUsed.size > 0) { return } @@ -319,7 +329,7 @@ module.exports = { // special case: namespace import const namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER) - if (typeof namespaceImports !== UNDEFINED) { + if (typeof namespaceImports !== 'undefined') { if (namespaceImports.whereUsed.size > 0) { return } @@ -334,7 +344,7 @@ module.exports = { value = exportedValue } - if (typeof exportStatement !== UNDEFINED){ + if (typeof exportStatement !== 'undefined'){ if ( exportStatement.whereUsed.size < 1) { context.report( node, @@ -363,7 +373,7 @@ module.exports = { // new module has been created during runtime // include it in further processing - if (typeof exports === UNDEFINED) { + if (typeof exports === 'undefined') { exports = new Map() } @@ -413,7 +423,7 @@ module.exports = { let exportAll = exports.get(EXPORT_ALL_DECLARATION) let namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER) - if (typeof namespaceImports === UNDEFINED) { + if (typeof namespaceImports === 'undefined') { namespaceImports = { whereUsed: new Set() } } @@ -433,7 +443,7 @@ module.exports = { } let oldImportPaths = importList.get(file) - if (typeof oldImportPaths === UNDEFINED) { + if (typeof oldImportPaths === 'undefined') { oldImportPaths = new Map() } @@ -521,7 +531,7 @@ module.exports = { newExportAll.forEach(value => { if (!oldExportAll.has(value)) { let imports = oldImportPaths.get(value) - if (typeof imports === UNDEFINED) { + if (typeof imports === 'undefined') { imports = new Set() } imports.add(EXPORT_ALL_DECLARATION) @@ -529,14 +539,14 @@ module.exports = { let exports = exportList.get(value) let currentExport - if (typeof exports !== UNDEFINED) { + if (typeof exports !== 'undefined') { currentExport = exports.get(EXPORT_ALL_DECLARATION) } else { exports = new Map() exportList.set(value, exports) } - if (typeof currentExport !== UNDEFINED) { + if (typeof currentExport !== 'undefined') { currentExport.whereUsed.add(file) } else { const whereUsed = new Set() @@ -552,9 +562,9 @@ module.exports = { imports.delete(EXPORT_ALL_DECLARATION) const exports = exportList.get(value) - if (typeof exports !== UNDEFINED) { + if (typeof exports !== 'undefined') { const currentExport = exports.get(EXPORT_ALL_DECLARATION) - if (typeof currentExport !== UNDEFINED) { + if (typeof currentExport !== 'undefined') { currentExport.whereUsed.delete(file) } } @@ -564,7 +574,7 @@ module.exports = { newDefaultImports.forEach(value => { if (!oldDefaultImports.has(value)) { let imports = oldImportPaths.get(value) - if (typeof imports === UNDEFINED) { + if (typeof imports === 'undefined') { imports = new Set() } imports.add(IMPORT_DEFAULT_SPECIFIER) @@ -572,14 +582,14 @@ module.exports = { let exports = exportList.get(value) let currentExport - if (typeof exports !== UNDEFINED) { + if (typeof exports !== 'undefined') { currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER) } else { exports = new Map() exportList.set(value, exports) } - if (typeof currentExport !== UNDEFINED) { + if (typeof currentExport !== 'undefined') { currentExport.whereUsed.add(file) } else { const whereUsed = new Set() @@ -595,9 +605,9 @@ module.exports = { imports.delete(IMPORT_DEFAULT_SPECIFIER) const exports = exportList.get(value) - if (typeof exports !== UNDEFINED) { + if (typeof exports !== 'undefined') { const currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER) - if (typeof currentExport !== UNDEFINED) { + if (typeof currentExport !== 'undefined') { currentExport.whereUsed.delete(file) } } @@ -607,7 +617,7 @@ module.exports = { newNamespaceImports.forEach(value => { if (!oldNamespaceImports.has(value)) { let imports = oldImportPaths.get(value) - if (typeof imports === UNDEFINED) { + if (typeof imports === 'undefined') { imports = new Set() } imports.add(IMPORT_NAMESPACE_SPECIFIER) @@ -615,14 +625,14 @@ module.exports = { let exports = exportList.get(value) let currentExport - if (typeof exports !== UNDEFINED) { + if (typeof exports !== 'undefined') { currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER) } else { exports = new Map() exportList.set(value, exports) } - if (typeof currentExport !== UNDEFINED) { + if (typeof currentExport !== 'undefined') { currentExport.whereUsed.add(file) } else { const whereUsed = new Set() @@ -638,9 +648,9 @@ module.exports = { imports.delete(IMPORT_NAMESPACE_SPECIFIER) const exports = exportList.get(value) - if (typeof exports !== UNDEFINED) { + if (typeof exports !== 'undefined') { const currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER) - if (typeof currentExport !== UNDEFINED) { + if (typeof currentExport !== 'undefined') { currentExport.whereUsed.delete(file) } } @@ -650,7 +660,7 @@ module.exports = { newImports.forEach((value, key) => { if (!oldImports.has(key)) { let imports = oldImportPaths.get(value) - if (typeof imports === UNDEFINED) { + if (typeof imports === 'undefined') { imports = new Set() } imports.add(key) @@ -658,14 +668,14 @@ module.exports = { let exports = exportList.get(value) let currentExport - if (typeof exports !== UNDEFINED) { + if (typeof exports !== 'undefined') { currentExport = exports.get(key) } else { exports = new Map() exportList.set(value, exports) } - if (typeof currentExport !== UNDEFINED) { + if (typeof currentExport !== 'undefined') { currentExport.whereUsed.add(file) } else { const whereUsed = new Set() @@ -681,9 +691,9 @@ module.exports = { imports.delete(key) const exports = exportList.get(value) - if (typeof exports !== UNDEFINED) { + if (typeof exports !== 'undefined') { const currentExport = exports.get(key) - if (typeof currentExport !== UNDEFINED) { + if (typeof currentExport !== 'undefined') { currentExport.whereUsed.delete(file) } } diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 428e834df5..d4dcc7724d 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -22,40 +22,6 @@ const unusedExportsOptions = [{ ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')], }] -describe('doPreparation throws correct errors', () => { - const context = { id: 'no-unused-modules' } - it('should throw an error, if src is not an array', () => { - expect(doPreparation.bind(doPreparation, null, null, context)).to.throw(`Rule ${context.id}: src option must be an array`) - }) - it('should throw an error, if ignoreExports is not an array', () => { - expect(doPreparation.bind(doPreparation, [], null, context)).to.throw(`Rule ${context.id}: ignoreExports option must be an array`) - }) - it('should throw an error, if src contains empty strings', () => { - expect(doPreparation.bind(doPreparation, [''], [], context)).to.throw(`Rule ${context.id}: src option must not contain empty strings`) - }) - it('should throw an error, if src contains values other than strings', () => { - expect(doPreparation.bind(doPreparation, [false], [], context)).to.throw(`Rule ${context.id}: src option must not contain values other than strings`) - }) - it('should throw an error, if src contains values other than strings', () => { - expect(doPreparation.bind(doPreparation, [null], [], context)).to.throw(`Rule ${context.id}: src option must not contain values other than strings`) - }) - it('should throw an error, if src contains values other than strings', () => { - expect(doPreparation.bind(doPreparation, [undefined], [], context)).to.throw(`Rule ${context.id}: src option must not contain values other than strings`) - }) - it('should throw an error, if ignoreExports contains empty strings', () => { - expect(doPreparation.bind(doPreparation, ['src'], [''], context)).to.throw(`Rule ${context.id}: ignoreExports option must not contain empty strings`) - }) - it('should throw an error, if ignoreExports contains values other than strings', () => { - expect(doPreparation.bind(doPreparation, ['src'], [false], context)).to.throw(`Rule ${context.id}: ignoreExports option must not contain values other than strings`) - }) - it('should throw an error, if ignoreExports contains values other than strings', () => { - expect(doPreparation.bind(doPreparation, ['src'], [null], context)).to.throw(`Rule ${context.id}: ignoreExports option must not contain values other than strings`) - }) - it('should throw an error, if ignoreExports contains values other than strings', () => { - expect(doPreparation.bind(doPreparation, ['src'], [undefined], context)).to.throw(`Rule ${context.id}: ignoreExports option must not contain values other than strings`) - }) -}) - describe('getSrc returns correct source', () => { it('if src is provided', () => { const src = ['file-a.js'] From 0f5c2a59662f382af5d9f21740edcae707078779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Tue, 18 Dec 2018 21:53:23 +0100 Subject: [PATCH 067/151] New: `no-unused-modules` rule - implemented latest feedback --- docs/rules/no-unused-modules.md | 10 +++---- src/ExportMap.js | 10 +++---- src/rules/no-unused-modules.js | 53 +++++++++++++++++---------------- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md index fa8d375ecf..96d689123a 100644 --- a/docs/rules/no-unused-modules.md +++ b/docs/rules/no-unused-modules.md @@ -1,8 +1,8 @@ # import/no-unused-modules Reports: - - modules without any or - - individual exports not being used within other modules + - modules without any exports + - individual exports not being statically `import`ed or `require`ed from other modules in the same project Note: dynamic imports are currently not supported. @@ -13,10 +13,10 @@ Note: dynamic imports are currently not supported. This rule takes the following option: -- `src`: an array with files/paths to be analyzed. It only for applies for unused exports. Defaults to `process.cwd()`, if not provided -- `ignoreExports`: an array with files/paths for which unused exports will not be reported (e.g module entry points) +- `src`: an array with files/paths to be analyzed. It only applies to unused exports. Defaults to `process.cwd()`, if not provided +- `ignoreExports`: an array with files/paths for which unused exports will not be reported (e.g module entry points in a published package) - `missingExports`: if `true`, files without any exports are reported -- `unusedExports`: if `true`, exports without any usage within other modules are reported. +- `unusedExports`: if `true`, exports without any static usage within other modules are reported. ### Example for missing exports diff --git a/src/ExportMap.js b/src/ExportMap.js index f18c0bc1b2..5e12351957 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -380,14 +380,12 @@ ExportMap.parse = function (path, content, context) { function captureDependency(declaration) { if (declaration.source == null) return null const importedSpecifiers = new Set() + const supportedTypes = new Set(['ImportDefaultSpecifier', 'ImportNamespaceSpecifier']) if (declaration.specifiers) { declaration.specifiers.forEach(specifier => { - if (specifier.type === 'ImportDefaultSpecifier') { - importedSpecifiers.add('ImportDefaultSpecifier') - } - if (specifier.type === 'ImportNamespaceSpecifier') { - importedSpecifiers.add('ImportNamespaceSpecifier') - } + if (supportedTypes.has(specifier.type)) { + importedSpecifiers.add(specifier.type) + } if (specifier.type === 'ImportSpecifier') { importedSpecifiers.add(specifier.local.name) } diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 1c8d0b8ec2..02de13bb07 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -11,9 +11,9 @@ import docsUrl from '../docsUrl' // eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3 let listFilesToProcess try { - listFilesToProcess = require('eslint/lib/util/glob-util').listFilesToProcess -} catch (err) { listFilesToProcess = require('eslint/lib/util/glob-utils').listFilesToProcess +} catch (err) { + listFilesToProcess = require('eslint/lib/util/glob-util').listFilesToProcess } const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration' @@ -32,7 +32,7 @@ const exportList = new Map() const ignoredFiles = new Set() const isNodeModule = path => { - return path.indexOf('node_modules') > -1 + return path.includes('node_modules') } /** @@ -49,10 +49,7 @@ const resolveFiles = (src, ignoreExports) => { ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename)) // prepare list of source files, don't consider files from node_modules - srcFileList.forEach(({ filename }) => { - if (isNodeModule(filename)) { - return - } + srcFileList.filter(({ filename }) => !isNodeModule(filename)).forEach(({ filename }) => { srcFiles.add(filename) }) return srcFiles @@ -185,25 +182,29 @@ const doPreparation = (src, ignoreExports, context) => { preparationDone = true } -const newNamespaceImportExists = specifiers => { - let hasNewNamespaceImport = false - specifiers.forEach(specifier => { - if (specifier.type === IMPORT_NAMESPACE_SPECIFIER) { - hasNewNamespaceImport = true - } - }) - return hasNewNamespaceImport -} - -const newDefaultImportExists = specifiers => { - let hasNewDefaultImport = false - specifiers.forEach(specifier => { - if (specifier.type === IMPORT_DEFAULT_SPECIFIER) { - hasNewDefaultImport = true - } - }) - return hasNewDefaultImport -} +// const newNamespaceImportExists = specifiers => { +// let hasNewNamespaceImport = false +// specifiers.forEach(specifier => { +// if (specifier.type === IMPORT_NAMESPACE_SPECIFIER) { +// hasNewNamespaceImport = true +// } +// }) +// return hasNewNamespaceImport +// } + +const newNamespaceImportExists = specifiers => specifiers.some(({ type }) => type === IMPORT_NAMESPACE_SPECIFIER) + +const newDefaultImportExists = specifiers => specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER) + +// const newDefaultImportExists = specifiers => { +// let hasNewDefaultImport = false +// specifiers.forEach(specifier => { +// if (specifier.type === IMPORT_DEFAULT_SPECIFIER) { +// hasNewDefaultImport = true +// } +// }) +// return hasNewDefaultImport +// } module.exports = { doPreparation, From 191c77bbeb2bb191d1382220622aa8e9fbda4a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sun, 23 Dec 2018 10:31:19 +0100 Subject: [PATCH 068/151] New: `no-unused-modules` rule - added trailing newlines --- package.json | 2 +- tests/files/no-unused-modules/file-0.js | 2 +- tests/files/no-unused-modules/file-b.js | 2 +- tests/files/no-unused-modules/file-c.js | 2 +- tests/files/no-unused-modules/file-d.js | 2 +- tests/files/no-unused-modules/file-e.js | 2 +- tests/files/no-unused-modules/file-f.js | 2 +- tests/files/no-unused-modules/file-g.js | 2 +- tests/files/no-unused-modules/file-h.js | 2 +- tests/files/no-unused-modules/file-i.js | 2 +- tests/files/no-unused-modules/file-ignored-a.js | 2 +- tests/files/no-unused-modules/file-ignored-b.js | 2 +- tests/files/no-unused-modules/file-ignored-c.js | 2 +- tests/files/no-unused-modules/file-ignored-d.js | 2 +- tests/files/no-unused-modules/file-ignored-e.js | 2 +- tests/files/no-unused-modules/file-ignored-l.js | 2 +- tests/files/no-unused-modules/file-j.js | 2 +- tests/files/no-unused-modules/file-k.js | 2 +- tests/files/no-unused-modules/file-l.js | 2 +- tests/files/no-unused-modules/file-m.js | 2 +- tests/files/no-unused-modules/file-n.js | 2 +- tests/files/no-unused-modules/file-o.js | 2 +- tests/src/rules/no-unused-modules.js | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 7d39395a76..cb001b0c87 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "chai": "^3.5.0", "coveralls": "^3.0.0", "cross-env": "^4.0.0", - "eslint": "2.x - 5.x", + "eslint": "^5.11.0", "eslint-import-resolver-node": "file:./resolvers/node", "eslint-import-resolver-typescript": "^1.0.2", "eslint-import-resolver-webpack": "file:./resolvers/webpack", diff --git a/tests/files/no-unused-modules/file-0.js b/tests/files/no-unused-modules/file-0.js index c90c2af6c7..e7615ad4f6 100644 --- a/tests/files/no-unused-modules/file-0.js +++ b/tests/files/no-unused-modules/file-0.js @@ -9,4 +9,4 @@ import { h2 } from './file-h' import * as l from './file-l' export * from './file-n' export { default, o0, o3 } from './file-o' -export { p } from './file-p' \ No newline at end of file +export { p } from './file-p' diff --git a/tests/files/no-unused-modules/file-b.js b/tests/files/no-unused-modules/file-b.js index 63a6208637..2e9a7c1c29 100644 --- a/tests/files/no-unused-modules/file-b.js +++ b/tests/files/no-unused-modules/file-b.js @@ -1 +1 @@ -export const b = 2 \ No newline at end of file +export const b = 2 diff --git a/tests/files/no-unused-modules/file-c.js b/tests/files/no-unused-modules/file-c.js index 0f47a8510f..44d2b2cc38 100644 --- a/tests/files/no-unused-modules/file-c.js +++ b/tests/files/no-unused-modules/file-c.js @@ -4,4 +4,4 @@ function c2() { return 3 } -export { c1, c2 } \ No newline at end of file +export { c1, c2 } diff --git a/tests/files/no-unused-modules/file-d.js b/tests/files/no-unused-modules/file-d.js index 90ae456580..c8b6f3fa67 100644 --- a/tests/files/no-unused-modules/file-d.js +++ b/tests/files/no-unused-modules/file-d.js @@ -1,3 +1,3 @@ export function d() { return 4 -} \ No newline at end of file +} diff --git a/tests/files/no-unused-modules/file-e.js b/tests/files/no-unused-modules/file-e.js index d93c4eface..a2b05f6041 100644 --- a/tests/files/no-unused-modules/file-e.js +++ b/tests/files/no-unused-modules/file-e.js @@ -1,3 +1,3 @@ const e0 = 5 -export { e0 as e } \ No newline at end of file +export { e0 as e } diff --git a/tests/files/no-unused-modules/file-f.js b/tests/files/no-unused-modules/file-f.js index a316dbbf82..b2a29e5597 100644 --- a/tests/files/no-unused-modules/file-f.js +++ b/tests/files/no-unused-modules/file-f.js @@ -1 +1 @@ -export default () => 1 \ No newline at end of file +export default () => 1 diff --git a/tests/files/no-unused-modules/file-g.js b/tests/files/no-unused-modules/file-g.js index 66e8462b10..4a6bb623d7 100644 --- a/tests/files/no-unused-modules/file-g.js +++ b/tests/files/no-unused-modules/file-g.js @@ -1 +1 @@ -export const g = 2 \ No newline at end of file +export const g = 2 diff --git a/tests/files/no-unused-modules/file-h.js b/tests/files/no-unused-modules/file-h.js index 38ec42a3ba..b38d70e548 100644 --- a/tests/files/no-unused-modules/file-h.js +++ b/tests/files/no-unused-modules/file-h.js @@ -4,4 +4,4 @@ function h2() { return 3 } -export { h1, h2 } \ No newline at end of file +export { h1, h2 } diff --git a/tests/files/no-unused-modules/file-i.js b/tests/files/no-unused-modules/file-i.js index 7a63049efb..6c1fee78bc 100644 --- a/tests/files/no-unused-modules/file-i.js +++ b/tests/files/no-unused-modules/file-i.js @@ -4,4 +4,4 @@ function i2() { return 3 } -export { i1, i2 } \ No newline at end of file +export { i1, i2 } diff --git a/tests/files/no-unused-modules/file-ignored-a.js b/tests/files/no-unused-modules/file-ignored-a.js index a316dbbf82..b2a29e5597 100644 --- a/tests/files/no-unused-modules/file-ignored-a.js +++ b/tests/files/no-unused-modules/file-ignored-a.js @@ -1 +1 @@ -export default () => 1 \ No newline at end of file +export default () => 1 diff --git a/tests/files/no-unused-modules/file-ignored-b.js b/tests/files/no-unused-modules/file-ignored-b.js index 63a6208637..2e9a7c1c29 100644 --- a/tests/files/no-unused-modules/file-ignored-b.js +++ b/tests/files/no-unused-modules/file-ignored-b.js @@ -1 +1 @@ -export const b = 2 \ No newline at end of file +export const b = 2 diff --git a/tests/files/no-unused-modules/file-ignored-c.js b/tests/files/no-unused-modules/file-ignored-c.js index 0f47a8510f..44d2b2cc38 100644 --- a/tests/files/no-unused-modules/file-ignored-c.js +++ b/tests/files/no-unused-modules/file-ignored-c.js @@ -4,4 +4,4 @@ function c2() { return 3 } -export { c1, c2 } \ No newline at end of file +export { c1, c2 } diff --git a/tests/files/no-unused-modules/file-ignored-d.js b/tests/files/no-unused-modules/file-ignored-d.js index 90ae456580..c8b6f3fa67 100644 --- a/tests/files/no-unused-modules/file-ignored-d.js +++ b/tests/files/no-unused-modules/file-ignored-d.js @@ -1,3 +1,3 @@ export function d() { return 4 -} \ No newline at end of file +} diff --git a/tests/files/no-unused-modules/file-ignored-e.js b/tests/files/no-unused-modules/file-ignored-e.js index d93c4eface..a2b05f6041 100644 --- a/tests/files/no-unused-modules/file-ignored-e.js +++ b/tests/files/no-unused-modules/file-ignored-e.js @@ -1,3 +1,3 @@ const e0 = 5 -export { e0 as e } \ No newline at end of file +export { e0 as e } diff --git a/tests/files/no-unused-modules/file-ignored-l.js b/tests/files/no-unused-modules/file-ignored-l.js index 7ce44d4b9d..48b2e14ad0 100644 --- a/tests/files/no-unused-modules/file-ignored-l.js +++ b/tests/files/no-unused-modules/file-ignored-l.js @@ -3,4 +3,4 @@ const l = 10 export { l0 as l1, l } -export default () => {} \ No newline at end of file +export default () => {} diff --git a/tests/files/no-unused-modules/file-j.js b/tests/files/no-unused-modules/file-j.js index e5928f8598..c59fb69273 100644 --- a/tests/files/no-unused-modules/file-j.js +++ b/tests/files/no-unused-modules/file-j.js @@ -1,3 +1,3 @@ export function j() { return 4 -} \ No newline at end of file +} diff --git a/tests/files/no-unused-modules/file-k.js b/tests/files/no-unused-modules/file-k.js index cdf5333b56..62edf882d7 100644 --- a/tests/files/no-unused-modules/file-k.js +++ b/tests/files/no-unused-modules/file-k.js @@ -1,3 +1,3 @@ const k0 = 5 -export { k0 as k } \ No newline at end of file +export { k0 as k } diff --git a/tests/files/no-unused-modules/file-l.js b/tests/files/no-unused-modules/file-l.js index 7ce44d4b9d..48b2e14ad0 100644 --- a/tests/files/no-unused-modules/file-l.js +++ b/tests/files/no-unused-modules/file-l.js @@ -3,4 +3,4 @@ const l = 10 export { l0 as l1, l } -export default () => {} \ No newline at end of file +export default () => {} diff --git a/tests/files/no-unused-modules/file-m.js b/tests/files/no-unused-modules/file-m.js index c11891f81d..f25fb35f47 100644 --- a/tests/files/no-unused-modules/file-m.js +++ b/tests/files/no-unused-modules/file-m.js @@ -3,4 +3,4 @@ const m = 10 export { m0 as m1, m } -export default () => {} \ No newline at end of file +export default () => {} diff --git a/tests/files/no-unused-modules/file-n.js b/tests/files/no-unused-modules/file-n.js index 92c1b6feb3..7ac2e63744 100644 --- a/tests/files/no-unused-modules/file-n.js +++ b/tests/files/no-unused-modules/file-n.js @@ -3,4 +3,4 @@ const n1 = 42 export { n0, n1 } -export default () => {} \ No newline at end of file +export default () => {} diff --git a/tests/files/no-unused-modules/file-o.js b/tests/files/no-unused-modules/file-o.js index 6f18f00c01..002bd8cb66 100644 --- a/tests/files/no-unused-modules/file-o.js +++ b/tests/files/no-unused-modules/file-o.js @@ -3,4 +3,4 @@ const o1 = 1 export { o0, o1 as o2 } -export default () => {} \ No newline at end of file +export default () => {} diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index d4dcc7724d..ad51d4ea84 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -560,4 +560,4 @@ describe('test behaviour for new file', () => { fs.unlinkSync(testFilePath('./no-unused-modules/file-added-4.js.js')) } }) -}) \ No newline at end of file +}) From ae9942fb5994f4dda8a41ebb77bfda4259785192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sun, 23 Dec 2018 15:10:03 +0100 Subject: [PATCH 069/151] New: `no-unused-modules` rule - added stricter check for node_modules --- src/rules/no-unused-modules.js | 31 +++++++--------------------- tests/src/rules/no-unused-modules.js | 17 ++++++++++++++- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 02de13bb07..23e2ff6dba 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -32,7 +32,7 @@ const exportList = new Map() const ignoredFiles = new Set() const isNodeModule = path => { - return path.includes('node_modules') + return /\/(node_modules)\//.test(path) } /** @@ -182,31 +182,14 @@ const doPreparation = (src, ignoreExports, context) => { preparationDone = true } -// const newNamespaceImportExists = specifiers => { -// let hasNewNamespaceImport = false -// specifiers.forEach(specifier => { -// if (specifier.type === IMPORT_NAMESPACE_SPECIFIER) { -// hasNewNamespaceImport = true -// } -// }) -// return hasNewNamespaceImport -// } - -const newNamespaceImportExists = specifiers => specifiers.some(({ type }) => type === IMPORT_NAMESPACE_SPECIFIER) - -const newDefaultImportExists = specifiers => specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER) - -// const newDefaultImportExists = specifiers => { -// let hasNewDefaultImport = false -// specifiers.forEach(specifier => { -// if (specifier.type === IMPORT_DEFAULT_SPECIFIER) { -// hasNewDefaultImport = true -// } -// }) -// return hasNewDefaultImport -// } +const newNamespaceImportExists = specifiers => + specifiers.some(({ type }) => type === IMPORT_NAMESPACE_SPECIFIER) + +const newDefaultImportExists = specifiers => + specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER) module.exports = { + isNodeModule, doPreparation, getSrc, meta: { diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index ad51d4ea84..eab66bf5c7 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -4,8 +4,8 @@ import { RuleTester } from 'eslint' import { expect } from 'chai' import fs from 'fs' -const doPreparation = require( '../../../src/rules/no-unused-modules').doPreparation const getSrc = require( '../../../src/rules/no-unused-modules').getSrc +const isNodeModule = require( '../../../src/rules/no-unused-modules').isNodeModule const ruleTester = new RuleTester() , rule = require('rules/no-unused-modules') @@ -32,6 +32,21 @@ describe('getSrc returns correct source', () => { }) }) +describe('isNodeModule returns correct value', () => { + it('true for "/node_modules/"', () => { + expect(isNodeModule('/node_modules/')).to.be.true + }) + it('true for "/node_modules/package/file.js"', () => { + expect(isNodeModule('/node_modules/package/file.js')).to.be.true + }) + it('false for "/node_modules.js"', () => { + expect(isNodeModule('/node_modules.js')).to.be.false + }) + it('false for "node_modules_old"', () => { + expect(isNodeModule('node_modules_old')).to.be.false + }) +}) + // tests for missing exports ruleTester.run('no-unused-modules', rule, { valid: [ From 32f4c232367c3d6e20b306d260176e08502cf882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sun, 23 Dec 2018 22:20:40 +0100 Subject: [PATCH 070/151] New: `no-unused-modules` rule - revert changing eslint version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cb001b0c87..7d39395a76 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "chai": "^3.5.0", "coveralls": "^3.0.0", "cross-env": "^4.0.0", - "eslint": "^5.11.0", + "eslint": "2.x - 5.x", "eslint-import-resolver-node": "file:./resolvers/node", "eslint-import-resolver-typescript": "^1.0.2", "eslint-import-resolver-webpack": "file:./resolvers/webpack", From 90f7217273daa92adbbb02a6f0f0a9df6b00ca5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sun, 23 Dec 2018 22:42:04 +0100 Subject: [PATCH 071/151] New: `no-unused-modules` rule - removed whitespace, replaced if-stmt --- src/rules/no-unused-modules.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 23e2ff6dba..7701279a2b 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -321,15 +321,10 @@ module.exports = { const exportStatement = exports.get(exportedValue) - let value = '' - if (exportedValue === IMPORT_DEFAULT_SPECIFIER) { - value = DEFAULT - } else { - value = exportedValue - } + const value = exportedValue === IMPORT_DEFAULT_SPECIFIER ? DEFAULT : exportedValue if (typeof exportStatement !== 'undefined'){ - if ( exportStatement.whereUsed.size < 1) { + if (exportStatement.whereUsed.size < 1) { context.report( node, `exported declaration '${value}' not used within other modules` From 17e29d8fe7e121dcfcbe18bd9046a003e6d97b61 Mon Sep 17 00:00:00 2001 From: Tim Kraut Date: Mon, 18 Feb 2019 11:59:08 +0100 Subject: [PATCH 072/151] Refactor no-useless-path-segments rule --- src/rules/no-useless-path-segments.js | 79 +++++++++++++++++---------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/src/rules/no-useless-path-segments.js b/src/rules/no-useless-path-segments.js index 2ad207fada..3fdb397c66 100644 --- a/src/rules/no-useless-path-segments.js +++ b/src/rules/no-useless-path-segments.js @@ -3,10 +3,9 @@ * @author Thomas Grainger */ -import path from 'path' -import sumBy from 'lodash/sumBy' -import resolve from 'eslint-module-utils/resolve' import moduleVisitor from 'eslint-module-utils/moduleVisitor' +import resolve from 'eslint-module-utils/resolve' +import path from 'path' import docsUrl from '../docsUrl' /** @@ -19,19 +18,22 @@ import docsUrl from '../docsUrl' * ..foo/bar -> ./..foo/bar * foo/bar -> ./foo/bar * - * @param rel {string} relative posix path potentially missing leading './' + * @param relativePath {string} relative posix path potentially missing leading './' * @returns {string} relative posix path that always starts with a ./ **/ -function toRel(rel) { - const stripped = rel.replace(/\/$/g, '') +function toRelativePath(relativePath) { + const stripped = relativePath.replace(/\/$/g, '') // Remove trailing / + return /^((\.\.)|(\.))($|\/)/.test(stripped) ? stripped : `./${stripped}` } function normalize(fn) { - return toRel(path.posix.normalize(fn)) + return toRelativePath(path.posix.normalize(fn)) } -const countRelParent = x => sumBy(x, v => v === '..') +const countRelativeParents = (pathSegments) => pathSegments.reduce( + (sum, pathSegment) => pathSegment === '..' ? sum + 1 : sum, 0 +) module.exports = { meta: { @@ -51,59 +53,76 @@ module.exports = { ], fixable: 'code', + messages: { + uselessPath: 'Useless path segments for "{{ path }}", should be "{{ proposedPath }}"', + }, }, - create: function (context) { + create(context) { const currentDir = path.dirname(context.getFilename()) + const config = context.options[0] function checkSourceValue(source) { - const { value } = source + const { value: importPath } = source - function report(proposed) { + function report(proposedPath) { context.report({ node: source, - message: `Useless path segments for "${value}", should be "${proposed}"`, - fix: fixer => fixer.replaceText(source, JSON.stringify(proposed)), + messageId: 'uselessPath', + data: { + path: importPath, + proposedPath, + }, + fix: fixer => fixer.replaceText(source, JSON.stringify(proposedPath)), }) } - if (!value.startsWith('.')) { + // Only relative imports are relevant for this rule --> Skip checking + if (!importPath.startsWith('.')) { return } - const resolvedPath = resolve(value, context) - const normed = normalize(value) - if (normed !== value && resolvedPath === resolve(normed, context)) { - return report(normed) + // Report rule violation if path is not the shortest possible + const resolvedPath = resolve(importPath, context) + const normedPath = normalize(importPath) + const resolvedNormedPath = resolve(normedPath, context) + if (normedPath !== importPath && resolvedPath === resolvedNormedPath) { + return report(normedPath) } - if (value.startsWith('./')) { + // Path is shortest possible + starts from the current directory --> Return directly + if (importPath.startsWith('./')) { return } + // Path is not existing --> Return directly (following code requires path to be defined) if (resolvedPath === undefined) { return } - const expected = path.relative(currentDir, resolvedPath) - const expectedSplit = expected.split(path.sep) - const valueSplit = value.replace(/^\.\//, '').split('/') - const valueNRelParents = countRelParent(valueSplit) - const expectedNRelParents = countRelParent(expectedSplit) - const diff = valueNRelParents - expectedNRelParents + const expected = path.relative(currentDir, resolvedPath) // Expected import path + const expectedSplit = expected.split(path.sep) // Split by / or \ (depending on OS) + const importPathSplit = importPath.replace(/^\.\//, '').split('/') + const countImportPathRelativeParents = countRelativeParents(importPathSplit) + const countExpectedRelativeParents = countRelativeParents(expectedSplit) + const diff = countImportPathRelativeParents - countExpectedRelativeParents + // Same number of relative parents --> Paths are the same --> Return directly if (diff <= 0) { return } + // Report and propose minimal number of required relative parents return report( - toRel(valueSplit - .slice(0, expectedNRelParents) - .concat(valueSplit.slice(valueNRelParents + diff)) - .join('/')) + toRelativePath( + importPathSplit + .slice(0, countExpectedRelativeParents) + .concat(importPathSplit.slice(countImportPathRelativeParents + diff)) + .join('/') + ) ) } - return moduleVisitor(checkSourceValue, context.options[0]) + return moduleVisitor(checkSourceValue, config) }, } From 651829d6a2862b8fa2e33426421a5dba526e4cdb Mon Sep 17 00:00:00 2001 From: jeffshaver Date: Wed, 20 Feb 2019 21:13:45 -0500 Subject: [PATCH 073/151] [Fix] allow aliases that start with @ to be "internal" Fixes #1293. --- src/core/importType.js | 5 +++-- tests/files/@my-alias/fn.js | 0 tests/src/core/importType.js | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 tests/files/@my-alias/fn.js diff --git a/src/core/importType.js b/src/core/importType.js index f2d6bda86e..5725e9ec62 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -57,7 +57,8 @@ export function isScopedMain(name) { } function isInternalModule(name, settings, path) { - return externalModuleRegExp.test(name) && !isExternalPath(path, name, settings) + const matchesScopedOrExternalRegExp = scopedRegExp.test(name) || externalModuleRegExp.test(name) + return (matchesScopedOrExternalRegExp && !isExternalPath(path, name, settings)) } function isRelativeToParent(name) { @@ -76,9 +77,9 @@ function isRelativeToSibling(name) { const typeTest = cond([ [isAbsolute, constant('absolute')], [isBuiltIn, constant('builtin')], + [isInternalModule, constant('internal')], [isExternalModule, constant('external')], [isScoped, constant('external')], - [isInternalModule, constant('internal')], [isRelativeToParent, constant('parent')], [isIndex, constant('index')], [isRelativeToSibling, constant('sibling')], diff --git a/tests/files/@my-alias/fn.js b/tests/files/@my-alias/fn.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js index 67de1d8a85..54a5adc3a6 100644 --- a/tests/src/core/importType.js +++ b/tests/src/core/importType.js @@ -50,6 +50,11 @@ describe('importType(name)', function () { const pathContext = testContext({ "import/resolver": { node: { paths: [ path.join(__dirname, '..', '..', 'files') ] } } }) 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: [path.join(__dirname, '..', '..', 'files')] } } }) + expect(importType('@my-alias/fn', pathContext)).to.equal('internal') + }) it("should return 'parent' for internal modules that go through the parent", function() { expect(importType('../foo', context)).to.equal('parent') From 246be822e6ac10b9dc757a395c701bc4e61557bd Mon Sep 17 00:00:00 2001 From: Tim Kraut Date: Mon, 18 Feb 2019 15:09:17 +0100 Subject: [PATCH 074/151] [Tests] update `chai` to v4 --- package.json | 2 +- tests/src/core/getExports.js | 24 ++++++++++++------------ utils/ignore.js | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index b114f9294d..51fd070ed4 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "babel-preset-es2015-argon": "latest", "babel-register": "^6.26.0", "babylon": "^6.15.0", - "chai": "^3.5.0", + "chai": "^4.2.0", "coveralls": "^3.0.2", "cross-env": "^4.0.0", "eslint": "2.x - 5.x", diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 8e01f62acf..80e9d843e3 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -110,26 +110,26 @@ describe('ExportMap', function () { expect(imports.has('fn')).to.be.true expect(imports.get('fn')) - .to.have.deep.property('doc.tags[0].title', 'deprecated') + .to.have.nested.property('doc.tags[0].title', 'deprecated') expect(imports.get('fn')) - .to.have.deep.property('doc.tags[0].description', "please use 'x' instead.") + .to.have.nested.property('doc.tags[0].description', 'please use \'x\' instead.') }) it('works with default imports.', function () { expect(imports.has('default')).to.be.true const importMeta = imports.get('default') - expect(importMeta).to.have.deep.property('doc.tags[0].title', 'deprecated') - expect(importMeta).to.have.deep.property('doc.tags[0].description', 'this is awful, use NotAsBadClass.') + expect(importMeta).to.have.nested.property('doc.tags[0].title', 'deprecated') + expect(importMeta).to.have.nested.property('doc.tags[0].description', 'this is awful, use NotAsBadClass.') }) it('works with variables.', function () { expect(imports.has('MY_TERRIBLE_ACTION')).to.be.true const importMeta = imports.get('MY_TERRIBLE_ACTION') - expect(importMeta).to.have.deep.property( + expect(importMeta).to.have.nested.property( 'doc.tags[0].title', 'deprecated') - expect(importMeta).to.have.deep.property( + expect(importMeta).to.have.nested.property( 'doc.tags[0].description', 'please stop sending/handling this action type.') }) @@ -138,27 +138,27 @@ describe('ExportMap', function () { expect(imports.has('CHAIN_A')).to.be.true const importMeta = imports.get('CHAIN_A') - expect(importMeta).to.have.deep.property( + expect(importMeta).to.have.nested.property( 'doc.tags[0].title', 'deprecated') - expect(importMeta).to.have.deep.property( + expect(importMeta).to.have.nested.property( 'doc.tags[0].description', 'this chain is awful') }) it('works for the second one', function () { expect(imports.has('CHAIN_B')).to.be.true const importMeta = imports.get('CHAIN_B') - expect(importMeta).to.have.deep.property( + expect(importMeta).to.have.nested.property( 'doc.tags[0].title', 'deprecated') - expect(importMeta).to.have.deep.property( + expect(importMeta).to.have.nested.property( 'doc.tags[0].description', 'so awful') }) it('works for the third one, etc.', function () { expect(imports.has('CHAIN_C')).to.be.true const importMeta = imports.get('CHAIN_C') - expect(importMeta).to.have.deep.property( + expect(importMeta).to.have.nested.property( 'doc.tags[0].title', 'deprecated') - expect(importMeta).to.have.deep.property( + expect(importMeta).to.have.nested.property( 'doc.tags[0].description', 'still terrible') }) }) diff --git a/utils/ignore.js b/utils/ignore.js index 91cc731a81..c1ddc8699a 100644 --- a/utils/ignore.js +++ b/utils/ignore.js @@ -1,4 +1,4 @@ -"use strict" +'use strict' exports.__esModule = true const extname = require('path').extname From a49ab8a6bfeb5acdad05dfb3a619da394115d6c4 Mon Sep 17 00:00:00 2001 From: Evan Henley Date: Tue, 5 Mar 2019 17:46:12 -0600 Subject: [PATCH 075/151] [fix] aliased internal modules that look like core modules --- src/core/importType.js | 4 +++- tests/files/constants/index.js | 1 + tests/src/core/importType.js | 24 +++++++++++++++++++++--- 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 tests/files/constants/index.js diff --git a/src/core/importType.js b/src/core/importType.js index 5725e9ec62..7755bb4a24 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -21,7 +21,9 @@ export function isAbsolute(name) { return name.indexOf('/') === 0 } -export function isBuiltIn(name, settings) { +// path is defined only when a resolver resolves to a non-standard path +export function isBuiltIn(name, settings, path) { + if (path) return false const base = baseModule(name) const extras = (settings && settings['import/core-modules']) || [] return coreModules[base] || extras.indexOf(base) > -1 diff --git a/tests/files/constants/index.js b/tests/files/constants/index.js new file mode 100644 index 0000000000..2d7500a680 --- /dev/null +++ b/tests/files/constants/index.js @@ -0,0 +1 @@ +export const FOO = 'FOO' \ No newline at end of file diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js index 54a5adc3a6..603d9afe35 100644 --- a/tests/src/core/importType.js +++ b/tests/src/core/importType.js @@ -7,6 +7,7 @@ import { testContext } from '../utils' describe('importType(name)', function () { const context = testContext() + const pathToTestFiles = path.join(__dirname, '..', '..', 'files') it("should return 'absolute' for paths starting with a /", function() { expect(importType('/', context)).to.equal('absolute') @@ -42,20 +43,37 @@ describe('importType(name)', function () { }) it("should return 'internal' for non-builtins resolved outside of node_modules", function () { - const pathContext = testContext({ "import/resolver": { node: { paths: [ path.join(__dirname, '..', '..', 'files') ] } } }) + const pathContext = testContext({ "import/resolver": { node: { paths: [pathToTestFiles] } } }) expect(importType('importType', pathContext)).to.equal('internal') }) it.skip("should return 'internal' for scoped packages resolved outside of node_modules", function () { - const pathContext = testContext({ "import/resolver": { node: { paths: [ path.join(__dirname, '..', '..', 'files') ] } } }) + 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: [path.join(__dirname, '..', '..', 'files')] } } }) + const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }) expect(importType('@my-alias/fn', pathContext)).to.equal('internal') }) + it("should return 'internal' for aliased internal modules that look like core modules (node resolver)", function () { + const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }) + expect(importType('constants/index', pathContext)).to.equal('internal') + expect(importType('constants/', pathContext)).to.equal('internal') + // resolves exact core modules over internal modules + expect(importType('constants', pathContext)).to.equal('builtin') + }) + + it("should return 'internal' for aliased internal modules that look like core modules (webpack resolver)", function () { + const webpackConfig = { resolve: { modules: [pathToTestFiles, 'node_modules'] } } + const pathContext = testContext({ 'import/resolver': { webpack: { config: webpackConfig } } }) + expect(importType('constants/index', pathContext)).to.equal('internal') + expect(importType('constants/', pathContext)).to.equal('internal') + // the following assertion fails because the webpack resolver runs an resolve.isCore('constants') without first checking paths config + // expect(importType('constants', pathContext)).to.equal('internal') + }) + it("should return 'parent' for internal modules that go through the parent", function() { expect(importType('../foo', context)).to.equal('parent') expect(importType('../../foo', context)).to.equal('parent') From 480894200e800d5e4f5cf90c4bf3d06d76fb2338 Mon Sep 17 00:00:00 2001 From: Tim Kraut Date: Mon, 18 Feb 2019 15:09:17 +0100 Subject: [PATCH 076/151] no-useless-path-segments: Add noUselessIndex option --- README.md | 11 ++ docs/rules/no-useless-path-segments.md | 27 +++++ src/rules/no-useless-path-segments.js | 51 ++++++---- tests/src/core/ignore.js | 34 ++++++- tests/src/rules/no-useless-path-segments.js | 107 ++++++++++++++++++-- utils/ignore.js | 1 + 6 files changed, 207 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 41bbff0a0d..7695ac368d 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,17 @@ A list of file extensions that will be parsed as modules and inspected for This defaults to `['.js']`, unless you are using the `react` shared config, in which case it is specified as `['.js', '.jsx']`. +```js +"settings": { + "import/extensions": [ + ".js", + ".jsx" + ] +} +``` + +If you require more granular extension definitions, you can use: + ```js "settings": { "import/resolver": { diff --git a/docs/rules/no-useless-path-segments.md b/docs/rules/no-useless-path-segments.md index b2ae82a3a7..6a02eab9fa 100644 --- a/docs/rules/no-useless-path-segments.md +++ b/docs/rules/no-useless-path-segments.md @@ -11,6 +11,9 @@ my-project ├── app.js ├── footer.js ├── header.js +└── helpers.js +└── helpers + └── index.js └── pages ├── about.js ├── contact.js @@ -30,6 +33,8 @@ import "../pages/about.js"; // should be "./pages/about.js" import "../pages/about"; // should be "./pages/about" import "./pages//about"; // should be "./pages/about" import "./pages/"; // should be "./pages" +import "./pages/index"; // should be "./pages" (except if there is a ./pages.js file) +import "./pages/index.js"; // should be "./pages" (except if there is a ./pages.js file) ``` The following patterns are NOT considered problems: @@ -46,3 +51,25 @@ import "."; import ".."; import fs from "fs"; ``` + +## Options + +### noUselessIndex + +If you want to detect unnecessary `/index` or `/index.js` (depending on the specified file extensions, see below) imports in your paths, you can enable the option `noUselessIndex`. By default it is set to `false`: +```js +"import/no-useless-path-segments": ["error", { + noUselessIndex: true, +}] +``` + +Additionally to the patterns described above, the following imports are considered problems if `noUselessIndex` is enabled: + +```js +// in my-project/app.js +import "./helpers/index"; // should be "./helpers/" (not auto-fixable to `./helpers` because this would lead to an ambiguous import of `./helpers.js` and `./helpers/index.js`) +import "./pages/index"; // should be "./pages" (auto-fixable) +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. diff --git a/src/rules/no-useless-path-segments.js b/src/rules/no-useless-path-segments.js index 3fdb397c66..ea72e6c54b 100644 --- a/src/rules/no-useless-path-segments.js +++ b/src/rules/no-useless-path-segments.js @@ -3,6 +3,7 @@ * @author Thomas Grainger */ +import { getFileExtensions } from 'eslint-module-utils/ignore' import moduleVisitor from 'eslint-module-utils/moduleVisitor' import resolve from 'eslint-module-utils/resolve' import path from 'path' @@ -31,9 +32,9 @@ function normalize(fn) { return toRelativePath(path.posix.normalize(fn)) } -const countRelativeParents = (pathSegments) => pathSegments.reduce( - (sum, pathSegment) => pathSegment === '..' ? sum + 1 : sum, 0 -) +function countRelativeParents(pathSegments) { + return pathSegments.reduce((sum, pathSegment) => pathSegment === '..' ? sum + 1 : sum, 0) +} module.exports = { meta: { @@ -47,33 +48,28 @@ module.exports = { type: 'object', properties: { commonjs: { type: 'boolean' }, + noUselessIndex: { type: 'boolean' }, }, additionalProperties: false, }, ], fixable: 'code', - messages: { - uselessPath: 'Useless path segments for "{{ path }}", should be "{{ proposedPath }}"', - }, }, create(context) { const currentDir = path.dirname(context.getFilename()) - const config = context.options[0] + const options = context.options[0] function checkSourceValue(source) { const { value: importPath } = source - function report(proposedPath) { + function reportWithProposedPath(proposedPath) { context.report({ node: source, - messageId: 'uselessPath', - data: { - path: importPath, - proposedPath, - }, - fix: fixer => fixer.replaceText(source, JSON.stringify(proposedPath)), + // Note: Using messageIds is not possible due to the support for ESLint 2 and 3 + message: `Useless path segments for "${importPath}", should be "${proposedPath}"`, + fix: fixer => proposedPath && fixer.replaceText(source, JSON.stringify(proposedPath)), }) } @@ -87,7 +83,28 @@ module.exports = { const normedPath = normalize(importPath) const resolvedNormedPath = resolve(normedPath, context) if (normedPath !== importPath && resolvedPath === resolvedNormedPath) { - return report(normedPath) + return reportWithProposedPath(normedPath) + } + + const fileExtensions = getFileExtensions(context.settings) + const regexUnnecessaryIndex = new RegExp( + `.*\\/index(\\${Array.from(fileExtensions).join('|\\')})?$` + ) + + // Check if path contains unnecessary index (including a configured extension) + if (options && options.noUselessIndex && regexUnnecessaryIndex.test(importPath)) { + const parentDirectory = path.dirname(importPath) + + // Try to find ambiguous imports + if (parentDirectory !== '.' && parentDirectory !== '..') { + for (let fileExtension of fileExtensions) { + if (resolve(`${parentDirectory}${fileExtension}`, context)) { + return reportWithProposedPath(`${parentDirectory}/`) + } + } + } + + return reportWithProposedPath(parentDirectory) } // Path is shortest possible + starts from the current directory --> Return directly @@ -113,7 +130,7 @@ module.exports = { } // Report and propose minimal number of required relative parents - return report( + return reportWithProposedPath( toRelativePath( importPathSplit .slice(0, countExpectedRelativeParents) @@ -123,6 +140,6 @@ module.exports = { ) } - return moduleVisitor(checkSourceValue, config) + return moduleVisitor(checkSourceValue, options) }, } diff --git a/tests/src/core/ignore.js b/tests/src/core/ignore.js index cc89f84543..8870158a51 100644 --- a/tests/src/core/ignore.js +++ b/tests/src/core/ignore.js @@ -1,6 +1,6 @@ import { expect } from 'chai' -import isIgnored, { hasValidExtension } from 'eslint-module-utils/ignore' +import isIgnored, { getFileExtensions, hasValidExtension } from 'eslint-module-utils/ignore' import * as utils from '../utils' @@ -55,4 +55,36 @@ describe('ignore', function () { }) }) + describe('getFileExtensions', function () { + it('returns a set with the file extension ".js" if "import/extensions" is not configured', function () { + const fileExtensions = getFileExtensions({}) + + expect(fileExtensions).to.include('.js') + }) + + it('returns a set with the file extensions configured in "import/extension"', function () { + const settings = { + 'import/extensions': ['.js', '.jsx'], + } + + const fileExtensions = getFileExtensions(settings) + + expect(fileExtensions).to.include('.js') + expect(fileExtensions).to.include('.jsx') + }) + + it('returns a set with the file extensions configured in "import/extension" and "import/parsers"', function () { + const settings = { + 'import/parsers': { + 'typescript-eslint-parser': ['.ts', '.tsx'], + }, + } + + const fileExtensions = getFileExtensions(settings) + + expect(fileExtensions).to.include('.js') // If "import/extensions" is not configured, this is the default + expect(fileExtensions).to.include('.ts') + expect(fileExtensions).to.include('.tsx') + }) + }) }) diff --git a/tests/src/rules/no-useless-path-segments.js b/tests/src/rules/no-useless-path-segments.js index ed20440012..923c7efe79 100644 --- a/tests/src/rules/no-useless-path-segments.js +++ b/tests/src/rules/no-useless-path-segments.js @@ -7,20 +7,31 @@ const rule = require('rules/no-useless-path-segments') function runResolverTests(resolver) { ruleTester.run(`no-useless-path-segments (${resolver})`, rule, { valid: [ - // commonjs with default options + // CommonJS modules with default options test({ code: 'require("./../files/malformed.js")' }), - // esmodule + // ES modules with default options test({ code: 'import "./malformed.js"' }), test({ code: 'import "./test-module"' }), test({ code: 'import "./bar/"' }), test({ code: 'import "."' }), test({ code: 'import ".."' }), test({ code: 'import fs from "fs"' }), + test({ code: 'import fs from "fs"' }), + + // ES modules + noUselessIndex + test({ code: 'import "../index"' }), // noUselessIndex is false by default + test({ code: 'import "../my-custom-index"', options: [{ noUselessIndex: true }] }), + test({ code: 'import "./bar.js"', options: [{ noUselessIndex: true }] }), // ./bar/index.js exists + test({ code: 'import "./bar"', options: [{ noUselessIndex: true }] }), + test({ code: 'import "./bar/"', options: [{ noUselessIndex: true }] }), // ./bar.js exists + test({ code: 'import "./malformed.js"', options: [{ noUselessIndex: true }] }), // ./malformed directory does not exist + test({ code: 'import "./malformed"', options: [{ noUselessIndex: true }] }), // ./malformed directory does not exist + test({ code: 'import "./importType"', options: [{ noUselessIndex: true }] }), // ./importType.js does not exist ], invalid: [ - // commonjs + // CommonJS modules test({ code: 'require("./../files/malformed.js")', options: [{ commonjs: true }], @@ -62,7 +73,49 @@ function runResolverTests(resolver) { errors: [ 'Useless path segments for "./deep//a", should be "./deep/a"'], }), - // esmodule + // CommonJS modules + noUselessIndex + test({ + code: 'require("./bar/index.js")', + options: [{ commonjs: true, noUselessIndex: true }], + errors: ['Useless path segments for "./bar/index.js", should be "./bar/"'], // ./bar.js exists + }), + test({ + code: 'require("./bar/index")', + options: [{ commonjs: true, noUselessIndex: true }], + errors: ['Useless path segments for "./bar/index", should be "./bar/"'], // ./bar.js exists + }), + test({ + code: 'require("./importPath/")', + options: [{ commonjs: true, noUselessIndex: true }], + errors: ['Useless path segments for "./importPath/", should be "./importPath"'], // ./importPath.js does not exist + }), + test({ + code: 'require("./importPath/index.js")', + options: [{ commonjs: true, noUselessIndex: true }], + errors: ['Useless path segments for "./importPath/index.js", should be "./importPath"'], // ./importPath.js does not exist + }), + test({ + code: 'require("./importType/index")', + options: [{ commonjs: true, noUselessIndex: true }], + errors: ['Useless path segments for "./importType/index", should be "./importType"'], // ./importPath.js does not exist + }), + test({ + code: 'require("./index")', + options: [{ commonjs: true, noUselessIndex: true }], + errors: ['Useless path segments for "./index", should be "."'], + }), + test({ + code: 'require("../index")', + options: [{ commonjs: true, noUselessIndex: true }], + errors: ['Useless path segments for "../index", should be ".."'], + }), + test({ + code: 'require("../index.js")', + options: [{ commonjs: true, noUselessIndex: true }], + errors: ['Useless path segments for "../index.js", should be ".."'], + }), + + // ES modules test({ code: 'import "./../files/malformed.js"', errors: [ 'Useless path segments for "./../files/malformed.js", should be "../files/malformed.js"'], @@ -95,8 +148,50 @@ function runResolverTests(resolver) { code: 'import "./deep//a"', errors: [ 'Useless path segments for "./deep//a", should be "./deep/a"'], }), - ], - }) + + // ES modules + noUselessIndex + test({ + code: 'import "./bar/index.js"', + options: [{ noUselessIndex: true }], + errors: ['Useless path segments for "./bar/index.js", should be "./bar/"'], // ./bar.js exists + }), + test({ + code: 'import "./bar/index"', + options: [{ noUselessIndex: true }], + errors: ['Useless path segments for "./bar/index", should be "./bar/"'], // ./bar.js exists + }), + test({ + code: 'import "./importPath/"', + options: [{ noUselessIndex: true }], + errors: ['Useless path segments for "./importPath/", should be "./importPath"'], // ./importPath.js does not exist + }), + test({ + code: 'import "./importPath/index.js"', + options: [{ noUselessIndex: true }], + errors: ['Useless path segments for "./importPath/index.js", should be "./importPath"'], // ./importPath.js does not exist + }), + test({ + code: 'import "./importPath/index"', + options: [{ noUselessIndex: true }], + errors: ['Useless path segments for "./importPath/index", should be "./importPath"'], // ./importPath.js does not exist + }), + test({ + code: 'import "./index"', + options: [{ noUselessIndex: true }], + errors: ['Useless path segments for "./index", should be "."'], + }), + test({ + code: 'import "../index"', + options: [{ noUselessIndex: true }], + errors: ['Useless path segments for "../index", should be ".."'], + }), + test({ + code: 'import "../index.js"', + options: [{ noUselessIndex: true }], + errors: ['Useless path segments for "../index.js", should be ".."'], + }), + ], + }) } ['node', 'webpack'].forEach(runResolverTests) diff --git a/utils/ignore.js b/utils/ignore.js index c1ddc8699a..47af8122dd 100644 --- a/utils/ignore.js +++ b/utils/ignore.js @@ -34,6 +34,7 @@ function makeValidExtensionSet(settings) { return exts } +exports.getFileExtensions = makeValidExtensionSet exports.default = function ignore(path, context) { // check extension whitelist first (cheap) From ba0aed9f7eecafa61717aa9e9036a10a579de288 Mon Sep 17 00:00:00 2001 From: Evan Henley Date: Sat, 9 Mar 2019 16:03:58 -0600 Subject: [PATCH 077/151] [webpack] [fix] match coreLibs after resolveSync in webpack-resolver --- resolvers/webpack/index.js | 12 ++++++------ tests/src/core/importType.js | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/resolvers/webpack/index.js b/resolvers/webpack/index.js index 146213a0ca..0f75a28400 100644 --- a/resolvers/webpack/index.js +++ b/resolvers/webpack/index.js @@ -38,10 +38,6 @@ exports.resolve = function (source, file, settings) { source = source.slice(0, finalQuestionMark) } - if (source in coreLibs) { - return { found: true, path: coreLibs[source] } - } - var webpackConfig var configPath = get(settings, 'config') @@ -73,7 +69,7 @@ exports.resolve = function (source, file, settings) { throw e } } else { - log("No config path found relative to", file, "; using {}") + log('No config path found relative to', file, '; using {}') webpackConfig = {} } @@ -123,6 +119,10 @@ exports.resolve = function (source, file, settings) { try { return { found: true, path: resolveSync(path.dirname(file), source) } } catch (err) { + if (source in coreLibs) { + return { found: true, path: coreLibs[source] } + } + log('Error during module resolution:', err) return { found: false } } @@ -136,7 +136,7 @@ function getResolveSync(configPath, webpackConfig) { if (!cached) { cached = { key: cacheKey, - value: createResolveSync(configPath, webpackConfig) + value: createResolveSync(configPath, webpackConfig), } // put in front and pop last item if (_cache.unshift(cached) > MAX_CACHE) { diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js index 603d9afe35..f60063991d 100644 --- a/tests/src/core/importType.js +++ b/tests/src/core/importType.js @@ -70,8 +70,7 @@ describe('importType(name)', function () { const pathContext = testContext({ 'import/resolver': { webpack: { config: webpackConfig } } }) expect(importType('constants/index', pathContext)).to.equal('internal') expect(importType('constants/', pathContext)).to.equal('internal') - // the following assertion fails because the webpack resolver runs an resolve.isCore('constants') without first checking paths config - // expect(importType('constants', pathContext)).to.equal('internal') + expect(importType('constants', pathContext)).to.equal('internal') }) it("should return 'parent' for internal modules that go through the parent", function() { From 37279e056d92f4f84b8b004b32d60c57609b3d2a Mon Sep 17 00:00:00 2001 From: Braden Napier Date: Tue, 19 Mar 2019 15:18:50 -0700 Subject: [PATCH 078/151] support export type named exports from typescript --- src/ExportMap.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ExportMap.js b/src/ExportMap.js index 61900db03b..20ed009d76 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -465,6 +465,7 @@ ExportMap.parse = function (path, content, context) { case 'TypeAlias': // flowtype with babel-eslint parser case 'InterfaceDeclaration': case 'TSEnumDeclaration': + case 'TSTypeAliasDeclaration': case 'TSInterfaceDeclaration': case 'TSAbstractClassDeclaration': case 'TSModuleDeclaration': From be4f1c7941e27e22ba853102b96305d02223e569 Mon Sep 17 00:00:00 2001 From: Andrew Tamura Date: Mon, 25 Mar 2019 11:16:25 -0700 Subject: [PATCH 079/151] Add rules that show corner cases for commonJS --- tests/src/rules/no-commonjs.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/src/rules/no-commonjs.js b/tests/src/rules/no-commonjs.js index b2985203da..72d5bfb19c 100644 --- a/tests/src/rules/no-commonjs.js +++ b/tests/src/rules/no-commonjs.js @@ -39,6 +39,17 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { { code: 'var zero = require(0);' }, { code: 'require("x")', options: [{ allowRequire: true }] }, + // commonJS doesn't care how the path is built. You can use a function to + // dynamically build the module path.t st + { code: 'require(rootRequire("x"))', options: [{ allowRequire: true }] }, + { code: 'require(String("x"))', options: [{ allowRequire: true }] }, + { code: 'require(["x", "y", "z"].join("/"))', options: [{ allowRequire: true }] }, + + // commonJS rules should be scoped to commonJS spec. `rootRequire` is not + // recognized by this commonJS plugin. + { code: 'rootRequire("x")', options: [{ allowRequire: true }] }, + { code: 'rootRequire("x")', options: [{ allowRequire: false}] }, + { code: 'module.exports = function () {}', options: ['allow-primitive-modules'] }, { code: 'module.exports = function () {}', options: [{ allowPrimitiveModules: true }] }, { code: 'module.exports = "foo"', options: ['allow-primitive-modules'] }, From 9ac41f368a63d6a9f7b157f47683e6bd5b538de9 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 30 Mar 2019 23:01:43 +0100 Subject: [PATCH 080/151] no-duplicates: Add autofix --- CHANGELOG.md | 6 + docs/rules/no-duplicates.md | 1 + src/rules/no-duplicates.js | 184 ++++++++++++++++++++++++- tests/src/rules/no-duplicates.js | 226 ++++++++++++++++++++++++++++++- 4 files changed, 410 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9427a79ae6..ef04278d62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] +### Added +- Autofixer for [`no-duplicates`] rule ([#1312], thanks [@lydell]) + ### Fixed - [`order`]: Fix interpreting some external modules being interpreted as internal modules ([#793], [#794] thanks [@ephys]) @@ -512,9 +515,11 @@ for info on changes for earlier releases. [`no-cycle`]: ./docs/rules/no-cycle.md [`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md [`no-named-export`]: ./docs/rules/no-named-export.md +[`no-duplicates`]: ./docs/rules/no-duplicates.md [`memo-parser`]: ./memo-parser/README.md +[#1312]: https://github.com/benmosher/eslint-plugin-import/pull/1312 [#1257]: https://github.com/benmosher/eslint-plugin-import/pull/1257 [#1232]: https://github.com/benmosher/eslint-plugin-import/pull/1232 [#1176]: https://github.com/benmosher/eslint-plugin-import/pull/1176 @@ -809,3 +814,4 @@ for info on changes for earlier releases. [@asapach]: https://github.com/asapach [@sergei-startsev]: https://github.com/sergei-startsev [@ephys]: https://github.com/ephys +[@lydell]: https://github.com/lydell diff --git a/docs/rules/no-duplicates.md b/docs/rules/no-duplicates.md index 580f360119..0641e44186 100644 --- a/docs/rules/no-duplicates.md +++ b/docs/rules/no-duplicates.md @@ -1,6 +1,7 @@ # import/no-duplicates Reports if a resolved path is imported more than once. ++(fixable) The `--fix` option on the [command line] automatically fixes some problems reported by this rule. ESLint core has a similar rule ([`no-duplicate-imports`](http://eslint.org/docs/rules/no-duplicate-imports)), but this version is different in two key ways: diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index 4632ea0ec9..c2b28ee3a0 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -2,21 +2,193 @@ import resolve from 'eslint-module-utils/resolve' import docsUrl from '../docsUrl' function checkImports(imported, context) { - for (let [module, nodes] of imported.entries()) { - if (nodes.size > 1) { - for (let node of nodes) { - context.report(node, `'${module}' imported multiple times.`) + for (const [module, nodes] of imported.entries()) { + if (nodes.length > 1) { + const message = `'${module}' imported multiple times.` + const [first, ...rest] = nodes + const sourceCode = context.getSourceCode() + const fix = getFix(first, rest, sourceCode) + + context.report({ + node: first.source, + message, + fix, // Attach the autofix (if any) to the first import. + }) + + for (const node of rest) { + context.report({ + node: node.source, + message, + }) + } + } + } +} + +function getFix(first, rest, sourceCode) { + const defaultImportNames = new Set( + [first, ...rest].map(getDefaultImportName).filter(Boolean) + ) + + // Bail if there are multiple different default import names – it's up to the + // user to choose which one to keep. + if (defaultImportNames.size > 1) { + return undefined + } + + // It's not obvious what the user wants to do with comments associated with + // duplicate imports, so skip imports with comments when autofixing. + const restWithoutComments = rest.filter(node => !( + hasCommentBefore(node, sourceCode) || + hasCommentAfter(node, sourceCode) || + hasCommentInsideNonSpecifiers(node, sourceCode) + )) + + const specifiers = restWithoutComments + .map(node => { + const tokens = sourceCode.getTokens(node) + const openBrace = tokens.find(token => isPunctuator(token, '{')) + const closeBrace = tokens.find(token => isPunctuator(token, '}')) + + if (openBrace == null || closeBrace == null) { + return undefined + } + + return { + importNode: node, + text: sourceCode.text.slice(openBrace.range[1], closeBrace.range[0]), + hasTrailingComma: isPunctuator(sourceCode.getTokenBefore(closeBrace), ','), + isEmpty: !hasSpecifiers(node), + } + }) + .filter(Boolean) + + const unnecessaryImports = restWithoutComments.filter(node => + !hasSpecifiers(node) && + !specifiers.some(specifier => specifier.importNode === node) + ) + + const shouldAddDefault = getDefaultImportName(first) == null && defaultImportNames.size === 1 + const shouldAddSpecifiers = specifiers.length > 0 + const shouldRemoveUnnecessary = unnecessaryImports.length > 0 + + if (!(shouldAddDefault || shouldAddSpecifiers || shouldRemoveUnnecessary)) { + return undefined + } + + return function* (fixer) { + const tokens = sourceCode.getTokens(first) + const openBrace = tokens.find(token => isPunctuator(token, '{')) + const closeBrace = tokens.find(token => isPunctuator(token, '}')) + const firstToken = sourceCode.getFirstToken(first) + const [defaultImportName] = defaultImportNames + + const firstHasTrailingComma = + closeBrace != null && + isPunctuator(sourceCode.getTokenBefore(closeBrace), ',') + const firstIsEmpty = !hasSpecifiers(first) + + const [specifiersText] = specifiers.reduce( + ([result, needsComma], specifier) => { + return [ + needsComma && !specifier.isEmpty + ? `${result},${specifier.text}` + : `${result}${specifier.text}`, + specifier.isEmpty ? needsComma : true, + ] + }, + ['', !firstHasTrailingComma && !firstIsEmpty] + ) + + if (shouldAddDefault && openBrace == null && shouldAddSpecifiers) { + // `import './foo'` → `import def, {...} from './foo'` + yield fixer.insertTextAfter(firstToken, ` ${defaultImportName}, {${specifiersText}} from`) + } else if (shouldAddDefault && openBrace == null && !shouldAddSpecifiers) { + // `import './foo'` → `import def from './foo'` + yield fixer.insertTextAfter(firstToken, ` ${defaultImportName} from`) + } else if (shouldAddDefault && openBrace != null && closeBrace != null) { + // `import {...} from './foo'` → `import def, {...} from './foo'` + yield fixer.insertTextAfter(firstToken, ` ${defaultImportName},`) + if (shouldAddSpecifiers) { + // `import def, {...} from './foo'` → `import def, {..., ...} from './foo'` + yield fixer.insertTextBefore(closeBrace, specifiersText) } + } else if (!shouldAddDefault && openBrace == null && shouldAddSpecifiers) { + // `import './foo'` → `import {...} from './foo'` + yield fixer.insertTextAfter(firstToken, ` {${specifiersText}} from`) + } else if (!shouldAddDefault && openBrace != null && closeBrace != null) { + // `import {...} './foo'` → `import {..., ...} from './foo'` + yield fixer.insertTextBefore(closeBrace, specifiersText) + } + + // Remove imports whose specifiers have been moved into the first import. + for (const specifier of specifiers) { + yield fixer.remove(specifier.importNode) + } + + // Remove imports whose default import has been moved to the first import, + // and side-effect-only imports that are unnecessary due to the first + // import. + for (const node of unnecessaryImports) { + yield fixer.remove(node) } } } +function isPunctuator(node, value) { + return node.type === 'Punctuator' && node.value === value +} + +// Get the name of the default import of `node`, if any. +function getDefaultImportName(node) { + const defaultSpecifier = node.specifiers + .find(specifier => specifier.type === 'ImportDefaultSpecifier') + return defaultSpecifier != null ? defaultSpecifier.local.name : undefined +} + +// Checks whether `node` has any non-default specifiers. +function hasSpecifiers(node) { + const specifiers = node.specifiers + .filter(specifier => specifier.type === 'ImportSpecifier') + return specifiers.length > 0 +} + +// Checks whether `node` has a comment (that ends) on the previous line or on +// the same line as `node` (starts). +function hasCommentBefore(node, sourceCode) { + return sourceCode.getCommentsBefore(node) + .some(comment => comment.loc.end.line >= node.loc.start.line - 1) +} + +// Checks whether `node` has a comment (that starts) on the same line as `node` +// (ends). +function hasCommentAfter(node, sourceCode) { + return sourceCode.getCommentsAfter(node) + .some(comment => comment.loc.start.line === node.loc.end.line) +} + +// Checks whether `node` has any comments _inside,_ except inside the `{...}` +// part (if any). +function hasCommentInsideNonSpecifiers(node, sourceCode) { + const tokens = sourceCode.getTokens(node) + const openBraceIndex = tokens.findIndex(token => isPunctuator(token, '{')) + const closeBraceIndex = tokens.findIndex(token => isPunctuator(token, '}')) + // Slice away the first token, since we're no looking for comments _before_ + // `node` (only inside). If there's a `{...}` part, look for comments before + // the `{`, but not before the `}` (hence the `+1`s). + const someTokens = openBraceIndex >= 0 && closeBraceIndex >= 0 + ? tokens.slice(1, openBraceIndex + 1).concat(tokens.slice(closeBraceIndex + 1)) + : tokens.slice(1) + return someTokens.some(token => sourceCode.getCommentsBefore(token).length > 0) +} + module.exports = { meta: { type: 'problem', docs: { url: docsUrl('no-duplicates'), }, + fixable: 'code', }, create: function (context) { @@ -29,9 +201,9 @@ module.exports = { const importMap = n.importKind === 'type' ? typesImported : imported if (importMap.has(resolvedPath)) { - importMap.get(resolvedPath).add(n.source) + importMap.get(resolvedPath).push(n) } else { - importMap.set(resolvedPath, new Set([n.source])) + importMap.set(resolvedPath, [n]) } }, diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index 82bccdee05..0da693f561 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -25,17 +25,20 @@ ruleTester.run('no-duplicates', rule, { invalid: [ test({ code: "import { x } from './foo'; import { y } from './foo'", + output: "import { x , y } from './foo'; ", errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), test({ - code: "import { x } from './foo'; import { y } from './foo'; import { z } from './foo'", + code: "import {x} from './foo'; import {y} from './foo'; import { z } from './foo'", + output: "import {x,y, z } from './foo'; ", errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), // ensure resolved path results in warnings test({ code: "import { x } from './bar'; import { y } from 'bar';", + output: "import { x , y } from './bar'; ", settings: { 'import/resolve': { paths: [path.join( process.cwd() , 'tests', 'files' @@ -46,6 +49,8 @@ ruleTester.run('no-duplicates', rule, { // #86: duplicate unresolved modules should be flagged test({ code: "import foo from 'non-existent'; import bar from 'non-existent';", + // Autofix bail because of different default import names. + output: "import foo from 'non-existent'; import bar from 'non-existent';", errors: [ "'non-existent' imported multiple times.", "'non-existent' imported multiple times.", @@ -54,8 +59,227 @@ ruleTester.run('no-duplicates', rule, { test({ code: "import type { x } from './foo'; import type { y } from './foo'", + output: "import type { x , y } from './foo'; ", parser: 'babel-eslint', errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), + + test({ + code: "import './foo'; import './foo'", + output: "import './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import { x, /* x */ } from './foo'; import {//y\ny//y2\n} from './foo'", + output: "import { x, /* x */ //y\ny//y2\n} from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import {x} from './foo'; import {} from './foo'", + output: "import {x} from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import {x} from './foo'; import {} from './foo'; import {/*c*/} from './foo'; import {y} from './foo'", + output: "import {x/*c*/,y} from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import { } from './foo'; import {x} from './foo'", + output: "import { x} from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import './foo'; import {x} from './foo'", + output: "import {x} from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import'./foo'; import {x} from './foo'", + output: "import {x} from'./foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import './foo'; import { /*x*/} from './foo'; import {//y\n} from './foo'; import {z} from './foo'", + output: "import { /*x*///y\nz} from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import './foo'; import def, {x} from './foo'", + output: "import def, {x} from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import './foo'; import def from './foo'", + output: "import def from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import {x} from './foo'; import def from './foo'", + output: "import def, {x} from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import{x} from './foo'; import def from './foo'", + output: "import def,{x} from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import {x} from './foo'; import def, {y} from './foo'", + output: "import def, {x,y} from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: ` + import {x} from './foo' + // some-tool-disable-next-line + import {y} from './foo' + `, + // Autofix bail because of comment. + output: ` + import {x} from './foo' + // some-tool-disable-next-line + import {y} from './foo' + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: ` + import {x} from './foo' + import {y} from './foo' // some-tool-disable-line + `, + // Autofix bail because of comment. + output: ` + import {x} from './foo' + import {y} from './foo' // some-tool-disable-line + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: ` + import {x} from './foo' + /* comment */ import {y} from './foo' + `, + // Autofix bail because of comment. + output: ` + import {x} from './foo' + /* comment */ import {y} from './foo' + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: ` + import {x} from './foo' + import {y} from './foo' /* comment + multiline */ + `, + // Autofix bail because of comment. + output: ` + import {x} from './foo' + import {y} from './foo' /* comment + multiline */ + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: ` + import {x} from './foo' + import {y} from './foo' + // some-tool-disable-next-line + `, + // Not autofix bail. + output: ` + import {x,y} from './foo' + + // some-tool-disable-next-line + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: ` + import {x} from './foo' + // comment + + import {y} from './foo' + `, + // Not autofix bail. + output: ` + import {x,y} from './foo' + // comment + + + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: ` + import {x} from './foo' + import/* comment */{y} from './foo' + `, + // Autofix bail because of comment. + output: ` + import {x} from './foo' + import/* comment */{y} from './foo' + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: ` + import {x} from './foo' + import/* comment */'./foo' + `, + // Autofix bail because of comment. + output: ` + import {x} from './foo' + import/* comment */'./foo' + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: ` + import {x} from './foo' + import{y}/* comment */from './foo' + `, + // Autofix bail because of comment. + output: ` + import {x} from './foo' + import{y}/* comment */from './foo' + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: ` + import {x} from './foo' + import{y}from/* comment */'./foo' + `, + // Autofix bail because of comment. + output: ` + import {x} from './foo' + import{y}from/* comment */'./foo' + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), ], }) From 71a64c6b678324418bfffc214cad024adc1757db Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 31 Mar 2019 03:02:51 +0200 Subject: [PATCH 081/151] no-duplicates: Disable autofix for ESLint <= 3 --- src/rules/no-duplicates.js | 5 +++++ tests/src/rules/no-duplicates.js | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index c2b28ee3a0..8c6c0e9f2d 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -26,6 +26,11 @@ function checkImports(imported, context) { } function getFix(first, rest, sourceCode) { + // Sorry ESLint <= 3 users, no autofix for you. + if (typeof sourceCode.getCommentsBefore !== 'function') { + return undefined + } + const defaultImportNames = new Set( [first, ...rest].map(getDefaultImportName).filter(Boolean) ) diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index 0da693f561..f8ef533848 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -1,11 +1,15 @@ import * as path from 'path' -import { test } from '../utils' +import { test as testUtil } from '../utils' import { RuleTester } from 'eslint' const ruleTester = new RuleTester() , rule = require('rules/no-duplicates') +const test = process.env.ESLINT_VERSION === '3' || process.env.ESLINT_VERSION === '2' + ? t => testUtil(Object.assign({}, t, {output: t.code})) + : testUtil + ruleTester.run('no-duplicates', rule, { valid: [ test({ code: 'import "./malformed.js"' }), From f47622c32e13f6e15254182a49724f1036346222 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 31 Mar 2019 10:58:13 +0200 Subject: [PATCH 082/151] no-duplicates: Add comment explaining ESLint <= 3 autofix disabling --- src/rules/no-duplicates.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index 8c6c0e9f2d..e6cd954e6e 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -26,7 +26,12 @@ function checkImports(imported, context) { } function getFix(first, rest, sourceCode) { - // Sorry ESLint <= 3 users, no autofix for you. + // Sorry ESLint <= 3 users, no autofix for you. Autofixing duplicate imports + // requires multiple `fixer.whatever()` calls in the `fix`: We both need to + // update the first one, and remove the rest. Support for multiple + // `fixer.whatever()` in a single `fix` was added in ESLint 4.1. + // `sourceCode.getCommentsBefore` was added in 4.0, so that's an easy thing to + // check for. if (typeof sourceCode.getCommentsBefore !== 'function') { return undefined } From cdb7c37570c4be290f1f89dfc7b9b688a2ed306b Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 31 Mar 2019 11:13:07 +0200 Subject: [PATCH 083/151] no-duplicates: Handle namespace imports when autofixing --- src/rules/no-duplicates.js | 20 ++++++++++++++++++-- tests/src/rules/no-duplicates.js | 14 ++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index e6cd954e6e..cdc5bb440f 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -36,6 +36,11 @@ function getFix(first, rest, sourceCode) { return undefined } + // If the first import is `import * as ns from './foo'` there's nothing we can do. + if (hasNamespace(first)) { + return undefined + } + const defaultImportNames = new Set( [first, ...rest].map(getDefaultImportName).filter(Boolean) ) @@ -47,11 +52,14 @@ function getFix(first, rest, sourceCode) { } // It's not obvious what the user wants to do with comments associated with - // duplicate imports, so skip imports with comments when autofixing. + // duplicate imports, so skip imports with comments when autofixing. Also skip + // `import * as ns from './foo'` imports, since they cannot be merged into + // another import. const restWithoutComments = rest.filter(node => !( hasCommentBefore(node, sourceCode) || hasCommentAfter(node, sourceCode) || - hasCommentInsideNonSpecifiers(node, sourceCode) + hasCommentInsideNonSpecifiers(node, sourceCode) || + hasNamespace(node) )) const specifiers = restWithoutComments @@ -75,6 +83,7 @@ function getFix(first, rest, sourceCode) { const unnecessaryImports = restWithoutComments.filter(node => !hasSpecifiers(node) && + !hasNamespace(node) && !specifiers.some(specifier => specifier.importNode === node) ) @@ -156,6 +165,13 @@ function getDefaultImportName(node) { return defaultSpecifier != null ? defaultSpecifier.local.name : undefined } +// Checks whether `node` has a namespace import. +function hasNamespace(node) { + const specifiers = node.specifiers + .filter(specifier => specifier.type === 'ImportNamespaceSpecifier') + return specifiers.length > 0 +} + // Checks whether `node` has any non-default specifiers. function hasSpecifiers(node) { const specifiers = node.specifiers diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index f8ef533848..a0f366b8b0 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -146,6 +146,20 @@ ruleTester.run('no-duplicates', rule, { errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), + test({ + code: "import * as ns from './foo'; import {y} from './foo'", + // Autofix bail because first import is a namespace import. + output: "import * as ns from './foo'; import {y} from './foo'", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + + test({ + code: "import {x} from './foo'; import * as ns from './foo'; import {y} from './foo'; import './foo'", + // Autofix could merge some imports, but not the namespace import. + output: "import {x,y} from './foo'; import * as ns from './foo'; ", + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + test({ code: ` import {x} from './foo' From f74a74ea8479199f69237d3862565ded9cb58e12 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 31 Mar 2019 11:23:38 +0200 Subject: [PATCH 084/151] no-duplicates: Bail autofix if first import has problematic comments --- src/rules/no-duplicates.js | 29 +++++++++++++------- tests/src/rules/no-duplicates.js | 45 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index cdc5bb440f..a37c737cff 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -36,8 +36,11 @@ function getFix(first, rest, sourceCode) { return undefined } - // If the first import is `import * as ns from './foo'` there's nothing we can do. - if (hasNamespace(first)) { + // Adjusting the first import might make it multiline, which could break + // `eslint-disable-next-line` comments and similar, so bail if the first + // import has comments. Also, if the first import is `import * as ns from + // './foo'` there's nothing we can do. + if (hasProblematicComments(first, sourceCode) || hasNamespace(first)) { return undefined } @@ -51,15 +54,11 @@ function getFix(first, rest, sourceCode) { return undefined } - // It's not obvious what the user wants to do with comments associated with - // duplicate imports, so skip imports with comments when autofixing. Also skip - // `import * as ns from './foo'` imports, since they cannot be merged into - // another import. + // Leave it to the user to handle comments. Also skip `import * as ns from + // './foo'` imports, since they cannot be merged into another import. const restWithoutComments = rest.filter(node => !( - hasCommentBefore(node, sourceCode) || - hasCommentAfter(node, sourceCode) || - hasCommentInsideNonSpecifiers(node, sourceCode) || - hasNamespace(node) + hasProblematicComments(node, sourceCode) || + hasNamespace(node) )) const specifiers = restWithoutComments @@ -179,6 +178,16 @@ function hasSpecifiers(node) { return specifiers.length > 0 } +// It's not obvious what the user wants to do with comments associated with +// duplicate imports, so skip imports with comments when autofixing. +function hasProblematicComments(node, sourceCode) { + return ( + hasCommentBefore(node, sourceCode) || + hasCommentAfter(node, sourceCode) || + hasCommentInsideNonSpecifiers(node, sourceCode) + ) +} + // Checks whether `node` has a comment (that ends) on the previous line or on // the same line as `node` (starts). function hasCommentBefore(node, sourceCode) { diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index a0f366b8b0..cdd365382c 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -160,6 +160,21 @@ ruleTester.run('no-duplicates', rule, { errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), + test({ + code: ` + // some-tool-disable-next-line + import {x} from './foo' + import {//y\ny} from './foo' + `, + // Autofix bail because of comment. + output: ` + // some-tool-disable-next-line + import {x} from './foo' + import {//y\ny} from './foo' + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + test({ code: ` import {x} from './foo' @@ -175,6 +190,19 @@ ruleTester.run('no-duplicates', rule, { errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), + test({ + code: ` + import {x} from './foo' // some-tool-disable-line + import {y} from './foo' + `, + // Autofix bail because of comment. + output: ` + import {x} from './foo' // some-tool-disable-line + import {y} from './foo' + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), + test({ code: ` import {x} from './foo' @@ -299,5 +327,22 @@ ruleTester.run('no-duplicates', rule, { `, errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), + + test({ + code: ` + import {x} from + // some-tool-disable-next-line + './foo' + import {y} from './foo' + `, + // Autofix bail because of comment. + output: ` + import {x} from + // some-tool-disable-next-line + './foo' + import {y} from './foo' + `, + errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], + }), ], }) From 0dd398c2fadd5961fb268508910398be4e063467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sun, 31 Mar 2019 17:46:23 +0200 Subject: [PATCH 085/151] ew: `no-unused-modules` rule - remove unnecessary exports --- src/rules/no-unused-modules.js | 3 --- tests/src/rules/no-unused-modules.js | 28 ---------------------------- 2 files changed, 31 deletions(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 7701279a2b..9f4203dc18 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -189,9 +189,6 @@ const newDefaultImportExists = specifiers => specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER) module.exports = { - isNodeModule, - doPreparation, - getSrc, meta: { docs: { url: docsUrl('no-unused-modules') }, schema: [{ diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index eab66bf5c7..214384130f 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -4,9 +4,6 @@ import { RuleTester } from 'eslint' import { expect } from 'chai' import fs from 'fs' -const getSrc = require( '../../../src/rules/no-unused-modules').getSrc -const isNodeModule = require( '../../../src/rules/no-unused-modules').isNodeModule - const ruleTester = new RuleTester() , rule = require('rules/no-unused-modules') @@ -22,31 +19,6 @@ const unusedExportsOptions = [{ ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')], }] -describe('getSrc returns correct source', () => { - it('if src is provided', () => { - const src = ['file-a.js'] - expect(getSrc(src)).to.eq(src) - }) - it('if src is not provided', () => { - expect(getSrc()).to.eql([process.cwd()]) - }) -}) - -describe('isNodeModule returns correct value', () => { - it('true for "/node_modules/"', () => { - expect(isNodeModule('/node_modules/')).to.be.true - }) - it('true for "/node_modules/package/file.js"', () => { - expect(isNodeModule('/node_modules/package/file.js')).to.be.true - }) - it('false for "/node_modules.js"', () => { - expect(isNodeModule('/node_modules.js')).to.be.false - }) - it('false for "node_modules_old"', () => { - expect(isNodeModule('node_modules_old')).to.be.false - }) -}) - // tests for missing exports ruleTester.run('no-unused-modules', rule, { valid: [ From 1ac4bd7a90791ec267552675373da4733af9c4d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sun, 31 Mar 2019 17:46:23 +0200 Subject: [PATCH 086/151] ew: `no-unused-modules` rule - remove unnecessary exports --- src/rules/no-unused-modules.js | 3 --- tests/src/rules/no-unused-modules.js | 28 ---------------------------- 2 files changed, 31 deletions(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 7701279a2b..9f4203dc18 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -189,9 +189,6 @@ const newDefaultImportExists = specifiers => specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER) module.exports = { - isNodeModule, - doPreparation, - getSrc, meta: { docs: { url: docsUrl('no-unused-modules') }, schema: [{ diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index eab66bf5c7..214384130f 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -4,9 +4,6 @@ import { RuleTester } from 'eslint' import { expect } from 'chai' import fs from 'fs' -const getSrc = require( '../../../src/rules/no-unused-modules').getSrc -const isNodeModule = require( '../../../src/rules/no-unused-modules').isNodeModule - const ruleTester = new RuleTester() , rule = require('rules/no-unused-modules') @@ -22,31 +19,6 @@ const unusedExportsOptions = [{ ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')], }] -describe('getSrc returns correct source', () => { - it('if src is provided', () => { - const src = ['file-a.js'] - expect(getSrc(src)).to.eq(src) - }) - it('if src is not provided', () => { - expect(getSrc()).to.eql([process.cwd()]) - }) -}) - -describe('isNodeModule returns correct value', () => { - it('true for "/node_modules/"', () => { - expect(isNodeModule('/node_modules/')).to.be.true - }) - it('true for "/node_modules/package/file.js"', () => { - expect(isNodeModule('/node_modules/package/file.js')).to.be.true - }) - it('false for "/node_modules.js"', () => { - expect(isNodeModule('/node_modules.js')).to.be.false - }) - it('false for "node_modules_old"', () => { - expect(isNodeModule('node_modules_old')).to.be.false - }) -}) - // tests for missing exports ruleTester.run('no-unused-modules', rule, { valid: [ From 3134ad36e1658b2bd0f0677aaeccbc07299d1e34 Mon Sep 17 00:00:00 2001 From: Andrew Schmadel Date: Mon, 1 Apr 2019 14:02:14 -0400 Subject: [PATCH 087/151] Test both typescript-eslint-parser and @typescript-eslint/parser augments https://github.com/benmosher/eslint-plugin-import/pull/1304 --- README.md | 14 +-- package.json | 3 +- tests/src/core/getExports.js | 1 + tests/src/rules/named.js | 190 ++++++++++++++++++----------------- tests/src/rules/order.js | 16 +++ 5 files changed, 124 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 7695ac368d..3e60280a80 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ rules: You may use the following shortcut or assemble your own config using the granular settings described below. -Make sure you have installed the [`@typescript-eslint/parser`] which is used in the following configuration. Unfortunately NPM does not allow to list optional peer dependencies. +Make sure you have installed [`@typescript-eslint/parser`] which is used in the following configuration. Unfortunately NPM does not allow to list optional peer dependencies. ```yaml extends: @@ -344,14 +344,16 @@ directly using webpack, for example: # .eslintrc.yml settings: import/parsers: - typescript-eslint-parser: [ .ts, .tsx ] + @typescript-eslint/parser: [ .ts, .tsx ] ``` -In this case, [`typescript-eslint-parser`](https://github.com/eslint/typescript-eslint-parser) must be installed and require-able from -the running `eslint` module's location (i.e., install it as a peer of ESLint). +In this case, [`@typescript-eslint/parser`](https://www.npmjs.com/package/@typescript-eslint/parser) +must be installed and require-able from the running `eslint` module's location +(i.e., install it as a peer of ESLint). -This is currently only tested with `typescript-eslint-parser` but should theoretically -work with any moderately ESTree-compliant parser. +This is currently only tested with `@typescript-eslint/parser` (and its predecessor, +`typescript-eslint-parser`) but should theoretically work with any moderately +ESTree-compliant parser. It's difficult to say how well various plugin features will be supported, too, depending on how far down the rabbit hole goes. Submit an issue if you find strange diff --git a/package.json b/package.json index 51fd070ed4..601f6a4f49 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ }, "homepage": "https://github.com/benmosher/eslint-plugin-import", "devDependencies": { + "@typescript-eslint/parser": "^1.5.0", "babel-eslint": "^8.2.6", "babel-plugin-istanbul": "^4.1.6", "babel-preset-es2015-argon": "latest", @@ -69,7 +70,7 @@ "rimraf": "^2.6.3", "sinon": "^2.4.1", "typescript": "^3.2.2", - "typescript-eslint-parser": "^21.0.2" + "typescript-eslint-parser": "^22.0.0" }, "peerDependencies": { "eslint": "2.x - 5.x" diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 80e9d843e3..00c96f40f7 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -315,6 +315,7 @@ describe('ExportMap', function () { const configs = [ // ['string form', { 'typescript-eslint-parser': '.ts' }], ['array form', { 'typescript-eslint-parser': ['.ts', '.tsx'] }], + ['array form', { '@typescript-eslint/parser': ['.ts', '.tsx'] }], ] configs.forEach(([description, parserConfig]) => { diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 569f3158a5..0cf9a07b6d 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -254,99 +254,103 @@ ruleTester.run('named (export *)', rule, { }) -context("Typescript", function () { +context('Typescript', function () { // Typescript - ruleTester.run("named", rule, { - valid: [ - test({ - code: 'import { MyType } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { Foo } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { Bar } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { getFoo } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { MyEnum } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: ` - import { MyModule } from "./typescript" - MyModule.ModuleFunction() - `, - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: ` - import { MyNamespace } from "./typescript" - MyNamespace.NSModule.NSModuleFunction() - `, - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - ], - - invalid: [ - test({ - code: 'import { MissingType } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - errors: [{ - message: "MissingType not found in './typescript'", - type: 'Identifier', - }], - }), - test({ - code: 'import { NotExported } from "./typescript"', - parser: 'typescript-eslint-parser', - settings: { - 'import/parsers': { 'typescript-eslint-parser': ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - errors: [{ - message: "NotExported not found in './typescript'", - type: 'Identifier', - }], - }), - ] + const parsers = ['@typescript-eslint/parser', 'typescript-eslint-parser'] + + parsers.forEach((parser) => { + ruleTester.run('named', rule, { + valid: [ + test({ + code: 'import { MyType } from "./typescript"', + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: 'import { Foo } from "./typescript"', + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: 'import { Bar } from "./typescript"', + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: 'import { getFoo } from "./typescript"', + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: 'import { MyEnum } from "./typescript"', + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: ` + import { MyModule } from "./typescript" + MyModule.ModuleFunction() + `, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: ` + import { MyNamespace } from "./typescript" + MyNamespace.NSModule.NSModuleFunction() + `, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + ], + + invalid: [ + test({ + code: 'import { MissingType } from "./typescript"', + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: [{ + message: "MissingType not found in './typescript'", + type: 'Identifier', + }], + }), + test({ + code: 'import { NotExported } from "./typescript"', + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: [{ + message: "NotExported not found in './typescript'", + type: 'Identifier', + }], + }), + ], + }) }) }) diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index 23487dac91..c338bd3883 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1276,5 +1276,21 @@ ruleTester.run('order', rule, { message: '`fs` import should occur before import of `async`', }], }), + // fix incorrect order with @typescript-eslint/parser + test({ + code: ` + var async = require('async'); + var fs = require('fs'); + `, + output: ` + var fs = require('fs'); + var async = require('async'); + `, + parser: '@typescript-eslint/parser', + errors: [{ + ruleId: 'order', + message: '`fs` import should occur before import of `async`', + }], + }), ], }) From f0eb91761de3c5f42f4b7c8d742873dfa9366335 Mon Sep 17 00:00:00 2001 From: Andrew Schmadel Date: Wed, 3 Apr 2019 10:31:08 -0400 Subject: [PATCH 088/151] skip @typescript-eslint/parser tests on older eslint versions --- package.json | 1 + tests/src/core/getExports.js | 7 ++++++- tests/src/rules/named.js | 9 +++++++-- tests/src/rules/order.js | 6 +++--- tests/src/utils.js | 6 ++++++ 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 601f6a4f49..2cafc4fe5d 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "nyc": "^11.9.0", "redux": "^3.7.2", "rimraf": "^2.6.3", + "semver": "^6.0.0", "sinon": "^2.4.1", "typescript": "^3.2.2", "typescript-eslint-parser": "^22.0.0" diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 00c96f40f7..7207ff34a9 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -1,4 +1,6 @@ import { expect } from 'chai' +import semver from 'semver' +import { linter } from 'eslint' import ExportMap from '../../../src/ExportMap' import * as fs from 'fs' @@ -315,9 +317,12 @@ describe('ExportMap', function () { const configs = [ // ['string form', { 'typescript-eslint-parser': '.ts' }], ['array form', { 'typescript-eslint-parser': ['.ts', '.tsx'] }], - ['array form', { '@typescript-eslint/parser': ['.ts', '.tsx'] }], ] + if (semver.satisfies(linter.version, '>5.0.0')) { + configs.push(['array form', { '@typescript-eslint/parser': ['.ts', '.tsx'] }]) + } + configs.forEach(([description, parserConfig]) => { describe(description, function () { diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 0cf9a07b6d..a3f55b35e4 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -1,5 +1,6 @@ import { test, SYNTAX_CASES } from '../utils' -import { RuleTester } from 'eslint' +import { RuleTester, linter } from 'eslint' +import semver from 'semver' import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve' @@ -256,7 +257,11 @@ ruleTester.run('named (export *)', rule, { context('Typescript', function () { // Typescript - const parsers = ['@typescript-eslint/parser', 'typescript-eslint-parser'] + const parsers = ['typescript-eslint-parser'] + + if (semver.satisfies(linter.version, '>5.0.0')) { + parsers.push('@typescript-eslint/parser') + } parsers.forEach((parser) => { ruleTester.run('named', rule, { diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index c338bd3883..dde5ae6cbe 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1,4 +1,4 @@ -import { test } from '../utils' +import { test, testVersion } from '../utils' import { RuleTester } from 'eslint' @@ -1277,7 +1277,7 @@ ruleTester.run('order', rule, { }], }), // fix incorrect order with @typescript-eslint/parser - test({ + testVersion('>5.0.0', { code: ` var async = require('async'); var fs = require('fs'); @@ -1292,5 +1292,5 @@ ruleTester.run('order', rule, { message: '`fs` import should occur before import of `async`', }], }), - ], + ].filter((t) => !!t), }) diff --git a/tests/src/utils.js b/tests/src/utils.js index 52e2d23cd5..70c5971a6a 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -1,4 +1,6 @@ import path from 'path' +import { linter } from 'eslint' +import semver from 'semver' // warms up the module cache. this import takes a while (>500ms) import 'babel-eslint' @@ -9,6 +11,10 @@ export function testFilePath(relativePath) { export const FILENAME = testFilePath('foo.js') +export function testVersion(specifier, t) { + return semver.satisfies(linter.version) && test(t) +} + export function test(t) { return Object.assign({ filename: FILENAME, From cca688da41ce62ffd27612c180f12c51383bc875 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 7 Apr 2019 01:17:16 +0200 Subject: [PATCH 089/151] no-duplicates: Use an array instead of generator for fixes --- src/rules/no-duplicates.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index a37c737cff..33e3357482 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -94,7 +94,7 @@ function getFix(first, rest, sourceCode) { return undefined } - return function* (fixer) { + return fixer => { const tokens = sourceCode.getTokens(first) const openBrace = tokens.find(token => isPunctuator(token, '{')) const closeBrace = tokens.find(token => isPunctuator(token, '}')) @@ -118,38 +118,44 @@ function getFix(first, rest, sourceCode) { ['', !firstHasTrailingComma && !firstIsEmpty] ) + const fixes = [] + if (shouldAddDefault && openBrace == null && shouldAddSpecifiers) { // `import './foo'` → `import def, {...} from './foo'` - yield fixer.insertTextAfter(firstToken, ` ${defaultImportName}, {${specifiersText}} from`) + fixes.push( + fixer.insertTextAfter(firstToken, ` ${defaultImportName}, {${specifiersText}} from`) + ) } else if (shouldAddDefault && openBrace == null && !shouldAddSpecifiers) { // `import './foo'` → `import def from './foo'` - yield fixer.insertTextAfter(firstToken, ` ${defaultImportName} from`) + fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName} from`)) } else if (shouldAddDefault && openBrace != null && closeBrace != null) { // `import {...} from './foo'` → `import def, {...} from './foo'` - yield fixer.insertTextAfter(firstToken, ` ${defaultImportName},`) + fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName},`)) if (shouldAddSpecifiers) { // `import def, {...} from './foo'` → `import def, {..., ...} from './foo'` - yield fixer.insertTextBefore(closeBrace, specifiersText) + fixes.push(fixer.insertTextBefore(closeBrace, specifiersText)) } } else if (!shouldAddDefault && openBrace == null && shouldAddSpecifiers) { // `import './foo'` → `import {...} from './foo'` - yield fixer.insertTextAfter(firstToken, ` {${specifiersText}} from`) + fixes.push(fixer.insertTextAfter(firstToken, ` {${specifiersText}} from`)) } else if (!shouldAddDefault && openBrace != null && closeBrace != null) { // `import {...} './foo'` → `import {..., ...} from './foo'` - yield fixer.insertTextBefore(closeBrace, specifiersText) + fixes.push(fixer.insertTextBefore(closeBrace, specifiersText)) } // Remove imports whose specifiers have been moved into the first import. for (const specifier of specifiers) { - yield fixer.remove(specifier.importNode) + fixes.push(fixer.remove(specifier.importNode)) } // Remove imports whose default import has been moved to the first import, // and side-effect-only imports that are unnecessary due to the first // import. for (const node of unnecessaryImports) { - yield fixer.remove(node) + fixes.push(fixer.remove(node)) } + + return fixes } } From 49af9d80754b6ed7fb1adb2848d37e228b6a448b Mon Sep 17 00:00:00 2001 From: Andrew Schmadel Date: Mon, 8 Apr 2019 11:53:32 -0400 Subject: [PATCH 090/151] Remove @typescript-eslint/parser before running ESLint Date: Thu, 11 Apr 2019 15:19:56 -0700 Subject: [PATCH 091/151] [*] [deps] update `resolve` --- package.json | 2 +- resolvers/node/package.json | 2 +- resolvers/webpack/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2cafc4fe5d..9398f0f9c4 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "lodash": "^4.17.11", "minimatch": "^3.0.4", "read-pkg-up": "^2.0.0", - "resolve": "^1.9.0" + "resolve": "^1.10.0" }, "nyc": { "require": [ diff --git a/resolvers/node/package.json b/resolvers/node/package.json index ceebe40d77..76529084d6 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.5.0" + "resolve": "^1.10.0" }, "devDependencies": { "chai": "^3.5.0", diff --git a/resolvers/webpack/package.json b/resolvers/webpack/package.json index 5985babe6c..6150ddcef8 100644 --- a/resolvers/webpack/package.json +++ b/resolvers/webpack/package.json @@ -38,7 +38,7 @@ "interpret": "^1.0.0", "lodash": "^4.17.4", "node-libs-browser": "^1.0.0 || ^2.0.0", - "resolve": "^1.4.0", + "resolve": "^1.10.0", "semver": "^5.3.0" }, "peerDependencies": { From 0ff1c8351c11906eb650bb64d6a3ae709b11bf9a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 11 Apr 2019 15:20:34 -0700 Subject: [PATCH 092/151] =?UTF-8?q?[dev=20deps]=20lock=20typescript=20to?= =?UTF-8?q?=20`~`,=20since=20it=20doesn=E2=80=99t=20follow=20semver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9398f0f9c4..b413989616 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "rimraf": "^2.6.3", "semver": "^6.0.0", "sinon": "^2.4.1", - "typescript": "^3.2.2", + "typescript": "~3.2.2", "typescript-eslint-parser": "^22.0.0" }, "peerDependencies": { From 918567d52d2c2cc21932bda88f2b5e359e6c7821 Mon Sep 17 00:00:00 2001 From: Zhibin Liu Date: Sun, 18 Nov 2018 21:36:13 +0800 Subject: [PATCH 093/151] [fix] `namespace`: add check for null ExportMap Fixes #1144. --- src/rules/namespace.js | 6 +++++- tests/files/re-export-common.js | 1 + tests/src/rules/namespace.js | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/files/re-export-common.js diff --git a/src/rules/namespace.js b/src/rules/namespace.js index 598b530d02..dd840b86f6 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -187,7 +187,11 @@ module.exports = { } path.push(property.key.name) - testKey(property.value, namespace.get(property.key.name).namespace, path) + const dependencyExportMap = namespace.get(property.key.name) + // could be null when ignored or ambiguous + if (dependencyExportMap !== null) { + testKey(property.value, dependencyExportMap.namespace, path) + } path.pop() } } diff --git a/tests/files/re-export-common.js b/tests/files/re-export-common.js new file mode 100644 index 0000000000..3c47cd8c15 --- /dev/null +++ b/tests/files/re-export-common.js @@ -0,0 +1 @@ +export { a as foo } from './common' diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index 7fa8cfcdb9..e642a2b484 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -104,6 +104,11 @@ const valid = [ parser: 'babel-eslint', }), + // #1144: should handle re-export CommonJS as namespace + test({ + code: `import * as ns from './re-export-common'; const {foo} = ns;`, + }), + // JSX test({ code: 'import * as Names from "./named-exports"; const Foo = ', From e4850df0c75b428c0dcb1d41b7c68022730317a1 Mon Sep 17 00:00:00 2001 From: Zhibin Liu Date: Fri, 16 Nov 2018 14:56:56 +0800 Subject: [PATCH 094/151] [ExportMap] fix condition for checking if block comment Fixes #1233. --- src/ExportMap.js | 2 +- tests/src/core/getExports.js | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/ExportMap.js b/src/ExportMap.js index 20ed009d76..b533946fa2 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -243,7 +243,7 @@ function captureJsDoc(comments) { // capture XSDoc comments.forEach(comment => { // skip non-block comments - if (comment.value.slice(0, 4) !== '*\n *') return + if (comment.type !== 'Block') return try { doc = doctrine.parse(comment.value, { unwrap: true }) } catch (err) { diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 7207ff34a9..6e09426125 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -96,12 +96,12 @@ describe('ExportMap', function () { context('deprecation metadata', function () { - function jsdocTests(parseContext) { + function jsdocTests(parseContext, lineEnding) { context('deprecated imports', function () { let imports before('parse file', function () { const path = getFilename('deprecated.js') - , contents = fs.readFileSync(path, { encoding: 'utf8' }) + , contents = fs.readFileSync(path, { encoding: 'utf8' }).replace(/[\r]\n/g, lineEnding) imports = ExportMap.parse(path, contents, parseContext) // sanity checks @@ -191,7 +191,15 @@ describe('ExportMap', function () { attachComment: true, }, settings: {}, - }) + }, '\n') + jsdocTests({ + parserPath: 'espree', + parserOptions: { + sourceType: 'module', + attachComment: true, + }, + settings: {}, + }, '\r\n') }) context('babel-eslint', function () { @@ -202,7 +210,15 @@ describe('ExportMap', function () { attachComment: true, }, settings: {}, - }) + }, '\n') + jsdocTests({ + parserPath: 'babel-eslint', + parserOptions: { + sourceType: 'module', + attachComment: true, + }, + settings: {}, + }, '\r\n') }) }) From 70a59fe1880b4061cd7c0f032c0b05c728223a0f Mon Sep 17 00:00:00 2001 From: vikr01 Date: Sat, 20 Oct 2018 23:09:20 -0700 Subject: [PATCH 095/151] [fix] Fix overwriting of dynamic import() CallExpression - fix import() to work with no-cycle - add tests using multiple imports in no-cycle Fixes #1035. Fixes #1166. --- src/rules/no-cycle.js | 6 +--- tests/src/rules/no-cycle.js | 32 +++++++++++++++++++ tests/src/rules/no-relative-parent-imports.js | 8 +++++ tests/src/rules/no-unresolved.js | 9 ++++++ tests/src/rules/no-useless-path-segments.js | 23 ++++++++++++- utils/moduleVisitor.js | 2 ++ 6 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index f769b862cc..62da3643b4 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -31,14 +31,10 @@ module.exports = { function checkSourceValue(sourceNode, importer) { const imported = Exports.get(sourceNode.value, context) - if (sourceNode.parent && sourceNode.parent.importKind === 'type') { + if (importer.importKind === 'type') { return // no Flow import resolution } - if (sourceNode._babelType === 'Literal') { - return // no Flow import resolution, workaround for ESLint < 5.x - } - if (imported == null) { return // no-unresolved territory } diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js index 4ee4daacb6..c88c7504bc 100644 --- a/tests/src/rules/no-cycle.js +++ b/tests/src/rules/no-cycle.js @@ -36,10 +36,23 @@ ruleTester.run('no-cycle', rule, { code: 'import { foo } from "./depth-two"', options: [{ maxDepth: 1 }], }), + test({ + code: 'import { foo, bar } from "./depth-two"', + options: [{ maxDepth: 1 }], + }), + test({ + code: 'import("./depth-two").then(function({ foo }){})', + options: [{ maxDepth: 1 }], + parser: 'babel-eslint', + }), test({ code: 'import type { FooType } from "./depth-one"', parser: 'babel-eslint', }), + test({ + code: 'import type { FooType, BarType } from "./depth-one"', + parser: 'babel-eslint', + }), ], invalid: [ test({ @@ -84,10 +97,29 @@ ruleTester.run('no-cycle', rule, { code: 'import { two } from "./depth-three-star"', errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)], }), + test({ + code: 'import one, { two, three } from "./depth-three-star"', + errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)], + }), test({ code: 'import { bar } from "./depth-three-indirect"', errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)], }), + test({ + code: 'import { bar } from "./depth-three-indirect"', + errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)], + parser: 'babel-eslint', + }), + test({ + code: 'import("./depth-three-star")', + errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)], + parser: 'babel-eslint', + }), + test({ + code: 'import("./depth-three-indirect")', + errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)], + parser: 'babel-eslint', + }), ], }) // }) diff --git a/tests/src/rules/no-relative-parent-imports.js b/tests/src/rules/no-relative-parent-imports.js index 8978230904..281edd1237 100644 --- a/tests/src/rules/no-relative-parent-imports.js +++ b/tests/src/rules/no-relative-parent-imports.js @@ -93,6 +93,14 @@ ruleTester.run('no-relative-parent-imports', rule, { line: 1, column: 17 }] + }), + test({ + code: 'import("../../api/service")', + errors: [ { + message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `../../api/service` or consider making `../../api/service` a package.', + line: 1, + column: 8 + }], }) ], }) diff --git a/tests/src/rules/no-unresolved.js b/tests/src/rules/no-unresolved.js index 9db6e1a5c5..9fa7019a73 100644 --- a/tests/src/rules/no-unresolved.js +++ b/tests/src/rules/no-unresolved.js @@ -29,6 +29,8 @@ function runResolverTests(resolver) { rest({ code: "import bar from './bar.js';" }), rest({ code: "import {someThing} from './test-module';" }), rest({ code: "import fs from 'fs';" }), + rest({ code: "import('fs');" + , parser: 'babel-eslint' }), rest({ code: 'import * as foo from "a"' }), @@ -114,6 +116,13 @@ function runResolverTests(resolver) { "module 'in-alternate-root'." , type: 'Literal', }]}), + rest({ + code: "import('in-alternate-root').then(function({DEEP}){});", + errors: [{ message: 'Unable to resolve path to ' + + "module 'in-alternate-root'." + , type: 'Literal', + }], + parser: 'babel-eslint'}), rest({ code: 'export { foo } from "./does-not-exist"' , errors: ["Unable to resolve path to module './does-not-exist'."] }), diff --git a/tests/src/rules/no-useless-path-segments.js b/tests/src/rules/no-useless-path-segments.js index 923c7efe79..52e66ec6f9 100644 --- a/tests/src/rules/no-useless-path-segments.js +++ b/tests/src/rules/no-useless-path-segments.js @@ -17,7 +17,6 @@ function runResolverTests(resolver) { test({ code: 'import "."' }), test({ code: 'import ".."' }), test({ code: 'import fs from "fs"' }), - test({ code: 'import fs from "fs"' }), // ES modules + noUselessIndex test({ code: 'import "../index"' }), // noUselessIndex is false by default @@ -28,6 +27,13 @@ function runResolverTests(resolver) { test({ code: 'import "./malformed.js"', options: [{ noUselessIndex: true }] }), // ./malformed directory does not exist test({ code: 'import "./malformed"', options: [{ noUselessIndex: true }] }), // ./malformed directory does not exist test({ code: 'import "./importType"', options: [{ noUselessIndex: true }] }), // ./importType.js does not exist + + test({ code: 'import(".")' + , parser: 'babel-eslint' }), + test({ code: 'import("..")' + , parser: 'babel-eslint' }), + test({ code: 'import("fs").then(function(fs){})' + , parser: 'babel-eslint' }), ], invalid: [ @@ -190,6 +196,21 @@ function runResolverTests(resolver) { options: [{ noUselessIndex: true }], errors: ['Useless path segments for "../index.js", should be ".."'], }), + test({ + code: 'import("./")', + errors: [ 'Useless path segments for "./", should be "."'], + parser: 'babel-eslint', + }), + test({ + code: 'import("../")', + errors: [ 'Useless path segments for "../", should be ".."'], + parser: 'babel-eslint', + }), + test({ + code: 'import("./deep//a")', + errors: [ 'Useless path segments for "./deep//a", should be "./deep/a"'], + parser: 'babel-eslint', + }), ], }) } diff --git a/utils/moduleVisitor.js b/utils/moduleVisitor.js index 2e736242e6..bc8c91b0af 100644 --- a/utils/moduleVisitor.js +++ b/utils/moduleVisitor.js @@ -91,7 +91,9 @@ exports.default = function visitModules(visitor, options) { } if (options.commonjs || options.amd) { + const currentCallExpression = visitors['CallExpression'] visitors['CallExpression'] = function (call) { + if (currentCallExpression) currentCallExpression(call) if (options.commonjs) checkCommon(call) if (options.amd) checkAMD(call) } From 2098797b0499d38c03cde4b476c65a3325326b18 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 11 Apr 2019 19:58:32 -0700 Subject: [PATCH 096/151] [fix] `export`: false positives for typescript type + value export --- package.json | 1 + src/rules/export.js | 17 ++++++++++++---- tests/src/rules/export.js | 41 ++++++++++++++++++++++++++++++++++++++- tests/src/utils.js | 2 +- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b413989616..7975e244fd 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "eslint": "2.x - 5.x" }, "dependencies": { + "array-includes": "^3.0.3", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", diff --git a/src/rules/export.js b/src/rules/export.js index db5c8c3c1b..a4691bbe97 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -1,5 +1,6 @@ import ExportMap, { recursivePatternCapture } from '../ExportMap' import docsUrl from '../docsUrl' +import includes from 'array-includes' module.exports = { meta: { @@ -12,12 +13,13 @@ module.exports = { create: function (context) { const named = new Map() - function addNamed(name, node) { - let nodes = named.get(name) + function addNamed(name, node, type) { + const key = type ? `${type}:${name}` : name + let nodes = named.get(key) if (nodes == null) { nodes = new Set() - named.set(name, nodes) + named.set(key, nodes) } nodes.add(node) @@ -34,7 +36,14 @@ module.exports = { if (node.declaration == null) return if (node.declaration.id != null) { - addNamed(node.declaration.id.name, node.declaration.id) + if (includes([ + 'TSTypeAliasDeclaration', + 'TSInterfaceDeclaration', + ], node.declaration.type)) { + addNamed(node.declaration.id.name, node.declaration.id, 'type') + } else { + addNamed(node.declaration.id.name, node.declaration.id) + } } if (node.declaration.declarations != null) { diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index 33f0121233..771cbb431f 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -1,6 +1,7 @@ import { test, SYNTAX_CASES } from '../utils' -import { RuleTester } from 'eslint' +import { RuleTester, linter } from 'eslint' +import semver from 'semver' var ruleTester = new RuleTester() , rule = require('rules/export') @@ -106,3 +107,41 @@ ruleTester.run('export', rule, { }), ], }) + + +context('Typescript', function () { + // Typescript + const parsers = ['typescript-eslint-parser'] + + if (semver.satisfies(linter.version, '>5.0.0')) { + parsers.push('@typescript-eslint/parser') + } + + parsers.forEach((parser) => { + const parserConfig = { + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + } + + ruleTester.run('export', rule, { + valid: [ + test(Object.assign({ + code: ` + export const Foo = 1; + export type Foo = number; + `, + }, parserConfig), + test(Object.assign({ + code: ` + export const Foo = 1; + export interface Foo {} + `, + }, parserConfig), + ], + invalid: [], + }) + }) +}) diff --git a/tests/src/utils.js b/tests/src/utils.js index 70c5971a6a..4d4f42dc84 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -12,7 +12,7 @@ export function testFilePath(relativePath) { export const FILENAME = testFilePath('foo.js') export function testVersion(specifier, t) { - return semver.satisfies(linter.version) && test(t) + return semver.satisfies(linter.version, specifier) && test(t) } export function test(t) { From 405900ebe775183588a03447b1fb152481662e3e Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 12 Apr 2019 00:41:08 -0700 Subject: [PATCH 097/151] [Tests] fix tests from #1319 --- tests/src/rules/export.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index 771cbb431f..95fb540634 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -139,7 +139,7 @@ context('Typescript', function () { export const Foo = 1; export interface Foo {} `, - }, parserConfig), + }, parserConfig))), ], invalid: [], }) From 6ab25ea159797ca97206796bc82c132a057a1c75 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 12 Apr 2019 12:55:38 -0700 Subject: [PATCH 098/151] [Tests] skip a TS test in eslint < 4 --- tests/src/rules/export.js | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index 95fb540634..a7a9e81922 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -126,21 +126,25 @@ context('Typescript', function () { }, } + const isLT4 = process.env.ESLINT_VERSION === '3' || process.env.ESLINT_VERSION === '2'; + const valid = [ + test(Object.assign({ + code: ` + export const Foo = 1; + export interface Foo {} + `, + }, parserConfig)), + ] + if (!isLT4) { + valid.unshift(test(Object.assign({ + code: ` + export const Foo = 1; + export type Foo = number; + `, + }, parserConfig))) + } ruleTester.run('export', rule, { - valid: [ - test(Object.assign({ - code: ` - export const Foo = 1; - export type Foo = number; - `, - }, parserConfig), - test(Object.assign({ - code: ` - export const Foo = 1; - export interface Foo {} - `, - }, parserConfig))), - ], + valid: valid, invalid: [], }) }) From 70c3679b7f0a426306705c4f2aefb5890ee6b69f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frederik=20Eycheni=C3=A9?= Date: Wed, 30 May 2018 13:14:33 +0200 Subject: [PATCH 099/151] [docs] make rule names consistent Renamed the title of this page to be the full name of the rule, like on the rest of the docs. --- docs/rules/no-default-export.md | 2 +- docs/rules/no-deprecated.md | 2 +- docs/rules/no-named-export.md | 2 +- docs/rules/no-relative-parent-imports.md | 2 +- docs/rules/no-self-import.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/rules/no-default-export.md b/docs/rules/no-default-export.md index dc026b00a6..4f1a300a26 100644 --- a/docs/rules/no-default-export.md +++ b/docs/rules/no-default-export.md @@ -1,4 +1,4 @@ -# no-default-export +# `import/no-default-export` Prohibit default exports. Mostly an inverse of [`prefer-default-export`]. diff --git a/docs/rules/no-deprecated.md b/docs/rules/no-deprecated.md index fae7d8daf2..c948b51781 100644 --- a/docs/rules/no-deprecated.md +++ b/docs/rules/no-deprecated.md @@ -1,4 +1,4 @@ -# import/no-deprecated +# `import/no-deprecated` Reports use of a deprecated name, as indicated by a JSDoc block with a `@deprecated` tag or TomDoc `Deprecated: ` comment. diff --git a/docs/rules/no-named-export.md b/docs/rules/no-named-export.md index b3a0ef8d63..0ff881e349 100644 --- a/docs/rules/no-named-export.md +++ b/docs/rules/no-named-export.md @@ -1,4 +1,4 @@ -# no-named-export +# `import/no-named-export` Prohibit named exports. Mostly an inverse of [`no-default-export`]. diff --git a/docs/rules/no-relative-parent-imports.md b/docs/rules/no-relative-parent-imports.md index 84913b5400..7d6e883cff 100644 --- a/docs/rules/no-relative-parent-imports.md +++ b/docs/rules/no-relative-parent-imports.md @@ -1,4 +1,4 @@ -# no-relative-parent-imports +# import/no-relative-parent-imports Use this rule to prevent imports to folders in relative parent paths. diff --git a/docs/rules/no-self-import.md b/docs/rules/no-self-import.md index 089f5e0294..bde063f5d3 100644 --- a/docs/rules/no-self-import.md +++ b/docs/rules/no-self-import.md @@ -1,4 +1,4 @@ -# Forbid a module from importing itself +# Forbid a module from importing itself (`import/no-self-import`) Forbid a module from importing itself. This can sometimes happen during refactoring. From 988e12b390c5d3d3b9bab4037bf6b47b10afe661 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Fri, 12 Apr 2019 10:14:11 -0700 Subject: [PATCH 100/151] fix(export): Support typescript namespaces Fixes #1300 --- src/rules/export.js | 86 +++++++++++++++----- tests/src/rules/export.js | 166 +++++++++++++++++++++++++++++++++----- 2 files changed, 215 insertions(+), 37 deletions(-) diff --git a/src/rules/export.js b/src/rules/export.js index a4691bbe97..caa28e119f 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -2,6 +2,28 @@ import ExportMap, { recursivePatternCapture } from '../ExportMap' import docsUrl from '../docsUrl' import includes from 'array-includes' +/* +Notes on Typescript namespaces aka TSModuleDeclaration: + +There are two forms: +- active namespaces: namespace Foo {} / module Foo {} +- ambient modules; declare module "eslint-plugin-import" {} + +active namespaces: +- cannot contain a default export +- cannot contain an export all +- cannot contain a multi name export (export { a, b }) +- can have active namespaces nested within them + +ambient namespaces: +- can only be defined in .d.ts files +- cannot be nested within active namespaces +- have no other restrictions +*/ + +const rootProgram = 'root' +const tsTypePrefix = 'type:' + module.exports = { meta: { type: 'problem', @@ -11,10 +33,15 @@ module.exports = { }, create: function (context) { - const named = new Map() + const namespace = new Map([[rootProgram, new Map()]]) + + function addNamed(name, node, parent, isType) { + if (!namespace.has(parent)) { + namespace.set(parent, new Map()) + } + const named = namespace.get(parent) - function addNamed(name, node, type) { - const key = type ? `${type}:${name}` : name + const key = isType ? `${tsTypePrefix}${name}` : name let nodes = named.get(key) if (nodes == null) { @@ -25,30 +52,43 @@ module.exports = { nodes.add(node) } + function getParent(node) { + if (node.parent && node.parent.type === 'TSModuleBlock') { + return node.parent.parent + } + + // just in case somehow a non-ts namespace export declaration isn't directly + // parented to the root Program node + return rootProgram + } + return { - 'ExportDefaultDeclaration': (node) => addNamed('default', node), + 'ExportDefaultDeclaration': (node) => addNamed('default', node, getParent(node)), - 'ExportSpecifier': function (node) { - addNamed(node.exported.name, node.exported) - }, + 'ExportSpecifier': (node) => addNamed(node.exported.name, node.exported, getParent(node)), 'ExportNamedDeclaration': function (node) { if (node.declaration == null) return + const parent = getParent(node) + // support for old typescript versions + const isTypeVariableDecl = node.declaration.kind === 'type' + if (node.declaration.id != null) { if (includes([ 'TSTypeAliasDeclaration', 'TSInterfaceDeclaration', ], node.declaration.type)) { - addNamed(node.declaration.id.name, node.declaration.id, 'type') + addNamed(node.declaration.id.name, node.declaration.id, parent, true) } else { - addNamed(node.declaration.id.name, node.declaration.id) + addNamed(node.declaration.id.name, node.declaration.id, parent, isTypeVariableDecl) } } if (node.declaration.declarations != null) { for (let declaration of node.declaration.declarations) { - recursivePatternCapture(declaration.id, v => addNamed(v.name, v)) + recursivePatternCapture(declaration.id, v => + addNamed(v.name, v, parent, isTypeVariableDecl)) } } }, @@ -63,11 +103,14 @@ module.exports = { remoteExports.reportErrors(context, node) return } + + const parent = getParent(node) + let any = false remoteExports.forEach((v, name) => name !== 'default' && (any = true) && // poor man's filter - addNamed(name, node)) + addNamed(name, node, parent)) if (!any) { context.report(node.source, @@ -76,13 +119,20 @@ module.exports = { }, 'Program:exit': function () { - for (let [name, nodes] of named) { - if (nodes.size <= 1) continue - - for (let node of nodes) { - if (name === 'default') { - context.report(node, 'Multiple default exports.') - } else context.report(node, `Multiple exports of name '${name}'.`) + for (let [, named] of namespace) { + for (let [name, nodes] of named) { + if (nodes.size <= 1) continue + + for (let node of nodes) { + if (name === 'default') { + context.report(node, 'Multiple default exports.') + } else { + context.report( + node, + `Multiple exports of name '${name.replace(tsTypePrefix, '')}'.` + ) + } + } } } }, diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index a7a9e81922..3aad5241e9 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -126,26 +126,154 @@ context('Typescript', function () { }, } - const isLT4 = process.env.ESLINT_VERSION === '3' || process.env.ESLINT_VERSION === '2'; - const valid = [ - test(Object.assign({ - code: ` - export const Foo = 1; - export interface Foo {} - `, - }, parserConfig)), - ] - if (!isLT4) { - valid.unshift(test(Object.assign({ - code: ` - export const Foo = 1; - export type Foo = number; - `, - }, parserConfig))) - } ruleTester.run('export', rule, { - valid: valid, - invalid: [], + valid: [ + // type/value name clash + test(Object.assign({ + code: ` + export const Foo = 1; + export type Foo = number; + `, + }, parserConfig)), + test(Object.assign({ + code: ` + export const Foo = 1; + export interface Foo {} + `, + }, parserConfig)), + + // namespace + test(Object.assign({ + code: ` + export const Bar = 1; + export namespace Foo { + export const Bar = 1; + } + `, + }, parserConfig)), + test(Object.assign({ + code: ` + export type Bar = string; + export namespace Foo { + export type Bar = string; + } + `, + }, parserConfig)), + test(Object.assign({ + code: ` + export const Bar = 1; + export type Bar = string; + export namespace Foo { + export const Bar = 1; + export type Bar = string; + } + `, + }, parserConfig)), + test(Object.assign({ + code: ` + export namespace Foo { + export const Foo = 1; + export namespace Bar { + export const Foo = 2; + } + export namespace Baz { + export const Foo = 3; + } + } + `, + }, parserConfig)), + ], + invalid: [ + // type/value name clash + test(Object.assign({ + code: ` + export type Foo = string; + export type Foo = number; + `, + errors: [ + { + message: `Multiple exports of name 'Foo'.`, + line: 2, + }, + { + message: `Multiple exports of name 'Foo'.`, + line: 3, + }, + ], + }, parserConfig)), + + // namespace + test(Object.assign({ + code: ` + export const a = 1 + export namespace Foo { + export const a = 2; + export const a = 3; + } + `, + errors: [ + { + message: `Multiple exports of name 'a'.`, + line: 4, + }, + { + message: `Multiple exports of name 'a'.`, + line: 5, + }, + ], + }, parserConfig)), + test(Object.assign({ + code: ` + declare module 'foo' { + const Foo = 1; + export default Foo; + export default Foo; + } + `, + errors: [ + { + message: 'Multiple default exports.', + line: 4, + }, + { + message: 'Multiple default exports.', + line: 5, + }, + ], + }, parserConfig)), + test(Object.assign({ + code: ` + export namespace Foo { + export namespace Bar { + export const Foo = 1; + export const Foo = 2; + } + export namespace Baz { + export const Bar = 3; + export const Bar = 4; + } + } + `, + errors: [ + { + message: `Multiple exports of name 'Foo'.`, + line: 4, + }, + { + message: `Multiple exports of name 'Foo'.`, + line: 5, + }, + { + message: `Multiple exports of name 'Bar'.`, + line: 8, + }, + { + message: `Multiple exports of name 'Bar'.`, + line: 9, + }, + ], + }, parserConfig)), + ], }) }) }) From f479635e10a5bfc076d0d0973df7c3d0e0b8986a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 12 Apr 2019 16:35:38 -0700 Subject: [PATCH 101/151] [webpack] v0.11.1 --- resolvers/webpack/CHANGELOG.md | 5 +++++ resolvers/webpack/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index 5526ca5401..204e0224ab 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -5,6 +5,10 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +## 0.11.1 - 2019-04-13 + +### Fixed +- [fix] match coreLibs after resolveSync in webpack-resolver ([#1297]) ## 0.11.0 - 2018-01-22 @@ -113,6 +117,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]). +[#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 [#1091]: https://github.com/benmosher/eslint-plugin-import/pull/1091 diff --git a/resolvers/webpack/package.json b/resolvers/webpack/package.json index 6150ddcef8..69a861c6ef 100644 --- a/resolvers/webpack/package.json +++ b/resolvers/webpack/package.json @@ -1,6 +1,6 @@ { "name": "eslint-import-resolver-webpack", - "version": "0.11.0", + "version": "0.11.1", "description": "Resolve paths to dependencies, given a webpack.config.js. Plugin for eslint-plugin-import.", "main": "index.js", "scripts": { From 0499050403b6ba3294b0fcb8b5aa8df84a6b3b2c Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 12 Apr 2019 23:44:09 -0700 Subject: [PATCH 102/151] bump to v2.17.0 --- CHANGELOG.md | 125 ++++++++++++++++++++++++++++++++++++--------------- package.json | 2 +- 2 files changed, 91 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef04278d62..36f32afd0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,33 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] + +## [2.17.0] - 2019-04-13 + ### Added - Autofixer for [`no-duplicates`] rule ([#1312], thanks [@lydell]) +- [`no-useless-path-segments`]: Add `noUselessIndex` option ([#1290], thanks [@timkraut]) +- [`no-duplicates`]: Add autofix ([#1312], thanks [@lydell]) +- Add [`no-unused-modules`] rule ([#1142], thanks [@rfermann]) +- support export type named exports from typescript ([#1304], thanks [@bradennapier] and [@schmod]) ### Fixed - [`order`]: Fix interpreting some external modules being interpreted as internal modules ([#793], [#794] thanks [@ephys]) +- allow aliases that start with @ to be "internal" ([#1293], [#1294], thanks [@jeffshaver]) +- aliased internal modules that look like core modules ([#1297], thanks [@echenley]) +- [`namespace`]: add check for null ExportMap ([#1235], [#1144], thanks [@ljqx]) +- [ExportMap] fix condition for checking if block comment ([#1234], [#1233], thanks [@ljqx]) +- Fix overwriting of dynamic import() CallExpression ([`no-cycle`], [`no-relative-parent-import`], [`no-unresolved`], [`no-useless-path-segments`]) ([#1218], [#1166], [#1035], thanks [@vikr01]) +- [`export`]: false positives for typescript type + value export ([#1319], thanks [@bradzacher]) +- [`export`]: Support typescript namespaces ([#1320], [#1300], thanks [@bradzacher]) + +### Docs +- Update readme for Typescript ([#1256], [#1277], thanks [@kirill-konshin]) +- make rule names consistent ([#1112], thanks [@feychenie]) + +### Tests +- 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 @@ -477,73 +499,89 @@ for info on changes for earlier releases. [`import/core-modules` setting]: ./README.md#importcore-modules [`import/external-module-folders` setting]: ./README.md#importexternal-module-folders -[`no-unresolved`]: ./docs/rules/no-unresolved.md -[`no-deprecated`]: ./docs/rules/no-deprecated.md -[`no-commonjs`]: ./docs/rules/no-commonjs.md -[`no-amd`]: ./docs/rules/no-amd.md -[`namespace`]: ./docs/rules/namespace.md -[`no-namespace`]: ./docs/rules/no-namespace.md -[`no-named-default`]: ./docs/rules/no-named-default.md -[`no-named-as-default`]: ./docs/rules/no-named-as-default.md -[`no-named-as-default-member`]: ./docs/rules/no-named-as-default-member.md -[`no-extraneous-dependencies`]: ./docs/rules/no-extraneous-dependencies.md +[`default`]: ./docs/rules/default.md +[`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md +[`export`]: ./docs/rules/export.md +[`exports-last`]: ./docs/rules/exports-last.md [`extensions`]: ./docs/rules/extensions.md [`first`]: ./docs/rules/first.md +[`group-exports`]: ./docs/rules/group-exports.md [`imports-first`]: ./docs/rules/first.md -[`no-nodejs-modules`]: ./docs/rules/no-nodejs-modules.md -[`order`]: ./docs/rules/order.md +[`max-dependencies`]: ./docs/rules/max-dependencies.md [`named`]: ./docs/rules/named.md -[`default`]: ./docs/rules/default.md -[`export`]: ./docs/rules/export.md +[`namespace`]: ./docs/rules/namespace.md [`newline-after-import`]: ./docs/rules/newline-after-import.md -[`no-mutable-exports`]: ./docs/rules/no-mutable-exports.md -[`prefer-default-export`]: ./docs/rules/prefer-default-export.md -[`no-restricted-paths`]: ./docs/rules/no-restricted-paths.md [`no-absolute-path`]: ./docs/rules/no-absolute-path.md -[`max-dependencies`]: ./docs/rules/max-dependencies.md -[`no-internal-modules`]: ./docs/rules/no-internal-modules.md -[`no-dynamic-require`]: ./docs/rules/no-dynamic-require.md -[`no-webpack-loader-syntax`]: ./docs/rules/no-webpack-loader-syntax.md -[`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md -[`unambiguous`]: ./docs/rules/unambiguous.md +[`no-amd`]: ./docs/rules/no-amd.md [`no-anonymous-default-export`]: ./docs/rules/no-anonymous-default-export.md -[`exports-last`]: ./docs/rules/exports-last.md -[`group-exports`]: ./docs/rules/group-exports.md -[`no-self-import`]: ./docs/rules/no-self-import.md -[`no-default-export`]: ./docs/rules/no-default-export.md -[`no-useless-path-segments`]: ./docs/rules/no-useless-path-segments.md +[`no-commonjs`]: ./docs/rules/no-commonjs.md [`no-cycle`]: ./docs/rules/no-cycle.md -[`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md -[`no-named-export`]: ./docs/rules/no-named-export.md +[`no-default-export`]: ./docs/rules/no-default-export.md +[`no-deprecated`]: ./docs/rules/no-deprecated.md [`no-duplicates`]: ./docs/rules/no-duplicates.md +[`no-dynamic-require`]: ./docs/rules/no-dynamic-require.md +[`no-extraneous-dependencies`]: ./docs/rules/no-extraneous-dependencies.md +[`no-internal-modules`]: ./docs/rules/no-internal-modules.md +[`no-mutable-exports`]: ./docs/rules/no-mutable-exports.md +[`no-named-as-default-member`]: ./docs/rules/no-named-as-default-member.md +[`no-named-as-default`]: ./docs/rules/no-named-as-default.md +[`no-named-default`]: ./docs/rules/no-named-default.md +[`no-named-export`]: ./docs/rules/no-named-export.md +[`no-namespace`]: ./docs/rules/no-namespace.md +[`no-nodejs-modules`]: ./docs/rules/no-nodejs-modules.md +[`no-restricted-paths`]: ./docs/rules/no-restricted-paths.md +[`no-self-import`]: ./docs/rules/no-self-import.md +[`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md +[`no-unresolved`]: ./docs/rules/no-unresolved.md +[`no-unused-modules`]: ./docs/rules/no-unused-modules.md +[`no-useless-path-segments`]: ./docs/rules/no-useless-path-segments.md +[`no-webpack-loader-syntax`]: ./docs/rules/no-webpack-loader-syntax.md +[`order`]: ./docs/rules/order.md +[`prefer-default-export`]: ./docs/rules/prefer-default-export.md +[`unambiguous`]: ./docs/rules/unambiguous.md [`memo-parser`]: ./memo-parser/README.md +[#1320]: https://github.com/benmosher/eslint-plugin-import/pull/1320 +[#1319]: https://github.com/benmosher/eslint-plugin-import/pull/1319 [#1312]: https://github.com/benmosher/eslint-plugin-import/pull/1312 +[#1308]: https://github.com/benmosher/eslint-plugin-import/pull/1308 +[#1304]: https://github.com/benmosher/eslint-plugin-import/pull/1304 +[#1297]: https://github.com/benmosher/eslint-plugin-import/pull/1297 +[#1295]: https://github.com/benmosher/eslint-plugin-import/pull/1295 +[#1294]: https://github.com/benmosher/eslint-plugin-import/pull/1294 +[#1290]: https://github.com/benmosher/eslint-plugin-import/pull/1290 +[#1277]: https://github.com/benmosher/eslint-plugin-import/pull/1277 [#1257]: https://github.com/benmosher/eslint-plugin-import/pull/1257 +[#1235]: https://github.com/benmosher/eslint-plugin-import/pull/1235 +[#1234]: https://github.com/benmosher/eslint-plugin-import/pull/1234 [#1232]: https://github.com/benmosher/eslint-plugin-import/pull/1232 +[#1218]: https://github.com/benmosher/eslint-plugin-import/pull/1218 [#1176]: https://github.com/benmosher/eslint-plugin-import/pull/1176 [#1163]: https://github.com/benmosher/eslint-plugin-import/pull/1163 [#1157]: https://github.com/benmosher/eslint-plugin-import/pull/1157 [#1151]: https://github.com/benmosher/eslint-plugin-import/pull/1151 +[#1142]: https://github.com/benmosher/eslint-plugin-import/pull/1142 [#1137]: https://github.com/benmosher/eslint-plugin-import/pull/1137 [#1135]: https://github.com/benmosher/eslint-plugin-import/pull/1135 [#1128]: https://github.com/benmosher/eslint-plugin-import/pull/1128 [#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 [#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 [#1068]: https://github.com/benmosher/eslint-plugin-import/pull/1068 [#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 [#908]: https://github.com/benmosher/eslint-plugin-import/pull/908 [#891]: https://github.com/benmosher/eslint-plugin-import/pull/891 [#889]: https://github.com/benmosher/eslint-plugin-import/pull/889 [#880]: https://github.com/benmosher/eslint-plugin-import/pull/880 +[#871]: https://github.com/benmosher/eslint-plugin-import/pull/871 [#858]: https://github.com/benmosher/eslint-plugin-import/pull/858 [#843]: https://github.com/benmosher/eslint-plugin-import/pull/843 -[#871]: https://github.com/benmosher/eslint-plugin-import/pull/871 [#797]: https://github.com/benmosher/eslint-plugin-import/pull/797 [#794]: https://github.com/benmosher/eslint-plugin-import/pull/794 [#744]: https://github.com/benmosher/eslint-plugin-import/pull/744 @@ -586,6 +624,7 @@ for info on changes for earlier releases. [#322]: https://github.com/benmosher/eslint-plugin-import/pull/322 [#321]: https://github.com/benmosher/eslint-plugin-import/pull/321 [#316]: https://github.com/benmosher/eslint-plugin-import/pull/316 +[#314]: https://github.com/benmosher/eslint-plugin-import/pull/314 [#308]: https://github.com/benmosher/eslint-plugin-import/pull/308 [#298]: https://github.com/benmosher/eslint-plugin-import/pull/298 [#297]: https://github.com/benmosher/eslint-plugin-import/pull/297 @@ -608,22 +647,27 @@ 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 -[#314]: https://github.com/benmosher/eslint-plugin-import/pull/314 -[#912]: https://github.com/benmosher/eslint-plugin-import/pull/912 +[#1300]: https://github.com/benmosher/eslint-plugin-import/issues/1300 +[#1293]: https://github.com/benmosher/eslint-plugin-import/issues/1293 [#1266]: https://github.com/benmosher/eslint-plugin-import/issues/1266 +[#1256]: https://github.com/benmosher/eslint-plugin-import/issues/1256 +[#1233]: https://github.com/benmosher/eslint-plugin-import/issues/1233 [#1175]: https://github.com/benmosher/eslint-plugin-import/issues/1175 +[#1166]: https://github.com/benmosher/eslint-plugin-import/issues/1166 +[#1144]: https://github.com/benmosher/eslint-plugin-import/issues/1144 [#1058]: https://github.com/benmosher/eslint-plugin-import/issues/1058 +[#1035]: https://github.com/benmosher/eslint-plugin-import/issues/1035 [#931]: https://github.com/benmosher/eslint-plugin-import/issues/931 [#886]: https://github.com/benmosher/eslint-plugin-import/issues/886 [#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 +[#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 [#686]: https://github.com/benmosher/eslint-plugin-import/issues/686 [#671]: https://github.com/benmosher/eslint-plugin-import/issues/671 -[#793]: https://github.com/benmosher/eslint-plugin-import/issues/793 [#660]: https://github.com/benmosher/eslint-plugin-import/issues/660 [#653]: https://github.com/benmosher/eslint-plugin-import/issues/653 [#627]: https://github.com/benmosher/eslint-plugin-import/issues/627 @@ -683,7 +727,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.16.0...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.17.0...HEAD +[2.17.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.16.0...v2.17.0 [2.16.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.15.0...v2.16.0 [2.15.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.14.0...v2.15.0 [2.14.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.13.0...v2.14.0 @@ -815,3 +860,13 @@ for info on changes for earlier releases. [@sergei-startsev]: https://github.com/sergei-startsev [@ephys]: https://github.com/ephys [@lydell]: https://github.com/lydell +[@jeffshaver]: https://github.com/jeffshaver +[@timkraut]: https://github.com/timkraut +[@TakeScoop]: https://github.com/TakeScoop +[@rfermann]: https://github.com/rfermann +[@bradennapier]: https://github.com/bradennapier +[@schmod]: https://github.com/schmod +[@echenley]: https://github.com/echenley +[@vikr01]: https://github.com/vikr01 +[@bradzacher]: https://github.com/bradzacher +[@feychenie]: https://github.com/feychenie diff --git a/package.json b/package.json index 7975e244fd..dffd8d3a72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.16.0", + "version": "2.17.0", "description": "Import with sanity.", "engines": { "node": ">=4" From 5124dde66158b77f6e0fdecef8512731c2fb0e5a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 13 Apr 2019 07:31:41 -0700 Subject: [PATCH 103/151] [utils] v2.4.0 --- utils/CHANGELOG.md | 15 +++++++++++++++ utils/package.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index cb86dc2259..1fcaa1bff8 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,6 +5,15 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +## v2.4.0 - 2019-04-13 + +### Added + - no-useless-path-segments: Add noUselessIndex option ([#1290], thanks [@timkraut]) + +### Fixed + - Fix overwriting of dynamic import() CallExpression ([`no-cycle`], [`no-relative-parent-import`], [`no-unresolved`], [`no-useless-path-segments`]) ([#1218], [#1166], [#1035], thanks [@vikr01]) + + ## v2.3.0 - 2019-01-22 ### Fixed - use `process.hrtime()` for cache dates ([#1160], thanks [@hulkish]) @@ -37,6 +46,12 @@ Yanked due to critical issue with cache key resulting from #839. +[#1290]: https://github.com/benmosher/eslint-plugin-import/pull/1290 +[#1218]: https://github.com/benmosher/eslint-plugin-import/pull/1218 +[#1166]: https://github.com/benmosher/eslint-plugin-import/issues/1166 [#1160]: https://github.com/benmosher/eslint-plugin-import/pull/1160 +[#1035]: https://github.com/benmosher/eslint-plugin-import/issues/1035 [@hulkish]: https://github.com/hulkish +[@timkraut]: https://github.com/timkraut +[@vikr01]: https://github.com/vikr01 diff --git a/utils/package.json b/utils/package.json index c380b6e2b1..be0c761475 100644 --- a/utils/package.json +++ b/utils/package.json @@ -1,6 +1,6 @@ { "name": "eslint-module-utils", - "version": "2.3.0", + "version": "2.4.0", "description": "Core utilities to support eslint-plugin-import and other module-related plugins.", "engines": { "node": ">=4" From f6716ad9407cb63ba3652f7ff14c997f4e2ff2f9 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 13 Apr 2019 07:33:07 -0700 Subject: [PATCH 104/151] [fix] require v2.4 of `eslint-module-utils` Fixes #1322. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dffd8d3a72..e0ed80d9c5 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "debug": "^2.6.9", "doctrine": "1.5.0", "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.3.0", + "eslint-module-utils": "^2.4.0", "has": "^1.0.3", "lodash": "^4.17.11", "minimatch": "^3.0.4", From b0c5e1abcbbf85e3d28ee8cb9b74970aa58843e4 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 13 Apr 2019 07:35:15 -0700 Subject: [PATCH 105/151] bump to v2.17.1 --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36f32afd0b..187ab26c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +## [2.17.1] - 2019-04-13 + +### Fixed +- require v2.4 of `eslint-module-utils` ([#1322]) + ## [2.17.0] - 2019-04-13 ### Added @@ -648,6 +653,7 @@ for info on changes for earlier releases. [#164]: https://github.com/benmosher/eslint-plugin-import/pull/164 [#157]: https://github.com/benmosher/eslint-plugin-import/pull/157 +[#1322]: https://github.com/benmosher/eslint-plugin-import/issues/1322 [#1300]: https://github.com/benmosher/eslint-plugin-import/issues/1300 [#1293]: https://github.com/benmosher/eslint-plugin-import/issues/1293 [#1266]: https://github.com/benmosher/eslint-plugin-import/issues/1266 diff --git a/package.json b/package.json index e0ed80d9c5..2416e99e01 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.17.0", + "version": "2.17.1", "description": "Import with sanity.", "engines": { "node": ">=4" From 9b7a9706f9956bdfec0828c66a2674f7cdb41d78 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 15 Apr 2019 11:17:43 -0700 Subject: [PATCH 106/151] [meta] add `npm run mocha` for easier unit testing --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2416e99e01..99c3edb04b 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,11 @@ "memo-parser" ], "scripts": { - "watch": "cross-env NODE_PATH=./src mocha --watch --compilers js:babel-register --recursive tests/src", + "watch": "npm run mocha -- --watch tests/src", "pretest": "linklocal", "posttest": "eslint ./src", - "test": "cross-env BABEL_ENV=test NODE_PATH=./src nyc -s mocha -R dot --recursive tests/src -t 5s", + "mocha": "cross-env BABEL_ENV=test NODE_PATH=./src nyc -s mocha -R dot --recursive -t 5s", + "test": "npm run mocha tests/src", "test-compiled": "npm run prepublish && NODE_PATH=./lib mocha --compilers js:babel-register --recursive tests/src", "test-all": "npm test && for resolver in ./resolvers/*; do cd $resolver && npm test && cd ../..; done", "prepublish": "gulp prepublish", From 8e0c0216938cabdec6295bb02ab495e70befffb4 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 15 Apr 2019 11:18:46 -0700 Subject: [PATCH 107/151] [Test] `no-unused-modules` add failing test case --- tests/src/rules/no-unused-modules.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 214384130f..9918b85f4c 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -22,6 +22,7 @@ const unusedExportsOptions = [{ // tests for missing exports ruleTester.run('no-unused-modules', rule, { valid: [ + test({ code: 'export default function noOptions() {}' }), test({ options: missingExportsOptions, code: 'export default () => 1'}), test({ options: missingExportsOptions, From 351256316a5c7ecae6c4cb7b83b94e1bd2e93701 Mon Sep 17 00:00:00 2001 From: Olena Sovyn Date: Mon, 15 Apr 2019 14:43:49 +0100 Subject: [PATCH 108/151] [fix] `no-unused-modules`: make sure that rule with no options will not fail --- src/rules/no-unused-modules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 9f4203dc18..91d1679bba 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -261,7 +261,7 @@ module.exports = { ignoreExports = [], missingExports, unusedExports, - } = context.options[0] + } = context.options[0] || {} if (unusedExports && !preparationDone) { doPreparation(src, ignoreExports, context) From b151d0492a8c372a55831323f48a99efe3276df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Mon, 15 Apr 2019 23:42:04 +0200 Subject: [PATCH 109/151] [fix] `no-unused-modules`: avoid crash when using `ignoreExports`-option Fixes #1323. --- src/rules/no-unused-modules.js | 4 ++++ tests/src/rules/no-unused-modules.js | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 91d1679bba..552004b6d3 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -274,6 +274,10 @@ module.exports = { return } + if (ignoredFiles.has(file)) { + return + } + const exportCount = exportList.get(file) const exportAll = exportCount.get(EXPORT_ALL_DECLARATION) const namespaceImports = exportCount.get(IMPORT_NAMESPACE_SPECIFIER) diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 9918b85f4c..7b11ee6d06 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -549,3 +549,18 @@ describe('test behaviour for new file', () => { } }) }) + +describe('do not report missing export for ignored file', () => { + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: [{ + src: [testFilePath('./no-unused-modules/**/*.js')], + ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')], + missingExports: true + }], + code: 'export const test = true', + filename: testFilePath('./no-unused-modules/file-ignored-a.js')}), + ], + invalid: [], + }) +}) From eddcfa9ff0affe64eff61cf749fef95e46d38b50 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 16 Apr 2019 13:44:20 -0700 Subject: [PATCH 110/151] Bump to v2.17.2 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 187ab26c61..adc7faddf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +## [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 @@ -547,6 +553,8 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1331]: https://github.com/benmosher/eslint-plugin-import/pull/1331 +[#1330]: https://github.com/benmosher/eslint-plugin-import/pull/1330 [#1320]: https://github.com/benmosher/eslint-plugin-import/pull/1320 [#1319]: https://github.com/benmosher/eslint-plugin-import/pull/1319 [#1312]: https://github.com/benmosher/eslint-plugin-import/pull/1312 @@ -653,6 +661,8 @@ for info on changes for earlier releases. [#164]: https://github.com/benmosher/eslint-plugin-import/pull/164 [#157]: https://github.com/benmosher/eslint-plugin-import/pull/157 +[#1334]: https://github.com/benmosher/eslint-plugin-import/issues/1334 +[#1323]: https://github.com/benmosher/eslint-plugin-import/issues/1323 [#1322]: https://github.com/benmosher/eslint-plugin-import/issues/1322 [#1300]: https://github.com/benmosher/eslint-plugin-import/issues/1300 [#1293]: https://github.com/benmosher/eslint-plugin-import/issues/1293 @@ -876,3 +886,4 @@ for info on changes for earlier releases. [@vikr01]: https://github.com/vikr01 [@bradzacher]: https://github.com/bradzacher [@feychenie]: https://github.com/feychenie +[@kiwka]: https://github.com/kiwka diff --git a/package.json b/package.json index 99c3edb04b..f8d46a1bd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.17.1", + "version": "2.17.2", "description": "Import with sanity.", "engines": { "node": ">=4" From 12bbfca747be9d88db32ff6d1659701cd8ed7fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Tue, 16 Apr 2019 18:28:12 +0200 Subject: [PATCH 111/151] [fix] `no-unused-modules`: make appveyor tests passing Fixes #1317. --- src/rules/no-unused-modules.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 552004b6d3..f80be0a6e1 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -462,7 +462,7 @@ module.exports = { // support for export { value } from 'module' if (astNode.type === EXPORT_NAMED_DECLARATION) { if (astNode.source) { - resolvedPath = resolve(astNode.source.value, context) + resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context) astNode.specifiers.forEach(specifier => { let name if (specifier.exported.name === DEFAULT) { @@ -476,12 +476,12 @@ module.exports = { } if (astNode.type === EXPORT_ALL_DECLARATION) { - resolvedPath = resolve(astNode.source.value, context) + resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context) newExportAll.add(resolvedPath) } if (astNode.type === IMPORT_DECLARATION) { - resolvedPath = resolve(astNode.source.value, context) + resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context) if (!resolvedPath) { return } From 174afbbffa9127db1728dae544fc26afb94eaf28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sun, 21 Apr 2019 21:37:52 +0200 Subject: [PATCH 112/151] [fix] `no-unused-modules`: make `import { name as otherName }` work --- src/rules/no-unused-modules.js | 2 +- tests/src/rules/no-unused-modules.js | 30 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index f80be0a6e1..d91411bf20 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -503,7 +503,7 @@ module.exports = { specifier.type === IMPORT_NAMESPACE_SPECIFIER) { return } - newImports.set(specifier.local.name, resolvedPath) + newImports.set(specifier.imported.name, resolvedPath) }) } }) diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 7b11ee6d06..fa4b59b303 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -254,6 +254,34 @@ ruleTester.run('no-unused-modules', rule, { ], }) +// add renamed named import for file with named export +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import { g as g1 } from '${testFilePath('./no-unused-modules/file-g.js')}'; import eslint from 'eslint'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + test({ options: unusedExportsOptions, + code: 'export const g = 2', + filename: testFilePath('./no-unused-modules/file-g.js')}), + ], + invalid: [], +}) + +// add different renamed named import for file with named export +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import { g1 as g } from '${testFilePath('./no-unused-modules/file-g.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js')}), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: 'export const g = 2', + filename: testFilePath('./no-unused-modules/file-g.js'), + errors: [error(`exported declaration 'g' not used within other modules`)]}), + ], +}) + // remove default import for file with default export ruleTester.run('no-unused-modules', rule, { valid: [ @@ -556,7 +584,7 @@ describe('do not report missing export for ignored file', () => { test({ options: [{ src: [testFilePath('./no-unused-modules/**/*.js')], ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')], - missingExports: true + missingExports: true, }], code: 'export const test = true', filename: testFilePath('./no-unused-modules/file-ignored-a.js')}), From 1db357eaa22d936e4211d1b00d5d4b31ea6659f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Tue, 23 Apr 2019 19:54:46 +0200 Subject: [PATCH 113/151] [fix] `no-unused-modules`: make `import { name as otherName }` work --- src/ExportMap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ExportMap.js b/src/ExportMap.js index b533946fa2..8513e3d395 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -405,7 +405,7 @@ ExportMap.parse = function (path, content, context) { importedSpecifiers.add(specifier.type) } if (specifier.type === 'ImportSpecifier') { - importedSpecifiers.add(specifier.local.name) + importedSpecifiers.add(specifier.imported.name) } }) } From fbe5c30cf85e4a2c9fd9e8d29a12903c46a86b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Wed, 24 Apr 2019 20:19:24 +0200 Subject: [PATCH 114/151] [fix] `no-unused-modules`: make `import { name as otherName }` work --- tests/files/no-unused-modules/file-h.js | 4 +++- tests/files/no-unused-modules/file-p.js | 1 + tests/src/rules/no-unused-modules.js | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 tests/files/no-unused-modules/file-p.js diff --git a/tests/files/no-unused-modules/file-h.js b/tests/files/no-unused-modules/file-h.js index b38d70e548..60b542e179 100644 --- a/tests/files/no-unused-modules/file-h.js +++ b/tests/files/no-unused-modules/file-h.js @@ -4,4 +4,6 @@ function h2() { return 3 } -export { h1, h2 } +const h3 = true + +export { h1, h2, h3 } diff --git a/tests/files/no-unused-modules/file-p.js b/tests/files/no-unused-modules/file-p.js new file mode 100644 index 0000000000..60f3fbae46 --- /dev/null +++ b/tests/files/no-unused-modules/file-p.js @@ -0,0 +1 @@ +import { h3 as h0 } from './file-h' \ No newline at end of file diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index fa4b59b303..8069479ae5 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -118,7 +118,7 @@ ruleTester.run('no-unused-modules', rule, { filename: testFilePath('./no-unused-modules/file-g.js'), errors: [error(`exported declaration 'g' not used within other modules`)]}), test({ options: unusedExportsOptions, - code: 'const h1 = 3; function h2() { return 3 }; export { h1, h2 }', + code: 'const h1 = 3; function h2() { return 3 }; const h3 = true; export { h1, h2, h3 }', filename: testFilePath('./no-unused-modules/file-h.js'), errors: [error(`exported declaration 'h1' not used within other modules`)]}), test({ options: unusedExportsOptions, From 4620185d0aa93da9675afb73ec713f5b1c821051 Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Wed, 24 Apr 2019 19:18:46 -0700 Subject: [PATCH 115/151] [fix] `named`: ignore Flow import typeof and export type --- CHANGELOG.md | 4 ++++ docs/rules/named.md | 2 +- src/rules/named.js | 9 ++++++--- tests/src/rules/named.js | 32 ++++++++++++++++++++++++++++++-- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adc7faddf2..70287d855a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +- [`named`]: ignore Flow `typeof` imports and `type` exports ([#1345], thanks [@loganfsmyth]) + ## [2.17.2] - 2019-04-16 ### Fixed @@ -553,6 +555,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1345]: https://github.com/benmosher/eslint-plugin-import/pull/1345 [#1331]: https://github.com/benmosher/eslint-plugin-import/pull/1331 [#1330]: https://github.com/benmosher/eslint-plugin-import/pull/1330 [#1320]: https://github.com/benmosher/eslint-plugin-import/pull/1320 @@ -887,3 +890,4 @@ for info on changes for earlier releases. [@bradzacher]: https://github.com/bradzacher [@feychenie]: https://github.com/feychenie [@kiwka]: https://github.com/kiwka +[@loganfsmyth]: https://github.com/loganfsmyth diff --git a/docs/rules/named.md b/docs/rules/named.md index 0830af5e4e..01ccb14ae7 100644 --- a/docs/rules/named.md +++ b/docs/rules/named.md @@ -8,7 +8,7 @@ Note: for packages, the plugin will find exported names from [`jsnext:main`], if present in `package.json`. Redux's npm module includes this key, and thereby is lintable, for example. -A module path that is [ignored] or not [unambiguously an ES module] will not be reported when imported. Note that type imports, as used by [Flow], are always ignored. +A module path that is [ignored] or not [unambiguously an ES module] will not be reported when imported. Note that type imports and exports, as used by [Flow], are always ignored. [ignored]: ../../README.md#importignore [unambiguously an ES module]: https://github.com/bmeck/UnambiguousJavaScriptGrammar diff --git a/src/rules/named.js b/src/rules/named.js index b1f261f32b..cc9199d480 100644 --- a/src/rules/named.js +++ b/src/rules/named.js @@ -12,8 +12,11 @@ module.exports = { create: function (context) { function checkSpecifiers(key, type, node) { - // ignore local exports and type imports - if (node.source == null || node.importKind === 'type') return + // ignore local exports and type imports/exports + if (node.source == null || node.importKind === 'type' || + node.importKind === 'typeof' || node.exportKind === 'type') { + return + } if (!node.specifiers .some(function (im) { return im.type === type })) { @@ -32,7 +35,7 @@ module.exports = { if (im.type !== type) return // ignore type imports - if (im.importKind === 'type') return + if (im.importKind === 'type' || im.importKind === 'typeof') return const deepLookup = imports.hasDeep(im[key].name) diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index a3f55b35e4..922914f90a 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -70,17 +70,45 @@ ruleTester.run('named', rule, { test({ code: 'import { deepProp } from "./named-exports"' }), test({ code: 'import { deepSparseElement } from "./named-exports"' }), - // should ignore imported flow types, even if they don’t exist + // should ignore imported/exported flow types, even if they don’t exist test({ code: 'import type { MissingType } from "./flowtypes"', parser: 'babel-eslint', }), + test({ + code: 'import typeof { MissingType } from "./flowtypes"', + parser: 'babel-eslint', + }), test({ code: 'import type { MyOpaqueType } from "./flowtypes"', parser: 'babel-eslint', }), test({ - code: 'import { type MyOpaqueType, MyClass } from "./flowtypes"', + code: 'import typeof { MyOpaqueType } from "./flowtypes"', + parser: 'babel-eslint', + }), + test({ + code: 'import { type MyOpaqueType, MyClass } from "./flowtypes"', + parser: 'babel-eslint', + }), + test({ + code: 'import { typeof MyOpaqueType, MyClass } from "./flowtypes"', + parser: 'babel-eslint', + }), + test({ + code: 'import typeof MissingType from "./flowtypes"', + parser: 'babel-eslint', + }), + test({ + code: 'import typeof * as MissingType from "./flowtypes"', + parser: 'babel-eslint', + }), + test({ + code: 'export type { MissingType } from "./flowtypes"', + parser: 'babel-eslint', + }), + test({ + code: 'export type { MyOpaqueType } from "./flowtypes"', parser: 'babel-eslint', }), From bb686defa4199d5c1f77980b165907b9d3c0971b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fermann?= Date: Sat, 27 Apr 2019 11:59:30 +0200 Subject: [PATCH 116/151] [fix] `no-unused-modules`: don't crash when lint file outside src-folder --- src/rules/no-unused-modules.js | 8 ++++++++ tests/src/rules/no-unused-modules.js | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index d91411bf20..3d59e850ef 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -302,6 +302,14 @@ module.exports = { return } + // refresh list of source files + const srcFiles = resolveFiles(getSrc(src), ignoreExports) + + // make sure file to be linted is included in source files + if (!srcFiles.has(file)) { + return + } + exports = exportList.get(file) // special case: export * from diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index 8069479ae5..c9e7fde157 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -592,3 +592,13 @@ describe('do not report missing export for ignored file', () => { invalid: [], }) }) + +// lint file not available in `src` +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `export const jsxFoo = 'foo'; export const jsxBar = 'bar'`, + filename: testFilePath('../jsx/named.jsx')}), + ], + invalid: [], +}) \ No newline at end of file From 7c13a4f781a02d35c37ce30f7759d85b4d40afc3 Mon Sep 17 00:00:00 2001 From: golopot Date: Sun, 12 May 2019 23:29:44 +0800 Subject: [PATCH 117/151] [Docs]: add missing `no-unused-modules` in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3e60280a80..7369367bd0 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a * Forbid a module from importing a module with a dependency path back to itself ([`no-cycle`]) * Prevent unnecessary path segments in import and require statements ([`no-useless-path-segments`]) * Forbid importing modules from parent directories ([`no-relative-parent-imports`]) +* Forbid modules without any export, and exports not imported by any modules. ([`no-unused-modules`]) [`no-unresolved`]: ./docs/rules/no-unresolved.md [`named`]: ./docs/rules/named.md @@ -41,6 +42,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a [`no-cycle`]: ./docs/rules/no-cycle.md [`no-useless-path-segments`]: ./docs/rules/no-useless-path-segments.md [`no-relative-parent-imports`]: ./docs/rules/no-relative-parent-imports.md +[`no-unused-modules`]: ./docs/rules/no-unused-modules.md ### Helpful warnings From 2f85fbea4c4eb498f8fcd7c797690cac0ba725b8 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 1 May 2019 15:15:42 -0700 Subject: [PATCH 118/151] [Docs] `no-unused-modules`: Indicates usage, plugin defaults to no-op, and add description to main README.md Fixes #1351 --- README.md | 2 ++ docs/rules/no-unused-modules.md | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7369367bd0..ddc95d4f81 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a * Report imported names marked with `@deprecated` documentation tag ([`no-deprecated`]) * Forbid the use of extraneous packages ([`no-extraneous-dependencies`]) * Forbid the use of mutable exports with `var` or `let`. ([`no-mutable-exports`]) +* Report modules without exports, or exports without matching import in another module ([`no-unused-modules`]) [`export`]: ./docs/rules/export.md [`no-named-as-default`]: ./docs/rules/no-named-as-default.md @@ -60,6 +61,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a [`no-deprecated`]: ./docs/rules/no-deprecated.md [`no-extraneous-dependencies`]: ./docs/rules/no-extraneous-dependencies.md [`no-mutable-exports`]: ./docs/rules/no-mutable-exports.md +[`no-unused-modules`]: ./docs/rules/no-unused-modules.md ### Module systems diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md index 96d689123a..32db6465fc 100644 --- a/docs/rules/no-unused-modules.md +++ b/docs/rules/no-unused-modules.md @@ -8,15 +8,26 @@ Note: dynamic imports are currently not supported. ## Rule Details +### Usage + +In order for this plugin to work, one of the options `missingExports` or `unusedExports` must be enabled (see "Options" section below). In the future, these options will be enabled by default (see https://github.com/benmosher/eslint-plugin-import/issues/1324) + +Example: +``` +"rules: { + ...otherRules, + "import/no-unused-modules": [1, {"unusedExports": true}] +} +``` ### Options This rule takes the following option: +- **`missingExports`**: if `true`, files without any exports are reported (defaults to `false`) +- **`unusedExports`**: if `true`, exports without any static usage within other modules are reported (defaults to `false`) - `src`: an array with files/paths to be analyzed. It only applies to unused exports. Defaults to `process.cwd()`, if not provided - `ignoreExports`: an array with files/paths for which unused exports will not be reported (e.g module entry points in a published package) -- `missingExports`: if `true`, files without any exports are reported -- `unusedExports`: if `true`, exports without any static usage within other modules are reported. ### Example for missing exports From 1edbbd03ec636469328ad59da922ed7edc8cd36d Mon Sep 17 00:00:00 2001 From: Charles Suh Date: Sun, 5 May 2019 16:47:41 -0700 Subject: [PATCH 119/151] [Fix] `no-common-js`: Also throw an error when assigning --- src/rules/no-commonjs.js | 1 + tests/src/rules/no-commonjs.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/rules/no-commonjs.js b/src/rules/no-commonjs.js index b6f11a7f0b..261654bbf2 100644 --- a/src/rules/no-commonjs.js +++ b/src/rules/no-commonjs.js @@ -91,6 +91,7 @@ module.exports = { if ( call.parent.type !== 'ExpressionStatement' && call.parent.type !== 'VariableDeclarator' + && call.parent.type !== 'AssignmentExpression' ) return if (call.callee.type !== 'Identifier') return diff --git a/tests/src/rules/no-commonjs.js b/tests/src/rules/no-commonjs.js index 72d5bfb19c..c8155938b4 100644 --- a/tests/src/rules/no-commonjs.js +++ b/tests/src/rules/no-commonjs.js @@ -60,6 +60,7 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { // imports { code: 'var x = require("x")', errors: [ { message: IMPORT_MESSAGE }] }, + { code: 'x = require("x")', errors: [ { message: IMPORT_MESSAGE }] }, { code: 'require("x")', errors: [ { message: IMPORT_MESSAGE }] }, // exports From aa290bbd5114332f4479011f94c18c03dc7d2fe6 Mon Sep 17 00:00:00 2001 From: Christopher Currie Date: Sat, 11 May 2019 16:32:00 -0700 Subject: [PATCH 120/151] Improve support for Typescript declare structures --- src/ExportMap.js | 15 ++ tests/files/typescript-declare.d.ts | 33 ++++ tests/files/typescript-export-assign.d.ts | 39 +++++ tests/src/rules/named.js | 186 +++++++++++----------- utils/unambiguous.js | 4 +- 5 files changed, 183 insertions(+), 94 deletions(-) create mode 100644 tests/files/typescript-declare.d.ts create mode 100644 tests/files/typescript-export-assign.d.ts diff --git a/src/ExportMap.js b/src/ExportMap.js index 8513e3d395..584cc2e0ff 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -464,6 +464,7 @@ ExportMap.parse = function (path, content, context) { case 'ClassDeclaration': case 'TypeAlias': // flowtype with babel-eslint parser case 'InterfaceDeclaration': + case 'TSDeclareFunction': case 'TSEnumDeclaration': case 'TSTypeAliasDeclaration': case 'TSInterfaceDeclaration': @@ -509,6 +510,20 @@ ExportMap.parse = function (path, content, context) { m.reexports.set(s.exported.name, { local, getImport: () => resolveImport(nsource) }) }) } + + // This doesn't declare anything, but changes what's being exported. + if (n.type === 'TSExportAssignment') { + const d = ast.body.find( + b => b.type === 'TSModuleDeclaration' && b.id.name === n.expression.name + ) + if (d && d.body && d.body.body) { + d.body.body.forEach(b => { + // Export-assignment exports all members in the namespace, explicitly exported or not. + const s = b.type === 'ExportNamedDeclaration' ? b.declaration : b + m.namespace.set(s.id.name, captureDoc(source, docStyleParsers, b)) + }) + } + } }) return m diff --git a/tests/files/typescript-declare.d.ts b/tests/files/typescript-declare.d.ts new file mode 100644 index 0000000000..5d526b85bf --- /dev/null +++ b/tests/files/typescript-declare.d.ts @@ -0,0 +1,33 @@ +export declare type MyType = string +export declare enum MyEnum { + Foo, + Bar, + Baz +} +export declare interface Foo { + native: string | number + typedef: MyType + enum: MyEnum +} + +export declare abstract class Bar { + abstract foo(): Foo + + method(); +} + +export declare function getFoo() : MyType; + +export declare module MyModule { + export function ModuleFunction(); +} + +export declare namespace MyNamespace { + export function NamespaceFunction(); + + export module NSModule { + export function NSModuleFunction(); + } +} + +interface NotExported {} diff --git a/tests/files/typescript-export-assign.d.ts b/tests/files/typescript-export-assign.d.ts new file mode 100644 index 0000000000..7a3392ee02 --- /dev/null +++ b/tests/files/typescript-export-assign.d.ts @@ -0,0 +1,39 @@ +export = AssignedNamespace; + +declare namespace AssignedNamespace { + type MyType = string + enum MyEnum { + Foo, + Bar, + Baz + } + + interface Foo { + native: string | number + typedef: MyType + enum: MyEnum + } + + abstract class Bar { + abstract foo(): Foo + + method(); + } + + export function getFoo() : MyType; + + export module MyModule { + export function ModuleFunction(); + } + + export namespace MyNamespace { + export function NamespaceFunction(); + + export module NSModule { + export function NSModuleFunction(); + } + } + + // Export-assignment exports all members in the namespace, explicitly exported or not. + // interface NotExported {} +} diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 922914f90a..8d8bd41c13 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -292,98 +292,100 @@ context('Typescript', function () { } parsers.forEach((parser) => { - ruleTester.run('named', rule, { - valid: [ - test({ - code: 'import { MyType } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { Foo } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { Bar } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { getFoo } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { MyEnum } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: ` - import { MyModule } from "./typescript" - MyModule.ModuleFunction() - `, - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: ` - import { MyNamespace } from "./typescript" - MyNamespace.NSModule.NSModuleFunction() - `, - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - ], - - invalid: [ - test({ - code: 'import { MissingType } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - errors: [{ - message: "MissingType not found in './typescript'", - type: 'Identifier', - }], - }), - test({ - code: 'import { NotExported } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - errors: [{ - message: "NotExported not found in './typescript'", - type: 'Identifier', - }], - }), - ], + ['typescript', 'typescript-declare', 'typescript-export-assign'].forEach((source) => { + ruleTester.run(`named`, rule, { + valid: [ + test({ + code: `import { MyType } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import { Foo } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import { Bar } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import { getFoo } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import { MyEnum } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: ` + import { MyModule } from "./${source}" + MyModule.ModuleFunction() + `, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: ` + import { MyNamespace } from "./${source}" + MyNamespace.NSModule.NSModuleFunction() + `, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + ], + + invalid: [ + test({ + code: `import { MissingType } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: [{ + message: `MissingType not found in './${source}'`, + type: 'Identifier', + }], + }), + test({ + code: `import { NotExported } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: [{ + message: `NotExported not found in './${source}'`, + type: 'Identifier', + }], + }), + ], + }) }) }) }) diff --git a/utils/unambiguous.js b/utils/unambiguous.js index a8e842cac3..390ad27b6d 100644 --- a/utils/unambiguous.js +++ b/utils/unambiguous.js @@ -2,7 +2,7 @@ exports.__esModule = true -const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*]))/m +const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*=]))/m /** * detect possible imports/exports without a full parse. * @@ -18,7 +18,7 @@ exports.test = function isMaybeUnambiguousModule(content) { } // future-/Babel-proof at the expense of being a little loose -const unambiguousNodeType = /^(Exp|Imp)ort.*Declaration$/ +const unambiguousNodeType = /^(((Exp|Imp)ort.*Declaration)|TSExportAssignment)$/ /** * Given an AST, return true if the AST unambiguously represents a module. From 288cedfce0b7fdabcdeb910ff4d4cc1ffe90b385 Mon Sep 17 00:00:00 2001 From: Christopher Currie Date: Sun, 12 May 2019 09:39:04 -0700 Subject: [PATCH 121/151] Make groups non-capturing. Co-Authored-By: Jordan Harband --- utils/unambiguous.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/unambiguous.js b/utils/unambiguous.js index 390ad27b6d..1dae1d6167 100644 --- a/utils/unambiguous.js +++ b/utils/unambiguous.js @@ -18,7 +18,7 @@ exports.test = function isMaybeUnambiguousModule(content) { } // future-/Babel-proof at the expense of being a little loose -const unambiguousNodeType = /^(((Exp|Imp)ort.*Declaration)|TSExportAssignment)$/ +const unambiguousNodeType = /^(?:(?:Exp|Imp)ort.*Declaration|TSExportAssignment)$/ /** * Given an AST, return true if the AST unambiguously represents a module. From 67b1e955f7b17a645d68695d6f1c317cd6100c70 Mon Sep 17 00:00:00 2001 From: Christopher Currie Date: Sun, 12 May 2019 10:02:50 -0700 Subject: [PATCH 122/151] Support older typescript parsers --- src/ExportMap.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/ExportMap.js b/src/ExportMap.js index 584cc2e0ff..c563c94aca 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -464,6 +464,7 @@ ExportMap.parse = function (path, content, context) { case 'ClassDeclaration': case 'TypeAlias': // flowtype with babel-eslint parser case 'InterfaceDeclaration': + case 'DeclareFunction': case 'TSDeclareFunction': case 'TSEnumDeclaration': case 'TSTypeAliasDeclaration': @@ -513,14 +514,20 @@ ExportMap.parse = function (path, content, context) { // This doesn't declare anything, but changes what's being exported. if (n.type === 'TSExportAssignment') { - const d = ast.body.find( - b => b.type === 'TSModuleDeclaration' && b.id.name === n.expression.name + const md = ast.body.find( + (b) => b.type === 'TSModuleDeclaration' && b.id.name === n.expression.name ) - if (d && d.body && d.body.body) { - d.body.body.forEach(b => { + if (md && md.body && md.body.body) { + md.body.body.forEach((b) => { // Export-assignment exports all members in the namespace, explicitly exported or not. const s = b.type === 'ExportNamedDeclaration' ? b.declaration : b - m.namespace.set(s.id.name, captureDoc(source, docStyleParsers, b)) + if (s.type === 'VariableDeclaration') { + s.declarations.forEach((d) => + recursivePatternCapture(d.id, + id => m.namespace.set(id.name, captureDoc(source, docStyleParsers, d, n)))) + } else { + m.namespace.set(s.id.name, captureDoc(source, docStyleParsers, b)) + } }) } } From d1e4455d01d41a386896733413f631c070c37da1 Mon Sep 17 00:00:00 2001 From: Christopher Currie Date: Sun, 12 May 2019 19:42:37 -0700 Subject: [PATCH 123/151] Verbose variable names --- src/ExportMap.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/ExportMap.js b/src/ExportMap.js index c563c94aca..949df3f196 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -514,19 +514,27 @@ ExportMap.parse = function (path, content, context) { // This doesn't declare anything, but changes what's being exported. if (n.type === 'TSExportAssignment') { - const md = ast.body.find( - (b) => b.type === 'TSModuleDeclaration' && b.id.name === n.expression.name + const moduleDecl = ast.body.find((bodyNode) => + bodyNode.type === 'TSModuleDeclaration' && bodyNode.id.name === n.expression.name ) - if (md && md.body && md.body.body) { - md.body.body.forEach((b) => { + log(moduleDecl) + log(moduleDecl.body) + if (moduleDecl && moduleDecl.body && moduleDecl.body.body) { + moduleDecl.body.body.forEach((moduleBlockNode) => { // Export-assignment exports all members in the namespace, explicitly exported or not. - const s = b.type === 'ExportNamedDeclaration' ? b.declaration : b - if (s.type === 'VariableDeclaration') { - s.declarations.forEach((d) => - recursivePatternCapture(d.id, - id => m.namespace.set(id.name, captureDoc(source, docStyleParsers, d, n)))) + const exportedDecl = moduleBlockNode.type === 'ExportNamedDeclaration' ? + moduleBlockNode.declaration : + moduleBlockNode + + if (exportedDecl.type === 'VariableDeclaration') { + exportedDecl.declarations.forEach((decl) => + recursivePatternCapture(decl.id,(id) => m.namespace.set( + id.name, captureDoc(source, docStyleParsers, decl, exportedDecl, moduleBlockNode)) + ) + ) } else { - m.namespace.set(s.id.name, captureDoc(source, docStyleParsers, b)) + m.namespace.set(exportedDecl.id.name, + captureDoc(source, docStyleParsers, moduleBlockNode)) } }) } From f66e0649601aae5ed16b29b67eb65c2695ad5b2a Mon Sep 17 00:00:00 2001 From: Christopher Currie Date: Sun, 12 May 2019 19:43:59 -0700 Subject: [PATCH 124/151] Remove log messages --- src/ExportMap.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ExportMap.js b/src/ExportMap.js index 949df3f196..fb242b564f 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -517,8 +517,6 @@ ExportMap.parse = function (path, content, context) { const moduleDecl = ast.body.find((bodyNode) => bodyNode.type === 'TSModuleDeclaration' && bodyNode.id.name === n.expression.name ) - log(moduleDecl) - log(moduleDecl.body) if (moduleDecl && moduleDecl.body && moduleDecl.body.body) { moduleDecl.body.body.forEach((moduleBlockNode) => { // Export-assignment exports all members in the namespace, explicitly exported or not. From 7aa13d14ca0fe890a34f7addadee08606484d68f Mon Sep 17 00:00:00 2001 From: Christopher Currie Date: Mon, 13 May 2019 14:56:02 -0700 Subject: [PATCH 125/151] PR feedback Co-Authored-By: Jordan Harband --- src/ExportMap.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ExportMap.js b/src/ExportMap.js index fb242b564f..dfc315b7d9 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -527,7 +527,8 @@ ExportMap.parse = function (path, content, context) { if (exportedDecl.type === 'VariableDeclaration') { exportedDecl.declarations.forEach((decl) => recursivePatternCapture(decl.id,(id) => m.namespace.set( - id.name, captureDoc(source, docStyleParsers, decl, exportedDecl, moduleBlockNode)) + id.name, + captureDoc(source, docStyleParsers, decl, exportedDecl, moduleBlockNode)) ) ) } else { From b52bf3e16bf399c5cf0681c198a3b362e6e7484b Mon Sep 17 00:00:00 2001 From: Christopher Currie Date: Mon, 13 May 2019 14:56:12 -0700 Subject: [PATCH 126/151] PR feedback Co-Authored-By: Jordan Harband --- src/ExportMap.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ExportMap.js b/src/ExportMap.js index dfc315b7d9..d49ab55605 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -532,7 +532,8 @@ ExportMap.parse = function (path, content, context) { ) ) } else { - m.namespace.set(exportedDecl.id.name, + m.namespace.set( + exportedDecl.id.name, captureDoc(source, docStyleParsers, moduleBlockNode)) } }) From 753c9dbf04cca2729bf693d99106b68c81119d41 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 14 May 2019 22:28:46 -0500 Subject: [PATCH 127/151] [refactor] fix eslint 6 compat by fixing imports --- .travis.yml | 8 ++++++- src/ExportMap.js | 2 +- src/rules/no-unused-modules.js | 41 +++++++++++++++++++++------------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index e8eaf9d967..441fc86dd1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ node_js: os: linux env: + - ESLINT_VERSION=^6.0.0-alpha - ESLINT_VERSION=5 - ESLINT_VERSION=4 - ESLINT_VERSION=3 @@ -49,12 +50,17 @@ matrix: exclude: - node_js: '4' env: ESLINT_VERSION=5 - + - node_js: '4' + env: ESLINT_VERSION=^6.0.0-alpha + - node_js: '6' + env: ESLINT_VERSION=^6.0.0-alpha + fast_finish: true allow_failures: # issues with typescript deps in this version intersection - node_js: '4' env: ESLINT_VERSION=4 + - env: ESLINT_VERSION=^6.0.0-alpha before_install: - 'nvm install-latest-npm' diff --git a/src/ExportMap.js b/src/ExportMap.js index 8513e3d395..243b885a7a 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -4,7 +4,7 @@ import doctrine from 'doctrine' import debug from 'debug' -import SourceCode from 'eslint/lib/util/source-code' +import { SourceCode } from 'eslint' import parse from 'eslint-module-utils/parse' import resolve from 'eslint-module-utils/resolve' diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 3d59e850ef..e168aea2e3 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -1,5 +1,5 @@ /** - * @fileOverview Ensures that modules contain exports and/or all + * @fileOverview Ensures that modules contain exports and/or all * modules are consumed within other modules. * @author René Fermann */ @@ -9,19 +9,28 @@ import resolve from 'eslint-module-utils/resolve' import docsUrl from '../docsUrl' // eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3 +// and has been moved to eslint/lib/cli-engine/file-enumerator in version 6 let listFilesToProcess try { - listFilesToProcess = require('eslint/lib/util/glob-utils').listFilesToProcess -} catch (err) { - listFilesToProcess = require('eslint/lib/util/glob-util').listFilesToProcess + var FileEnumerator = require('eslint/lib/cli-engine/file-enumerator').FileEnumerator + listFilesToProcess = function (src) { + var e = new FileEnumerator() + return Array.from(e.iterateFiles(src)) + } +} catch (e1) { + try { + listFilesToProcess = require('eslint/lib/util/glob-utils').listFilesToProcess + } catch (e2) { + listFilesToProcess = require('eslint/lib/util/glob-util').listFilesToProcess + } } const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration' const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration' const EXPORT_ALL_DECLARATION = 'ExportAllDeclaration' -const IMPORT_DECLARATION = 'ImportDeclaration' +const IMPORT_DECLARATION = 'ImportDeclaration' const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier' -const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier' +const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier' const VARIABLE_DECLARATION = 'VariableDeclaration' const FUNCTION_DECLARATION = 'FunctionDeclaration' const DEFAULT = 'default' @@ -37,7 +46,7 @@ const isNodeModule = path => { /** * read all files matching the patterns in src and ignoreExports - * + * * return all files matching src pattern, which are not matching the ignoreExports pattern */ const resolveFiles = (src, ignoreExports) => { @@ -73,7 +82,7 @@ const prepareImportsAndExports = (srcFiles, context) => { currentExportAll.add(value().path) }) exportAll.set(file, currentExportAll) - + reexports.forEach((value, key) => { if (key === DEFAULT) { exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() }) @@ -106,7 +115,7 @@ const prepareImportsAndExports = (srcFiles, context) => { imports.set(key, value.importedSpecifiers) }) importList.set(file, imports) - + // build up export list only, if file is not ignored if (ignoredFiles.has(file)) { return @@ -266,7 +275,7 @@ module.exports = { if (unusedExports && !preparationDone) { doPreparation(src, ignoreExports, context) } - + const file = context.getFilename() const checkExportPresence = node => { @@ -329,9 +338,9 @@ module.exports = { } const exportStatement = exports.get(exportedValue) - + const value = exportedValue === IMPORT_DEFAULT_SPECIFIER ? DEFAULT : exportedValue - + if (typeof exportStatement !== 'undefined'){ if (exportStatement.whereUsed.size < 1) { context.report( @@ -410,7 +419,7 @@ module.exports = { // preserve information about namespace imports let exportAll = exports.get(EXPORT_ALL_DECLARATION) let namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER) - + if (typeof namespaceImports === 'undefined') { namespaceImports = { whereUsed: new Set() } } @@ -434,13 +443,13 @@ module.exports = { if (typeof oldImportPaths === 'undefined') { oldImportPaths = new Map() } - + const oldNamespaceImports = new Set() const newNamespaceImports = new Set() const oldExportAll = new Set() const newExportAll = new Set() - + const oldDefaultImports = new Set() const newDefaultImports = new Set() @@ -493,7 +502,7 @@ module.exports = { if (!resolvedPath) { return } - + if (isNodeModule(resolvedPath)) { return } From c09c0ce09c2666d92b1dfbd1a022f155543d19dd Mon Sep 17 00:00:00 2001 From: Ken Gregory Date: Wed, 15 May 2019 17:49:38 -0400 Subject: [PATCH 128/151] Issue #1258 (docs) Document `env` option for `eslint-import-resolver-webpack` --- resolvers/webpack/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/resolvers/webpack/README.md b/resolvers/webpack/README.md index 711b663f28..a9869aec44 100644 --- a/resolvers/webpack/README.md +++ b/resolvers/webpack/README.md @@ -66,3 +66,16 @@ settings: - .js - .jsx ``` + +If your config relies on [environment variables](https://webpack.js.org/guides/environment-variables/), they can be specified using the `env` parameter. If your config is a function, it will be invoked with the value assigned to `env`: + +```yaml +--- +settings: + import/resolver: + webpack: + config: 'webpack.config.js' + env: + NODE_ENV: 'local' + production: true +``` From 557a3e21642454190b32d741e6cbe10420c4b126 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 23 May 2019 13:29:54 -0700 Subject: [PATCH 129/151] [Deps] update `resolve` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8d46a1bd6..bad7484cb1 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "lodash": "^4.17.11", "minimatch": "^3.0.4", "read-pkg-up": "^2.0.0", - "resolve": "^1.10.0" + "resolve": "^1.11.0" }, "nyc": { "require": [ From caae65c57b309daac7c54bc5855bdf758d9c198e Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 23 May 2019 13:58:54 -0700 Subject: [PATCH 130/151] [Tests] eslint 2 does not have `linter.version` --- tests/src/core/getExports.js | 4 ++-- tests/src/rules/export.js | 5 +++-- tests/src/rules/named.js | 5 +++-- tests/src/utils.js | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 6e09426125..78b5319bb4 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -1,6 +1,6 @@ import { expect } from 'chai' import semver from 'semver' -import { linter } from 'eslint' +import eslintPkg from 'eslint/package.json' import ExportMap from '../../../src/ExportMap' import * as fs from 'fs' @@ -335,7 +335,7 @@ describe('ExportMap', function () { ['array form', { 'typescript-eslint-parser': ['.ts', '.tsx'] }], ] - if (semver.satisfies(linter.version, '>5.0.0')) { + if (semver.satisfies(eslintPkg.version, '>5.0.0')) { configs.push(['array form', { '@typescript-eslint/parser': ['.ts', '.tsx'] }]) } diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index 3aad5241e9..6431c542ce 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -1,6 +1,7 @@ import { test, SYNTAX_CASES } from '../utils' -import { RuleTester, linter } from 'eslint' +import { RuleTester } from 'eslint' +import eslintPkg from 'eslint/package.json' import semver from 'semver' var ruleTester = new RuleTester() @@ -113,7 +114,7 @@ context('Typescript', function () { // Typescript const parsers = ['typescript-eslint-parser'] - if (semver.satisfies(linter.version, '>5.0.0')) { + if (semver.satisfies(eslintPkg.version, '>5.0.0')) { parsers.push('@typescript-eslint/parser') } diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 8d8bd41c13..6592aa958b 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -1,5 +1,6 @@ import { test, SYNTAX_CASES } from '../utils' -import { RuleTester, linter } from 'eslint' +import { RuleTester } from 'eslint' +import eslintPkg from 'eslint/package.json' import semver from 'semver' import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve' @@ -287,7 +288,7 @@ context('Typescript', function () { // Typescript const parsers = ['typescript-eslint-parser'] - if (semver.satisfies(linter.version, '>5.0.0')) { + if (semver.satisfies(eslintPkg.version, '>5.0.0')) { parsers.push('@typescript-eslint/parser') } diff --git a/tests/src/utils.js b/tests/src/utils.js index 4d4f42dc84..b416ec83d2 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -1,5 +1,5 @@ import path from 'path' -import { linter } from 'eslint' +import eslintPkg from 'eslint/package.json' import semver from 'semver' // warms up the module cache. this import takes a while (>500ms) @@ -12,7 +12,7 @@ export function testFilePath(relativePath) { export const FILENAME = testFilePath('foo.js') export function testVersion(specifier, t) { - return semver.satisfies(linter.version, specifier) && test(t) + return semver.satisfies(eslintPkg.version, specifier) && test(t) } export function test(t) { From cf5573b5784a8b19c1a7c3e4003005dfaadc4375 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 24 May 2019 14:32:05 -0700 Subject: [PATCH 131/151] Bump to v2.17.3 --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70287d855a..e9a08b1146 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,22 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +## [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]) +- [`no-unused-modules`]: make `import { name as otherName }` work ([#1340], [#1342], thanks [@rfermann]) +- [`no-unused-modules`]: make appveyor tests passing ([#1333], thanks [@rfermann]) - [`named`]: ignore Flow `typeof` imports and `type` exports ([#1345], thanks [@loganfsmyth]) +- [refactor] fix eslint 6 compat by fixing imports (thank [@ljharb]) +- Improve support for Typescript declare structures ([#1356], thanks [@christophercurrie]) + +### 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 @@ -555,7 +570,16 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1363]: https://github.com/benmosher/eslint-plugin-import/pull/1363 +[#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 +[#1352]: https://github.com/benmosher/eslint-plugin-import/pull/1352 +[#1347]: https://github.com/benmosher/eslint-plugin-import/pull/1347 [#1345]: https://github.com/benmosher/eslint-plugin-import/pull/1345 +[#1342]: https://github.com/benmosher/eslint-plugin-import/pull/1342 +[#1340]: https://github.com/benmosher/eslint-plugin-import/pull/1340 +[#1333]: https://github.com/benmosher/eslint-plugin-import/pull/1333 [#1331]: https://github.com/benmosher/eslint-plugin-import/pull/1331 [#1330]: https://github.com/benmosher/eslint-plugin-import/pull/1330 [#1320]: https://github.com/benmosher/eslint-plugin-import/pull/1320 @@ -891,3 +915,7 @@ for info on changes for earlier releases. [@feychenie]: https://github.com/feychenie [@kiwka]: https://github.com/kiwka [@loganfsmyth]: https://github.com/loganfsmyth +[@johndevedu]: https://github.com/johndevedu +[@charlessuh]: https://github.com/charlessuh +[@kgregory]: https://github.com/kgregory +[@christophercurrie]: https://github.com/christophercurrie diff --git a/package.json b/package.json index bad7484cb1..99f729b3fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.17.2", + "version": "2.17.3", "description": "Import with sanity.", "engines": { "node": ">=4" From d27aeaf996799e5edcf99cd2002924d1fef92569 Mon Sep 17 00:00:00 2001 From: Alex Page Date: Tue, 28 May 2019 16:26:27 -0400 Subject: [PATCH 132/151] Update no-cycle.md --- docs/rules/no-cycle.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/rules/no-cycle.md b/docs/rules/no-cycle.md index ef961e1b5a..8819d6704f 100644 --- a/docs/rules/no-cycle.md +++ b/docs/rules/no-cycle.md @@ -10,7 +10,9 @@ This includes cycles of depth 1 (imported module imports me) to `Infinity`, if t import './dep-a.js' export function b() { /* ... */ } +``` +```js // dep-a.js import { b } from './dep-b.js' // reported: Dependency cycle detected. ``` @@ -36,12 +38,16 @@ There is a `maxDepth` option available to prevent full expansion of very deep de // dep-c.js import './dep-a.js' +``` +```js // dep-b.js import './dep-c.js' export function b() { /* ... */ } +``` +```js // dep-a.js import { b } from './dep-b.js' // not reported as the cycle is at depth 2 ``` From 8b55e8f86b282f408d0310f478213bcd9b2c0be8 Mon Sep 17 00:00:00 2001 From: Chiawen Chen Date: Wed, 29 May 2019 20:25:19 +0800 Subject: [PATCH 133/151] [fix] `no-unused-modules`: handle ClassDeclaration Fixes #1368 --- src/rules/no-unused-modules.js | 11 +++++++++-- tests/files/no-unused-modules/file-0.js | 1 + tests/files/no-unused-modules/file-q.js | 3 +++ tests/src/rules/no-unused-modules.js | 9 +++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 tests/files/no-unused-modules/file-q.js diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index e168aea2e3..d86afbb464 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -33,6 +33,7 @@ const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier' const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier' const VARIABLE_DECLARATION = 'VariableDeclaration' const FUNCTION_DECLARATION = 'FunctionDeclaration' +const CLASS_DECLARATION = 'ClassDeclaration' const DEFAULT = 'default' let preparationDone = false @@ -390,7 +391,10 @@ module.exports = { }) } if (declaration) { - if (declaration.type === FUNCTION_DECLARATION) { + if ( + declaration.type === FUNCTION_DECLARATION || + declaration.type === CLASS_DECLARATION + ) { newExportIdentifiers.add(declaration.id.name) } if (declaration.type === VARIABLE_DECLARATION) { @@ -712,7 +716,10 @@ module.exports = { checkUsage(node, specifier.exported.name) }) if (node.declaration) { - if (node.declaration.type === FUNCTION_DECLARATION) { + if ( + node.declaration.type === FUNCTION_DECLARATION || + node.declaration.type === CLASS_DECLARATION + ) { checkUsage(node, node.declaration.id.name) } if (node.declaration.type === VARIABLE_DECLARATION) { diff --git a/tests/files/no-unused-modules/file-0.js b/tests/files/no-unused-modules/file-0.js index e7615ad4f6..a5319b5fcc 100644 --- a/tests/files/no-unused-modules/file-0.js +++ b/tests/files/no-unused-modules/file-0.js @@ -7,6 +7,7 @@ import { e } from './file-e' import { e2 } from './file-e' import { h2 } from './file-h' import * as l from './file-l' +import {q} from './file-q' export * from './file-n' export { default, o0, o3 } from './file-o' export { p } from './file-p' diff --git a/tests/files/no-unused-modules/file-q.js b/tests/files/no-unused-modules/file-q.js new file mode 100644 index 0000000000..c0d4f699de --- /dev/null +++ b/tests/files/no-unused-modules/file-q.js @@ -0,0 +1,3 @@ +export class q { + q0() {} +} diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index c9e7fde157..cefa6ff214 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -35,6 +35,8 @@ ruleTester.run('no-unused-modules', rule, { code: 'const a = 1; const b = 2; export { a, b }'}), test({ options: missingExportsOptions, code: 'const a = 1; export default a'}), + test({ options: missingExportsOptions, + code: 'export class Foo {}'}), ], invalid: [ test({ @@ -67,6 +69,9 @@ ruleTester.run('no-unused-modules', rule, { test({ options: unusedExportsOptions, code: 'export function d() { return 4 }', filename: testFilePath('./no-unused-modules/file-d.js')}), + test({ options: unusedExportsOptions, + code: 'export class q { q0() {} }', + filename: testFilePath('./no-unused-modules/file-q.js')}), test({ options: unusedExportsOptions, code: 'const e0 = 5; export { e0 as e }', filename: testFilePath('./no-unused-modules/file-e.js')}), @@ -132,6 +137,10 @@ ruleTester.run('no-unused-modules', rule, { code: 'export function j() { return 4 }', filename: testFilePath('./no-unused-modules/file-j.js'), errors: [error(`exported declaration 'j' not used within other modules`)]}), + test({ options: unusedExportsOptions, + code: 'export class q { q0() {} }', + filename: testFilePath('./no-unused-modules/file-q.js'), + errors: [error(`exported declaration 'q' not used within other modules`)]}), test({ options: unusedExportsOptions, code: 'const k0 = 5; export { k0 as k }', filename: testFilePath('./no-unused-modules/file-k.js'), From e4334e31308c74b19cc205ad8516d604f1da6d72 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Mon, 3 Jun 2019 11:17:58 -0400 Subject: [PATCH 134/151] add TS def extensions + defer to TS over JS closes #1366, thanks @davidNHK for bringing this up --- config/typescript.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config/typescript.js b/config/typescript.js index 6a2a08618c..002176c69c 100644 --- a/config/typescript.js +++ b/config/typescript.js @@ -1,9 +1,8 @@ /** * Adds `.jsx`, `.ts` and `.tsx` as an extension, and enables JSX/TSX parsing. */ -var jsExtensions = ['.js', '.jsx']; -var tsExtensions = ['.ts', '.tsx']; -var allExtensions = jsExtensions.concat(tsExtensions); + +var allExtensions = ['.ts', '.tsx', '.d.ts', '.js', '.jsx']; module.exports = { From 9a0455e9e41ee4e60a48b5de053c639a3a18ad5a Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Mon, 3 Jun 2019 11:18:30 -0400 Subject: [PATCH 135/151] fix goof w/ TS-specific extensions --- config/typescript.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/typescript.js b/config/typescript.js index 002176c69c..a8efe8e9a7 100644 --- a/config/typescript.js +++ b/config/typescript.js @@ -9,7 +9,7 @@ module.exports = { settings: { 'import/extensions': allExtensions, 'import/parsers': { - '@typescript-eslint/parser': tsExtensions + '@typescript-eslint/parser': ['.ts', '.tsx', '.d.ts'] }, 'import/resolver': { 'node': { From 15e5c6114ba4ded4f229e8d12cb4106f03b37c83 Mon Sep 17 00:00:00 2001 From: Chiawen Chen Date: Wed, 29 May 2019 21:03:54 +0800 Subject: [PATCH 136/151] [New] `order`: add fixer for destructuring commonjs import fixes #1337 --- src/rules/order.js | 3 ++- tests/src/rules/order.js | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/rules/order.js b/src/rules/order.js index 5c68f1b310..685671bfc1 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -140,7 +140,8 @@ function isPlainRequireModule(node) { return false } const decl = node.declarations[0] - const result = (decl.id != null && decl.id.type === 'Identifier') && + const result = decl.id && + (decl.id.type === 'Identifier' || decl.id.type === 'ObjectPattern') && decl.init != null && decl.init.type === 'CallExpression' && decl.init.callee != null && diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index dde5ae6cbe..477ed8a7ae 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -518,6 +518,21 @@ ruleTester.run('order', rule, { message: '`fs` import should occur before import of `async`', }], }), + // fix destructured commonjs import + test({ + code: ` + var {b} = require('async'); + var {a} = require('fs'); + `, + output: ` + var {a} = require('fs'); + var {b} = require('async'); + `, + errors: [{ + ruleId: 'order', + message: '`fs` import should occur before import of `async`', + }], + }), // fix order of multile import test({ code: ` From e1c505440432412bc3e3c89db5bd106053775695 Mon Sep 17 00:00:00 2001 From: Sebastian Werner Date: Thu, 6 Jun 2019 15:23:38 +0200 Subject: [PATCH 137/151] Added support for correctly ordering unknown types e.g. custom aliases --- docs/rules/order.md | 4 +- src/rules/order.js | 4 +- tests/src/rules/order.js | 87 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/docs/rules/order.md b/docs/rules/order.md index 45bde6acc1..ab25cf6b1e 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -77,7 +77,7 @@ This rule supports the following options: ### `groups: [array]`: -How groups are defined, and the order to respect. `groups` must be an array of `string` or [`string`]. The only allowed `string`s are: `"builtin"`, `"external"`, `"internal"`, `"parent"`, `"sibling"`, `"index"`. The enforced order is the same as the order of each element in a group. Omitted types are implicitly grouped together as the last element. Example: +How groups are defined, and the order to respect. `groups` must be an array of `string` or [`string`]. The only allowed `string`s are: `"builtin"`, `"external"`, `"internal"`, `"unknown"`, `"parent"`, `"sibling"`, `"index"`. The enforced order is the same as the order of each element in a group. Omitted types are implicitly grouped together as the last element. Example: ```js [ 'builtin', // Built-in types are first @@ -86,7 +86,7 @@ How groups are defined, and the order to respect. `groups` must be an array of ` // Then the rest: internal and external type ] ``` -The default value is `["builtin", "external", "parent", "sibling", "index"]`. +The default value is `["builtin", "external", "unknown", "parent", "sibling", "index"]`. You can set the options like this: diff --git a/src/rules/order.js b/src/rules/order.js index 685671bfc1..71787a0bce 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -4,7 +4,7 @@ import importType from '../core/importType' import isStaticRequire from '../core/staticRequire' import docsUrl from '../docsUrl' -const defaultGroups = ['builtin', 'external', 'parent', 'sibling', 'index'] +const defaultGroups = ['builtin', 'external', 'unknown', 'parent', 'sibling', 'index'] // REPORTING AND FIXING @@ -259,7 +259,7 @@ function isInVariableDeclarator(node) { (node.type === 'VariableDeclarator' || isInVariableDeclarator(node.parent)) } -const types = ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'] +const types = ['builtin', 'external', 'internal', 'unknown', 'parent', 'sibling', 'index'] // Creates an object with type-rank pairs. // Example: { index: 0, sibling: 1, parent: 1, external: 1, builtin: 2, internal: 2 } diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index 477ed8a7ae..5ef91a66b7 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -164,6 +164,42 @@ ruleTester.run('order', rule, { var index = require('./'); `, }), + // Using unknown import types (e.g. using an resolver alias via babel) + test({ + code: ` + import fs from 'fs'; + import { Input } from '-/components/Input'; + import { Button } from '-/components/Button'; + import { add } from './helper';`, + }), + // Using unknown import types (e.g. using an resolver alias via babel) with + // a custom group list. + test({ + code: ` + import { Input } from '-/components/Input'; + import { Button } from '-/components/Button'; + import fs from 'fs'; + import { add } from './helper';`, + options: [{ + groups: [ 'unknown', 'builtin', 'external', 'parent', 'sibling', 'index' ], + }], + }), + // Using unknown import types (e.g. using an resolver alias via babel) + // Option: newlines-between: 'always' + test({ + code: ` + import fs from 'fs'; + + import { Input } from '-/components/Input'; + import { Button } from '-/components/Button'; + + import { add } from './helper';`, + options: [ + { + 'newlines-between': 'always', + }, + ], + }), // Option: newlines-between: 'always' test({ code: ` @@ -885,6 +921,57 @@ ruleTester.run('order', rule, { message: '`fs` import should occur after import of `../foo/bar`', }], }), + // Default order using import with custom import alias + test({ + code: ` + import { Button } from '-/components/Button'; + import { add } from './helper'; + import fs from 'fs'; + `, + output: ` + import fs from 'fs'; + import { Button } from '-/components/Button'; + import { add } from './helper'; + `, + errors: [ + { + line: 4, + message: '`fs` import should occur before import of `-/components/Button`', + }, + ], + }), + // Default order using import with custom import alias + test({ + code: ` + import fs from 'fs'; + import { Button } from '-/components/Button'; + import { LinkButton } from '-/components/Link'; + import { add } from './helper'; + `, + output: ` + import fs from 'fs'; + + import { Button } from '-/components/Button'; + import { LinkButton } from '-/components/Link'; + + import { add } from './helper'; + `, + options: [ + { + 'newlines-between': 'always', + }, + ], + errors: [ + { + line: 2, + message: 'There should be at least one empty line between import groups', + }, + { + line: 4, + message: 'There should be at least one empty line between import groups', + }, + ], + }), // Option newlines-between: 'never' - should report unnecessary line between groups test({ code: ` From 4827e727e3d792a8a72f6ff0013da214e3cbfa7e Mon Sep 17 00:00:00 2001 From: Sebastian Werner Date: Thu, 6 Jun 2019 15:26:10 +0200 Subject: [PATCH 138/151] Added changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9a08b1146..1f3c66cf66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +- [`order`]: Adds support for correctly sorting unknown types into a single group (thanks [@swernerx]) + ## [2.17.3] - 2019-05-23 ### Fixed From d81a5c824f19095be1733fe332286d224eea2f4c Mon Sep 17 00:00:00 2001 From: Sebastian Werner Date: Tue, 11 Jun 2019 09:14:13 +0200 Subject: [PATCH 139/151] fix(ordering): changed default groups to not contain unknown --- docs/rules/order.md | 2 +- src/rules/order.js | 2 +- tests/src/rules/order.js | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/rules/order.md b/docs/rules/order.md index ab25cf6b1e..88ddca46fb 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -86,7 +86,7 @@ How groups are defined, and the order to respect. `groups` must be an array of ` // Then the rest: internal and external type ] ``` -The default value is `["builtin", "external", "unknown", "parent", "sibling", "index"]`. +The default value is `["builtin", "external", "parent", "sibling", "index"]`. You can set the options like this: diff --git a/src/rules/order.js b/src/rules/order.js index 71787a0bce..3d3e1b96b7 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -4,7 +4,7 @@ import importType from '../core/importType' import isStaticRequire from '../core/staticRequire' import docsUrl from '../docsUrl' -const defaultGroups = ['builtin', 'external', 'unknown', 'parent', 'sibling', 'index'] +const defaultGroups = ['builtin', 'external', 'parent', 'sibling', 'index'] // REPORTING AND FIXING diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index 5ef91a66b7..b310dd07ff 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -164,16 +164,19 @@ ruleTester.run('order', rule, { var index = require('./'); `, }), - // Using unknown import types (e.g. using an resolver alias via babel) + // Addijg unknown import types (e.g. using an resolver alias via babel) to the groups. test({ code: ` import fs from 'fs'; import { Input } from '-/components/Input'; import { Button } from '-/components/Button'; import { add } from './helper';`, + options: [{ + groups: ['builtin', 'external', 'unknown', 'parent', 'sibling', 'index'], + }], }), // Using unknown import types (e.g. using an resolver alias via babel) with - // a custom group list. + // an alternative custom group list. test({ code: ` import { Input } from '-/components/Input'; @@ -197,6 +200,7 @@ ruleTester.run('order', rule, { options: [ { 'newlines-between': 'always', + groups: ['builtin', 'external', 'unknown', 'parent', 'sibling', 'index'], }, ], }), @@ -933,6 +937,11 @@ ruleTester.run('order', rule, { import { Button } from '-/components/Button'; import { add } from './helper'; `, + options: [ + { + groups: ['builtin', 'external', 'unknown', 'parent', 'sibling', 'index'], + }, + ], errors: [ { line: 4, @@ -958,6 +967,7 @@ ruleTester.run('order', rule, { `, options: [ { + groups: ['builtin', 'external', 'unknown', 'parent', 'sibling', 'index'], 'newlines-between': 'always', }, ], From 8974346beb20707656502d17a2f8378d1cd2f081 Mon Sep 17 00:00:00 2001 From: golopot Date: Mon, 17 Jun 2019 09:10:42 +0800 Subject: [PATCH 140/151] [Build] make node 12 pass CI --- .travis.yml | 8 ++++++++ appveyor.yml | 1 + gulpfile.js | 18 ------------------ package.json | 8 +++++--- 4 files changed, 14 insertions(+), 21 deletions(-) delete mode 100644 gulpfile.js diff --git a/.travis.yml b/.travis.yml index 441fc86dd1..7d88426b71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: node_js node_js: + - '12' - '10' - '8' - '6' @@ -17,6 +18,8 @@ env: # osx backlog is often deep, so to be polite we can just hit these highlights matrix: include: + - env: PACKAGE=resolvers/node + node_js: 12 - env: PACKAGE=resolvers/node node_js: 10 - env: PACKAGE=resolvers/node @@ -25,6 +28,8 @@ matrix: node_js: 6 - env: PACKAGE=resolvers/node node_js: 4 + - env: PACKAGE=resolvers/webpack + node_js: 12 - env: PACKAGE=resolvers/webpack node_js: 10 - env: PACKAGE=resolvers/webpack @@ -34,6 +39,9 @@ matrix: - env: PACKAGE=resolvers/webpack node_js: 4 + - os: osx + env: ESLINT_VERSION=5 + node_js: 12 - os: osx env: ESLINT_VERSION=5 node_js: 10 diff --git a/appveyor.yml b/appveyor.yml index bb435695f5..e985479525 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,7 @@ # Test against this version of Node.js environment: matrix: + - nodejs_version: "12" - nodejs_version: "10" - nodejs_version: "8" # - nodejs_version: "6" diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 0fe6be4ba8..0000000000 --- a/gulpfile.js +++ /dev/null @@ -1,18 +0,0 @@ -var gulp = require('gulp') - , babel = require('gulp-babel') - , rimraf = require('rimraf') - -var SRC = 'src/**/*.js' - , DEST = 'lib' - -gulp.task('src', ['clean'], function () { - return gulp.src(SRC) - .pipe(babel()) - .pipe(gulp.dest(DEST)) -}) - -gulp.task('clean', function (done) { - rimraf(DEST, done) -}) - -gulp.task('prepublish', ['src']) diff --git a/package.json b/package.json index 99f729b3fd..bedfde4ae1 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "memo-parser" ], "scripts": { + "build": "babel --quiet --out-dir lib src", + "prebuild": "rimraf lib", "watch": "npm run mocha -- --watch tests/src", "pretest": "linklocal", "posttest": "eslint ./src", @@ -22,7 +24,7 @@ "test": "npm run mocha tests/src", "test-compiled": "npm run prepublish && NODE_PATH=./lib mocha --compilers js:babel-register --recursive tests/src", "test-all": "npm test && for resolver in ./resolvers/*; do cd $resolver && npm test && cd ../..; done", - "prepublish": "gulp prepublish", + "prepublish": "npm run build", "coveralls": "nyc report --reporter lcovonly && cat ./coverage/lcov.info | coveralls" }, "repository": { @@ -46,6 +48,8 @@ "homepage": "https://github.com/benmosher/eslint-plugin-import", "devDependencies": { "@typescript-eslint/parser": "^1.5.0", + "babel-cli": "^6.26.0", + "babel-core": "^6.26.3", "babel-eslint": "^8.2.6", "babel-plugin-istanbul": "^4.1.6", "babel-preset-es2015-argon": "latest", @@ -62,8 +66,6 @@ "eslint-import-test-order-redirect": "file:./tests/files/order-redirect", "@eslint/import-test-order-redirect-scoped": "file:./tests/files/order-redirect-scoped", "eslint-plugin-import": "2.x", - "gulp": "^3.9.1", - "gulp-babel": "6.1.2", "linklocal": "^2.8.2", "mocha": "^3.5.3", "nyc": "^11.9.0", From 4140870618e554c4458571a961056ad469381f59 Mon Sep 17 00:00:00 2001 From: fooloomanzoo Date: Fri, 21 Jun 2019 17:42:17 +0200 Subject: [PATCH 141/151] Update no-named-as-default-member.md --- docs/rules/no-named-as-default-member.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/no-named-as-default-member.md b/docs/rules/no-named-as-default-member.md index b6fdb13dd4..da6ae3f1d4 100644 --- a/docs/rules/no-named-as-default-member.md +++ b/docs/rules/no-named-as-default-member.md @@ -14,7 +14,7 @@ fixed in Babel 6. Before upgrading an existing codebase to Babel 6, it can be useful to run this lint rule. -[blog]: https://medium.com/@kentcdodds/misunderstanding-es6-modules-upgrading-babel-tears-and-a-solution-ad2d5ab93ce0 +[blog]: https://kentcdodds.com/blog/misunderstanding-es6-modules-upgrading-babel-tears-and-a-solution ## Rule Details From b5ff64e5d005a18c0a65bede75d8409d03c6eb24 Mon Sep 17 00:00:00 2001 From: Chris Shepherd Date: Sun, 23 Jun 2019 22:16:49 +0100 Subject: [PATCH 142/151] Transform output of FileEnumerator into what rule expects --- src/rules/no-unused-modules.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index d86afbb464..16130d47d5 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -15,7 +15,10 @@ try { var FileEnumerator = require('eslint/lib/cli-engine/file-enumerator').FileEnumerator listFilesToProcess = function (src) { var e = new FileEnumerator() - return Array.from(e.iterateFiles(src)) + return Array.from(e.iterateFiles(src), ({ filePath, ignored }) => ({ + ignored, + filename: filePath, + })) } } catch (e1) { try { From 1029b4ff3d6ee461c1dcfb2a8cd1bba4891c1a00 Mon Sep 17 00:00:00 2001 From: Chris Shepherd Date: Sun, 23 Jun 2019 21:56:48 +0100 Subject: [PATCH 143/151] Add ecmaVersion when required --- tests/src/core/getExports.js | 10 ++-- tests/src/core/parse.js | 2 +- tests/src/rules/namespace.js | 1 + tests/src/rules/newline-after-import.js | 70 ++++++++++++------------- tests/src/rules/no-amd.js | 4 +- tests/src/rules/no-commonjs.js | 14 ++--- tests/src/rules/no-namespace.js | 28 +++++----- tests/src/rules/unambiguous.js | 20 +++---- 8 files changed, 77 insertions(+), 72 deletions(-) diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 78b5319bb4..1b0bb8273c 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -187,6 +187,7 @@ describe('ExportMap', function () { jsdocTests({ parserPath: 'espree', parserOptions: { + ecmaVersion: 2015, sourceType: 'module', attachComment: true, }, @@ -195,6 +196,7 @@ describe('ExportMap', function () { jsdocTests({ parserPath: 'espree', parserOptions: { + ecmaVersion: 2015, sourceType: 'module', attachComment: true, }, @@ -206,6 +208,7 @@ describe('ExportMap', function () { jsdocTests({ parserPath: 'babel-eslint', parserOptions: { + ecmaVersion: 2015, sourceType: 'module', attachComment: true, }, @@ -214,6 +217,7 @@ describe('ExportMap', function () { jsdocTests({ parserPath: 'babel-eslint', parserOptions: { + ecmaVersion: 2015, sourceType: 'module', attachComment: true, }, @@ -223,8 +227,8 @@ describe('ExportMap', function () { }) context('exported static namespaces', function () { - const espreeContext = { parserPath: 'espree', parserOptions: { sourceType: 'module' }, settings: {} } - const babelContext = { parserPath: 'babel-eslint', parserOptions: { sourceType: 'module' }, settings: {} } + const espreeContext = { parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} } + const babelContext = { parserPath: 'babel-eslint', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} } it('works with espree & traditional namespace exports', function () { const path = getFilename('deep/a.js') @@ -255,7 +259,7 @@ describe('ExportMap', function () { }) context('deep namespace caching', function () { - const espreeContext = { parserPath: 'espree', parserOptions: { sourceType: 'module' }, settings: {} } + const espreeContext = { parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} } let a before('sanity check and prime cache', function (done) { // first version diff --git a/tests/src/core/parse.js b/tests/src/core/parse.js index 4b0f12c626..4d8b5ab58c 100644 --- a/tests/src/core/parse.js +++ b/tests/src/core/parse.js @@ -20,7 +20,7 @@ describe('parse(content, { settings, ecmaFeatures })', function () { }) it('infers jsx from ecmaFeatures when using stock parser', function () { - expect(() => parse(path, content, { settings: {}, parserPath: 'espree', parserOptions: { sourceType: 'module', ecmaFeatures: { jsx: true } } })) + expect(() => parse(path, content, { settings: {}, parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module', ecmaFeatures: { jsx: true } } })) .not.to.throw(Error) }) diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index e642a2b484..f147ddc2c4 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -25,6 +25,7 @@ const valid = [ parserOptions: { sourceType: 'module', ecmaFeatures: { jsx: true }, + ecmaVersion: 2015, }, }), test({ code: "import * as foo from './common';" }), diff --git a/tests/src/rules/newline-after-import.js b/tests/src/rules/newline-after-import.js index 730cef3636..490fad97dd 100644 --- a/tests/src/rules/newline-after-import.js +++ b/tests/src/rules/newline-after-import.js @@ -65,63 +65,63 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { return somethingElse(); } }`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import path from 'path';\nimport foo from 'foo';\n`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import path from 'path';import foo from 'foo';\n`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import path from 'path';import foo from 'foo';\n\nvar bar = 42;`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import foo from 'foo';\n\nvar bar = 'bar';`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import foo from 'foo';\n\n\nvar bar = 'bar';`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, options: [{ 'count': 2 }], }, { code: `import foo from 'foo';\n\n\n\n\nvar bar = 'bar';`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, options: [{ 'count': 4 }], }, { code: `var foo = require('foo-module');\n\nvar foo = 'bar';`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `var foo = require('foo-module');\n\n\nvar foo = 'bar';`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, options: [{ 'count': 2 }], }, { code: `var foo = require('foo-module');\n\n\n\n\nvar foo = 'bar';`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, options: [{ 'count': 4 }], }, { code: `require('foo-module');\n\nvar foo = 'bar';`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import foo from 'foo';\nimport { bar } from './bar-lib';`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import foo from 'foo';\n\nvar a = 123;\n\nimport { bar } from './bar-lib';`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `var foo = require('foo-module');\n\nvar a = 123;\n\nvar bar = require('bar-lib');`, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: ` @@ -130,7 +130,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { foo(); } `, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: ` @@ -139,7 +139,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { foo(); } `, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: ` @@ -149,7 +149,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { var bar = 42; } `, - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `//issue 592 @@ -157,13 +157,13 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { @SomeDecorator(require('./some-file')) class App {} `, - parserOptions: { sourceType: 'module' }, - parser: 'babel-eslint', + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + parser: require.resolve('babel-eslint'), }, { code: `var foo = require('foo');\n\n@SomeDecorator(foo)\nclass Foo {}`, - parserOptions: { sourceType: 'module' }, - parser: 'babel-eslint', + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + parser: require.resolve('babel-eslint'), }, ], @@ -176,7 +176,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 1, message: IMPORT_ERROR_MESSAGE, } ], - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import foo from 'foo';\n\nexport default function() {};`, @@ -187,7 +187,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 1, message: IMPORT_ERROR_MESSAGE_MULTIPLE(2), } ], - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `var foo = require('foo-module');\nvar something = 123;`, @@ -197,7 +197,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 1, message: REQUIRE_ERROR_MESSAGE, } ], - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import foo from 'foo';\nexport default function() {};`, @@ -208,7 +208,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 1, message: IMPORT_ERROR_MESSAGE, } ], - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `var foo = require('foo-module');\nvar something = 123;`, @@ -218,7 +218,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 1, message: REQUIRE_ERROR_MESSAGE, } ], - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import foo from 'foo';\nvar a = 123;\n\nimport { bar } from './bar-lib';\nvar b=456;`, @@ -234,7 +234,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 1, message: IMPORT_ERROR_MESSAGE, }], - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `var foo = require('foo-module');\nvar a = 123;\n\nvar bar = require('bar-lib');\nvar b=456;`, @@ -250,7 +250,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 1, message: REQUIRE_ERROR_MESSAGE, }], - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `var foo = require('foo-module');\nvar a = 123;\n\nrequire('bar-lib');\nvar b=456;`, @@ -266,7 +266,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 1, message: REQUIRE_ERROR_MESSAGE, }], - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `var path = require('path');\nvar foo = require('foo');\nvar bar = 42;`, @@ -316,7 +316,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 1, message: IMPORT_ERROR_MESSAGE, } ], - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import path from 'path';import foo from 'foo';var bar = 42;`, @@ -326,7 +326,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 25, message: IMPORT_ERROR_MESSAGE, } ], - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import foo from 'foo';\n@SomeDecorator(foo)\nclass Foo {}`, @@ -336,8 +336,8 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 1, message: IMPORT_ERROR_MESSAGE, } ], - parserOptions: { sourceType: 'module' }, - parser: 'babel-eslint', + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + parser: require.resolve('babel-eslint'), }, { code: `var foo = require('foo');\n@SomeDecorator(foo)\nclass Foo {}`, @@ -347,8 +347,8 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { column: 1, message: REQUIRE_ERROR_MESSAGE, } ], - parserOptions: { sourceType: 'module' }, - parser: 'babel-eslint', + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + parser: require.resolve('babel-eslint'), }, ], }) diff --git a/tests/src/rules/no-amd.js b/tests/src/rules/no-amd.js index 5b0e39f4f6..d67739f716 100644 --- a/tests/src/rules/no-amd.js +++ b/tests/src/rules/no-amd.js @@ -4,8 +4,8 @@ var ruleTester = new RuleTester() ruleTester.run('no-amd', require('rules/no-amd'), { valid: [ - { code: 'import "x";', parserOptions: { sourceType: 'module' } }, - { code: 'import x from "x"', parserOptions: { sourceType: 'module' } }, + { code: 'import "x";', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, + { code: 'import x from "x"', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, 'var x = require("x")', 'require("x")', diff --git a/tests/src/rules/no-commonjs.js b/tests/src/rules/no-commonjs.js index c8155938b4..ae0377f4a1 100644 --- a/tests/src/rules/no-commonjs.js +++ b/tests/src/rules/no-commonjs.js @@ -9,14 +9,14 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { valid: [ // imports - { code: 'import "x";', parserOptions: { sourceType: 'module' } }, - { code: 'import x from "x"', parserOptions: { sourceType: 'module' } }, - { code: 'import x from "x"', parserOptions: { sourceType: 'module' } }, - { code: 'import { x } from "x"', parserOptions: { sourceType: 'module' } }, + { code: 'import "x";', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, + { code: 'import x from "x"', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, + { code: 'import x from "x"', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, + { code: 'import { x } from "x"', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, // exports - { code: 'export default "x"', parserOptions: { sourceType: 'module' } }, - { code: 'export function house() {}', parserOptions: { sourceType: 'module' } }, + { code: 'export default "x"', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, + { code: 'export function house() {}', parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, { code: 'function someFunc() {\n'+ @@ -24,7 +24,7 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { '\n'+ ' expect(exports.someProp).toEqual({ a: \'value\' });\n'+ '}', - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, // allowed requires diff --git a/tests/src/rules/no-namespace.js b/tests/src/rules/no-namespace.js index c65f317c9e..d9ef3423c2 100644 --- a/tests/src/rules/no-namespace.js +++ b/tests/src/rules/no-namespace.js @@ -1,15 +1,15 @@ import { RuleTester } from 'eslint' -const ERROR_MESSAGE = 'Unexpected namespace import.'; +const ERROR_MESSAGE = 'Unexpected namespace import.' const ruleTester = new RuleTester() ruleTester.run('no-namespace', require('rules/no-namespace'), { valid: [ - { code: "import { a, b } from 'foo';", parserOptions: { sourceType: 'module' } }, - { code: "import { a, b } from './foo';", parserOptions: { sourceType: 'module' } }, - { code: "import bar from 'bar';", parserOptions: { sourceType: 'module' } }, - { code: "import bar from './bar';", parserOptions: { sourceType: 'module' } } + { code: "import { a, b } from 'foo';", parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, + { code: "import { a, b } from './foo';", parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, + { code: "import bar from 'bar';", parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, + { code: "import bar from './bar';", parserOptions: { ecmaVersion: 2015, sourceType: 'module' } }, ], invalid: [ @@ -18,27 +18,27 @@ ruleTester.run('no-namespace', require('rules/no-namespace'), { errors: [ { line: 1, column: 8, - message: ERROR_MESSAGE + message: ERROR_MESSAGE, } ], - parserOptions: { sourceType: 'module' } + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: "import defaultExport, * as foo from 'foo';", errors: [ { line: 1, column: 23, - message: ERROR_MESSAGE + message: ERROR_MESSAGE, } ], - parserOptions: { sourceType: 'module' } + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: "import * as foo from './foo';", errors: [ { line: 1, column: 8, - message: ERROR_MESSAGE + message: ERROR_MESSAGE, } ], - parserOptions: { sourceType: 'module' } - } - ] -}); + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + }, + ], +}) diff --git a/tests/src/rules/unambiguous.js b/tests/src/rules/unambiguous.js index c1a89e829f..b9455118e6 100644 --- a/tests/src/rules/unambiguous.js +++ b/tests/src/rules/unambiguous.js @@ -10,46 +10,46 @@ ruleTester.run('unambiguous', rule, { { code: 'import y from "z"; function x() {}', - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: 'import * as y from "z"; function x() {}', - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: 'import { y } from "z"; function x() {}', - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: 'import z, { y } from "z"; function x() {}', - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: 'function x() {}; export {}', - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: 'function x() {}; export { x }', - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: 'function x() {}; export { y } from "z"', - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: 'function x() {}; export * as y from "z"', parser: 'babel-eslint', - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: 'export function x() {}', - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, ], invalid: [ { code: 'function x() {}', - parserOptions: { sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, errors: ['This module could be parsed as a valid script.'], }, ], From e6ea127a39adb7fb8b571351b570ca135c7b42b8 Mon Sep 17 00:00:00 2001 From: Chris Shepherd Date: Sun, 23 Jun 2019 22:09:59 +0100 Subject: [PATCH 144/151] Use require.resolve when passing parser to RuleTester --- tests/src/rules/default.js | 26 +++++++------- tests/src/rules/dynamic-import-chunkname.js | 2 +- tests/src/rules/export.js | 4 +-- tests/src/rules/max-dependencies.js | 2 +- tests/src/rules/named.js | 36 +++++++++---------- tests/src/rules/namespace.js | 18 +++++----- tests/src/rules/no-cycle.js | 12 +++---- tests/src/rules/no-default-export.js | 10 +++--- tests/src/rules/no-duplicates.js | 4 +-- tests/src/rules/no-extraneous-dependencies.js | 4 +-- tests/src/rules/no-mutable-exports.js | 4 +-- tests/src/rules/no-named-as-default.js | 10 +++--- tests/src/rules/no-named-default.js | 2 +- tests/src/rules/no-named-export.js | 10 +++--- tests/src/rules/no-relative-parent-imports.js | 14 ++++---- tests/src/rules/no-unresolved.js | 12 +++---- tests/src/rules/no-useless-path-segments.js | 12 +++---- tests/src/rules/order.js | 4 +-- tests/src/rules/prefer-default-export.js | 8 ++--- tests/src/rules/unambiguous.js | 2 +- tests/src/utils.js | 4 +-- 21 files changed, 100 insertions(+), 100 deletions(-) diff --git a/tests/src/rules/default.js b/tests/src/rules/default.js index 027c5a93de..c02b364489 100644 --- a/tests/src/rules/default.js +++ b/tests/src/rules/default.js @@ -28,19 +28,19 @@ ruleTester.run('default', rule, { // es7 export syntax test({ code: 'export bar from "./bar"' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), test({ code: 'export { default as bar } from "./bar"' }), test({ code: 'export bar, { foo } from "./bar"' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), test({ code: 'export { default as bar, foo } from "./bar"' }), test({ code: 'export bar, * as names from "./bar"' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), // sanity check test({ code: 'export {a} from "./named-exports"' }), test({ code: 'import twofer from "./trampoline"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // jsx @@ -68,27 +68,27 @@ ruleTester.run('default', rule, { // from no-errors test({ code: "import Foo from './jsx/FooES7.js';", - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // #545: more ES7 cases test({ code: "import bar from './default-export-from.js';", - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: "import bar from './default-export-from-named.js';", - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: "import bar from './default-export-from-ignored.js';", settings: { 'import/ignore': ['common'] }, - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: "export bar from './default-export-from-ignored.js';", settings: { 'import/ignore': ['common'] }, - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), ...SYNTAX_CASES, @@ -113,23 +113,23 @@ ruleTester.run('default', rule, { // es7 export syntax test({ code: 'export baz from "./named-exports"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: ['No default export found in module.'], }), test({ code: 'export baz, { bar } from "./named-exports"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: ['No default export found in module.'], }), test({ code: 'export baz, * as names from "./named-exports"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: ['No default export found in module.'], }), // exports default from a module with no default test({ code: 'import twofer from "./broken-trampoline"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: ['No default export found in module.'], }), diff --git a/tests/src/rules/dynamic-import-chunkname.js b/tests/src/rules/dynamic-import-chunkname.js index 6b0d448da9..d19667c162 100644 --- a/tests/src/rules/dynamic-import-chunkname.js +++ b/tests/src/rules/dynamic-import-chunkname.js @@ -14,7 +14,7 @@ const pickyCommentOptions = [{ const multipleImportFunctionOptions = [{ importFunctions: ['dynamicImport', 'definitelyNotStaticImport'], }] -const parser = 'babel-eslint' +const parser = require.resolve('babel-eslint') const noLeadingCommentError = 'dynamic imports require a leading comment with the webpack chunkname' const nonBlockCommentError = 'dynamic imports require a /* foo */ style comment, not a // foo comment' diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index 6431c542ce..3792970966 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -112,10 +112,10 @@ ruleTester.run('export', rule, { context('Typescript', function () { // Typescript - const parsers = ['typescript-eslint-parser'] + const parsers = [require.resolve('typescript-eslint-parser')] if (semver.satisfies(eslintPkg.version, '>5.0.0')) { - parsers.push('@typescript-eslint/parser') + parsers.push(require.resolve('@typescript-eslint/parser')) } parsers.forEach((parser) => { diff --git a/tests/src/rules/max-dependencies.js b/tests/src/rules/max-dependencies.js index 7377c14510..ee35b648fb 100644 --- a/tests/src/rules/max-dependencies.js +++ b/tests/src/rules/max-dependencies.js @@ -66,7 +66,7 @@ ruleTester.run('max-dependencies', rule, { test({ code: 'import type { x } from \'./foo\'; import type { y } from \'./bar\'', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), options: [{ max: 1, }], diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 6592aa958b..8827ce5e42 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -55,11 +55,11 @@ ruleTester.run('named', rule, { // es7 test({ code: 'export bar, { foo } from "./bar"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import { foo, bar } from "./named-trampoline"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // regression tests @@ -74,43 +74,43 @@ ruleTester.run('named', rule, { // should ignore imported/exported flow types, even if they don’t exist test({ code: 'import type { MissingType } from "./flowtypes"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import typeof { MissingType } from "./flowtypes"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import type { MyOpaqueType } from "./flowtypes"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import typeof { MyOpaqueType } from "./flowtypes"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import { type MyOpaqueType, MyClass } from "./flowtypes"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import { typeof MyOpaqueType, MyClass } from "./flowtypes"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import typeof MissingType from "./flowtypes"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import typeof * as MissingType from "./flowtypes"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'export type { MissingType } from "./flowtypes"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'export type { MyOpaqueType } from "./flowtypes"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // jsnext @@ -188,17 +188,17 @@ ruleTester.run('named', rule, { // es7 test({ code: 'export bar2, { bar } from "./bar"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: ["bar not found in './bar'"], }), test({ code: 'import { foo, bar, baz } from "./named-trampoline"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: ["baz not found in './named-trampoline'"], }), test({ code: 'import { baz } from "./broken-trampoline"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: ["baz not found via broken-trampoline.js -> named-exports.js"], }), @@ -214,7 +214,7 @@ ruleTester.run('named', rule, { test({ code: 'import { type MyOpaqueType, MyMissingClass } from "./flowtypes"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: ["MyMissingClass not found in './flowtypes'"], }), @@ -286,10 +286,10 @@ ruleTester.run('named (export *)', rule, { context('Typescript', function () { // Typescript - const parsers = ['typescript-eslint-parser'] + const parsers = [require.resolve('typescript-eslint-parser')] if (semver.satisfies(eslintPkg.version, '>5.0.0')) { - parsers.push('@typescript-eslint/parser') + parsers.push(require.resolve('@typescript-eslint/parser')) } parsers.forEach((parser) => { diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index f147ddc2c4..cfc6305d5a 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -56,16 +56,16 @@ const valid = [ // es7 // ///////// test({ code: 'export * as names from "./named-exports"' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), test({ code: 'export defport, * as names from "./named-exports"' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), // non-existent is handled by no-unresolved test({ code: 'export * as names from "./does-not-exist"' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), test({ code: 'import * as Endpoints from "./issue-195/Endpoints"; console.log(Endpoints.Users)', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // respect hoisting @@ -80,11 +80,11 @@ const valid = [ test({ code: "import * as names from './default-export'; console.log(names.default)" }), test({ code: 'export * as names from "./default-export"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'export defport, * as names from "./default-export"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // #456: optionally ignore computed references @@ -102,7 +102,7 @@ const valid = [ }), test({ code: `import * as names from './named-exports'; const {a, b, ...rest} = names;`, - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // #1144: should handle re-export CommonJS as namespace @@ -163,7 +163,7 @@ const invalid = [ test({ code: 'import * as Endpoints from "./issue-195/Endpoints"; console.log(Endpoints.Foo)', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: ["'Foo' not found in imported namespace 'Endpoints'."], }), @@ -217,7 +217,7 @@ const invalid = [ /////////////////////// // deep dereferences // ////////////////////// -;[['deep', 'espree'], ['deep-es7', 'babel-eslint']].forEach(function ([folder, parser]) { // close over params +;[['deep', require.resolve('espree')], ['deep-es7', require.resolve('babel-eslint')]].forEach(function ([folder, parser]) { // close over params valid.push( test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.c.d.e)` }), test({ parser, code: `import { b } from "./${folder}/a"; console.log(b.c.d.e)` }), diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js index c88c7504bc..18fe88af18 100644 --- a/tests/src/rules/no-cycle.js +++ b/tests/src/rules/no-cycle.js @@ -43,15 +43,15 @@ ruleTester.run('no-cycle', rule, { test({ code: 'import("./depth-two").then(function({ foo }){})', options: [{ maxDepth: 1 }], - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import type { FooType } from "./depth-one"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import type { FooType, BarType } from "./depth-one"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), ], invalid: [ @@ -108,17 +108,17 @@ ruleTester.run('no-cycle', rule, { test({ code: 'import { bar } from "./depth-three-indirect"', errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)], - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import("./depth-three-star")', errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)], - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import("./depth-three-indirect")', errors: [error(`Dependency cycle via ./depth-two:1=>./depth-one:1`)], - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), ], }) diff --git a/tests/src/rules/no-default-export.js b/tests/src/rules/no-default-export.js index 5977ef19b2..dd71c167ea 100644 --- a/tests/src/rules/no-default-export.js +++ b/tests/src/rules/no-default-export.js @@ -58,7 +58,7 @@ ruleTester.run('no-default-export', rule, { }), test({ code: 'export { a, b } from "foo.js"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // no exports at all @@ -74,15 +74,15 @@ ruleTester.run('no-default-export', rule, { test({ code: `export type UserId = number;`, - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'export foo from "foo.js"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: `export Memory, { MemoryValue } from './Memory'`, - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), ], invalid: [ @@ -112,7 +112,7 @@ ruleTester.run('no-default-export', rule, { }), test({ code: 'export default from "foo.js"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: [{ ruleId: 'ExportNamedDeclaration', message: 'Prefer named exports.', diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index cdd365382c..29b080466f 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -23,7 +23,7 @@ ruleTester.run('no-duplicates', rule, { // #225: ignore duplicate if is a flow type import test({ code: "import { x } from './foo'; import type { y } from './foo'", - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), ], invalid: [ @@ -64,7 +64,7 @@ ruleTester.run('no-duplicates', rule, { test({ code: "import type { x } from './foo'; import type { y } from './foo'", output: "import type { x , y } from './foo'; ", - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index c29c8e5bd0..b9d24580ec 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -76,7 +76,7 @@ ruleTester.run('no-extraneous-dependencies', rule, { test({ code: 'import type MyType from "myflowtyped";', options: [{packageDir: packageDirWithFlowTyped}], - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import react from "react";', @@ -289,5 +289,5 @@ ruleTester.run('no-extraneous-dependencies', rule, { message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it", }], }), - ] + ], }) diff --git a/tests/src/rules/no-mutable-exports.js b/tests/src/rules/no-mutable-exports.js index b597f70d52..6d83566b74 100644 --- a/tests/src/rules/no-mutable-exports.js +++ b/tests/src/rules/no-mutable-exports.js @@ -25,11 +25,11 @@ ruleTester.run('no-mutable-exports', rule, { test({ code: 'class Counter {}\nexport default Counter'}), test({ code: 'class Counter {}\nexport { Counter as default }'}), test({ - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), code: 'export Something from "./something";', }), test({ - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), code: 'type Foo = {}\nexport type {Foo}', }), ], diff --git a/tests/src/rules/no-named-as-default.js b/tests/src/rules/no-named-as-default.js index d249545f4c..a5e0f041f5 100644 --- a/tests/src/rules/no-named-as-default.js +++ b/tests/src/rules/no-named-as-default.js @@ -13,13 +13,13 @@ ruleTester.run('no-named-as-default', rule, { // es7 test({ code: 'export bar, { foo } from "./bar";' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), test({ code: 'export bar from "./bar";' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), // #566: don't false-positive on `default` itself test({ code: 'export default from "./bar";' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), ...SYNTAX_CASES, ], @@ -39,13 +39,13 @@ ruleTester.run('no-named-as-default', rule, { // es7 test({ code: 'export foo from "./bar";', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: [ { message: 'Using exported name \'foo\' as identifier for default export.' , type: 'ExportDefaultSpecifier' } ] }), test({ code: 'export foo, { foo as bar } from "./bar";', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: [ { message: 'Using exported name \'foo\' as identifier for default export.' , type: 'ExportDefaultSpecifier' } ] }), diff --git a/tests/src/rules/no-named-default.js b/tests/src/rules/no-named-default.js index b7013fcc7b..f9109fa8fd 100644 --- a/tests/src/rules/no-named-default.js +++ b/tests/src/rules/no-named-default.js @@ -19,7 +19,7 @@ ruleTester.run('no-named-default', rule, { message: 'Use default import syntax to import \'default\'.', type: 'Identifier', }], - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }),*/ test({ code: 'import { default as bar } from "./bar";', diff --git a/tests/src/rules/no-named-export.js b/tests/src/rules/no-named-export.js index c56a135421..c4ef9c9c7f 100644 --- a/tests/src/rules/no-named-export.js +++ b/tests/src/rules/no-named-export.js @@ -14,7 +14,7 @@ ruleTester.run('no-named-export', rule, { }), test({ code: 'export default from "foo.js"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // no exports at all @@ -146,7 +146,7 @@ ruleTester.run('no-named-export', rule, { }), test({ code: 'export { a, b } from "foo.js"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: [{ ruleId: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', @@ -154,7 +154,7 @@ ruleTester.run('no-named-export', rule, { }), test({ code: `export type UserId = number;`, - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: [{ ruleId: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', @@ -162,7 +162,7 @@ ruleTester.run('no-named-export', rule, { }), test({ code: 'export foo from "foo.js"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: [{ ruleId: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', @@ -170,7 +170,7 @@ ruleTester.run('no-named-export', rule, { }), test({ code: `export Memory, { MemoryValue } from './Memory'`, - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), errors: [{ ruleId: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', diff --git a/tests/src/rules/no-relative-parent-imports.js b/tests/src/rules/no-relative-parent-imports.js index 281edd1237..05ef9d8bff 100644 --- a/tests/src/rules/no-relative-parent-imports.js +++ b/tests/src/rules/no-relative-parent-imports.js @@ -4,7 +4,7 @@ import { test as _test, testFilePath } from '../utils' const test = def => _test(Object.assign(def, { filename: testFilePath('./internal-modules/plugins/plugin2/index.js'), - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), })) const ruleTester = new RuleTester() @@ -83,24 +83,24 @@ ruleTester.run('no-relative-parent-imports', rule, { errors: [ { message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `./../plugin.js` or consider making `./../plugin.js` a package.', line: 1, - column: 17 - }] + column: 17, + }], }), test({ code: 'import foo from "../../api/service"', errors: [ { message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `../../api/service` or consider making `../../api/service` a package.', line: 1, - column: 17 - }] + column: 17, + }], }), test({ code: 'import("../../api/service")', errors: [ { message: 'Relative imports from parent directories are not allowed. Please either pass what you\'re importing through at runtime (dependency injection), move `index.js` to same directory as `../../api/service` or consider making `../../api/service` a package.', line: 1, - column: 8 + column: 8, }], - }) + }), ], }) diff --git a/tests/src/rules/no-unresolved.js b/tests/src/rules/no-unresolved.js index 9fa7019a73..124ac84830 100644 --- a/tests/src/rules/no-unresolved.js +++ b/tests/src/rules/no-unresolved.js @@ -30,7 +30,7 @@ function runResolverTests(resolver) { rest({ code: "import {someThing} from './test-module';" }), rest({ code: "import fs from 'fs';" }), rest({ code: "import('fs');" - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), rest({ code: 'import * as foo from "a"' }), @@ -40,9 +40,9 @@ function runResolverTests(resolver) { // stage 1 proposal for export symmetry, rest({ code: 'export * as bar from "./bar"' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), rest({ code: 'export bar from "./bar"' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), rest({ code: 'import foo from "./jsx/MyUnCoolComponent.jsx"' }), // commonjs setting @@ -122,7 +122,7 @@ function runResolverTests(resolver) { "module 'in-alternate-root'." , type: 'Literal', }], - parser: 'babel-eslint'}), + parser: require.resolve('babel-eslint')}), rest({ code: 'export { foo } from "./does-not-exist"' , errors: ["Unable to resolve path to module './does-not-exist'."] }), @@ -133,11 +133,11 @@ function runResolverTests(resolver) { // export symmetry proposal rest({ code: 'export * as bar from "./does-not-exist"' - , parser: 'babel-eslint' + , parser: require.resolve('babel-eslint') , errors: ["Unable to resolve path to module './does-not-exist'."], }), rest({ code: 'export bar from "./does-not-exist"' - , parser: 'babel-eslint' + , parser: require.resolve('babel-eslint') , errors: ["Unable to resolve path to module './does-not-exist'."], }), diff --git a/tests/src/rules/no-useless-path-segments.js b/tests/src/rules/no-useless-path-segments.js index 52e66ec6f9..366e753541 100644 --- a/tests/src/rules/no-useless-path-segments.js +++ b/tests/src/rules/no-useless-path-segments.js @@ -29,11 +29,11 @@ function runResolverTests(resolver) { test({ code: 'import "./importType"', options: [{ noUselessIndex: true }] }), // ./importType.js does not exist test({ code: 'import(".")' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), test({ code: 'import("..")' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), test({ code: 'import("fs").then(function(fs){})' - , parser: 'babel-eslint' }), + , parser: require.resolve('babel-eslint') }), ], invalid: [ @@ -199,17 +199,17 @@ function runResolverTests(resolver) { test({ code: 'import("./")', errors: [ 'Useless path segments for "./", should be "."'], - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import("../")', errors: [ 'Useless path segments for "../", should be ".."'], - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'import("./deep//a")', errors: [ 'Useless path segments for "./deep//a", should be "./deep/a"'], - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), ], }) diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index b310dd07ff..0898cd85d9 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1382,7 +1382,7 @@ ruleTester.run('order', rule, { var fs = require('fs'); var async = require('async'); `, - parser: 'typescript-eslint-parser', + parser: require.resolve('typescript-eslint-parser'), errors: [{ ruleId: 'order', message: '`fs` import should occur before import of `async`', @@ -1398,7 +1398,7 @@ ruleTester.run('order', rule, { var fs = require('fs'); var async = require('async'); `, - parser: '@typescript-eslint/parser', + parser: require.resolve('@typescript-eslint/parser'), errors: [{ ruleId: 'order', message: '`fs` import should occur before import of `async`', diff --git a/tests/src/rules/prefer-default-export.js b/tests/src/rules/prefer-default-export.js index 3a89c6aa7f..1d670a02fe 100644 --- a/tests/src/rules/prefer-default-export.js +++ b/tests/src/rules/prefer-default-export.js @@ -60,7 +60,7 @@ ruleTester.run('prefer-default-export', rule, { }), test({ code: `export Memory, { MemoryValue } from './Memory'`, - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // no exports at all @@ -71,17 +71,17 @@ ruleTester.run('prefer-default-export', rule, { test({ code: `export type UserId = number;`, - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // issue #653 test({ code: 'export default from "foo.js"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), test({ code: 'export { a, b } from "foo.js"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), }), // ...SYNTAX_CASES, diff --git a/tests/src/rules/unambiguous.js b/tests/src/rules/unambiguous.js index b9455118e6..09ca57ef9a 100644 --- a/tests/src/rules/unambiguous.js +++ b/tests/src/rules/unambiguous.js @@ -38,7 +38,7 @@ ruleTester.run('unambiguous', rule, { }, { code: 'function x() {}; export * as y from "z"', - parser: 'babel-eslint', + parser: require.resolve('babel-eslint'), parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { diff --git a/tests/src/utils.js b/tests/src/utils.js index b416ec83d2..a842b788a9 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -46,7 +46,7 @@ export const SYNTAX_CASES = [ test({ code: 'for (let [ foo, bar ] of baz) {}' }), test({ code: 'const { x, y } = bar' }), - test({ code: 'const { x, y, ...z } = bar', parser: 'babel-eslint' }), + test({ code: 'const { x, y, ...z } = bar', parser: require.resolve('babel-eslint') }), // all the exports test({ code: 'let x; export { x }' }), @@ -54,7 +54,7 @@ export const SYNTAX_CASES = [ // not sure about these since they reference a file // test({ code: 'export { x } from "./y.js"'}), - // test({ code: 'export * as y from "./y.js"', parser: 'babel-eslint'}), + // test({ code: 'export * as y from "./y.js"', parser: require.resolve('babel-eslint')}), test({ code: 'export const x = null' }), test({ code: 'export var x = null' }), From 2f1f4daef5a9814d3342a8d3d38f26edb535c65f Mon Sep 17 00:00:00 2001 From: Chris Shepherd Date: Sun, 23 Jun 2019 19:59:29 +0100 Subject: [PATCH 145/151] Allow ESLint@6 --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index bedfde4ae1..f5ccac825b 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ }, "homepage": "https://github.com/benmosher/eslint-plugin-import", "devDependencies": { + "@eslint/import-test-order-redirect-scoped": "file:./tests/files/order-redirect-scoped", "@typescript-eslint/parser": "^1.5.0", "babel-cli": "^6.26.0", "babel-core": "^6.26.3", @@ -58,13 +59,12 @@ "chai": "^4.2.0", "coveralls": "^3.0.2", "cross-env": "^4.0.0", - "eslint": "2.x - 5.x", + "eslint": "2.x - 6.x", "eslint-import-resolver-node": "file:./resolvers/node", "eslint-import-resolver-typescript": "^1.0.2", "eslint-import-resolver-webpack": "file:./resolvers/webpack", - "eslint-module-utils": "file:./utils", "eslint-import-test-order-redirect": "file:./tests/files/order-redirect", - "@eslint/import-test-order-redirect-scoped": "file:./tests/files/order-redirect-scoped", + "eslint-module-utils": "file:./utils", "eslint-plugin-import": "2.x", "linklocal": "^2.8.2", "mocha": "^3.5.3", @@ -77,7 +77,7 @@ "typescript-eslint-parser": "^22.0.0" }, "peerDependencies": { - "eslint": "2.x - 5.x" + "eslint": "2.x - 6.x" }, "dependencies": { "array-includes": "^3.0.3", From c2b19d0148a64a0c70cd5433b33ea295d8d8d9de Mon Sep 17 00:00:00 2001 From: Chris Shepherd Date: Sun, 23 Jun 2019 22:45:57 +0100 Subject: [PATCH 146/151] Update to @typescript-eslint/parser canary Includes fixes for the use of `eslint/lib/util/traverser` that is now removed in ESLint 6 Co-Authored-By: golopot --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f5ccac825b..b415fa362b 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "homepage": "https://github.com/benmosher/eslint-plugin-import", "devDependencies": { "@eslint/import-test-order-redirect-scoped": "file:./tests/files/order-redirect-scoped", - "@typescript-eslint/parser": "^1.5.0", + "@typescript-eslint/parser": "1.10.3-alpha.13", "babel-cli": "^6.26.0", "babel-core": "^6.26.3", "babel-eslint": "^8.2.6", From d9b72583c6699bb0ea094d5c1738627aaef77a99 Mon Sep 17 00:00:00 2001 From: Chris Shepherd Date: Sun, 23 Jun 2019 23:02:06 +0100 Subject: [PATCH 147/151] Only run tests using typescript-eslint-parser on eslint@<6 --- tests/src/core/getExports.js | 5 ++++- tests/src/rules/export.js | 6 +++++- tests/src/rules/named.js | 6 +++++- tests/src/rules/order.js | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 1b0bb8273c..44edcf6292 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -336,13 +336,16 @@ describe('ExportMap', function () { const configs = [ // ['string form', { 'typescript-eslint-parser': '.ts' }], - ['array form', { 'typescript-eslint-parser': ['.ts', '.tsx'] }], ] if (semver.satisfies(eslintPkg.version, '>5.0.0')) { configs.push(['array form', { '@typescript-eslint/parser': ['.ts', '.tsx'] }]) } + if (semver.satisfies(eslintPkg.version, '<6.0.0')) { + configs.push(['array form', { 'typescript-eslint-parser': ['.ts', '.tsx'] }]) + } + configs.forEach(([description, parserConfig]) => { describe(description, function () { diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index 3792970966..5ca7f458c3 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -112,12 +112,16 @@ ruleTester.run('export', rule, { context('Typescript', function () { // Typescript - const parsers = [require.resolve('typescript-eslint-parser')] + const parsers = [] if (semver.satisfies(eslintPkg.version, '>5.0.0')) { parsers.push(require.resolve('@typescript-eslint/parser')) } + if (semver.satisfies(eslintPkg.version, '<6.0.0')) { + parsers.push(require.resolve('typescript-eslint-parser')) + } + parsers.forEach((parser) => { const parserConfig = { parser: parser, diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 8827ce5e42..90e9c8b9b5 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -286,12 +286,16 @@ ruleTester.run('named (export *)', rule, { context('Typescript', function () { // Typescript - const parsers = [require.resolve('typescript-eslint-parser')] + const parsers = [] if (semver.satisfies(eslintPkg.version, '>5.0.0')) { parsers.push(require.resolve('@typescript-eslint/parser')) } + if (semver.satisfies(eslintPkg.version, '<6.0.0')) { + parsers.push(require.resolve('typescript-eslint-parser')) + } + parsers.forEach((parser) => { ['typescript', 'typescript-declare', 'typescript-export-assign'].forEach((source) => { ruleTester.run(`named`, rule, { diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index 0898cd85d9..4f86fc9f8b 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1373,7 +1373,7 @@ ruleTester.run('order', rule, { }], })), // fix incorrect order with typescript-eslint-parser - test({ + testVersion('<6.0.0', { code: ` var async = require('async'); var fs = require('fs'); From 3bee716c2ef732dedb870854516e13a2ffe552bb Mon Sep 17 00:00:00 2001 From: Chris Shepherd Date: Sun, 23 Jun 2019 23:37:17 +0100 Subject: [PATCH 148/151] Use `eslint@6` instead of `eslint@6.0.0-alpha` in Travis --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7d88426b71..ed0bf4fefe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ node_js: os: linux env: - - ESLINT_VERSION=^6.0.0-alpha + - ESLINT_VERSION=6 - ESLINT_VERSION=5 - ESLINT_VERSION=4 - ESLINT_VERSION=3 @@ -59,16 +59,16 @@ matrix: - node_js: '4' env: ESLINT_VERSION=5 - node_js: '4' - env: ESLINT_VERSION=^6.0.0-alpha + env: ESLINT_VERSION=6 - node_js: '6' - env: ESLINT_VERSION=^6.0.0-alpha + env: ESLINT_VERSION=6 fast_finish: true allow_failures: # issues with typescript deps in this version intersection - node_js: '4' env: ESLINT_VERSION=4 - - env: ESLINT_VERSION=^6.0.0-alpha + - env: ESLINT_VERSION=6 before_install: - 'nvm install-latest-npm' From d7023f688bf8cb1c77935e120b21c5a39d71c12b Mon Sep 17 00:00:00 2001 From: Chris Shepherd Date: Sun, 23 Jun 2019 23:38:19 +0100 Subject: [PATCH 149/151] Remove ESLint 6 from allowed failures in Travis It's now supported --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ed0bf4fefe..606c355367 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,7 +68,6 @@ matrix: # issues with typescript deps in this version intersection - node_js: '4' env: ESLINT_VERSION=4 - - env: ESLINT_VERSION=6 before_install: - 'nvm install-latest-npm' From 7e41d29b1438ff73a41420bc66f6964f80ae9f3a Mon Sep 17 00:00:00 2001 From: Chris Shepherd Date: Sun, 23 Jun 2019 23:53:44 +0100 Subject: [PATCH 150/151] Make testVersion take a function to delay running require.resolve --- tests/src/rules/order.js | 8 ++++---- tests/src/utils.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index 4f86fc9f8b..04236420c1 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1373,7 +1373,7 @@ ruleTester.run('order', rule, { }], })), // fix incorrect order with typescript-eslint-parser - testVersion('<6.0.0', { + testVersion('<6.0.0', () => ({ code: ` var async = require('async'); var fs = require('fs'); @@ -1387,9 +1387,9 @@ ruleTester.run('order', rule, { ruleId: 'order', message: '`fs` import should occur before import of `async`', }], - }), + })), // fix incorrect order with @typescript-eslint/parser - testVersion('>5.0.0', { + testVersion('>5.0.0', () => ({ code: ` var async = require('async'); var fs = require('fs'); @@ -1403,6 +1403,6 @@ ruleTester.run('order', rule, { ruleId: 'order', message: '`fs` import should occur before import of `async`', }], - }), + })), ].filter((t) => !!t), }) diff --git a/tests/src/utils.js b/tests/src/utils.js index a842b788a9..9d71ad84ac 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -12,7 +12,7 @@ export function testFilePath(relativePath) { export const FILENAME = testFilePath('foo.js') export function testVersion(specifier, t) { - return semver.satisfies(eslintPkg.version, specifier) && test(t) + return semver.satisfies(eslintPkg.version, specifier) && test(t()) } export function test(t) { From c924f5d66e2852afd50fb4de94a48d2c29bbb9d7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 24 Jun 2019 13:48:24 -0700 Subject: [PATCH 151/151] Bump to v2.18.0 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++-- package.json | 2 +- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f3c66cf66..a5c4623ee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,21 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] -- [`order`]: Adds support for correctly sorting unknown types into a single group (thanks [@swernerx]) + +## [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]) +- [`order`]: add fixer for destructuring commonjs import ([#1372], thanks [@golopot]) +- typescript config: add TS def extensions + defer to TS over JS ([#1366], thanks [@benmosher]) + +### Fixes +- [`no-unused-modules`]: handle ClassDeclaration ([#1371], thanks [@golopot]) + +### Docs +- [`no-cycle`]: split code examples so file separation is obvious ([#1370], thanks [@alex-page]) +- [`no-named-as-default-member`]: update broken link ([#1389], thanks [@fooloomanzoo]) ## [2.17.3] - 2019-05-23 @@ -572,6 +586,12 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1393]: https://github.com/benmosher/eslint-plugin-import/pull/1393 +[#1389]: https://github.com/benmosher/eslint-plugin-import/pull/1389 +[#1375]: https://github.com/benmosher/eslint-plugin-import/pull/1375 +[#1372]: https://github.com/benmosher/eslint-plugin-import/pull/1372 +[#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 [#1358]: https://github.com/benmosher/eslint-plugin-import/pull/1358 [#1356]: https://github.com/benmosher/eslint-plugin-import/pull/1356 @@ -690,6 +710,7 @@ for info on changes for earlier releases. [#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 [#1322]: https://github.com/benmosher/eslint-plugin-import/issues/1322 @@ -772,7 +793,11 @@ 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.17.0...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.18.0...HEAD +[2.18.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.17.3...v2.18.0 +[2.17.3]: https://github.com/benmosher/eslint-plugin-import/compare/v2.17.2...v2.17.3 +[2.17.2]: https://github.com/benmosher/eslint-plugin-import/compare/v2.17.1...v2.17.2 +[2.17.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.17.0...v2.17.1 [2.17.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.16.0...v2.17.0 [2.16.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.15.0...v2.16.0 [2.15.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.14.0...v2.15.0 @@ -921,3 +946,7 @@ for info on changes for earlier releases. [@charlessuh]: https://github.com/charlessuh [@kgregory]: https://github.com/kgregory [@christophercurrie]: https://github.com/christophercurrie +[@alex-page]: https://github.com/alex-page +[@benmosher]: https://github.com/benmosher +[@fooloomanzoo]: https://github.com/fooloomanzoo +[@sheepsteak]: https://github.com/sheepsteak diff --git a/package.json b/package.json index b415fa362b..7a25c7363f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.17.3", + "version": "2.18.0", "description": "Import with sanity.", "engines": { "node": ">=4"