diff --git a/.babelrc b/.babelrc index 2cbf5c811c..604f307fee 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,7 @@ { "presets": [ "es2015-argon" ], "sourceMaps": "inline", + "retainLines": true, "env": { "test": { "plugins": [ diff --git a/.eslintrc.yml b/.eslintrc.yml index 8c270e9533..4f8ccaac37 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -20,6 +20,7 @@ rules: semi: [2, "never"] curly: [2, "multi-line"] comma-dangle: [2, always-multiline] + eol-last: [2, "always"] eqeqeq: [2, "allow-null"] no-shadow: 1 quotes: diff --git a/.travis.yml b/.travis.yml index fda8f0a5a6..5aec9ffcad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,9 @@ matrix: include: - env: LINT=true node_js: lts/* + - env: TS_PARSER=2 ESLINT_VERSION=7 + node_js: lts/* + before_script: 'npm install --no-save @typescript-eslint/parser@2' - env: PACKAGE=resolvers/node node_js: 14 - env: PACKAGE=resolvers/node diff --git a/CHANGELOG.md b/CHANGELOG.md index ca63eb5f8d..c0aaae190d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,32 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +## [2.22.0] - 2020-06-26 +### Added +- [`no-unused-modules`]: consider exported TypeScript interfaces, types and enums ([#1819], thanks [@nicolashenry]) +- [`no-cycle`]: allow `maxDepth` option to be `"∞"` (thanks [@ljharb]) + +### Fixed +- [`order`]/TypeScript: properly support `import = object` expressions ([#1823], thanks [@manuth]) +- [`no-extraneous-dependencies`]/TypeScript: do not error when importing type from dev dependencies ([#1820], thanks [@fernandopasik]) +- [`default`]: avoid crash with `export =` ([#1822], thanks [@AndrewLeedham]) +- [`order`]/[`newline-after-import`]: ignore TypeScript's "export import object" ([#1830], thanks [@be5invis]) +- [`dynamic-import-chunkname`]/TypeScript: supports `@typescript-eslint/parser` ([#1833], thanks [@noelebrun]) +- [`order`]/TypeScript: ignore ordering of object imports ([#1831], thanks [@manuth]) +- [`namespace`]: do not report on shadowed import names ([#518], thanks [@ljharb]) +- [`export`]: avoid warning on `export * as` non-conflicts ([#1834], thanks [@ljharb]) + +### Changed +- [`no-extraneous-dependencies`]: add tests for importing types ([#1824], thanks [@taye]) +- [docs] [`no-default-export`]: Fix docs url ([#1836], thanks [@beatrizrezener]) +- [docs] [`imports-first`]: deprecation info and link to `first` docs ([#1835], thanks [@beatrizrezener]) + +## [2.21.2] - 2020-06-09 +### Fixed +- [`order`]: avoid a crash on TypeScript’s `export import` syntax ([#1808], thanks [@ljharb]) +- [`newline-after-import`]: consider TypeScript `import =` syntax' ([#1811], thanks [@ljharb]) +- [`no-internal-modules`]: avoid a crash on a named export declaration ([#1814], thanks [@ljharb]) + ## [2.21.1] - 2020-06-07 ### Fixed - TypeScript: [`import/named`]: avoid requiring `typescript` when not using TS ([#1805], thanks [@ljharb]) @@ -696,6 +722,17 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1836]: https://github.com/benmosher/eslint-plugin-import/pull/1836 +[#1835]: https://github.com/benmosher/eslint-plugin-import/pull/1835 +[#1834]: https://github.com/benmosher/eslint-plugin-import/issues/1834 +[#1833]: https://github.com/benmosher/eslint-plugin-import/pull/1833 +[#1831]: https://github.com/benmosher/eslint-plugin-import/pull/1831 +[#1830]: https://github.com/benmosher/eslint-plugin-import/pull/1830 +[#1824]: https://github.com/benmosher/eslint-plugin-import/pull/1824 +[#1823]: https://github.com/benmosher/eslint-plugin-import/pull/1823 +[#1822]: https://github.com/benmosher/eslint-plugin-import/pull/1822 +[#1820]: https://github.com/benmosher/eslint-plugin-import/pull/1820 +[#1819]: https://github.com/benmosher/eslint-plugin-import/pull/1819 [#1802]: https://github.com/benmosher/eslint-plugin-import/pull/1802 [#1801]: https://github.com/benmosher/eslint-plugin-import/issues/1801 [#1788]: https://github.com/benmosher/eslint-plugin-import/pull/1788 @@ -855,6 +892,7 @@ for info on changes for earlier releases. [#555]: https://github.com/benmosher/eslint-plugin-import/pull/555 [#538]: https://github.com/benmosher/eslint-plugin-import/pull/538 [#527]: https://github.com/benmosher/eslint-plugin-import/pull/527 +[#518]: https://github.com/benmosher/eslint-plugin-import/pull/518 [#509]: https://github.com/benmosher/eslint-plugin-import/pull/509 [#508]: https://github.com/benmosher/eslint-plugin-import/pull/508 [#503]: https://github.com/benmosher/eslint-plugin-import/pull/503 @@ -897,6 +935,10 @@ 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 +[#1814]: https://github.com/benmosher/eslint-plugin-import/issues/1814 +[#1811]: https://github.com/benmosher/eslint-plugin-import/issues/1811 +[#1808]: https://github.com/benmosher/eslint-plugin-import/issues/1808 +[#1805]: https://github.com/benmosher/eslint-plugin-import/issues/1805 [#1565]: https://github.com/benmosher/eslint-plugin-import/issues/1565 [#1366]: https://github.com/benmosher/eslint-plugin-import/issues/1366 [#1334]: https://github.com/benmosher/eslint-plugin-import/issues/1334 @@ -982,7 +1024,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.21.1...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.22.0...HEAD +[2.22.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.21.1...v2.22.0 +[2.21.2]: https://github.com/benmosher/eslint-plugin-import/compare/v2.21.1...v2.21.2 [2.21.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.21.0...v2.21.1 [2.21.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.20.2...v2.21.0 [2.20.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.20.1...v2.20.2 @@ -1205,3 +1249,10 @@ for info on changes for earlier releases. [@adjerbetian]: https://github.com/adjerbetian [@Maxim-Mazurok]: https://github.com/Maxim-Mazurok [@malykhinvi]: https://github.com/malykhinvi +[@nicolashenry]: https://github.com/nicolashenry +[@fernandopasik]: https://github.com/fernandopasik +[@taye]: https://github.com/taye +[@AndrewLeedham]: https://github.com/AndrewLeedham +[@be5invis]: https://github.com/be5invis +[@noelebrun]: https://github.com/noelebrun +[@beatrizrezener]: https://github.com/beatrizrezener diff --git a/docs/rules/imports-first.md b/docs/rules/imports-first.md new file mode 100644 index 0000000000..b7f20754af --- /dev/null +++ b/docs/rules/imports-first.md @@ -0,0 +1,3 @@ +# imports-first + +This rule was **deprecated** in eslint-plugin-import v2.0.0. Please use the corresponding rule [`first`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/first.md). diff --git a/docs/rules/no-cycle.md b/docs/rules/no-cycle.md index 6329bb272e..7d54e81ff8 100644 --- a/docs/rules/no-cycle.md +++ b/docs/rules/no-cycle.md @@ -2,7 +2,7 @@ Ensures that there is no resolvable path back to this module via its dependencies. -This includes cycles of depth 1 (imported module imports me) to `Infinity`, if the +This includes cycles of depth 1 (imported module imports me) to `"∞"` (or `Infinity`), if the [`maxDepth`](#maxdepth) option is not set. ```js diff --git a/docs/rules/order.md b/docs/rules/order.md index 3aa41bbf50..7d91efd6f5 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -22,6 +22,8 @@ import bar from './bar'; import baz from './bar/baz'; // 6. "index" of the current directory import main from './'; +// 7. "object"-imports (only available in TypeScript) +import log = console.log; ``` Unassigned imports are ignored, as the order they are imported in may be important. @@ -77,12 +79,15 @@ 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"`, `"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: +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"`, `"object"`. +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 ['sibling', 'parent'], // Then sibling and parent types. They can be mingled together 'index', // Then the index file + 'object', // Then the rest: internal and external type ] ``` @@ -91,7 +96,7 @@ The default value is `["builtin", "external", "parent", "sibling", "index"]`. You can set the options like this: ```js -"import/order": ["error", {"groups": ["index", "sibling", "parent", "internal", "external", "builtin"]}] +"import/order": ["error", {"groups": ["index", "sibling", "parent", "internal", "external", "builtin", "object"]}] ``` ### `pathGroups: [array of objects]`: diff --git a/package.json b/package.json index 4d6de7644c..8356d2443e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.21.1", + "version": "2.22.0", "description": "Import with sanity.", "engines": { "node": ">=4" @@ -55,7 +55,7 @@ "devDependencies": { "@eslint/import-test-order-redirect-scoped": "file:./tests/files/order-redirect-scoped", "@test-scope/some-module": "file:./tests/files/symlinked-module", - "@typescript-eslint/parser": "^2.23.0", + "@typescript-eslint/parser": "^2.23.0 || ^3.3.0", "array.prototype.flatmap": "^1.2.3", "babel-cli": "^6.26.0", "babel-core": "^6.26.3", @@ -80,7 +80,7 @@ "eslint-plugin-json": "^2.1.1", "fs-copy-file-sync": "^1.1.1", "glob": "^7.1.6", - "in-publish": "^2.0.0", + "in-publish": "^2.0.1", "linklocal": "^2.8.2", "lodash.isarray": "^4.0.0", "mocha": "^3.5.3", @@ -90,7 +90,7 @@ "rimraf": "^2.7.1", "semver": "^6.3.0", "sinon": "^2.4.1", - "typescript": "~3.8.3", + "typescript": "~3.9.5", "typescript-eslint-parser": "^22.0.0" }, "peerDependencies": { diff --git a/resolvers/node/CHANGELOG.md b/resolvers/node/CHANGELOG.md index 1418844082..8fa31bed7d 100644 --- a/resolvers/node/CHANGELOG.md +++ b/resolvers/node/CHANGELOG.md @@ -4,9 +4,15 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## Unreleased + +## v0.3.4 - 2020-06-16 ### Added - add `.node` extension ([#1663]) +## v0.3.3 - 2020-01-10 +### Changed +- [meta] copy LICENSE file to all npm packages on prepublish ([#1595], thanks [@opichals]) + ## v0.3.2 - 2018-01-05 ### Added - `.mjs` extension detected by default to support `experimental-modules` ([#939]) @@ -45,6 +51,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [#438]: https://github.com/benmosher/eslint-plugin-import/pull/438 [#1663]: https://github.com/benmosher/eslint-plugin-import/issues/1663 +[#1595]: https://github.com/benmosher/eslint-plugin-import/pull/1595 [#939]: https://github.com/benmosher/eslint-plugin-import/issues/939 [#531]: https://github.com/benmosher/eslint-plugin-import/issues/531 [#437]: https://github.com/benmosher/eslint-plugin-import/issues/437 @@ -53,3 +60,4 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [@lukeapage]: https://github.com/lukeapage [@SkeLLLa]: https://github.com/SkeLLLa [@ljharb]: https://github.com/ljharb +[@opichals]: https://github.com/opichals diff --git a/resolvers/node/package.json b/resolvers/node/package.json index 03eadafbf6..27daa907f9 100644 --- a/resolvers/node/package.json +++ b/resolvers/node/package.json @@ -1,6 +1,6 @@ { "name": "eslint-import-resolver-node", - "version": "0.3.3", + "version": "0.3.4", "description": "Node default behavior import resolution plugin for eslint-plugin-import.", "main": "index.js", "files": [ diff --git a/resolvers/node/test/native.js b/resolvers/node/test/native.js index 8212295922..c2a4339049 100644 --- a/resolvers/node/test/native.js +++ b/resolvers/node/test/native.js @@ -1 +1 @@ -exports.natively = function () { return "but where do we feature?" } \ No newline at end of file +exports.natively = function () { return "but where do we feature?" } diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index e06203823d..5b31c350ac 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -5,9 +5,16 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +## 0.12.2 - 2020-06-16 + ### Fixed - [fix] provide config fallback ([#1705], thanks [@migueloller]) +## 0.12.1 - 2020-01-10 + +### Changed +- [meta] copy LICENSE file to all npm packages on prepublish ([#1595], thanks [@opichals]) + ## 0.12.0 - 2019-12-07 ### Added @@ -126,6 +133,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel Thanks to [@gausie] for the initial PR ([#164], ages ago! 😅) and [@jquense] for tests ([#278]). [#1705]: https://github.com/benmosher/eslint-plugin-import/pull/1705 +[#1595]: https://github.com/benmosher/eslint-plugin-import/pull/1595 [#1503]: https://github.com/benmosher/eslint-plugin-import/pull/1503 [#1297]: https://github.com/benmosher/eslint-plugin-import/pull/1297 [#1261]: https://github.com/benmosher/eslint-plugin-import/pull/1261 @@ -179,3 +187,4 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [@echenley]: https://github.com/echenley [@Aghassi]: https://github.com/Aghassi [@migueloller]: https://github.com/migueloller +[@opichals]: https://github.com/opichals diff --git a/resolvers/webpack/package.json b/resolvers/webpack/package.json index 72959fa886..7de2690b2d 100644 --- a/resolvers/webpack/package.json +++ b/resolvers/webpack/package.json @@ -1,6 +1,6 @@ { "name": "eslint-import-resolver-webpack", - "version": "0.12.1", + "version": "0.12.2", "description": "Resolve paths to dependencies, given a webpack.config.js. Plugin for eslint-plugin-import.", "main": "index.js", "scripts": { diff --git a/src/ExportMap.js b/src/ExportMap.js index c8f03cf4f6..6978b844cb 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -562,7 +562,9 @@ ExportMap.parse = function (path, content, context) { // This doesn't declare anything, but changes what's being exported. if (includes(exports, n.type)) { - const exportedName = n.expression && n.expression.name || n.id.name + const exportedName = n.type === 'TSNamespaceExportDeclaration' + ? n.id.name + : n.expression && n.expression.name || n.expression.id.name const declTypes = [ 'VariableDeclaration', 'ClassDeclaration', diff --git a/src/core/importType.js b/src/core/importType.js index 4d56b86d4b..ff2d10b60f 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -12,7 +12,7 @@ function baseModule(name) { } export function isAbsolute(name) { - return name.indexOf('/') === 0 + return name && name.startsWith('/') } // path is defined only when a resolver resolves to a non-standard path diff --git a/src/rules/dynamic-import-chunkname.js b/src/rules/dynamic-import-chunkname.js index 40b99239ab..cff4b1c2a1 100644 --- a/src/rules/dynamic-import-chunkname.js +++ b/src/rules/dynamic-import-chunkname.js @@ -34,78 +34,85 @@ module.exports = { const chunkSubstrFormat = ` webpackChunkName: "${webpackChunknameFormat}",? ` const chunkSubstrRegex = new RegExp(chunkSubstrFormat) - return { - CallExpression(node) { - if (node.callee.type !== 'Import' && importFunctions.indexOf(node.callee.name) < 0) { - return - } + function run(node, arg) { + const sourceCode = context.getSourceCode() + const leadingComments = sourceCode.getCommentsBefore + ? sourceCode.getCommentsBefore(arg) // This method is available in ESLint >= 4. + : sourceCode.getComments(arg).leading // This method is deprecated in ESLint 7. - const sourceCode = context.getSourceCode() - const arg = node.arguments[0] - const leadingComments = sourceCode.getCommentsBefore - ? sourceCode.getCommentsBefore(arg) // This method is available in ESLint >= 4. - : sourceCode.getComments(arg).leading // This method is deprecated in ESLint 7. + if (!leadingComments || leadingComments.length === 0) { + context.report({ + node, + message: 'dynamic imports require a leading comment with the webpack chunkname', + }) + return + } - if (!leadingComments || leadingComments.length === 0) { + let isChunknamePresent = false + + for (const comment of leadingComments) { + if (comment.type !== 'Block') { context.report({ node, - message: 'dynamic imports require a leading comment with the webpack chunkname', + 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 (!paddedCommentRegex.test(comment.value)) { + context.report({ + node, + message: `dynamic imports require a block comment padded with spaces - /* foo */`, + }) + return + } - if (chunkSubstrRegex.test(comment.value)) { - isChunknamePresent = true - } + 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 (!isChunknamePresent) { + 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 + } + } + + if (!isChunknamePresent) { + context.report({ + node, + message: + `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`, + }) + } + } + + return { + ImportExpression(node) { + run(node, node.source) + }, + + CallExpression(node) { + if (node.callee.type !== 'Import' && importFunctions.indexOf(node.callee.name) < 0) { + return + } + + run(node, node.arguments[0]) }, } }, diff --git a/src/rules/export.js b/src/rules/export.js index f131374df3..340972eda0 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -118,6 +118,9 @@ module.exports = { 'ExportAllDeclaration': function (node) { if (node.source == null) return // not sure if this is ever true + // `export * as X from 'path'` does not conflict + if (node.exported && node.exported.name) return + const remoteExports = ExportMap.get(node.source.value, context) if (remoteExports == null) return @@ -135,8 +138,10 @@ module.exports = { addNamed(name, node, parent)) if (!any) { - context.report(node.source, - `No named exports found in module '${node.source.value}'.`) + context.report( + node.source, + `No named exports found in module '${node.source.value}'.` + ) } }, diff --git a/src/rules/namespace.js b/src/rules/namespace.js index dd840b86f6..90784c076e 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -12,17 +12,15 @@ module.exports = { schema: [ { - 'type': 'object', - 'properties': { - 'allowComputed': { - 'description': - 'If `false`, will report computed (and thus, un-lintable) references ' + - 'to namespace members.', - 'type': 'boolean', - 'default': false, + type: 'object', + properties: { + allowComputed: { + description: 'If `false`, will report computed (and thus, un-lintable) references to namespace members.', + type: 'boolean', + default: false, }, }, - 'additionalProperties': false, + additionalProperties: false, }, ], }, @@ -37,15 +35,12 @@ module.exports = { const namespaces = new Map() function makeMessage(last, namepath) { - return `'${last.name}' not found in` + - (namepath.length > 1 ? ' deeply ' : ' ') + - `imported namespace '${namepath.join('.')}'.` + return `'${last.name}' not found in ${namepath.length > 1 ? 'deeply ' : ''}imported namespace '${namepath.join('.')}'.` } return { - // pick up all imports at body entry time, to properly respect hoisting - Program: function ({ body }) { + Program({ body }) { function processBodyStatement(declaration) { if (declaration.type !== 'ImportDeclaration') return @@ -63,8 +58,10 @@ module.exports = { switch (specifier.type) { case 'ImportNamespaceSpecifier': if (!imports.size) { - context.report(specifier, - `No exported names found in module '${declaration.source.value}'.`) + context.report( + specifier, + `No exported names found in module '${declaration.source.value}'.` + ) } namespaces.set(specifier.local.name, imports) break @@ -72,8 +69,9 @@ module.exports = { case 'ImportSpecifier': { const meta = imports.get( // default to 'default' for default http://i.imgur.com/nj6qAWy.jpg - specifier.imported ? specifier.imported.name : 'default') - if (!meta || !meta.namespace) break + specifier.imported ? specifier.imported.name : 'default' + ) + if (!meta || !meta.namespace) { break } namespaces.set(specifier.local.name, meta.namespace) break } @@ -84,7 +82,7 @@ module.exports = { }, // same as above, but does not add names to local map - ExportNamespaceSpecifier: function (namespace) { + ExportNamespaceSpecifier(namespace) { var declaration = importDeclaration(context) var imports = Exports.get(declaration.source.value, context) @@ -96,35 +94,39 @@ module.exports = { } if (!imports.size) { - context.report(namespace, - `No exported names found in module '${declaration.source.value}'.`) + context.report( + namespace, + `No exported names found in module '${declaration.source.value}'.` + ) } }, // todo: check for possible redefinition - MemberExpression: function (dereference) { + MemberExpression(dereference) { if (dereference.object.type !== 'Identifier') return if (!namespaces.has(dereference.object.name)) return + if (declaredScope(context, dereference.object.name) !== 'module') return - if (dereference.parent.type === 'AssignmentExpression' && - dereference.parent.left === dereference) { - context.report(dereference.parent, - `Assignment to member of namespace '${dereference.object.name}'.`) + if (dereference.parent.type === 'AssignmentExpression' && dereference.parent.left === dereference) { + context.report( + dereference.parent, + `Assignment to member of namespace '${dereference.object.name}'.` + ) } // go deep var namespace = namespaces.get(dereference.object.name) var namepath = [dereference.object.name] // while property is namespace and parent is member expression, keep validating - while (namespace instanceof Exports && - dereference.type === 'MemberExpression') { + while (namespace instanceof Exports && dereference.type === 'MemberExpression') { if (dereference.computed) { if (!allowComputed) { - context.report(dereference.property, - 'Unable to validate computed reference to imported namespace \'' + - dereference.object.name + '\'.') + context.report( + dereference.property, + `Unable to validate computed reference to imported namespace '${dereference.object.name}'.` + ) } return } @@ -132,7 +134,8 @@ module.exports = { if (!namespace.has(dereference.property.name)) { context.report( dereference.property, - makeMessage(dereference.property, namepath)) + makeMessage(dereference.property, namepath) + ) break } @@ -147,7 +150,7 @@ module.exports = { }, - VariableDeclarator: function ({ id, init }) { + VariableDeclarator({ id, init }) { if (init == null) return if (init.type !== 'Identifier') return if (!namespaces.has(init.name)) return @@ -199,7 +202,7 @@ module.exports = { testKey(id, namespaces.get(init.name)) }, - JSXMemberExpression: function({object, property}) { + JSXMemberExpression({object, property}) { if (!namespaces.has(object.name)) return var namespace = namespaces.get(object.name) if (!namespace.has(property.name)) { diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js index 7807dfcdab..0336b0dc25 100644 --- a/src/rules/newline-after-import.js +++ b/src/rules/newline-after-import.js @@ -115,16 +115,24 @@ after ${type} statement not followed by another ${type}.`, level-- } - return { - ImportDeclaration: function (node) { + function checkImport(node) { const { parent } = node const nodePosition = parent.body.indexOf(node) const nextNode = parent.body[nodePosition + 1] + + // skip "export import"s + if (node.type === 'TSImportEqualsDeclaration' && node.isExport) { + return + } - if (nextNode && nextNode.type !== 'ImportDeclaration') { + if (nextNode && nextNode.type !== 'ImportDeclaration' && (nextNode.type !== 'TSImportEqualsDeclaration' || nextNode.isExport)) { checkForNewLine(node, nextNode, 'import') } - }, + } + + return { + ImportDeclaration: checkImport, + TSImportEqualsDeclaration: checkImport, CallExpression: function(node) { if (isStaticRequire(node) && level === 0) { requireCalls.push(node) diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index 8f39246b5c..2ad381e91a 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -14,10 +14,18 @@ module.exports = { type: 'suggestion', docs: { url: docsUrl('no-cycle') }, schema: [makeOptionsSchema({ - maxDepth:{ - description: 'maximum dependency depth to traverse', - type: 'integer', - minimum: 1, + maxDepth: { + oneOf: [ + { + description: 'maximum dependency depth to traverse', + type: 'integer', + minimum: 1, + }, + { + enum: ['∞'], + type: 'string', + }, + ], }, ignoreExternal: { description: 'ignore external modules', @@ -32,7 +40,7 @@ module.exports = { if (myPath === '') return {} // can't cycle-check a non-file const options = context.options[0] || {} - const maxDepth = options.maxDepth || Infinity + const maxDepth = typeof options.maxDepth === 'number' ? options.maxDepth : Infinity const ignoreModule = (name) => options.ignoreExternal ? isExternalModule(name) : false function checkSourceValue(sourceNode, importer) { diff --git a/src/rules/no-default-export.js b/src/rules/no-default-export.js index 0a46fd35f7..fdc709696d 100644 --- a/src/rules/no-default-export.js +++ b/src/rules/no-default-export.js @@ -1,7 +1,11 @@ +import docsUrl from '../docsUrl' + module.exports = { meta: { type: 'suggestion', - docs: {}, + docs: { + url: docsUrl('no-default-export'), + }, schema: [], }, diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 03c45526c0..366a684c40 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -111,7 +111,7 @@ function optDepErrorMessage(packageName) { function reportIfMissing(context, deps, depsOptions, node, name) { // Do not report when importing types - if (node.importKind === 'type') { + if (node.importKind === 'type' || (node.parent && node.parent.importKind === 'type')) { return } diff --git a/src/rules/no-internal-modules.js b/src/rules/no-internal-modules.js index b5d7496a2a..bd13ab07d0 100644 --- a/src/rules/no-internal-modules.js +++ b/src/rules/no-internal-modules.js @@ -95,7 +95,9 @@ module.exports = { checkImportForReaching(node.source.value, node.source) }, ExportNamedDeclaration(node) { - checkImportForReaching(node.source.value, node.source) + if (node.source) { + checkImportForReaching(node.source.value, node.source) + } }, CallExpression(node) { if (isStaticRequire(node)) { diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 25139b681f..d277ca9aed 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -63,8 +63,33 @@ const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier' const VARIABLE_DECLARATION = 'VariableDeclaration' const FUNCTION_DECLARATION = 'FunctionDeclaration' const CLASS_DECLARATION = 'ClassDeclaration' +const INTERFACE_DECLARATION = 'InterfaceDeclaration' +const TYPE_ALIAS = 'TypeAlias' +const TS_INTERFACE_DECLARATION = 'TSInterfaceDeclaration' +const TS_TYPE_ALIAS_DECLARATION = 'TSTypeAliasDeclaration' +const TS_ENUM_DECLARATION = 'TSEnumDeclaration' const DEFAULT = 'default' +function forEachDeclarationIdentifier(declaration, cb) { + if (declaration) { + if ( + declaration.type === FUNCTION_DECLARATION || + declaration.type === CLASS_DECLARATION || + declaration.type === INTERFACE_DECLARATION || + declaration.type === TYPE_ALIAS || + declaration.type === TS_INTERFACE_DECLARATION || + declaration.type === TS_TYPE_ALIAS_DECLARATION || + declaration.type === TS_ENUM_DECLARATION + ) { + cb(declaration.id.name) + } else if (declaration.type === VARIABLE_DECLARATION) { + declaration.declarations.forEach(({ id }) => { + cb(id.name) + }) + } + } +} + /** * List of imports per file. * @@ -559,19 +584,9 @@ module.exports = { } }) } - if (declaration) { - if ( - declaration.type === FUNCTION_DECLARATION || - declaration.type === CLASS_DECLARATION - ) { - newExportIdentifiers.add(declaration.id.name) - } - if (declaration.type === VARIABLE_DECLARATION) { - declaration.declarations.forEach(({ id }) => { - newExportIdentifiers.add(id.name) - }) - } - } + forEachDeclarationIdentifier(declaration, (name) => { + newExportIdentifiers.add(name) + }) } }) @@ -883,19 +898,9 @@ module.exports = { node.specifiers.forEach(specifier => { checkUsage(node, specifier.exported.name) }) - if (node.declaration) { - if ( - node.declaration.type === FUNCTION_DECLARATION || - node.declaration.type === CLASS_DECLARATION - ) { - checkUsage(node, node.declaration.id.name) - } - if (node.declaration.type === VARIABLE_DECLARATION) { - node.declaration.declarations.forEach(declaration => { - checkUsage(node, declaration.id.name) - }) - } - } + forEachDeclarationIdentifier(node.declaration, (name) => { + checkUsage(node, name) + }) }, } }, diff --git a/src/rules/order.js b/src/rules/order.js index 9edac3af91..1d7d3efd62 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -11,11 +11,7 @@ const defaultGroups = ['builtin', 'external', 'parent', 'sibling', 'index'] function reverse(array) { return array.map(function (v) { - return { - name: v.name, - rank: -v.rank, - node: v.node, - } + return Object.assign({}, v, { rank: -v.rank }) }).reverse() } @@ -197,8 +193,7 @@ function fixOutOfOrder(context, firstNode, secondNode, order) { newCode = newCode + '\n' } - const message = '`' + secondNode.name + '` import should occur ' + order + - ' import of `' + firstNode.name + '`' + const message = `\`${secondNode.displayName}\` import should occur ${order} import of \`${firstNode.displayName}\`` if (order === 'before') { context.report({ @@ -248,14 +243,14 @@ function makeOutOfOrderReport(context, imported) { } function getSorter(ascending) { - let multiplier = (ascending ? 1 : -1) + const multiplier = ascending ? 1 : -1 return function importsSorter(importA, importB) { let result - if ((importA < importB) || importB === null) { + if (importA < importB) { result = -1 - } else if ((importA > importB) || importA === null) { + } else if (importA > importB) { result = 1 } else { result = 0 @@ -270,7 +265,7 @@ function mutateRanksToAlphabetize(imported, alphabetizeOptions) { if (!Array.isArray(acc[importedItem.rank])) { acc[importedItem.rank] = [] } - acc[importedItem.rank].push(importedItem.name) + acc[importedItem.rank].push(importedItem.value) return acc }, {}) @@ -295,7 +290,7 @@ function mutateRanksToAlphabetize(imported, alphabetizeOptions) { // mutate the original group-rank with alphabetized-rank imported.forEach(function(importedItem) { - importedItem.rank = alphabetizedRanks[importedItem.name] + importedItem.rank = alphabetizedRanks[importedItem.value] }) } @@ -310,26 +305,31 @@ function computePathRank(ranks, pathGroups, path, maxPosition) { } } -function computeRank(context, ranks, name, type, excludedImportTypes) { - const impType = importType(name, context) +function computeRank(context, ranks, importEntry, excludedImportTypes) { + let impType let rank + if (importEntry.type === 'import:object') { + impType = 'object' + } else { + impType = importType(importEntry.value, context) + } if (!excludedImportTypes.has(impType)) { - rank = computePathRank(ranks.groups, ranks.pathGroups, name, ranks.maxPosition) + rank = computePathRank(ranks.groups, ranks.pathGroups, importEntry.value, ranks.maxPosition) } if (typeof rank === 'undefined') { rank = ranks.groups[impType] } - if (type !== 'import') { + if (importEntry.type !== 'import' && !importEntry.type.startsWith('import:')) { rank += 100 } return rank } -function registerNode(context, node, name, type, ranks, imported, excludedImportTypes) { - const rank = computeRank(context, ranks, name, type, excludedImportTypes) +function registerNode(context, importEntry, ranks, imported, excludedImportTypes) { + const rank = computeRank(context, ranks, importEntry, excludedImportTypes) if (rank !== -1) { - imported.push({name, rank, node}) + imported.push(Object.assign({}, importEntry, { rank })) } } @@ -338,7 +338,7 @@ function isInVariableDeclarator(node) { (node.type === 'VariableDeclarator' || isInVariableDeclarator(node.parent)) } -const types = ['builtin', 'external', 'internal', 'unknown', 'parent', 'sibling', 'index'] +const types = ['builtin', 'external', 'internal', 'unknown', 'parent', 'sibling', 'index', 'object'] // Creates an object with type-rank pairs. // Example: { index: 0, sibling: 1, parent: 1, external: 1, builtin: 2, internal: 2 } @@ -563,7 +563,7 @@ module.exports = { create: function importOrderRule (context) { const options = context.options[0] || {} const newlinesBetweenImports = options['newlines-between'] || 'ignore' - const pathGroupsExcludedImportTypes = new Set(options['pathGroupsExcludedImportTypes'] || ['builtin', 'external']) + const pathGroupsExcludedImportTypes = new Set(options['pathGroupsExcludedImportTypes'] || ['builtin', 'external', 'object']) const alphabetize = getAlphabetizeConfig(options) let ranks @@ -598,9 +598,12 @@ module.exports = { const name = node.source.value registerNode( context, - node, - name, - 'import', + { + node, + value: name, + displayName: name, + type: 'import', + }, ranks, imported, pathGroupsExcludedImportTypes @@ -608,17 +611,30 @@ module.exports = { } }, TSImportEqualsDeclaration: function handleImports(node) { - let name + let displayName + let value + let type + // skip "export import"s + if (node.isExport) { + return + } if (node.moduleReference.type === 'TSExternalModuleReference') { - name = node.moduleReference.expression.value + value = node.moduleReference.expression.value + displayName = value + type = 'import' } else { - name = null + value = '' + displayName = context.getSourceCode().getText(node.moduleReference) + type = 'import:object' } registerNode( context, - node, - name, - 'import', + { + node, + value, + displayName, + type, + }, ranks, imported, pathGroupsExcludedImportTypes @@ -631,9 +647,12 @@ module.exports = { const name = node.arguments[0].value registerNode( context, - node, - name, - 'require', + { + node, + value: name, + displayName: name, + type: 'require', + }, ranks, imported, pathGroupsExcludedImportTypes diff --git a/tests/files/color.js b/tests/files/color.js new file mode 100644 index 0000000000..dcdbf84ac3 --- /dev/null +++ b/tests/files/color.js @@ -0,0 +1 @@ +export const example = 'example'; diff --git a/tests/files/named-export-collision/a.js b/tests/files/named-export-collision/a.js new file mode 100644 index 0000000000..cb04b2cb26 --- /dev/null +++ b/tests/files/named-export-collision/a.js @@ -0,0 +1 @@ +export const FOO = 'a-foobar'; diff --git a/tests/files/named-export-collision/b.js b/tests/files/named-export-collision/b.js new file mode 100644 index 0000000000..ebf954ee0c --- /dev/null +++ b/tests/files/named-export-collision/b.js @@ -0,0 +1 @@ +export const FOO = 'b-foobar'; diff --git a/tests/files/no-unused-modules/typescript/file-ts-a.ts b/tests/files/no-unused-modules/typescript/file-ts-a.ts index a4272256e6..a5cc566715 100644 --- a/tests/files/no-unused-modules/typescript/file-ts-a.ts +++ b/tests/files/no-unused-modules/typescript/file-ts-a.ts @@ -1,3 +1,8 @@ import {b} from './file-ts-b'; +import {c} from './file-ts-c'; +import {d} from './file-ts-d'; +import {e} from './file-ts-e'; -export const a = b + 1; +export const a = b + 1 + e.f; +export const a2: c = {}; +export const a3: d = {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-c.ts b/tests/files/no-unused-modules/typescript/file-ts-c.ts new file mode 100644 index 0000000000..aedf4062be --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-c.ts @@ -0,0 +1 @@ +export interface c {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-d.ts b/tests/files/no-unused-modules/typescript/file-ts-d.ts new file mode 100644 index 0000000000..7679b3de03 --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-d.ts @@ -0,0 +1 @@ +export type d = {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-e.ts b/tests/files/no-unused-modules/typescript/file-ts-e.ts new file mode 100644 index 0000000000..d1787a11af --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-e.ts @@ -0,0 +1 @@ +export enum e { f }; diff --git a/tests/files/typescript-export-assign-function.ts b/tests/files/typescript-export-assign-function.ts new file mode 100644 index 0000000000..930d6dacee --- /dev/null +++ b/tests/files/typescript-export-assign-function.ts @@ -0,0 +1 @@ +export = function foo() {}; diff --git a/tests/files/with-typescript-dev-dependencies/package.json b/tests/files/with-typescript-dev-dependencies/package.json new file mode 100644 index 0000000000..e17fbd9777 --- /dev/null +++ b/tests/files/with-typescript-dev-dependencies/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@types/json-schema": "*" + } +} diff --git a/tests/src/cli.js b/tests/src/cli.js index 5e0a74e36c..9a2d796ade 100644 --- a/tests/src/cli.js +++ b/tests/src/cli.js @@ -58,14 +58,14 @@ describe('CLI regression tests', function () { nodeType: results.results[0].messages[0].nodeType, // we don't care about this one ruleId: 'json/*', severity: 2, - source: '\n', + source: results.results[0].messages[0].source, // NewLine-characters might differ depending on git-settings }, ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, - source: ',\n', + source: results.results[0].source, // NewLine-characters might differ depending on git-settings }, ], errorCount: 1, diff --git a/tests/src/rules/default.js b/tests/src/rules/default.js index d3d4aae4a7..c040a478d5 100644 --- a/tests/src/rules/default.js +++ b/tests/src/rules/default.js @@ -174,6 +174,14 @@ context('TypeScript', function () { 'import/resolver': { 'eslint-import-resolver-typescript': true }, }, }), + test({ + code: `import foobar from "./typescript-export-assign-function"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), test({ code: `import foobar from "./typescript-export-assign-mixed"`, parser: parser, diff --git a/tests/src/rules/dynamic-import-chunkname.js b/tests/src/rules/dynamic-import-chunkname.js index e8cbb9c6f9..938f542e91 100644 --- a/tests/src/rules/dynamic-import-chunkname.js +++ b/tests/src/rules/dynamic-import-chunkname.js @@ -1,5 +1,6 @@ -import { SYNTAX_CASES } from '../utils' +import { SYNTAX_CASES, getTSParsers } from '../utils' import { RuleTester } from 'eslint' +import semver from 'semver' const rule = require('rules/dynamic-import-chunkname') const ruleTester = new RuleTester() @@ -482,3 +483,308 @@ ruleTester.run('dynamic-import-chunkname', rule, { }, ], }) + +context('TypeScript', () => { + getTSParsers().forEach((typescriptParser) => { + const nodeType = typescriptParser.includes('typescript-eslint-parser') || (typescriptParser.includes('@typescript-eslint/parser') && semver.satisfies(require('@typescript-eslint/parser/package.json').version, '^2')) + ? 'CallExpression' + : 'ImportExpression' + + ruleTester.run('dynamic-import-chunkname', rule, { + valid: [ + { + code: `import( + /* webpackChunkName: "someModule" */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "Some_Other_Module" */ + "test" + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "SomeModule123" */ + "test" + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule", webpackPrefetch: true */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule", webpackPrefetch: true, */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunkName: "someModule" */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunkName: "someModule", */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackPrefetch: true */ + /* webpackChunkName: "someModule" */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule" */ + /* webpackPrefetch: true */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule" */ + 'someModule' + )`, + options: pickyCommentOptions, + parser: typescriptParser, + errors: [{ + message: pickyCommentFormatError, + type: nodeType, + }], + }, + ], + invalid: [ + { + code: `import( + // webpackChunkName: "someModule" + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + // webpackChunkName: "someModule" + 'someModule' + )`, + errors: [{ + message: nonBlockCommentError, + type: nodeType, + }], + }, + { + code: 'import(\'test\')', + options, + parser: typescriptParser, + output: 'import(\'test\')', + errors: [{ + message: noLeadingCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: someModule */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName: someModule */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: 'someModule' */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName: 'someModule' */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName "someModule" */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName:"someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName:"someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /*webpackChunkName: "someModule"*/ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /*webpackChunkName: "someModule"*/ + 'someModule' + )`, + errors: [{ + message: noPaddingCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName : "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName : "someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: "someModule" ; */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName: "someModule" ; */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* totally not webpackChunkName: "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* totally not webpackChunkName: "someModule" */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackPrefetch: true */ + /* webpackChunk: "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackPrefetch: true */ + /* webpackChunk: "someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunk: "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackPrefetch: true, webpackChunk: "someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: "someModule123" */ + 'someModule' + )`, + options: pickyCommentOptions, + parser: typescriptParser, + output: `import( + /* webpackChunkName: "someModule123" */ + 'someModule' + )`, + errors: [{ + message: pickyCommentFormatError, + type: nodeType, + }], + }, + ], + }) + }) +}) diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index fb301495e7..f21da9b708 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -1,6 +1,8 @@ import { test, testFilePath, SYNTAX_CASES, getTSParsers } from '../utils' import { RuleTester } from 'eslint' +import eslintPkg from 'eslint/package.json' +import semver from 'semver' var ruleTester = new RuleTester() , rule = require('rules/export') @@ -24,6 +26,15 @@ ruleTester.run('export', rule, { test({ code: 'export default foo; export * from "./bar"' }), ...SYNTAX_CASES, + + test({ + code: ` + import * as A from './named-export-collision/a'; + import * as B from './named-export-collision/b'; + + export { A, B }; + `, + }), ], invalid: [ @@ -191,6 +202,16 @@ context('TypeScript', function () { code: 'export * from "./file1.ts"', filename: testFilePath('typescript-d-ts/file-2.ts'), }, parserConfig)), + + ...(semver.satisfies(eslintPkg.version, '< 6') ? [] : [ + test({ + code: ` + export * as A from './named-export-collision/a'; + export * as B from './named-export-collision/b'; + `, + parser: parser, + }), + ]), ], invalid: [ // type/value name clash diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index 5627c7132a..93d503eb4f 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -152,9 +152,26 @@ const valid = [ 'import/resolver': { 'eslint-import-resolver-typescript': true }, }, }), + + test({ + code: 'export = function name() {}', + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), ]), ...SYNTAX_CASES, + + test({ + code: ` + import * as color from './color'; + export const getBackgroundFromColor = (color) => color.bg; + export const getExampleColor = () => color.example + `, + }), ] const invalid = [ diff --git a/tests/src/rules/newline-after-import.js b/tests/src/rules/newline-after-import.js index bb94b56dad..fcd7c72a9f 100644 --- a/tests/src/rules/newline-after-import.js +++ b/tests/src/rules/newline-after-import.js @@ -1,4 +1,7 @@ import { RuleTester } from 'eslint' +import flatMap from 'array.prototype.flatmap' + +import { getTSParsers } from '../utils' const IMPORT_ERROR_MESSAGE = 'Expected 1 empty line after import statement not followed by another import.' const IMPORT_ERROR_MESSAGE_MULTIPLE = (count) => { @@ -175,6 +178,60 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { parserOptions: { sourceType: 'module' }, parser: require.resolve('babel-eslint'), }, + ...flatMap(getTSParsers(), (parser) => [ + { + code: ` + import { ExecaReturnValue } from 'execa'; + import execa = require('execa'); + `, + parser: parser, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + }, + { + code: ` + import execa = require('execa'); + import { ExecaReturnValue } from 'execa'; + `, + parser: parser, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + }, + { + code: ` + import { ExecaReturnValue } from 'execa'; + import execa = require('execa'); + import { ExecbReturnValue } from 'execb'; + `, + parser: parser, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + }, + { + code: ` + import execa = require('execa'); + import { ExecaReturnValue } from 'execa'; + import execb = require('execb'); + `, + parser: parser, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + }, + { + code: ` + export import a = obj;\nf(a); + `, + parser: parser, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + }, + { + code: ` + import { a } from "./a"; + + export namespace SomeNamespace { + export import a2 = a; + f(a); + }`, + parser: parser, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + }, + ]), ], invalid: [ diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js index b0f4153e8d..2539ba5945 100644 --- a/tests/src/rules/no-cycle.js +++ b/tests/src/rules/no-cycle.js @@ -161,6 +161,16 @@ ruleTester.run('no-cycle', rule, { parser: require.resolve('babel-eslint'), errors: [error(`Dependency cycle via ./flow-types-depth-two:4=>./depth-one:1`)], }), + test({ + code: 'import { foo } from "./depth-two"', + options: [{ maxDepth: Infinity }], + errors: [error(`Dependency cycle via ./depth-one:1`)], + }), + test({ + code: 'import { foo } from "./depth-two"', + options: [{ maxDepth: '∞' }], + errors: [error(`Dependency cycle via ./depth-one:1`)], + }), ], }) // }) diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index 97279d8538..77de28b339 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -1,11 +1,15 @@ -import { test } from '../utils' -import * as path from 'path' -import * as fs from 'fs' +import { getTSParsers, test, testFilePath } from '../utils' +import typescriptConfig from '../../../config/typescript' +import path from 'path' +import fs from 'fs' +import semver from 'semver' +import eslintPkg from 'eslint/package.json' import { RuleTester } from 'eslint' import flatMap from 'array.prototype.flatmap' const ruleTester = new RuleTester() +const typescriptRuleTester = new RuleTester(typescriptConfig) const rule = require('rules/no-extraneous-dependencies') const packageDirWithSyntaxError = path.join(__dirname, '../../files/with-syntax-error') @@ -17,6 +21,7 @@ const packageFileWithSyntaxErrorMessage = (() => { } })() const packageDirWithFlowTyped = path.join(__dirname, '../../files/with-flow-typed') +const packageDirWithTypescriptDevDependencies = path.join(__dirname, '../../files/with-typescript-dev-dependencies') const packageDirMonoRepoRoot = path.join(__dirname, '../../files/monorepo') const packageDirMonoRepoWithNested = path.join(__dirname, '../../files/monorepo/packages/nested-package') const packageDirWithEmpty = path.join(__dirname, '../../files/empty') @@ -312,3 +317,74 @@ ruleTester.run('no-extraneous-dependencies', rule, { }), ], }) + +describe('TypeScript', function () { + getTSParsers().forEach((parser) => { + const parserConfig = { + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + } + + if (parser !== require.resolve('typescript-eslint-parser')) { + ruleTester.run('no-extraneous-dependencies', rule, { + valid: [ + test(Object.assign({ + code: 'import type { JSONSchema7Type } from "@types/json-schema";', + options: [{packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }], + }, parserConfig)), + ], + invalid: [ + test(Object.assign({ + code: 'import { JSONSchema7Type } from "@types/json-schema";', + options: [{packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }], + errors: [{ + message: "'@types/json-schema' should be listed in the project's dependencies, not devDependencies.", + }], + }, parserConfig)), + ], + }) + } else { + ruleTester.run('no-extraneous-dependencies', rule, { + valid: [], + invalid: [ + test(Object.assign({ + code: 'import { JSONSchema7Type } from "@types/json-schema";', + options: [{packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }], + errors: [{ + message: "'@types/json-schema' should be listed in the project's dependencies, not devDependencies.", + }], + }, parserConfig)), + test(Object.assign({ + code: 'import type { JSONSchema7Type } from "@types/json-schema";', + options: [{packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }], + errors: [{ + message: "'@types/json-schema' should be listed in the project's dependencies, not devDependencies.", + }], + }, parserConfig)), + ], + }) + } + }) +}) + +if (semver.satisfies(eslintPkg.version, '>5.0.0')) { + typescriptRuleTester.run('no-extraneous-dependencies typescript type imports', rule, { + valid: [ + test({ + code: 'import type MyType from "not-a-dependency";', + filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'), + parser: require.resolve('babel-eslint'), + }), + test({ + code: 'import type { MyType } from "not-a-dependency";', + filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'), + parser: require.resolve('babel-eslint'), + }), + ], + invalid: [ + ], + }) +} diff --git a/tests/src/rules/no-internal-modules.js b/tests/src/rules/no-internal-modules.js index 5058fcb349..da9a4ca1a0 100644 --- a/tests/src/rules/no-internal-modules.js +++ b/tests/src/rules/no-internal-modules.js @@ -1,7 +1,8 @@ import { RuleTester } from 'eslint' +import flatMap from 'array.prototype.flatmap' import rule from 'rules/no-internal-modules' -import { test, testFilePath } from '../utils' +import { test, testFilePath, getTSParsers } from '../utils' const ruleTester = new RuleTester() @@ -92,6 +93,27 @@ ruleTester.run('no-internal-modules', rule, { allow: [ '**/index{.js,}' ], } ], }), + test({ + code: ` + export class AuthHelper { + + static checkAuth(auth) { + } + } + `, + }), + ...flatMap(getTSParsers(), (parser) => [ + test({ + code: ` + export class AuthHelper { + + public static checkAuth(auth?: string): boolean { + } + } + `, + parser: parser, + }), + ]), ], invalid: [ diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index ef2d3e66c2..74200fb0d9 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -1,4 +1,4 @@ -import { test, testFilePath } from '../utils' +import { test, testFilePath, getTSParsers } from '../utils' import jsxConfig from '../../../config/react' import typescriptConfig from '../../../config/typescript' @@ -736,10 +736,81 @@ describe('correctly work with Typescript only files', () => { error(`exported declaration 'b' not used within other modules`), ], }), + test({ + options: unusedExportsTypescriptOptions, + code: `export interface c {};`, + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/typescript/file-ts-c.ts'), + errors: [ + error(`exported declaration 'c' not used within other modules`), + ], + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export type d = {};`, + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/typescript/file-ts-d.ts'), + errors: [ + error(`exported declaration 'd' not used within other modules`), + ], + }), ], }) }) +context('TypeScript', function () { + getTSParsers().forEach((parser) => { + typescriptRuleTester.run('no-unused-modules', rule, { + valid: [ + test({ + options: unusedExportsTypescriptOptions, + code: 'import a from "file-ts-a";', + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'), + }), + ], + invalid: [ + test({ + options: unusedExportsTypescriptOptions, + code: `export const b = 2;`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-b.ts'), + errors: [ + error(`exported declaration 'b' not used within other modules`), + ], + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export interface c {};`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-c.ts'), + errors: [ + error(`exported declaration 'c' not used within other modules`), + ], + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export type d = {};`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-d.ts'), + errors: [ + error(`exported declaration 'd' not used within other modules`), + ], + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export enum e { f };`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-e.ts'), + errors: [ + error(`exported declaration 'e' not used within other modules`), + ], + }), + ], + }) + }) +}) + describe('correctly work with JSX only files', () => { jsxRuleTester.run('no-unused-modules', rule, { valid: [ diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index f6e2dddbaa..0c5405823f 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -3,6 +3,7 @@ import { test, getTSParsers, getNonDefaultParsers } from '../utils' import { RuleTester } from 'eslint' import eslintPkg from 'eslint/package.json' import semver from 'semver' +import flatMap from 'array.prototype.flatmap' const ruleTester = new RuleTester() , rule = require('rules/order') @@ -167,8 +168,8 @@ ruleTester.run('order', rule, { var index = require('./'); `, }), - // Export equals expressions should be on top alongside with ordinary import-statements. - ...getTSParsers().map(parser => ( + ...flatMap(getTSParsers(), parser => [ + // Export equals expressions should be on top alongside with ordinary import-statements. test({ code: ` import async, {foo1} from 'async'; @@ -181,8 +182,15 @@ ruleTester.run('order', rule, { var index = require('./'); `, parser, - }) - )), + }), + + test({ + code: ` + export import CreateSomething = _CreateSomething; + `, + parser, + }), + ]), // Adding unknown import types (e.g. using a resolver alias via babel) to the groups. test({ code: ` @@ -703,6 +711,78 @@ ruleTester.run('order', rule, { }, ], }), + ...flatMap(getTSParsers, parser => [ + // Order of the `import ... = require(...)` syntax + test({ + code: ` + import blah = require('./blah'); + import { hello } from './hello';`, + parser, + options: [ + { + alphabetize: { + order: 'asc', + }, + }, + ], + }), + // Order of object-imports + test({ + code: ` + import blah = require('./blah'); + import log = console.log;`, + parser, + options: [ + { + alphabetize: { + order: 'asc', + }, + }, + ], + }), + // Object-imports should not be forced to be alphabetized + test({ + code: ` + import debug = console.debug; + import log = console.log;`, + parser, + options: [ + { + alphabetize: { + order: 'asc', + }, + }, + ], + }), + test({ + code: ` + import log = console.log; + import debug = console.debug;`, + parser, + options: [ + { + alphabetize: { + order: 'asc', + }, + }, + ], + }), + test({ + code: ` + import { a } from "./a"; + export namespace SomeNamespace { + export import a2 = a; + } + `, + parser, + options: [ + { + groups: ['external', 'index'], + alphabetize: { order: 'asc' }, + }, + ], + }), + ]), ], invalid: [ // builtin before external module (require) @@ -1158,7 +1238,8 @@ ruleTester.run('order', rule, { message: '`fs` import should occur after import of `../foo/bar`', }], }), - ...getTSParsers().map(parser => ( + ...flatMap(getTSParsers(), parser => [ + // Order of the `import ... = require(...)` syntax test({ code: ` var fs = require('fs'); @@ -1174,8 +1255,54 @@ ruleTester.run('order', rule, { errors: [{ message: '`fs` import should occur after import of `../foo/bar`', }], - }) - )), + }), + test({ + code: ` + var async = require('async'); + var fs = require('fs'); + `, + output: ` + var fs = require('fs'); + var async = require('async'); + `, + parser, + errors: [{ + message: '`fs` import should occur before import of `async`', + }], + }), + test({ + code: ` + import sync = require('sync'); + import async, {foo1} from 'async'; + + import index from './'; + `, + output: ` + import async, {foo1} from 'async'; + import sync = require('sync'); + + import index from './'; + `, + options: [{ + groups: ['external', 'index'], + alphabetize: {order: 'asc'}, + }], + parser, + errors: [{ + message: '`async` import should occur before import of `sync`', + }], + }), + // Order of object-imports + test({ + code: ` + import log = console.log; + import blah = require('./blah');`, + parser, + errors: [{ + message: '`./blah` import should occur before import of `console.log`', + }], + }), + ]), // Default order using import with custom import alias test({ code: ` @@ -1909,20 +2036,6 @@ ruleTester.run('order', rule, { message: '`fs` import should occur before import of `async`', }], })), - ...getTSParsers().map(parser => ({ - code: ` - var async = require('async'); - var fs = require('fs'); - `, - output: ` - var fs = require('fs'); - var async = require('async'); - `, - parser, - errors: [{ - message: '`fs` import should occur before import of `async`', - }], - })), // Option alphabetize: {order: 'asc'} test({ code: ` @@ -1947,30 +2060,6 @@ ruleTester.run('order', rule, { message: '`Bar` import should occur before import of `bar`', }], }), - ...getTSParsers().map(parser => ( - test({ - code: ` - import sync = require('sync'); - import async, {foo1} from 'async'; - - import index from './'; - `, - output: ` - import async, {foo1} from 'async'; - import sync = require('sync'); - - import index from './'; - `, - options: [{ - groups: ['external', 'index'], - alphabetize: {order: 'asc'}, - }], - parser, - errors: [{ - message: '`async` import should occur before import of `sync`', - }], - }) - )), // Option alphabetize: {order: 'desc'} test({ code: `