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 b/.eslintrc new file mode 100644 index 0000000000..d02c02a69d --- /dev/null +++ b/.eslintrc @@ -0,0 +1,67 @@ +{ + "root": true, + "plugins": [ + "eslint-plugin", + "import", + ], + "extends": [ + "eslint:recommended", + "plugin:eslint-plugin/recommended", + "plugin:import/recommended", + ], + "env": { + "node": true, + "es6": true, + }, + "parserOptions": { + "sourceType": "module", + "ecmaVersion": 6, + }, + "rules": { + "max-len": [1, 99, 2], + "semi": [2, "never"], + "curly": [2, "multi-line"], + "comma-dangle": [2, "always-multiline"], + "eol-last": [2, "always"], + "eqeqeq": [2, "allow-null"], + "no-shadow": 1, + "quotes": [2, "single", { + "allowTemplateLiterals": true, + "avoidEscape": true, + }], + "eslint-plugin/consistent-output": [ + "error", + "always", + ], + "eslint-plugin/meta-property-ordering": "error", + "eslint-plugin/no-deprecated-context-methods": "error", + "eslint-plugin/no-deprecated-report-api": "off", + "eslint-plugin/prefer-replace-text": "error", + "eslint-plugin/report-message-format": "error", + "eslint-plugin/require-meta-schema": "error", + "eslint-plugin/require-meta-type": "error", + + // dog fooding + "import/no-extraneous-dependencies": "error", + "import/unambiguous": "off", + }, + + "settings": { + "import/resolver": { + "node": { + "paths": [ + "src", + ], + }, + }, + }, + + "overrides": [ + { + "files": "scripts/**", + "rules": { + "no-console": "off", + }, + }, + ], +} diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index 8c270e9533..0000000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,47 +0,0 @@ ---- -plugins: - - eslint-plugin - - import -extends: - - eslint:recommended - - plugin:eslint-plugin/recommended - - plugin:import/recommended - -env: - node: true - es6: true - -parserOptions: - sourceType: module - ecmaVersion: 6 - -rules: - max-len: [1, 99, 2] - semi: [2, "never"] - curly: [2, "multi-line"] - comma-dangle: [2, always-multiline] - eqeqeq: [2, "allow-null"] - no-shadow: 1 - quotes: - - 2 - - single - - allowTemplateLiterals: true - avoidEscape: true - - eslint-plugin/consistent-output: ["error", "always"] - eslint-plugin/meta-property-ordering: "error" - eslint-plugin/no-deprecated-context-methods: "error" - eslint-plugin/no-deprecated-report-api: "off" - eslint-plugin/prefer-replace-text: "error" - eslint-plugin/report-message-format: "error" - eslint-plugin/require-meta-schema: "error" - eslint-plugin/require-meta-type: "error" - - # dog fooding - import/no-extraneous-dependencies: "error" - import/unambiguous: "off" - -settings: - import/resolver: - node: - paths: [ src ] 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 1d583358ce..5ba0cb63a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,36 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +## [2.22.1] - 2020-09-27 +### Fixed +- [`default`]/TypeScript: avoid crash on `export =` with a MemberExpression ([#1841], thanks [@ljharb]) +- [`extensions`]/importType: Fix @/abc being treated as scoped module ([#1854], thanks [@3nuc]) +- allow using rest operator in named export ([#1878], thanks [@foray1010]) +- [`dynamic-import-chunkname`]: allow single quotes to match Webpack support ([#1848], thanks [@straub]) + +### Changed +- [`export`]: add tests for a name collision with `export * from` ([#1704], thanks @tomprats) + +## [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]) @@ -702,6 +732,21 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1878]: https://github.com/benmosher/eslint-plugin-import/pull/1878 +[#1854]: https://github.com/benmosher/eslint-plugin-import/issues/1854 +[#1848]: https://github.com/benmosher/eslint-plugin-import/pull/1848 +[#1841]: https://github.com/benmosher/eslint-plugin-import/issues/1841 +[#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 @@ -719,6 +764,7 @@ for info on changes for earlier releases. [#1724]: https://github.com/benmosher/eslint-plugin-import/pull/1724 [#1722]: https://github.com/benmosher/eslint-plugin-import/issues/1722 [#1719]: https://github.com/benmosher/eslint-plugin-import/pull/1719 +[#1704]: https://github.com/benmosher/eslint-plugin-import/issues/1704 [#1702]: https://github.com/benmosher/eslint-plugin-import/issues/1702 [#1691]: https://github.com/benmosher/eslint-plugin-import/pull/1691 [#1690]: https://github.com/benmosher/eslint-plugin-import/pull/1690 @@ -861,6 +907,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 @@ -992,7 +1039,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.2...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.22.1...HEAD +[2.22.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.22.0...v2.22.1 +[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 @@ -1216,3 +1265,14 @@ 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 +[@3nuc]: https://github.com/3nuc +[@foray1010]: https://github.com/foray1010 +[@tomprats]: https://github.com/tomprats +[@straub]: https://github.com/straub diff --git a/docs/rules/dynamic-import-chunkname.md b/docs/rules/dynamic-import-chunkname.md index 4bcc5a98b1..d29c06bbaa 100644 --- a/docs/rules/dynamic-import-chunkname.md +++ b/docs/rules/dynamic-import-chunkname.md @@ -39,12 +39,6 @@ import( 'someModule', ); -// using single quotes instead of double quotes -import( - /* webpackChunkName: 'someModule' */ - 'someModule', -); - // invalid syntax for webpack comment import( /* totally not webpackChunkName: "someModule" */ @@ -78,6 +72,12 @@ The following patterns are valid: /* webpackChunkName: "someModule", webpackPrefetch: true */ 'someModule', ); + + // using single quotes instead of double quotes + import( + /* webpackChunkName: 'someModule' */ + 'someModule', + ); ``` ## When Not To Use It 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 9b42324f66..18fd70af20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.21.2", + "version": "2.22.1", "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", @@ -67,17 +67,17 @@ "babel-register": "^6.26.0", "babylon": "^6.18.0", "chai": "^4.2.0", - "coveralls": "^3.0.6", + "coveralls": "^3.1.0", "cross-env": "^4.0.0", "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0", "eslint-import-resolver-node": "file:./resolvers/node", - "eslint-import-resolver-typescript": "^1.0.2", + "eslint-import-resolver-typescript": "^1.1.1", "eslint-import-resolver-webpack": "file:./resolvers/webpack", "eslint-import-test-order-redirect": "file:./tests/files/order-redirect", "eslint-module-utils": "file:./utils", - "eslint-plugin-eslint-plugin": "^2.2.1", + "eslint-plugin-eslint-plugin": "^2.3.0", "eslint-plugin-import": "2.x", - "eslint-plugin-json": "^2.1.1", + "eslint-plugin-json": "^2.1.2", "fs-copy-file-sync": "^1.1.1", "glob": "^7.1.6", "in-publish": "^2.0.1", @@ -102,7 +102,7 @@ "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.3", + "eslint-import-resolver-node": "^0.3.4", "eslint-module-utils": "^2.6.0", "has": "^1.0.3", "minimatch": "^3.0.4", 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/README.md b/resolvers/webpack/README.md index 4fc3b0ffff..9646dc24e4 100644 --- a/resolvers/webpack/README.md +++ b/resolvers/webpack/README.md @@ -42,7 +42,7 @@ settings: config: 'webpack.dev.config.js' ``` -or with explicit config file name: +or with explicit config file index: ```yaml --- @@ -53,6 +53,16 @@ settings: config-index: 1 # take the config at index 1 ``` +or with explicit config file path relative to your projects's working directory: + +```yaml +--- +settings: + import/resolver: + webpack: + config: './configs/webpack.dev.config.js' +``` + or with explicit config object: ```yaml diff --git a/resolvers/webpack/index.js b/resolvers/webpack/index.js index 20c594847b..677d7584cf 100644 --- a/resolvers/webpack/index.js +++ b/resolvers/webpack/index.js @@ -48,7 +48,7 @@ exports.resolve = function (source, file, settings) { var webpackConfig - var configPath = get(settings, 'config') + var _configPath = get(settings, 'config') /** * Attempt to set the current working directory. * If none is passed, default to the `cwd` where the config is located. @@ -59,6 +59,10 @@ exports.resolve = function (source, file, settings) { , argv = get(settings, 'argv', {}) , packageDir + var configPath = typeof _configPath === 'string' && _configPath.startsWith('.') + ? path.resolve(_configPath) + : _configPath + log('Config path from settings:', configPath) // see if we've got a config path, a config object, an array of config objects or a config function 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/resolvers/webpack/test/config.js b/resolvers/webpack/test/config.js index ff0c0bd669..add282b672 100644 --- a/resolvers/webpack/test/config.js +++ b/resolvers/webpack/test/config.js @@ -72,6 +72,14 @@ describe("config", function () { .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js')) }) + it("finds config object when config uses a path relative to working dir", function () { + var settings = { + config: './test/files/some/absolute.path.webpack.config.js', + } + expect(resolve('foo', file, settings)).to.have.property('path') + .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js')) + }) + it("finds the first config with a resolve section when config is an array of config objects", function () { var settings = { config: require(path.join(__dirname, './files/webpack.config.multiple.js')), diff --git a/src/ExportMap.js b/src/ExportMap.js index c8f03cf4f6..837546aeb4 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 && n.expression.id.name) || null) const declTypes = [ 'VariableDeclaration', 'ClassDeclaration', @@ -648,6 +650,10 @@ export function recursivePatternCapture(pattern, callback) { case 'ObjectPattern': pattern.properties.forEach(p => { + if (p.type === 'ExperimentalRestProperty' || p.type === 'RestElement') { + callback(p.argument) + return + } recursivePatternCapture(p.value, callback) }) break @@ -655,6 +661,10 @@ export function recursivePatternCapture(pattern, callback) { case 'ArrayPattern': pattern.elements.forEach((element) => { if (element == null) return + if (element.type === 'ExperimentalRestProperty' || element.type === 'RestElement') { + callback(element.argument) + return + } recursivePatternCapture(element, callback) }) break diff --git a/src/core/importType.js b/src/core/importType.js index ff2d10b60f..25ab2bdced 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -93,7 +93,7 @@ function typeTest(name, settings, path) { } export function isScopedModule(name) { - return name.indexOf('@') === 0 + return name.indexOf('@') === 0 && !name.startsWith('@/') } export default function resolveImportType(name, context) { diff --git a/src/rules/dynamic-import-chunkname.js b/src/rules/dynamic-import-chunkname.js index 40b99239ab..5ac89e1e64 100644 --- a/src/rules/dynamic-import-chunkname.js +++ b/src/rules/dynamic-import-chunkname.js @@ -30,82 +30,89 @@ module.exports = { const { webpackChunknameFormat = '[0-9a-zA-Z-_/.]+' } = config || {} const paddedCommentRegex = /^ (\S[\s\S]+\S) $/ - const commentStyleRegex = /^( \w+: ("[^"]*"|\d+|false|true),?)+ $/ - const chunkSubstrFormat = ` webpackChunkName: "${webpackChunknameFormat}",? ` + const commentStyleRegex = /^( \w+: (["'][^"']*["']|\d+|false|true),?)+ $/ + 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 8255b189cc..0336b0dc25 100644 --- a/src/rules/newline-after-import.js +++ b/src/rules/newline-after-import.js @@ -119,8 +119,13 @@ after ${type} statement not followed by another ${type}.`, 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' && nextNode.type !== 'TSImportEqualsDeclaration') { + if (nextNode && nextNode.type !== 'ImportDeclaration' && (nextNode.type !== 'TSImportEqualsDeclaration' || nextNode.isExport)) { checkForNewLine(node, nextNode, 'import') } } 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-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 b407145405..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,19 +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 - } else if (node.isExport) { - name = node.moduleReference.name + 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 @@ -633,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/named-exports.js b/tests/files/named-exports.js index f2881c10c5..d8b17bb908 100644 --- a/tests/files/named-exports.js +++ b/tests/files/named-exports.js @@ -13,9 +13,9 @@ export class ExportedClass { // destructuring exports -export var { destructuredProp } = {} +export var { destructuredProp, ...restProps } = {} , { destructingAssign = null } = {} , { destructingAssign: destructingRenamedAssign = null } = {} - , [ arrayKeyProp ] = [] + , [ arrayKeyProp, ...arrayRestKeyProps ] = [] , [ { deepProp } ] = [] , { arr: [ ,, deepSparseElement ] } = {} 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/core/getExports.js b/tests/src/core/getExports.js index d61544e7a9..145f236f10 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -295,7 +295,7 @@ describe('ExportMap', function () { context('#size', function () { it('counts the names', () => expect(ExportMap.get('./named-exports', fakeContext)) - .to.have.property('size', 10)) + .to.have.property('size', 12)) it('includes exported namespace size', () => expect(ExportMap.get('./export-all', fakeContext)) .to.have.property('size', 1)) diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js index b3bfb6bb62..f6db95158c 100644 --- a/tests/src/core/importType.js +++ b/tests/src/core/importType.js @@ -1,7 +1,7 @@ import { expect } from 'chai' import * as path from 'path' -import importType, {isExternalModule} from 'core/importType' +import importType, {isExternalModule, isScopedModule} from 'core/importType' import { testContext, testFilePath } from '../utils' @@ -237,4 +237,9 @@ describe('importType(name)', function () { 'import/external-module-folders': ['E:\\path\\to\\node_modules'], }, 'E:\\path\\to\\node_modules\\foo')).to.equal(true) }) + + it('correctly identifies scoped modules with `isScopedModule`', () => { + expect(isScopedModule('@/abc')).to.equal(false) + expect(isScopedModule('@a/abc')).to.equal(true) + }) }) diff --git a/tests/src/rules/default.js b/tests/src/rules/default.js index d3d4aae4a7..3f2c8dac1f 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, @@ -212,6 +220,14 @@ context('TypeScript', function () { tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-as-default-namespace/'), }, }), + test({ + code: `import foobar from "./typescript-export-assign-property"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), ], invalid: [ diff --git a/tests/src/rules/dynamic-import-chunkname.js b/tests/src/rules/dynamic-import-chunkname.js index e8cbb9c6f9..cd321019d2 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() @@ -20,8 +21,8 @@ const noLeadingCommentError = 'dynamic imports require a leading comment with th const nonBlockCommentError = 'dynamic imports require a /* foo */ style comment, not a // foo comment' 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}",? */` +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: [ @@ -131,6 +132,14 @@ ruleTester.run('dynamic-import-chunkname', rule, { options, parser, }, + { + code: `import( + /* webpackChunkName: 'someModule' */ + 'someModule' + )`, + options, + parser, + }, { code: `import( /* webpackChunkName: "someModule" */ @@ -191,17 +200,33 @@ ruleTester.run('dynamic-import-chunkname', rule, { }, { code: `import( - /* webpackChunkName: 'someModule' */ + /* webpackChunkName: "someModule' */ 'someModule' )`, options, parser, output: `import( - /* webpackChunkName: 'someModule' */ + /* webpackChunkName: "someModule' */ 'someModule' )`, errors: [{ - message: commentFormatError, + message: invalidSyntaxCommentError, + type: 'CallExpression', + }], + }, + { + code: `import( + /* webpackChunkName: 'someModule" */ + 'someModule' + )`, + options, + parser, + output: `import( + /* webpackChunkName: 'someModule" */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, type: 'CallExpression', }], }, @@ -420,21 +445,6 @@ ruleTester.run('dynamic-import-chunkname', rule, { type: 'CallExpression', }], }, - { - code: `dynamicImport( - /* webpackChunkName: 'someModule' */ - 'someModule' - )`, - options, - output: `dynamicImport( - /* webpackChunkName: 'someModule' */ - 'someModule' - )`, - errors: [{ - message: commentFormatError, - type: 'CallExpression', - }], - }, { code: `dynamicImport( /* webpackChunkName "someModule" */ @@ -482,3 +492,332 @@ 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, + }], + }, + { + code: `import( + /* webpackChunkName: 'someModule' */ + 'test' + )`, + options, + parser: typescriptParser, + }, + ], + 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: invalidSyntaxCommentError, + 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: 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..5ecfcae20e 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -1,12 +1,14 @@ -import { test, testFilePath, SYNTAX_CASES, getTSParsers } from '../utils' +import { test, testFilePath, SYNTAX_CASES, getTSParsers, testVersion } from '../utils' import { RuleTester } from 'eslint' +import eslintPkg from 'eslint/package.json' +import semver from 'semver' var ruleTester = new RuleTester() , rule = require('rules/export') ruleTester.run('export', rule, { - valid: [ + valid: [].concat( test({ code: 'import "./malformed.js"' }), // default @@ -24,7 +26,25 @@ 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 }; + `, + }), + testVersion('>= 6', () => ({ + code: ` + export * as A from './named-export-collision/a'; + export * as B from './named-export-collision/b'; + `, + parserOptions: { + ecmaVersion: 2020, + }, + })) || [], + ), invalid: [ // multiple defaults @@ -191,6 +211,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/extensions.js b/tests/src/rules/extensions.js index 8cdb3399d8..93860c16ab 100644 --- a/tests/src/rules/extensions.js +++ b/tests/src/rules/extensions.js @@ -444,5 +444,15 @@ ruleTester.run('extensions', rule, { }, ], }), + test({ + code: 'import foo from "@/ImNotAScopedModule"', + options: ['always'], + errors: [ + { + message: 'Missing file extension for "@/ImNotAScopedModule"', + line: 1, + }, + ], + }), ], }) 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 626e6e0261..fcd7c72a9f 100644 --- a/tests/src/rules/newline-after-import.js +++ b/tests/src/rules/newline-after-import.js @@ -213,6 +213,24 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { 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' }, + }, ]), ], 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-unused-modules.js b/tests/src/rules/no-unused-modules.js index ef2d3e66c2..b6554d129a 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' @@ -37,25 +37,53 @@ const unusedExportsJsxOptions = [{ // 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, - 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'}), - test({ options: missingExportsOptions, - code: 'export class Foo {}'}), - test({ options: missingExportsOptions, - code: 'export const [foobar] = [];'}), - test({ options: missingExportsOptions, - code: 'export const [foobar] = foobarFactory();'}), + test({ + code: 'export default function noOptions() {}', + }), + 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', + }), + test({ + options: missingExportsOptions, + code: 'export class Foo {}', + }), + test({ + options: missingExportsOptions, + code: 'export const [foobar] = [];', + }), + test({ + options: missingExportsOptions, + code: 'export const [foobar] = foobarFactory();', + }), + test({ + options: missingExportsOptions, + code: ` + export default function NewComponent () { + return 'I am new component' + } + `, + }), ], invalid: [ test({ @@ -736,10 +764,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 e8ee82ec6c..0c5405823f 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -711,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) @@ -1167,6 +1239,7 @@ ruleTester.run('order', rule, { }], }), ...flatMap(getTSParsers(), parser => [ + // Order of the `import ... = require(...)` syntax test({ code: ` var fs = require('fs'); @@ -1183,7 +1256,7 @@ ruleTester.run('order', rule, { message: '`fs` import should occur after import of `../foo/bar`', }], }), - { + test({ code: ` var async = require('async'); var fs = require('fs'); @@ -1196,7 +1269,7 @@ ruleTester.run('order', rule, { errors: [{ message: '`fs` import should occur before import of `async`', }], - }, + }), test({ code: ` import sync = require('sync'); @@ -1219,6 +1292,16 @@ ruleTester.run('order', rule, { 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({ diff --git a/tests/src/utils.js b/tests/src/utils.js index 4bc8f0119a..0f45e7bf28 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -37,7 +37,7 @@ export function test(t) { }, t, { parserOptions: Object.assign({ sourceType: 'module', - ecmaVersion: 6, + ecmaVersion: 9, }, t.parserOptions), }) }