diff --git a/.eslintignore b/.eslintignore index 93daf655e..9d2200682 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,6 +4,8 @@ coverage node_modules tests/files/malformed.js tests/files/with-syntax-error +tests/files/just-json-files/invalid.json +tests/files/typescript-d-ts/ resolvers/webpack/test/files # we want to ignore "tests/files" here, but unfortunately doing so would # interfere with unit test and fail it for some reason. diff --git a/.travis.yml b/.travis.yml index a6824a6c5..fda8f0a5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: node_js node_js: + - '14' - '13' - '12' - '10' @@ -10,6 +11,7 @@ node_js: os: linux env: + - ESLINT_VERSION=^7.0.0-0 - ESLINT_VERSION=6 - ESLINT_VERSION=5 - ESLINT_VERSION=4 @@ -22,7 +24,7 @@ matrix: - env: LINT=true node_js: lts/* - env: PACKAGE=resolvers/node - node_js: 13 + node_js: 14 - env: PACKAGE=resolvers/node node_js: 12 - env: PACKAGE=resolvers/node @@ -33,6 +35,8 @@ matrix: node_js: 6 - env: PACKAGE=resolvers/node node_js: 4 + - env: PACKAGE=resolvers/webpack + node_js: 14 - env: PACKAGE=resolvers/webpack node_js: 12 - env: PACKAGE=resolvers/webpack @@ -44,6 +48,9 @@ matrix: - env: PACKAGE=resolvers/webpack node_js: 4 + - os: osx + env: ESLINT_VERSION=5 + node_js: 14 - os: osx env: ESLINT_VERSION=5 node_js: 12 @@ -65,8 +72,14 @@ matrix: env: ESLINT_VERSION=5 - node_js: '4' env: ESLINT_VERSION=6 + - node_js: '4' + env: ESLINT_VERSION=^7.0.0-0 - node_js: '6' env: ESLINT_VERSION=6 + - node_js: '6' + env: ESLINT_VERSION=^7.0.0-0 + - node_js: '8' + env: ESLINT_VERSION=^7.0.0-0 fast_finish: true allow_failures: @@ -76,6 +89,7 @@ matrix: before_install: - 'nvm install-latest-npm' + - 'npm install' - 'npm run copy-metafiles' - 'if [ -n "${PACKAGE-}" ]; then cd "${PACKAGE}"; fi' install: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b76324d4..df4599a35 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.21.0] - 2020-06-07 +### Added +- [`import/default`]: support default export in TSExportAssignment ([#1528], thanks [@joaovieira]) +- [`no-cycle`]: add `ignoreExternal` option ([#1681], thanks [@sveyret]) +- [`order`]: Add support for TypeScript's "import equals"-expressions ([#1785], thanks [@manuth]) +- [`import/default`]: support default export in TSExportAssignment ([#1689], thanks [@Maxim-Mazurok]) +- [`no-restricted-paths`]: add custom message support ([#1802], thanks [@malykhinvi]) + +### Fixed +- [`group-exports`]: Flow type export awareness ([#1702], thanks [@ernestostifano]) +- [`order`]: Recognize pathGroup config for first group ([#1719], [#1724], thanks [@forivall], [@xpl]) +- [`no-unused-modules`]: Fix re-export not counting as usage when used in combination with import ([#1722], thanks [@Ephem]) +- [`no-duplicates`]: Handle TS import type ([#1676], thanks [@kmui2]) +- [`newline-after-import`]: recognize decorators ([#1139], thanks [@atos1990]) +- [`no-unused-modules`]: Revert "[flow] `no-unused-modules`: add flow type support" ([#1770], thanks [@Hypnosphi]) +- TypeScript: Add nested namespace handling ([#1763], thanks [@julien1619]) +- [`namespace`]/`ExportMap`: Fix interface declarations for TypeScript ([#1764], thanks [@julien1619]) +- [`no-unused-modules`]: avoid order-dependence ([#1744], thanks [@darkartur]) +- [`no-internal-modules`]: also check `export from` syntax ([#1691], thanks [@adjerbetian]) +- TypeScript: [`export`]: avoid a crash with `export =` ([#1801], thanks [@ljharb]) + +### Changed +- [Refactor] `no-extraneous-dependencies`: use moduleVisitor ([#1735], thanks [@adamborowski]) +- TypeScript config: Disable [`named`][] ([#1726], thanks [@astorije]) +- [readme] Remove duplicate no-unused-modules from docs ([#1690], thanks [@arvigeus]) +- [Docs] `order`: fix bad inline config ([#1788], thanks [@nickofthyme]) +- [Tests] Add fix for Windows Subsystem for Linux ([#1786], thanks [@manuth]) +- [Docs] `no-unused-rules`: Fix docs for unused exports ([#1776], thanks [@barbogast]) +- [eslint] bump minimum v7 version to v7.2.0 + ## [2.20.2] - 2020-03-28 ### Fixed - [`order`]: fix `isExternalModule` detect on windows ([#1651], thanks [@fisker]) @@ -13,6 +43,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-duplicates`]: fix fixer on cases with default import ([#1666], thanks [@golopot]) - [`no-unused-modules`]: Handle `export { default } from` syntax ([#1631], thanks [@richardxia]) - [`first`]: Add a way to disable `absolute-first` explicitly ([#1664], thanks [@TheCrueltySage]) +- [Docs] `no-webpack-loader-syntax`: Updates webpack URLs ([#1751], thanks [@MikeyBeLike]) ## [2.20.1] - 2020-02-01 ### Fixed @@ -29,6 +60,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Changed - [`import/external-module-folders` setting] behavior is more strict now: it will only match complete path segments ([#1605], thanks [@skozin]) - [meta] fix "files" field to include/exclude the proper files ([#1635], thanks [@ljharb]) +- [Tests] `order`: Add TS import type tests ([#1736], thanks [@kmui2]) ## [2.20.0] - 2020-01-10 ### Added @@ -660,6 +692,29 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#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 +[#1786]: https://github.com/benmosher/eslint-plugin-import/pull/1786 +[#1785]: https://github.com/benmosher/eslint-plugin-import/pull/1785 +[#1776]: https://github.com/benmosher/eslint-plugin-import/pull/1776 +[#1770]: https://github.com/benmosher/eslint-plugin-import/pull/1770 +[#1764]: https://github.com/benmosher/eslint-plugin-import/pull/1764 +[#1763]: https://github.com/benmosher/eslint-plugin-import/pull/1763 +[#1751]: https://github.com/benmosher/eslint-plugin-import/pull/1751 +[#1744]: https://github.com/benmosher/eslint-plugin-import/pull/1744 +[#1736]: https://github.com/benmosher/eslint-plugin-import/pull/1736 +[#1735]: https://github.com/benmosher/eslint-plugin-import/pull/1735 +[#1726]: https://github.com/benmosher/eslint-plugin-import/pull/1726 +[#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 +[#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 +[#1689]: https://github.com/benmosher/eslint-plugin-import/pull/1689 +[#1681]: https://github.com/benmosher/eslint-plugin-import/pull/1681 +[#1676]: https://github.com/benmosher/eslint-plugin-import/pull/1676 [#1666]: https://github.com/benmosher/eslint-plugin-import/pull/1666 [#1664]: https://github.com/benmosher/eslint-plugin-import/pull/1664 [#1658]: https://github.com/benmosher/eslint-plugin-import/pull/1658 @@ -682,6 +737,7 @@ for info on changes for earlier releases. [#1560]: https://github.com/benmosher/eslint-plugin-import/pull/1560 [#1551]: https://github.com/benmosher/eslint-plugin-import/pull/1551 [#1542]: https://github.com/benmosher/eslint-plugin-import/pull/1542 +[#1528]: https://github.com/benmosher/eslint-plugin-import/pull/1528 [#1526]: https://github.com/benmosher/eslint-plugin-import/pull/1526 [#1521]: https://github.com/benmosher/eslint-plugin-import/pull/1521 [#1519]: https://github.com/benmosher/eslint-plugin-import/pull/1519 @@ -747,6 +803,7 @@ for info on changes for earlier releases. [#1157]: https://github.com/benmosher/eslint-plugin-import/pull/1157 [#1151]: https://github.com/benmosher/eslint-plugin-import/pull/1151 [#1142]: https://github.com/benmosher/eslint-plugin-import/pull/1142 +[#1139]: https://github.com/benmosher/eslint-plugin-import/pull/1139 [#1137]: https://github.com/benmosher/eslint-plugin-import/pull/1137 [#1135]: https://github.com/benmosher/eslint-plugin-import/pull/1135 [#1128]: https://github.com/benmosher/eslint-plugin-import/pull/1128 @@ -921,7 +978,8 @@ for info on changes for earlier releases. [#119]: https://github.com/benmosher/eslint-plugin-import/issues/119 [#89]: https://github.com/benmosher/eslint-plugin-import/issues/89 -[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.20.2...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.21.0...HEAD +[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 [2.20.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.20.0...v2.20.1 [2.19.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.19.1...v2.20.0 @@ -988,6 +1046,7 @@ for info on changes for earlier releases. [0.12.1]: https://github.com/benmosher/eslint-plugin-import/compare/v0.12.0...v0.12.1 [0.12.0]: https://github.com/benmosher/eslint-plugin-import/compare/v0.11.0...v0.12.0 [0.11.0]: https://github.com/benmosher/eslint-plugin-import/compare/v0.10.1...v0.11.0 + [@mathieudutour]: https://github.com/mathieudutour [@gausie]: https://github.com/gausie [@singles]: https://github.com/singles @@ -1122,3 +1181,22 @@ for info on changes for earlier releases. [@fisker]: https://github.com/fisker [@richardxia]: https://github.com/richardxia [@TheCrueltySage]: https://github.com/TheCrueltySage +[@ernestostifano]: https://github.com/ernestostifano +[@forivall]: https://github.com/forivall +[@xpl]: https://github.com/xpl +[@astorije]: https://github.com/astorije +[@Ephem]: https://github.com/Ephem +[@kmui2]: https://github.com/kmui2 +[@arvigeus]: https://github.com/arvigeus +[@atos1990]: https://github.com/atos1990 +[@Hypnosphi]: https://github.com/Hypnosphi +[@nickofthyme]: https://github.com/nickofthyme +[@manuth]: https://github.com/manuth +[@julien1619]: https://github.com/julien1619 +[@darkartur]: https://github.com/darkartur +[@MikeyBeLike]: https://github.com/MikeyBeLike +[@barbogast]: https://github.com/barbogast +[@adamborowski]: https://github.com/adamborowski +[@adjerbetian]: https://github.com/adjerbetian +[@Maxim-Mazurok]: https://github.com/Maxim-Mazurok +[@malykhinvi]: https://github.com/malykhinvi diff --git a/README.md b/README.md index cc9d1d789..e08e72ffa 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a * Forbid a module from importing a module with a dependency path back to itself ([`no-cycle`]) * Prevent unnecessary path segments in import and require statements ([`no-useless-path-segments`]) * Forbid importing modules from parent directories ([`no-relative-parent-imports`]) -* Forbid modules without any export, and exports not imported by any modules. ([`no-unused-modules`]) [`no-unresolved`]: ./docs/rules/no-unresolved.md [`named`]: ./docs/rules/named.md @@ -42,7 +41,6 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a [`no-cycle`]: ./docs/rules/no-cycle.md [`no-useless-path-segments`]: ./docs/rules/no-useless-path-segments.md [`no-relative-parent-imports`]: ./docs/rules/no-relative-parent-imports.md -[`no-unused-modules`]: ./docs/rules/no-unused-modules.md ### Helpful warnings diff --git a/appveyor.yml b/appveyor.yml index 29e310a18..b79315b7b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,14 +1,23 @@ +configuration: + - Native + - WSL + # Test against this version of Node.js environment: matrix: - - nodejs_version: "12" - - nodejs_version: "10" - - nodejs_version: "8" + - nodejs_version: "14" + - nodejs_version: "12" + - nodejs_version: "10" + - nodejs_version: "8" # - nodejs_version: "6" # - nodejs_version: "4" +image: Visual Studio 2019 matrix: fast_finish: false + exclude: + - configuration: WSL + nodejs_version: "8" # allow_failures: # - nodejs_version: "4" # for eslint 5 @@ -17,45 +26,113 @@ matrix: # - x86 # - x64 -# Install scripts. (runs after repo cloning) -install: - # Get the latest stable version of Node.js or io.js - - ps: Install-Product node $env:nodejs_version - - # install modules +# Initialization scripts. (runs before repo cloning) +init: + # Declare version-numbers of packages to install - ps: >- if ($env:nodejs_version -eq "4") { - npm install -g npm@3; + $env:NPM_VERSION="3" } if ($env:nodejs_version -in @("8", "10", "12")) { - npm install -g npm@6.10.3; + $env:NPM_VERSION="6.14.5" + } + - ps: >- + if ([int]$env:nodejs_version -le 8) { + $env:ESLINT_VERSION="6" } - - npm install + - ps: $env:WINDOWS_NYC_VERSION = "15.0.1" + + # Add `ci`-command to `PATH` for running commands either using cmd or wsl depending on the configuration + - ps: $env:PATH += ";$(Join-Path $(pwd) "scripts")" + +# Install scripts. (runs after repo cloning) +before_build: + # Install propert `npm`-version + - IF DEFINED NPM_VERSION ci sudo npm install -g npm@%NPM_VERSION% + + # Install dependencies + - ci npm install # fix symlinks - - cmd: git config core.symlinks true - - cmd: git reset --hard + - git config core.symlinks true + - git reset --hard + - ci git reset --hard + + # Install dependencies of resolvers + - ps: >- + $resolverDir = "./resolvers"; + $resolvers = @(); + Get-ChildItem -Directory $resolverDir | + ForEach { + $resolvers += "$(Resolve-Path $(Join-Path $resolverDir $_))"; + } + $env:RESOLVERS = [string]::Join(";", $resolvers); + - FOR %%G in ("%RESOLVERS:;=";"%") do ( pushd %%~G & ci npm install & popd ) - # todo: learn how to do this for all .\resolvers\* on Windows - - cd .\resolvers\webpack && npm install && cd ..\.. - - cd .\resolvers\node && npm install && cd ..\.. + # Install proper `eslint`-version + - IF DEFINED ESLINT_VERSION ci npm install --no-save eslint@%ESLINT_VERSION% -# Post-install test scripts. -test_script: +# Build scripts (project isn't actually built) +build_script: + - ps: "# This Project isn't actually built" +# Test scripts +test_script: # Output useful info for debugging. - - node --version - - npm --version + - ci node --version + - ci npm --version - # core tests - - npm test + # Run core tests + - ci npm run pretest + - ci npm run tests-only - # resolver tests - - cd .\resolvers\webpack && npm test && cd ..\.. - - cd .\resolvers\node && npm test && cd ..\.. + # Run resolver tests + - ps: >- + $resolverDir = "./resolvers"; + $resolvers = @(); + Get-ChildItem -Directory $resolverDir | + ForEach { + $resolvers += "$(Resolve-Path $(Join-Path $resolverDir $_))"; + } + $env:RESOLVERS = [string]::Join(";", $resolvers); + - FOR %%G in ("%RESOLVERS:;=";"%") do ( pushd %%~G & ci npm test & popd ) on_success: - - npm run coveralls + - ci npm run coveralls + +# Configuration-specific steps +for: + - matrix: + except: + - configuration: WSL + install: + # Get the latest stable version of Node.js or io.js + - ps: Install-Product node $env:nodejs_version + before_test: + # Upgrade nyc + - ci npm i --no-save nyc@%WINDOWS_NYC_VERSION% + - ps: >- + $resolverDir = "./resolvers"; + $resolvers = @(); + Get-ChildItem -Directory $resolverDir | + ForEach { + Push-Location $(Resolve-Path $(Join-Path $resolverDir $_)); + ci npm ls nyc > $null; + if ($?) { + $resolvers += "$(pwd)"; + } + Pop-Location; + } + $env:RESOLVERS = [string]::Join(";", $resolvers); + - IF DEFINED RESOLVERS FOR %%G in ("%RESOLVERS:;=";"%") do ( pushd %%~G & ci npm install --no-save nyc@%WINDOWS_NYC_VERSION% & popd ) + - matrix: + only: + - configuration: WSL + # Install scripts. (runs after repo cloning) + install: + # Get a specific version of Node.js + - ps: $env:WSLENV += ":nodejs_version" + - ps: wsl curl -sL 'https://deb.nodesource.com/setup_${nodejs_version}.x' `| sudo APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=1 -E bash - + - wsl sudo DEBIAN_FRONTEND=noninteractive apt install -y nodejs -# Don't actually build. -build: off +build: on diff --git a/config/typescript.js b/config/typescript.js index 262e3c799..705faaf37 100644 --- a/config/typescript.js +++ b/config/typescript.js @@ -19,4 +19,10 @@ module.exports = { }, }, + rules: { + // analysis/correctness + + // TypeScript compilation already ensures that named imports exist in the referenced module + 'import/named': 'off', + }, } diff --git a/docs/rules/group-exports.md b/docs/rules/group-exports.md index f61ff5306..e6b9887b2 100644 --- a/docs/rules/group-exports.md +++ b/docs/rules/group-exports.md @@ -60,6 +60,15 @@ test.another = true module.exports = test ``` +```flow js +const first = true; +type firstType = boolean + +// A single named export declaration (type exports handled separately) -> ok +export {first} +export type {firstType} +``` + ### Invalid @@ -94,6 +103,15 @@ module.exports.first = true module.exports.second = true ``` +```flow js +type firstType = boolean +type secondType = any + +// Multiple named type export statements -> not ok! +export type {firstType} +export type {secondType} +``` + ## When Not To Use It If you do not mind having your exports spread across the file, you can safely turn this rule off. diff --git a/docs/rules/no-cycle.md b/docs/rules/no-cycle.md index 8819d6704..6329bb272 100644 --- a/docs/rules/no-cycle.md +++ b/docs/rules/no-cycle.md @@ -55,6 +55,26 @@ import { b } from './dep-b.js' // not reported as the cycle is at depth 2 This is not necessarily recommended, but available as a cost/benefit tradeoff mechanism for reducing total project lint time, if needed. +#### `ignoreExternal` + +An `ignoreExternal` option is available to prevent the cycle detection to expand to external modules: + +```js +/*eslint import/no-cycle: [2, { ignoreExternal: true }]*/ + +// dep-a.js +import 'module-b/dep-b.js' + +export function a() { /* ... */ } +``` + +```js +// node_modules/module-b/dep-b.js +import { a } from './dep-a.js' // not reported as this module is external +``` + +Its value is `false` by default, but can be set to `true` for reducing total project lint time, if needed. + ## When Not To Use It This rule is comparatively computationally expensive. If you are pressed for lint @@ -65,5 +85,8 @@ this rule enabled. - [Original inspiring issue](https://github.com/benmosher/eslint-plugin-import/issues/941) - Rule to detect that module imports itself: [`no-self-import`] +- [`import/external-module-folders`] setting [`no-self-import`]: ./no-self-import.md + +[`import/external-module-folders`]: ../../README.md#importexternal-module-folders diff --git a/docs/rules/no-internal-modules.md b/docs/rules/no-internal-modules.md index 8d99c3529..7bbb2edd1 100644 --- a/docs/rules/no-internal-modules.md +++ b/docs/rules/no-internal-modules.md @@ -49,6 +49,9 @@ The following patterns are considered problems: import { settings } from './app/index'; // Reaching to "./app/index" is not allowed import userReducer from './reducer/user'; // Reaching to "./reducer/user" is not allowed import configureStore from './redux/configureStore'; // Reaching to "./redux/configureStore" is not allowed + +export { settings } from './app/index'; // Reaching to "./app/index" is not allowed +export * from './reducer/user'; // Reaching to "./reducer/user" is not allowed ``` The following patterns are NOT considered problems: @@ -61,4 +64,7 @@ The following patterns are NOT considered problems: import 'source-map-support/register'; import { settings } from '../app'; import getUser from '../actions/getUser'; + +export * from 'source-map-support/register'; +export { settings } from '../app'; ``` diff --git a/docs/rules/no-restricted-paths.md b/docs/rules/no-restricted-paths.md index 377669983..bfcb9af23 100644 --- a/docs/rules/no-restricted-paths.md +++ b/docs/rules/no-restricted-paths.md @@ -10,6 +10,7 @@ In order to prevent such scenarios this rule allows you to define restricted zon This rule has one option. The option is an object containing the definition of all restricted `zones` and the optional `basePath` which is used to resolve relative paths within. The default value for `basePath` is the current working directory. Each zone consists of the `target` path and a `from` path. The `target` is the path where the restricted imports should be applied. The `from` path defines the folder that is not allowed to be used in an import. An optional `except` may be defined for a zone, allowing exception paths that would otherwise violate the related `from`. Note that `except` is relative to `from` and cannot backtrack to a parent directory. +You may also specify an optional `message` for a zone, which will be displayed in case of the rule violation. ### Examples diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md index 4302bc845..8c234202f 100644 --- a/docs/rules/no-unused-modules.md +++ b/docs/rules/no-unused-modules.md @@ -58,28 +58,22 @@ given file-f: ```js import { e } from 'file-a' import { f } from 'file-b' -import * from 'file-c' -export * from 'file-d' -export { default, i0 } from 'file-e' // both will be reported +import * as fileC from 'file-c' +export { default, i0 } from 'file-d' // both will be reported export const j = 99 // will be reported ``` -and file-e: +and file-d: ```js export const i0 = 9 // will not be reported export const i1 = 9 // will be reported export default () => {} // will not be reported ``` -and file-d: +and file-c: ```js export const h = 8 // will not be reported export default () => {} // will be reported, as export * only considers named exports and ignores default exports ``` -and file-c: -```js -export const g = 7 // will not be reported -export default () => {} // will not be reported -``` and file-b: ```js import two, { b, c, doAnything } from 'file-a' diff --git a/docs/rules/no-webpack-loader-syntax.md b/docs/rules/no-webpack-loader-syntax.md index 37b39a432..271c76ca8 100644 --- a/docs/rules/no-webpack-loader-syntax.md +++ b/docs/rules/no-webpack-loader-syntax.md @@ -2,12 +2,12 @@ Forbid Webpack loader syntax in imports. -[Webpack](http://webpack.github.io) allows specifying the [loaders](http://webpack.github.io/docs/loaders.html) to use in the import source string using a special syntax like this: +[Webpack](https://webpack.js.org) allows specifying the [loaders](https://webpack.js.org/concepts/loaders/) to use in the import source string using a special syntax like this: ```js var moduleWithOneLoader = require("my-loader!./my-awesome-module"); ``` -This syntax is non-standard, so it couples the code to Webpack. The recommended way to specify Webpack loader configuration is in a [Webpack configuration file](http://webpack.github.io/docs/loaders.html#loaders-by-config). +This syntax is non-standard, so it couples the code to Webpack. The recommended way to specify Webpack loader configuration is in a [Webpack configuration file](https://webpack.js.org/concepts/loaders/#configuration). ## Rule Details diff --git a/docs/rules/order.md b/docs/rules/order.md index 667b63374..3aa41bbf5 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -229,7 +229,7 @@ alphabetize: { This will fail the rule check: ```js -/* eslint import/order: ["error", {"alphabetize": true}] */ +/* eslint import/order: ["error", {"alphabetize": {"order": "asc", "caseInsensitive": true}}] */ import React, { PureComponent } from 'react'; import aTypes from 'prop-types'; import { compose, apply } from 'xcompose'; @@ -240,7 +240,7 @@ import blist from 'BList'; While this will pass: ```js -/* eslint import/order: ["error", {"alphabetize": true}] */ +/* eslint import/order: ["error", {"alphabetize": {"order": "asc", "caseInsensitive": true}}] */ import blist from 'BList'; import * as classnames from 'classnames'; import aTypes from 'prop-types'; diff --git a/package.json b/package.json index aa637e202..a2beb0cc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.20.2", + "version": "2.21.0", "description": "Import with sanity.", "engines": { "node": ">=4" @@ -21,17 +21,17 @@ "prebuild": "rimraf lib", "build": "babel --quiet --out-dir lib src", "postbuild": "npm run copy-metafiles", - "copy-metafiles": "for DIR in memo-parser resolvers/node resolvers/webpack utils; do cp LICENSE .npmrc \"${DIR}/\"; done", + "copy-metafiles": "node --require babel-register ./scripts/copyMetafiles", "watch": "npm run tests-only -- -- --watch", "pretest": "linklocal", "posttest": "eslint .", - "mocha": "nyc -s mocha", - "tests-only": "cross-env BABEL_ENV=test npm run mocha tests/src", + "mocha": "cross-env BABEL_ENV=test nyc -s mocha", + "tests-only": "npm run mocha tests/src", "test": "npm run tests-only", "test-compiled": "npm run prepublish && BABEL_ENV=testCompiled mocha --compilers js:babel-register tests/src", - "test-all": "npm test && for resolver in ./resolvers/*; do cd $resolver && npm test && cd ../..; done", + "test-all": "node --require babel-register ./scripts/testAll", "prepublish": "not-in-publish || npm run build", - "coveralls": "nyc report --reporter lcovonly && cat ./coverage/lcov.info | coveralls" + "coveralls": "nyc report --reporter lcovonly && coveralls < ./coverage/lcov.info" }, "repository": { "type": "git", @@ -55,19 +55,21 @@ "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": "1.10.3-alpha.13", + "@typescript-eslint/parser": "^2.23.0", + "array.prototype.flatmap": "^1.2.3", "babel-cli": "^6.26.0", "babel-core": "^6.26.3", "babel-eslint": "^8.2.6", "babel-plugin-istanbul": "^4.1.6", "babel-plugin-module-resolver": "^2.7.1", "babel-preset-es2015-argon": "latest", + "babel-preset-flow": "^6.23.0", "babel-register": "^6.26.0", "babylon": "^6.18.0", "chai": "^4.2.0", "coveralls": "^3.0.6", "cross-env": "^4.0.0", - "eslint": "2.x - 6.x", + "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-webpack": "file:./resolvers/webpack", @@ -75,34 +77,39 @@ "eslint-module-utils": "file:./utils", "eslint-plugin-eslint-plugin": "^2.2.1", "eslint-plugin-import": "2.x", - "esquery": "~1.1.0", + "eslint-plugin-json": "^2.1.1", + "fs-copy-file-sync": "^1.1.1", + "glob": "^7.1.6", "in-publish": "^2.0.0", "linklocal": "^2.8.2", + "lodash.isarray": "^4.0.0", "mocha": "^3.5.3", + "npm-which": "^3.0.1", "nyc": "^11.9.0", "redux": "^3.7.2", "rimraf": "^2.7.1", "semver": "^6.3.0", "sinon": "^2.4.1", - "typescript": "~3.2.2", + "typescript": "~3.8.3", "typescript-eslint-parser": "^22.0.0" }, "peerDependencies": { - "eslint": "2.x - 6.x" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0" }, "dependencies": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.1", + "eslint-import-resolver-node": "^0.3.3", + "eslint-module-utils": "^2.6.0", "has": "^1.0.3", "minimatch": "^3.0.4", - "object.values": "^1.1.0", + "object.values": "^1.1.1", "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" }, "nyc": { "require": [ diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index 78e01348e..e06203823 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -5,6 +5,9 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +### Fixed +- [fix] provide config fallback ([#1705], thanks [@migueloller]) + ## 0.12.0 - 2019-12-07 ### Added @@ -13,7 +16,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## 0.11.1 - 2019-04-13 ### Fixed -- [fix] match coreLibs after resolveSync in webpack-resolver ([#1297]) +- [fix] match coreLibs after resolveSync in webpack-resolver ([#1297], thanks [@echenley]) ## 0.11.0 - 2018-01-22 @@ -122,6 +125,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - `interpret` configs (such as `.babel.js`). Thanks to [@gausie] for the initial PR ([#164], ages ago! 😅) and [@jquense] for tests ([#278]). +[#1705]: https://github.com/benmosher/eslint-plugin-import/pull/1705 [#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 @@ -172,4 +176,6 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [@mattkrick]: https://github.com/mattkrick [@idudinov]: https://github.com/idudinov [@keann]: https://github.com/keann +[@echenley]: https://github.com/echenley [@Aghassi]: https://github.com/Aghassi +[@migueloller]: https://github.com/migueloller diff --git a/resolvers/webpack/index.js b/resolvers/webpack/index.js index dd3fc7a38..20c594847 100644 --- a/resolvers/webpack/index.js +++ b/resolvers/webpack/index.js @@ -119,6 +119,12 @@ exports.resolve = function (source, file, settings) { } } + if (webpackConfig == null) { + webpackConfig = {} + + console.warn('No webpack configuration with a "resolve" field found. Using empty object instead') + } + log('Using config: ', webpackConfig) // externals diff --git a/scripts/GetCI/GetCI.psm1 b/scripts/GetCI/GetCI.psm1 new file mode 100644 index 000000000..818ce32fe --- /dev/null +++ b/scripts/GetCI/GetCI.psm1 @@ -0,0 +1,12 @@ +function Get-CICommand { + $arguments = [System.Collections.ArrayList]$args + if ($env:CONFIGURATION -eq "WSL") { + $arguments.Insert(0, "wsl"); + } else { + if ($arguments[0] -eq "sudo") { + $arguments.RemoveAt(0) + } + } + $arguments.Insert(0, "echo"); + cmd /c $arguments[0] $arguments[1..$($arguments.Count - 1)]; +} diff --git a/scripts/ci.cmd b/scripts/ci.cmd new file mode 100644 index 000000000..04ac20265 --- /dev/null +++ b/scripts/ci.cmd @@ -0,0 +1,8 @@ +@echo off + +FOR /F "tokens=* usebackq" %%F IN (`powershell -Command "& { Import-Module %~dp0GetCI; Get-CICommand %* }"`) DO ( + SET args=%%F +) + +echo ^> cmd /c %args% +cmd /c %args% diff --git a/scripts/copyMetafiles.js b/scripts/copyMetafiles.js new file mode 100644 index 000000000..441e421e8 --- /dev/null +++ b/scripts/copyMetafiles.js @@ -0,0 +1,22 @@ +import path from 'path' +import copyFileSync from 'fs-copy-file-sync' +import resolverDirectories from './resolverDirectories' + +let files = [ + 'LICENSE', + '.npmrc', +] + +let directories = [ + 'memo-parser', + ...resolverDirectories, + 'utils', +] + +for (let directory of directories) { + for (let file of files) { + let destination = path.join(directory, file) + copyFileSync(file, destination) + console.log(`${file} -> ${destination}`) + } +} diff --git a/scripts/resolverDirectories.js b/scripts/resolverDirectories.js new file mode 100644 index 000000000..eea0620d3 --- /dev/null +++ b/scripts/resolverDirectories.js @@ -0,0 +1,3 @@ +import glob from 'glob' + +export default glob.sync('./resolvers/*/') diff --git a/scripts/testAll.js b/scripts/testAll.js new file mode 100644 index 000000000..358ef3e89 --- /dev/null +++ b/scripts/testAll.js @@ -0,0 +1,20 @@ +import { spawnSync } from 'child_process' +import npmWhich from 'npm-which' +import resolverDirectories from './resolverDirectories' + +let npmPath = npmWhich(__dirname).sync('npm') +let spawnOptions = { + stdio: 'inherit', +} + +spawnSync( + npmPath, + ['test'], + Object.assign({ cwd: __dirname }, spawnOptions)) + +for (let resolverDir of resolverDirectories) { + spawnSync( + npmPath, + ['test'], + Object.assign({ cwd: resolverDir }, spawnOptions)) +} diff --git a/src/ExportMap.js b/src/ExportMap.js index 525f64a48..dfd9ca155 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -13,6 +13,12 @@ import isIgnored, { hasValidExtension } from 'eslint-module-utils/ignore' import { hashObject } from 'eslint-module-utils/hash' import * as unambiguous from 'eslint-module-utils/unambiguous' +import { tsConfigLoader } from 'tsconfig-paths/lib/tsconfig-loader' + +import includes from 'array-includes' + +import {parseConfigFileTextToJson} from 'typescript' + const log = debug('eslint-plugin-import:ExportMap') const exportCache = new Map() @@ -445,8 +451,23 @@ ExportMap.parse = function (path, content, context) { const source = makeSourceCode(content, ast) - ast.body.forEach(function (n) { + function isEsModuleInterop() { + const tsConfigInfo = tsConfigLoader({ + cwd: context.parserOptions && context.parserOptions.tsconfigRootDir || process.cwd(), + getEnv: (key) => process.env[key], + }) + try { + if (tsConfigInfo.tsConfigPath !== undefined) { + const jsonText = fs.readFileSync(tsConfigInfo.tsConfigPath).toString() + const tsConfig = parseConfigFileTextToJson(tsConfigInfo.tsConfigPath, jsonText).config + return tsConfig.compilerOptions.esModuleInterop + } + } catch (e) { + return false + } + } + ast.body.forEach(function (n) { if (n.type === 'ExportDefaultDeclaration') { const exportMeta = captureDoc(source, docStyleParsers, n) if (n.declaration.type === 'Identifier') { @@ -528,32 +549,66 @@ ExportMap.parse = function (path, content, context) { }) } + const isEsModuleInteropTrue = isEsModuleInterop() + + const exports = ['TSExportAssignment'] + isEsModuleInteropTrue && exports.push('TSNamespaceExportDeclaration') + // This doesn't declare anything, but changes what's being exported. - if (n.type === 'TSExportAssignment') { - const moduleDecls = ast.body.filter((bodyNode) => - bodyNode.type === 'TSModuleDeclaration' && bodyNode.id.name === n.expression.name - ) - moduleDecls.forEach((moduleDecl) => { - if (moduleDecl && moduleDecl.body && moduleDecl.body.body) { - moduleDecl.body.body.forEach((moduleBlockNode) => { - // Export-assignment exports all members in the namespace, explicitly exported or not. - const exportedDecl = moduleBlockNode.type === 'ExportNamedDeclaration' ? - moduleBlockNode.declaration : - moduleBlockNode - - if (exportedDecl.type === 'VariableDeclaration') { - exportedDecl.declarations.forEach((decl) => - recursivePatternCapture(decl.id,(id) => m.namespace.set( - id.name, - captureDoc(source, docStyleParsers, decl, exportedDecl, moduleBlockNode)) + if (includes(exports, n.type)) { + const exportedName = n.expression && n.expression.name || n.id.name + const declTypes = [ + 'VariableDeclaration', + 'ClassDeclaration', + 'TSDeclareFunction', + 'TSEnumDeclaration', + 'TSTypeAliasDeclaration', + 'TSInterfaceDeclaration', + 'TSAbstractClassDeclaration', + 'TSModuleDeclaration', + ] + const exportedDecls = ast.body.filter(({ type, id, declarations }) => includes(declTypes, type) && ( + (id && id.name === exportedName) || (declarations && declarations.find((d) => d.id.name === exportedName)) + )) + if (exportedDecls.length === 0) { + // Export is not referencing any local declaration, must be re-exporting + m.namespace.set('default', captureDoc(source, docStyleParsers, n)) + return + } + if (isEsModuleInteropTrue) { + m.namespace.set('default', {}) + } + exportedDecls.forEach((decl) => { + if (decl.type === 'TSModuleDeclaration') { + if (decl.body && decl.body.type === 'TSModuleDeclaration') { + m.namespace.set(decl.body.id.name, captureDoc(source, docStyleParsers, decl.body)) + } else if (decl.body && decl.body.body) { + decl.body.body.forEach((moduleBlockNode) => { + // Export-assignment exports all members in the namespace, + // explicitly exported or not. + const namespaceDecl = moduleBlockNode.type === 'ExportNamedDeclaration' ? + moduleBlockNode.declaration : + moduleBlockNode + + if (!namespaceDecl) { + // TypeScript can check this for us; we needn't + } else if (namespaceDecl.type === 'VariableDeclaration') { + namespaceDecl.declarations.forEach((d) => + recursivePatternCapture(d.id, (id) => m.namespace.set( + id.name, + captureDoc(source, docStyleParsers, decl, namespaceDecl, moduleBlockNode) + )) ) - ) - } else { - m.namespace.set( - exportedDecl.id.name, - captureDoc(source, docStyleParsers, moduleBlockNode)) - } - }) + } else { + m.namespace.set( + namespaceDecl.id.name, + captureDoc(source, docStyleParsers, moduleBlockNode)) + } + }) + } + } else { + // Export as default + m.namespace.set('default', captureDoc(source, docStyleParsers, decl)) } }) } diff --git a/src/rules/dynamic-import-chunkname.js b/src/rules/dynamic-import-chunkname.js index 44fb5611c..40b99239a 100644 --- a/src/rules/dynamic-import-chunkname.js +++ b/src/rules/dynamic-import-chunkname.js @@ -42,7 +42,9 @@ module.exports = { const sourceCode = context.getSourceCode() const arg = node.arguments[0] - const leadingComments = sourceCode.getComments(arg).leading + 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({ diff --git a/src/rules/group-exports.js b/src/rules/group-exports.js index cd7fc992d..8abeb3d23 100644 --- a/src/rules/group-exports.js +++ b/src/rules/group-exports.js @@ -46,19 +46,28 @@ function accessorChain(node) { function create(context) { const nodes = { - modules: new Set(), - commonjs: new Set(), - sources: {}, + modules: { + set: new Set(), + sources: {}, + }, + types: { + set: new Set(), + sources: {}, + }, + commonjs: { + set: new Set(), + }, } return { ExportNamedDeclaration(node) { + let target = node.exportKind === 'type' ? nodes.types : nodes.modules if (!node.source) { - nodes.modules.add(node) - } else if (Array.isArray(nodes.sources[node.source.value])) { - nodes.sources[node.source.value].push(node) + target.set.add(node) + } else if (Array.isArray(target.sources[node.source.value])) { + target.sources[node.source.value].push(node) } else { - nodes.sources[node.source.value] = [node] + target.sources[node.source.value] = [node] } }, @@ -73,21 +82,21 @@ function create(context) { // Deeper assignments are ignored since they just modify what's already being exported // (ie. module.exports.exported.prop = true is ignored) if (chain[0] === 'module' && chain[1] === 'exports' && chain.length <= 3) { - nodes.commonjs.add(node) + nodes.commonjs.set.add(node) return } // Assignments to exports (exports.* = *) if (chain[0] === 'exports' && chain.length === 2) { - nodes.commonjs.add(node) + nodes.commonjs.set.add(node) return } }, 'Program:exit': function onExit() { // Report multiple `export` declarations (ES2015 modules) - if (nodes.modules.size > 1) { - nodes.modules.forEach(node => { + if (nodes.modules.set.size > 1) { + nodes.modules.set.forEach(node => { context.report({ node, message: errors[node.type], @@ -96,7 +105,27 @@ function create(context) { } // Report multiple `aggregated exports` from the same module (ES2015 modules) - flat(values(nodes.sources) + flat(values(nodes.modules.sources) + .filter(nodesWithSource => Array.isArray(nodesWithSource) && nodesWithSource.length > 1)) + .forEach((node) => { + context.report({ + node, + message: errors[node.type], + }) + }) + + // Report multiple `export type` declarations (FLOW ES2015 modules) + if (nodes.types.set.size > 1) { + nodes.types.set.forEach(node => { + context.report({ + node, + message: errors[node.type], + }) + }) + } + + // Report multiple `aggregated type exports` from the same module (FLOW ES2015 modules) + flat(values(nodes.types.sources) .filter(nodesWithSource => Array.isArray(nodesWithSource) && nodesWithSource.length > 1)) .forEach((node) => { context.report({ @@ -106,8 +135,8 @@ function create(context) { }) // Report multiple `module.exports` assignments (CommonJS) - if (nodes.commonjs.size > 1) { - nodes.commonjs.forEach(node => { + if (nodes.commonjs.set.size > 1) { + nodes.commonjs.set.forEach(node => { context.report({ node, message: errors[node.type], diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js index 690826eb4..7807dfcda 100644 --- a/src/rules/newline-after-import.js +++ b/src/rules/newline-after-import.js @@ -43,6 +43,10 @@ function isClassWithDecorator(node) { return node.type === 'ClassDeclaration' && node.decorators && node.decorators.length } +function isExportDefaultClass(node) { + return node.type === 'ExportDefaultDeclaration' && node.declaration.type === 'ClassDeclaration' +} + module.exports = { meta: { type: 'layout', @@ -68,7 +72,13 @@ module.exports = { const requireCalls = [] function checkForNewLine(node, nextNode, type) { - if (isClassWithDecorator(nextNode)) { + if (isExportDefaultClass(nextNode)) { + let classNode = nextNode.declaration + + if (isClassWithDecorator(classNode)) { + nextNode = classNode.decorators[0] + } + } else if (isClassWithDecorator(nextNode)) { nextNode = nextNode.decorators[0] } diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index 62da3643b..8f39246b5 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -4,6 +4,7 @@ */ import Exports from '../ExportMap' +import { isExternalModule } from '../core/importType' import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor' import docsUrl from '../docsUrl' @@ -18,6 +19,11 @@ module.exports = { type: 'integer', minimum: 1, }, + ignoreExternal: { + description: 'ignore external modules', + type: 'boolean', + default: false, + }, })], }, @@ -27,8 +33,13 @@ module.exports = { const options = context.options[0] || {} const maxDepth = options.maxDepth || Infinity + const ignoreModule = (name) => options.ignoreExternal ? isExternalModule(name) : false function checkSourceValue(sourceNode, importer) { + if (ignoreModule(sourceNode.value)) { + return // ignore external modules + } + const imported = Exports.get(sourceNode.value, context) if (importer.importKind === 'type') { @@ -54,6 +65,7 @@ module.exports = { for (let [path, { getter, source }] of m.imports) { if (path === myPath) return true if (traversed.has(path)) continue + if (ignoreModule(source.value)) continue if (route.length + 1 < maxDepth) { untraversed.push({ mget: getter, diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 7746f489e..03c45526c 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -3,8 +3,8 @@ import fs from 'fs' import readPkgUp from 'read-pkg-up' import minimatch from 'minimatch' import resolve from 'eslint-module-utils/resolve' +import moduleVisitor from 'eslint-module-utils/moduleVisitor' import importType from '../core/importType' -import isStaticRequire from '../core/staticRequire' import docsUrl from '../docsUrl' function hasKeys(obj = {}) { @@ -200,28 +200,8 @@ module.exports = { allowBundledDeps: testConfig(options.bundledDependencies, filename) !== false, } - // todo: use module visitor from module-utils core - return { - ImportDeclaration: function (node) { - if (node.source) { - reportIfMissing(context, deps, depsOptions, node, node.source.value) - } - }, - ExportNamedDeclaration: function (node) { - if (node.source) { - reportIfMissing(context, deps, depsOptions, node, node.source.value) - } - }, - ExportAllDeclaration: function (node) { - if (node.source) { - reportIfMissing(context, deps, depsOptions, node, node.source.value) - } - }, - CallExpression: function handleRequires(node) { - if (isStaticRequire(node)) { - reportIfMissing(context, deps, depsOptions, node, node.arguments[0].value) - } - }, - } + return moduleVisitor(node => { + reportIfMissing(context, deps, depsOptions, node, node.value) + }, {commonjs: true}) }, } diff --git a/src/rules/no-internal-modules.js b/src/rules/no-internal-modules.js index 9987dfd5c..b5d7496a2 100644 --- a/src/rules/no-internal-modules.js +++ b/src/rules/no-internal-modules.js @@ -91,6 +91,12 @@ module.exports = { ImportDeclaration(node) { checkImportForReaching(node.source.value, node.source) }, + ExportAllDeclaration(node) { + checkImportForReaching(node.source.value, node.source) + }, + ExportNamedDeclaration(node) { + checkImportForReaching(node.source.value, node.source) + }, CallExpression(node) { if (isStaticRequire(node)) { const [ firstArgument ] = node.arguments diff --git a/src/rules/no-restricted-paths.js b/src/rules/no-restricted-paths.js index 221457b1c..a94b11ec1 100644 --- a/src/rules/no-restricted-paths.js +++ b/src/rules/no-restricted-paths.js @@ -32,6 +32,7 @@ module.exports = { }, uniqueItems: true, }, + message: { type: 'string' }, }, additionalProperties: false, }, @@ -102,7 +103,7 @@ module.exports = { context.report({ node, - message: `Unexpected path "{{importPath}}" imported in restricted zone.`, + message: `Unexpected path "{{importPath}}" imported in restricted zone.${zone.message ? ` ${zone.message}` : ''}`, data: { importPath }, }) }) diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 9468dc87d..25139b681 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -64,7 +64,6 @@ const VARIABLE_DECLARATION = 'VariableDeclaration' const FUNCTION_DECLARATION = 'FunctionDeclaration' const CLASS_DECLARATION = 'ClassDeclaration' const DEFAULT = 'default' -const TYPE_ALIAS = 'TypeAlias' /** * List of imports per file. @@ -196,7 +195,13 @@ const prepareImportsAndExports = (srcFiles, context) => { if (isNodeModule(key)) { return } - imports.set(key, value.importedSpecifiers) + let localImport = imports.get(key) + if (typeof localImport !== 'undefined') { + localImport = new Set([...localImport, ...value.importedSpecifiers]) + } else { + localImport = value.importedSpecifiers + } + imports.set(key, localImport) }) importList.set(file, imports) @@ -557,8 +562,7 @@ module.exports = { if (declaration) { if ( declaration.type === FUNCTION_DECLARATION || - declaration.type === CLASS_DECLARATION || - declaration.type === TYPE_ALIAS + declaration.type === CLASS_DECLARATION ) { newExportIdentifiers.add(declaration.id.name) } @@ -650,13 +654,12 @@ module.exports = { if (astNode.source) { resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context) astNode.specifiers.forEach(specifier => { - let name - if (specifier.exported.name === DEFAULT) { - name = IMPORT_DEFAULT_SPECIFIER + const name = specifier.local.name + if (specifier.local.name === DEFAULT) { + newDefaultImports.add(resolvedPath) } else { - name = specifier.local.name + newImports.set(name, resolvedPath) } - newImports.set(name, resolvedPath) }) } } @@ -883,8 +886,7 @@ module.exports = { if (node.declaration) { if ( node.declaration.type === FUNCTION_DECLARATION || - node.declaration.type === CLASS_DECLARATION || - node.declaration.type === TYPE_ALIAS + node.declaration.type === CLASS_DECLARATION ) { checkUsage(node, node.declaration.id.name) } diff --git a/src/rules/order.js b/src/rules/order.js index 948c5f427..9edac3af9 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -157,8 +157,12 @@ function isPlainImportModule(node) { return node.type === 'ImportDeclaration' && node.specifiers != null && node.specifiers.length > 0 } +function isPlainImportEquals(node) { + return node.type === 'TSImportEqualsDeclaration' && node.moduleReference.expression +} + function canCrossNodeWhileReorder(node) { - return isPlainRequireModule(node) || isPlainImportModule(node) + return isPlainRequireModule(node) || isPlainImportModule(node) || isPlainImportEquals(node) } function canReorderItems(firstNode, secondNode) { @@ -243,28 +247,22 @@ function makeOutOfOrderReport(context, imported) { reportOutOfOrder(context, imported, outOfOrder, 'before') } -function importsSorterAsc(importA, importB) { - if (importA < importB) { - return -1 - } - - if (importA > importB) { - return 1 - } +function getSorter(ascending) { + let multiplier = (ascending ? 1 : -1) - return 0 -} + return function importsSorter(importA, importB) { + let result -function importsSorterDesc(importA, importB) { - if (importA < importB) { - return 1 - } + if ((importA < importB) || importB === null) { + result = -1 + } else if ((importA > importB) || importA === null) { + result = 1 + } else { + result = 0 + } - if (importA > importB) { - return -1 + return result * multiplier } - - return 0 } function mutateRanksToAlphabetize(imported, alphabetizeOptions) { @@ -278,7 +276,7 @@ function mutateRanksToAlphabetize(imported, alphabetizeOptions) { const groupRanks = Object.keys(groupedByRanks) - const sorterFn = alphabetizeOptions.order === 'asc' ? importsSorterAsc : importsSorterDesc + const sorterFn = getSorter(alphabetizeOptions.order === 'asc') const comparator = alphabetizeOptions.caseInsensitive ? (a, b) => sorterFn(String(a).toLowerCase(), String(b).toLowerCase()) : (a, b) => sorterFn(a, b) // sort imports locally within their group groupRanks.forEach(function(groupRank) { @@ -318,7 +316,7 @@ function computeRank(context, ranks, name, type, excludedImportTypes) { if (!excludedImportTypes.has(impType)) { rank = computePathRank(ranks.groups, ranks.pathGroups, name, ranks.maxPosition) } - if (!rank) { + if (typeof rank === 'undefined') { rank = ranks.groups[impType] } if (type !== 'import') { @@ -609,6 +607,23 @@ module.exports = { ) } }, + TSImportEqualsDeclaration: function handleImports(node) { + let name + if (node.moduleReference.type === 'TSExternalModuleReference') { + name = node.moduleReference.expression.value + } else { + name = null + } + registerNode( + context, + node, + name, + 'import', + ranks, + imported, + pathGroupsExcludedImportTypes + ) + }, CallExpression: function handleRequires(node) { if (level !== 0 || !isStaticRequire(node) || !isInVariableDeclarator(node.parent)) { return diff --git a/tests/files/cycles/external-depth-two.js b/tests/files/cycles/external-depth-two.js new file mode 100644 index 000000000..fbb6bfcbb --- /dev/null +++ b/tests/files/cycles/external-depth-two.js @@ -0,0 +1,2 @@ +import { foo } from "cycles/external/depth-one" +export { foo } diff --git a/tests/files/cycles/external/depth-one.js b/tests/files/cycles/external/depth-one.js new file mode 100644 index 000000000..9caa76250 --- /dev/null +++ b/tests/files/cycles/external/depth-one.js @@ -0,0 +1,2 @@ +import foo from "../depth-zero" +export { foo } diff --git a/tests/files/internal-modules/typescript/plugin2/app/index.ts b/tests/files/internal-modules/typescript/plugin2/app/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/internal-modules/typescript/plugin2/index.ts b/tests/files/internal-modules/typescript/plugin2/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/internal-modules/typescript/plugin2/internal.ts b/tests/files/internal-modules/typescript/plugin2/internal.ts new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/internal-modules/typescript/plugins.ts b/tests/files/internal-modules/typescript/plugins.ts new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/just-json-files/.eslintrc.json b/tests/files/just-json-files/.eslintrc.json new file mode 100644 index 000000000..4fbf13a72 --- /dev/null +++ b/tests/files/just-json-files/.eslintrc.json @@ -0,0 +1,22 @@ +{ + "root": true, + + "plugins": ["import", "json"], + + "rules": { + "import/no-unused-modules": [ + "error", + { + "missingExports": false, + "unusedExports": true + } + ] + }, + + "overrides": [ + { + "files": "*.json", + "extends": "plugin:json/recommended" + } + ] +} diff --git a/tests/files/just-json-files/invalid.json b/tests/files/just-json-files/invalid.json new file mode 100644 index 000000000..7edb2fa5b --- /dev/null +++ b/tests/files/just-json-files/invalid.json @@ -0,0 +1 @@ +, diff --git a/tests/files/no-unused-modules/flow-0.js b/tests/files/no-unused-modules/flow-0.js deleted file mode 100644 index 46bda6879..000000000 --- a/tests/files/no-unused-modules/flow-0.js +++ /dev/null @@ -1 +0,0 @@ -import { type FooType } from './flow-2'; diff --git a/tests/files/no-unused-modules/flow-1.js b/tests/files/no-unused-modules/flow-1.js deleted file mode 100644 index bb7266d3c..000000000 --- a/tests/files/no-unused-modules/flow-1.js +++ /dev/null @@ -1,2 +0,0 @@ -// @flow strict -export type Bar = number; diff --git a/tests/files/no-unused-modules/flow-2.js b/tests/files/no-unused-modules/flow-2.js deleted file mode 100644 index 0cbb836a6..000000000 --- a/tests/files/no-unused-modules/flow-2.js +++ /dev/null @@ -1,2 +0,0 @@ -// @flow strict -export type FooType = string; diff --git a/tests/files/no-unused-modules/import-export-1.js b/tests/files/no-unused-modules/import-export-1.js new file mode 100644 index 000000000..218c3cff7 --- /dev/null +++ b/tests/files/no-unused-modules/import-export-1.js @@ -0,0 +1,2 @@ +export const a = 5; +export const b = 'b'; diff --git a/tests/files/no-unused-modules/import-export-2.js b/tests/files/no-unused-modules/import-export-2.js new file mode 100644 index 000000000..9cfb2747b --- /dev/null +++ b/tests/files/no-unused-modules/import-export-2.js @@ -0,0 +1,2 @@ +import { a } from './import-export-1'; +export { b } from './import-export-1'; diff --git a/tests/files/no-unused-modules/renameDefault-2/ComponentA.js b/tests/files/no-unused-modules/renameDefault-2/ComponentA.js new file mode 100644 index 000000000..b4517920f --- /dev/null +++ b/tests/files/no-unused-modules/renameDefault-2/ComponentA.js @@ -0,0 +1 @@ +export default function ComponentA() {} diff --git a/tests/files/no-unused-modules/renameDefault-2/ComponentB.js b/tests/files/no-unused-modules/renameDefault-2/ComponentB.js new file mode 100644 index 000000000..72e0f2ee7 --- /dev/null +++ b/tests/files/no-unused-modules/renameDefault-2/ComponentB.js @@ -0,0 +1 @@ +export default function ComponentB() {} diff --git a/tests/files/no-unused-modules/renameDefault-2/components.js b/tests/files/no-unused-modules/renameDefault-2/components.js new file mode 100644 index 000000000..5a72952a3 --- /dev/null +++ b/tests/files/no-unused-modules/renameDefault-2/components.js @@ -0,0 +1,2 @@ +export { default as ComponentA } from "./ComponentA"; +export { default as ComponentB } from "./ComponentB"; diff --git a/tests/files/no-unused-modules/renameDefault-2/usage.js b/tests/files/no-unused-modules/renameDefault-2/usage.js new file mode 100644 index 000000000..7298baa55 --- /dev/null +++ b/tests/files/no-unused-modules/renameDefault-2/usage.js @@ -0,0 +1 @@ +import { ComponentA, ComponentB } from './components' diff --git a/tests/files/no-unused-modules/renameDefault/Component.js b/tests/files/no-unused-modules/renameDefault/Component.js new file mode 100644 index 000000000..c6be8faf0 --- /dev/null +++ b/tests/files/no-unused-modules/renameDefault/Component.js @@ -0,0 +1 @@ +export default function Component() {} diff --git a/tests/files/no-unused-modules/renameDefault/components.js b/tests/files/no-unused-modules/renameDefault/components.js new file mode 100644 index 000000000..4a877cb1f --- /dev/null +++ b/tests/files/no-unused-modules/renameDefault/components.js @@ -0,0 +1 @@ +export { default as Component } from './Component' diff --git a/tests/files/no-unused-modules/renameDefault/usage.js b/tests/files/no-unused-modules/renameDefault/usage.js new file mode 100644 index 000000000..6ee988988 --- /dev/null +++ b/tests/files/no-unused-modules/renameDefault/usage.js @@ -0,0 +1 @@ +import { Component } from './components' diff --git a/tests/files/typescript-d-ts/.eslintrc b/tests/files/typescript-d-ts/.eslintrc new file mode 100644 index 000000000..f22e9cb62 --- /dev/null +++ b/tests/files/typescript-d-ts/.eslintrc @@ -0,0 +1,12 @@ +{ + "overrides": [ + { + "files": "**.ts", + "parser": "@typescript-eslint/parser", + "extends": "../../../config/typescript", + "rules": { + "import/export": "error", + }, + }, + ], +} diff --git a/tests/files/typescript-d-ts/file1.ts b/tests/files/typescript-d-ts/file1.ts new file mode 100644 index 000000000..872c30e8a --- /dev/null +++ b/tests/files/typescript-d-ts/file1.ts @@ -0,0 +1,6 @@ +declare namespace ts { + const x: string; + export { x }; +} + +export = ts; diff --git a/tests/files/typescript-d-ts/file2.ts b/tests/files/typescript-d-ts/file2.ts new file mode 100644 index 000000000..e8ed5afca --- /dev/null +++ b/tests/files/typescript-d-ts/file2.ts @@ -0,0 +1 @@ +export * from './file1.ts' diff --git a/tests/files/typescript-declare-interface.d.ts b/tests/files/typescript-declare-interface.d.ts new file mode 100644 index 000000000..b572b62e9 --- /dev/null +++ b/tests/files/typescript-declare-interface.d.ts @@ -0,0 +1,11 @@ +declare interface foo { + a: string; +} + +declare namespace SomeNamespace { + type foobar = foo & { + b: string; + } +} + +export = SomeNamespace diff --git a/tests/files/typescript-declare-nested.d.ts b/tests/files/typescript-declare-nested.d.ts new file mode 100644 index 000000000..dc6b0049a --- /dev/null +++ b/tests/files/typescript-declare-nested.d.ts @@ -0,0 +1,15 @@ +declare namespace foo { + interface SomeInterface { + a: string; + } +} + +declare namespace foo.bar { + interface SomeOtherInterface { + b: string; + } + + function MyFunction(); +} + +export = foo; diff --git a/tests/files/typescript-default.ts b/tests/files/typescript-default.ts new file mode 100644 index 000000000..6d9a8f42c --- /dev/null +++ b/tests/files/typescript-default.ts @@ -0,0 +1 @@ +export default function foobar() {}; diff --git a/tests/files/typescript-export-as-default-namespace/index.d.ts b/tests/files/typescript-export-as-default-namespace/index.d.ts new file mode 100644 index 000000000..953c3410b --- /dev/null +++ b/tests/files/typescript-export-as-default-namespace/index.d.ts @@ -0,0 +1,3 @@ +export as namespace Foo + +export function bar(): void diff --git a/tests/files/typescript-export-as-default-namespace/tsconfig.json b/tests/files/typescript-export-as-default-namespace/tsconfig.json new file mode 100644 index 000000000..a72ee3e88 --- /dev/null +++ b/tests/files/typescript-export-as-default-namespace/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/tests/files/typescript-export-assign-default-namespace/index.d.ts b/tests/files/typescript-export-assign-default-namespace/index.d.ts new file mode 100644 index 000000000..2ad4822f7 --- /dev/null +++ b/tests/files/typescript-export-assign-default-namespace/index.d.ts @@ -0,0 +1,3 @@ +export = FooBar; + +declare namespace FooBar {} diff --git a/tests/files/typescript-export-assign-default-namespace/tsconfig.json b/tests/files/typescript-export-assign-default-namespace/tsconfig.json new file mode 100644 index 000000000..a72ee3e88 --- /dev/null +++ b/tests/files/typescript-export-assign-default-namespace/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/tests/files/typescript-export-assign-default-reexport.ts b/tests/files/typescript-export-assign-default-reexport.ts new file mode 100644 index 000000000..2fd502539 --- /dev/null +++ b/tests/files/typescript-export-assign-default-reexport.ts @@ -0,0 +1,2 @@ +import { getFoo } from './typescript'; +export = getFoo; diff --git a/tests/files/typescript-export-assign-default.d.ts b/tests/files/typescript-export-assign-default.d.ts new file mode 100644 index 000000000..f871ed926 --- /dev/null +++ b/tests/files/typescript-export-assign-default.d.ts @@ -0,0 +1,3 @@ +export = foobar; + +declare const foobar: number; diff --git a/tests/files/typescript-export-assign-mixed.d.ts b/tests/files/typescript-export-assign-mixed.d.ts new file mode 100644 index 000000000..8bf4c34b8 --- /dev/null +++ b/tests/files/typescript-export-assign-mixed.d.ts @@ -0,0 +1,11 @@ +export = foobar; + +declare function foobar(): void; +declare namespace foobar { + type MyType = string + enum MyEnum { + Foo, + Bar, + Baz + } +} diff --git a/tests/files/typescript-export-assign-merged.d.ts b/tests/files/typescript-export-assign-namespace-merged.d.ts similarity index 100% rename from tests/files/typescript-export-assign-merged.d.ts rename to tests/files/typescript-export-assign-namespace-merged.d.ts diff --git a/tests/files/typescript-export-assign.d.ts b/tests/files/typescript-export-assign-namespace.d.ts similarity index 100% rename from tests/files/typescript-export-assign.d.ts rename to tests/files/typescript-export-assign-namespace.d.ts diff --git a/tests/src/cli.js b/tests/src/cli.js index 93a4d43d7..5e0a74e36 100644 --- a/tests/src/cli.js +++ b/tests/src/cli.js @@ -1,8 +1,12 @@ /** * tests that require fully booting up ESLint */ +import path from 'path' + import { expect } from 'chai' import { CLIEngine } from 'eslint' +import eslintPkg from 'eslint/package.json' +import semver from 'semver' describe('CLI regression tests', function () { describe('issue #210', function () { @@ -18,7 +22,58 @@ describe('CLI regression tests', function () { }) }) it("doesn't throw an error on gratuitous, erroneous self-reference", function () { - expect(() => cli.executeOnFiles(['./tests/files/issue210.js'])).not.to.throw(Error) + expect(() => cli.executeOnFiles(['./tests/files/issue210.js'])).not.to.throw() + }) + }) + + describe('issue #1645', function () { + let cli + beforeEach(function () { + if (semver.satisfies(eslintPkg.version, '< 6')) { + this.skip() + } else { + cli = new CLIEngine({ + useEslintrc: false, + configFile: './tests/files/just-json-files/.eslintrc.json', + rulePaths: ['./src/rules'], + ignore: false, + }) + } + }) + + it('throws an error on invalid JSON', () => { + const invalidJSON = './tests/files/just-json-files/invalid.json' + const results = cli.executeOnFiles([invalidJSON]) + expect(results).to.eql({ + results: [ + { + filePath: path.resolve(invalidJSON), + messages: [ + { + column: 2, + endColumn: 3, + endLine: 1, + line: 1, + message: 'Expected a JSON object, array or literal.', + nodeType: results.results[0].messages[0].nodeType, // we don't care about this one + ruleId: 'json/*', + severity: 2, + source: '\n', + }, + ], + errorCount: 1, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: ',\n', + }, + ], + errorCount: 1, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + usedDeprecatedRules: results.usedDeprecatedRules, // we don't care about this one + }) }) }) }) diff --git a/tests/src/rules/default.js b/tests/src/rules/default.js index 4544285af..d3d4aae4a 100644 --- a/tests/src/rules/default.js +++ b/tests/src/rules/default.js @@ -1,4 +1,5 @@ -import { test, SYNTAX_CASES } from '../utils' +import path from 'path' +import { test, SYNTAX_CASES, getTSParsers } from '../utils' import { RuleTester } from 'eslint' import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve' @@ -152,3 +153,96 @@ if (!CASE_SENSITIVE_FS) { ], }) } + +context('TypeScript', function () { + getTSParsers().forEach((parser) => { + ruleTester.run(`default`, rule, { + valid: [ + test({ + code: `import foobar from "./typescript-default"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import foobar from "./typescript-export-assign-default"`, + 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, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import foobar from "./typescript-export-assign-default-reexport"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import React from "./typescript-export-assign-default-namespace"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + parserOptions: { + tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-assign-default-namespace/'), + }, + }), + test({ + code: `import Foo from "./typescript-export-as-default-namespace"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + parserOptions: { + tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-as-default-namespace/'), + }, + }), + ], + + invalid: [ + test({ + code: `import foobar from "./typescript"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: ['No default export found in imported module "./typescript".'], + }), + test({ + code: `import React from "./typescript-export-assign-default-namespace"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: ['No default export found in imported module "./typescript-export-assign-default-namespace".'], + }), + test({ + code: `import FooBar from "./typescript-export-as-default-namespace"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: ['No default export found in imported module "./typescript-export-as-default-namespace".'], + }), + ], + }) + }) +}) diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index 76fae4567..fb301495e 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -1,4 +1,4 @@ -import { test, SYNTAX_CASES, getTSParsers } from '../utils' +import { test, testFilePath, SYNTAX_CASES, getTSParsers } from '../utils' import { RuleTester } from 'eslint' @@ -187,6 +187,10 @@ context('TypeScript', function () { } `, }, parserConfig)), + test(Object.assign({ + code: 'export * from "./file1.ts"', + filename: testFilePath('typescript-d-ts/file-2.ts'), + }, parserConfig)), ], invalid: [ // type/value name clash diff --git a/tests/src/rules/exports-last.js b/tests/src/rules/exports-last.js index 770e123d3..cc853ba76 100644 --- a/tests/src/rules/exports-last.js +++ b/tests/src/rules/exports-last.js @@ -6,7 +6,6 @@ import rule from 'rules/exports-last' const ruleTester = new RuleTester() const error = type => ({ - ruleId: 'exports-last', message: 'Export statements should appear at the end of the file', type, }) diff --git a/tests/src/rules/group-exports.js b/tests/src/rules/group-exports.js index 9a0c2c1ba..0766a325e 100644 --- a/tests/src/rules/group-exports.js +++ b/tests/src/rules/group-exports.js @@ -1,6 +1,8 @@ import { test } from '../utils' import { RuleTester } from 'eslint' import rule from 'rules/group-exports' +import {resolve} from 'path' +import {default as babelPresetFlow} from 'babel-preset-flow' /* eslint-disable max-len */ const errors = { @@ -8,7 +10,16 @@ const errors = { commonjs: 'Multiple CommonJS exports; consolidate all exports into a single assignment to `module.exports`', } /* eslint-enable max-len */ -const ruleTester = new RuleTester() +const ruleTester = new RuleTester({ + parser: resolve(__dirname, '../../../node_modules/babel-eslint'), + parserOptions: { + babelOptions: { + configFile: false, + babelrc: false, + presets: [babelPresetFlow], + }, + }, +}) ruleTester.run('group-exports', rule, { valid: [ @@ -103,6 +114,27 @@ ruleTester.run('group-exports', rule, { unrelated = 'assignment' module.exports.test = true ` }), + test({ code: ` + type firstType = { + propType: string + }; + const first = {}; + export type { firstType }; + export { first }; + ` }), + test({ code: ` + type firstType = { + propType: string + }; + type secondType = { + propType: string + }; + export type { firstType, secondType }; + ` }), + test({ code: ` + export type { type1A, type1B } from './module-1' + export { method1 } from './module-1' + ` }), ], invalid: [ test({ @@ -231,5 +263,33 @@ ruleTester.run('group-exports', rule, { errors.commonjs, ], }), + test({ + code: ` + type firstType = { + propType: string + }; + type secondType = { + propType: string + }; + const first = {}; + export type { firstType }; + export type { secondType }; + export { first }; + `, + errors: [ + errors.named, + errors.named, + ], + }), + test({ + code: ` + export type { type1 } from './module-1' + export type { type2 } from './module-1' + `, + errors: [ + errors.named, + errors.named, + ], + }), ], }) diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 303df1e14..eba7bec1a 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -285,7 +285,12 @@ ruleTester.run('named (export *)', rule, { context('TypeScript', function () { getTSParsers().forEach((parser) => { - ['typescript', 'typescript-declare', 'typescript-export-assign', 'typescript-export-assign-merged'].forEach((source) => { + [ + 'typescript', + 'typescript-declare', + 'typescript-export-assign-namespace', + 'typescript-export-assign-namespace-merged', + ].forEach((source) => { ruleTester.run(`named`, rule, { valid: [ test({ diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index cfc6305d5..5627c7132 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -1,5 +1,6 @@ -import { test, SYNTAX_CASES } from '../utils' +import { test, SYNTAX_CASES, getTSParsers } from '../utils' import { RuleTester } from 'eslint' +import flatMap from 'array.prototype.flatmap' var ruleTester = new RuleTester({ env: { es6: true }}) , rule = require('rules/namespace') @@ -120,6 +121,39 @@ const valid = [ }, }), + // Typescript + ...flatMap(getTSParsers(), (parser) => [ + test({ + code: ` + import * as foo from "./typescript-declare-nested" + foo.bar.MyFunction() + `, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + + test({ + code: `import { foobar } from "./typescript-declare-interface"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + + test({ + code: 'export * from "typescript/lib/typescript.d"', + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + ]), + ...SYNTAX_CASES, ] diff --git a/tests/src/rules/newline-after-import.js b/tests/src/rules/newline-after-import.js index 220b217d7..bb94b56da 100644 --- a/tests/src/rules/newline-after-import.js +++ b/tests/src/rules/newline-after-import.js @@ -165,6 +165,16 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, parser: require.resolve('babel-eslint'), }, + { + code : `// issue 1004\nimport foo from 'foo';\n\n@SomeDecorator(foo)\nexport default class Test {}`, + parserOptions: { sourceType: 'module' }, + parser: require.resolve('babel-eslint'), + }, + { + code : `// issue 1004\nconst foo = require('foo');\n\n@SomeDecorator(foo)\nexport default class Test {}`, + parserOptions: { sourceType: 'module' }, + parser: require.resolve('babel-eslint'), + }, ], invalid: [ @@ -340,5 +350,27 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, parser: require.resolve('babel-eslint'), }, + { + code: `// issue 10042\nimport foo from 'foo';\n@SomeDecorator(foo)\nexport default class Test {}`, + output: `// issue 10042\nimport foo from 'foo';\n\n@SomeDecorator(foo)\nexport default class Test {}`, + errors: [ { + line: 2, + column: 1, + message: IMPORT_ERROR_MESSAGE, + } ], + parserOptions: { sourceType: 'module' }, + parser: require.resolve('babel-eslint'), + }, + { + code: `// issue 1004\nconst foo = require('foo');\n@SomeDecorator(foo)\nexport default class Test {}`, + output: `// issue 1004\nconst foo = require('foo');\n\n@SomeDecorator(foo)\nexport default class Test {}`, + errors: [ { + line: 2, + column: 1, + message: REQUIRE_ERROR_MESSAGE, + } ], + parserOptions: { sourceType: 'module' }, + parser: require.resolve('babel-eslint'), + }, ], }) diff --git a/tests/src/rules/no-absolute-path.js b/tests/src/rules/no-absolute-path.js index 8689997b4..2a95829b0 100644 --- a/tests/src/rules/no-absolute-path.js +++ b/tests/src/rules/no-absolute-path.js @@ -6,7 +6,6 @@ const ruleTester = new RuleTester() , rule = require('rules/no-absolute-path') const error = { - ruleId: 'no-absolute-path', message: 'Do not import modules using an absolute path', } diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js index df1e6d143..b0f4153e8 100644 --- a/tests/src/rules/no-cycle.js +++ b/tests/src/rules/no-cycle.js @@ -5,7 +5,7 @@ import { RuleTester } from 'eslint' const ruleTester = new RuleTester() , rule = require('rules/no-cycle') -const error = message => ({ ruleId: 'no-cycle', message }) +const error = message => ({ message }) const test = def => _test(Object.assign(def, { filename: testFilePath('./cycles/depth-zero.js'), @@ -40,6 +40,22 @@ ruleTester.run('no-cycle', rule, { code: 'import { foo, bar } from "./depth-two"', options: [{ maxDepth: 1 }], }), + test({ + code: 'import { foo } from "cycles/external/depth-one"', + options: [{ ignoreExternal: true }], + settings: { + 'import/resolver': 'webpack', + 'import/external-module-folders': ['external'], + }, + }), + test({ + code: 'import { foo } from "./external-depth-two"', + options: [{ ignoreExternal: true }], + settings: { + 'import/resolver': 'webpack', + 'import/external-module-folders': ['external'], + }, + }), test({ code: 'import("./depth-two").then(function({ foo }){})', options: [{ maxDepth: 1 }], @@ -63,6 +79,22 @@ ruleTester.run('no-cycle', rule, { code: 'import { foo } from "./depth-one"', errors: [error(`Dependency cycle detected.`)], }), + test({ + code: 'import { foo } from "cycles/external/depth-one"', + errors: [error(`Dependency cycle detected.`)], + settings: { + 'import/resolver': 'webpack', + 'import/external-module-folders': ['external'], + }, + }), + test({ + code: 'import { foo } from "./external-depth-two"', + errors: [error(`Dependency cycle via cycles/external/depth-one:1`)], + settings: { + 'import/resolver': 'webpack', + 'import/external-module-folders': ['external'], + }, + }), test({ code: 'import { foo } from "./depth-one"', options: [{ maxDepth: 1 }], diff --git a/tests/src/rules/no-default-export.js b/tests/src/rules/no-default-export.js index dd71c167e..d11b7d3b1 100644 --- a/tests/src/rules/no-default-export.js +++ b/tests/src/rules/no-default-export.js @@ -89,7 +89,7 @@ ruleTester.run('no-default-export', rule, { test({ code: 'export default function bar() {};', errors: [{ - ruleId: 'ExportDefaultDeclaration', + type: 'ExportDefaultDeclaration', message: 'Prefer named exports.', }], }), @@ -98,14 +98,14 @@ ruleTester.run('no-default-export', rule, { export const foo = 'foo'; export default bar;`, errors: [{ - ruleId: 'ExportDefaultDeclaration', + type: 'ExportDefaultDeclaration', message: 'Prefer named exports.', }], }), test({ code: 'let foo; export { foo as default }', errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Do not alias `foo` as `default`. Just export `foo` itself ' + 'instead.', }], @@ -114,7 +114,7 @@ ruleTester.run('no-default-export', rule, { code: 'export default from "foo.js"', parser: require.resolve('babel-eslint'), errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Prefer named exports.', }], }), diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index 917d0e400..0137221b0 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -1,5 +1,5 @@ import * as path from 'path' -import { test as testUtil } from '../utils' +import { test as testUtil, getNonDefaultParsers } from '../utils' import { RuleTester } from 'eslint' @@ -399,3 +399,32 @@ ruleTester.run('no-duplicates', rule, { }), ], }) + +context('TypeScript', function() { + getNonDefaultParsers() + .filter((parser) => parser !== require.resolve('typescript-eslint-parser')) + .forEach((parser) => { + const parserConfig = { + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + } + + ruleTester.run('no-duplicates', rule, { + valid: [ + // #1667: ignore duplicate if is a typescript type import + test( + { + code: "import type { x } from './foo'; import y from './foo'", + parser, + }, + parserConfig, + ), + ], + invalid: [], + }) + }) +}) + diff --git a/tests/src/rules/no-dynamic-require.js b/tests/src/rules/no-dynamic-require.js index 8793d0dd8..584603200 100644 --- a/tests/src/rules/no-dynamic-require.js +++ b/tests/src/rules/no-dynamic-require.js @@ -6,7 +6,6 @@ const ruleTester = new RuleTester() , rule = require('rules/no-dynamic-require') const error = { - ruleId: 'no-dynamic-require', message: 'Calls to require() should use string literals', } diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index e70a60174..97279d853 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -3,8 +3,10 @@ import * as path from 'path' import * as fs from 'fs' import { RuleTester } from 'eslint' +import flatMap from 'array.prototype.flatmap' + const ruleTester = new RuleTester() - , rule = require('rules/no-extraneous-dependencies') +const rule = require('rules/no-extraneous-dependencies') const packageDirWithSyntaxError = path.join(__dirname, '../../files/with-syntax-error') const packageFileWithSyntaxErrorMessage = (() => { @@ -22,21 +24,25 @@ const packageDirBundleDeps = path.join(__dirname, '../../files/bundled-dependenc const packageDirBundledDepsAsObject = path.join(__dirname, '../../files/bundled-dependencies/as-object') const packageDirBundledDepsRaceCondition = path.join(__dirname, '../../files/bundled-dependencies/race-condition') +const { + dependencies: deps, + devDependencies: devDeps, +} = require('../../files/package.json') + ruleTester.run('no-extraneous-dependencies', rule, { valid: [ - test({ code: 'import "lodash.cond"'}), - test({ code: 'import "pkg-up"'}), - test({ code: 'import foo, { bar } from "lodash.cond"'}), - test({ code: 'import foo, { bar } from "pkg-up"'}), + ...flatMap(Object.keys(deps).concat(Object.keys(devDeps)), (pkg) => [ + test({ code: `import "${pkg}"` }), + test({ code: `import foo, { bar } from "${pkg}"` }), + test({ code: `require("${pkg}")` }), + test({ code: `var foo = require("${pkg}")` }), + test({ code: `export { foo } from "${pkg}"` }), + test({ code: `export * from "${pkg}"` }), + ]), test({ code: 'import "eslint"'}), test({ code: 'import "eslint/lib/api"'}), - test({ code: 'require("lodash.cond")'}), - test({ code: 'require("pkg-up")'}), - test({ code: 'var foo = require("lodash.cond")'}), - test({ code: 'var foo = require("pkg-up")'}), test({ code: 'import "fs"'}), test({ code: 'import "./foo"'}), - test({ code: 'import "lodash.isarray"'}), test({ code: 'import "@org/package"'}), test({ code: 'import "electron"', settings: { 'import/core-modules': ['electron'] } }), @@ -113,8 +119,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import foo from "@generated/foo"', options: [{packageDir: packageDirBundledDepsRaceCondition}], }), - test({ code: 'export { foo } from "lodash.cond"' }), - test({ code: 'export * from "lodash.cond"' }), test({ code: 'export function getToken() {}' }), test({ code: 'export class Component extends React.Component {}' }), test({ code: 'export function Component() {}' }), @@ -126,7 +130,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { filename: path.join(packageDirMonoRepoRoot, 'foo.js'), options: [{packageDir: packageDirMonoRepoRoot }], errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', }], }), @@ -135,7 +138,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { filename: path.join(packageDirMonoRepoWithNested, 'foo.js'), options: [{packageDir: packageDirMonoRepoRoot}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', }], }), @@ -143,28 +145,24 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import "not-a-dependency"', options: [{packageDir: packageDirMonoRepoRoot}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', }], }), test({ code: 'import "not-a-dependency"', errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', }], }), test({ code: 'var donthaveit = require("@org/not-a-dependency")', errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'@org/not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S @org/not-a-dependency\' to add it', }], }), test({ code: 'var donthaveit = require("@org/not-a-dependency/foo")', errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'@org/not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S @org/not-a-dependency\' to add it', }], }), @@ -172,7 +170,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import "eslint"', options: [{devDependencies: false, peerDependencies: false}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'eslint\' should be listed in the project\'s dependencies, not devDependencies.', }], }), @@ -180,14 +177,12 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import "lodash.isarray"', options: [{optionalDependencies: false}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'lodash.isarray\' should be listed in the project\'s dependencies, not optionalDependencies.', }], }), test({ code: 'var foo = require("not-a-dependency")', errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', }], }), @@ -195,7 +190,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'var glob = require("glob")', options: [{devDependencies: false}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'glob\' should be listed in the project\'s dependencies, not devDependencies.', }], }), @@ -204,7 +198,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { options: [{devDependencies: ['*.test.js']}], filename: 'foo.tes.js', errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.', }], }), @@ -213,7 +206,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { options: [{devDependencies: ['*.test.js']}], filename: path.join(process.cwd(), 'foo.tes.js'), errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.', }], }), @@ -222,7 +214,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { options: [{devDependencies: ['*.test.js', '*.spec.js']}], filename: 'foo.tes.js', errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.', }], }), @@ -231,7 +222,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { options: [{devDependencies: ['*.test.js', '*.spec.js']}], filename: path.join(process.cwd(), 'foo.tes.js'), errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.', }], }), @@ -239,7 +229,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'var eslint = require("lodash.isarray")', options: [{optionalDependencies: false}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'lodash.isarray\' should be listed in the project\'s dependencies, not optionalDependencies.', }], }), @@ -247,7 +236,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import "not-a-dependency"', options: [{packageDir: path.join(__dirname, '../../../')}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', }], }), @@ -255,7 +243,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import "bar"', options: [{packageDir: path.join(__dirname, './doesn-exist/')}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: 'The package.json file could not be found.', }], }), @@ -263,7 +250,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import foo from "foo"', options: [{packageDir: packageDirWithSyntaxError}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: 'The package.json file could not be parsed: ' + packageFileWithSyntaxErrorMessage, }], }), @@ -272,7 +258,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { filename: path.join(packageDirMonoRepoWithNested, 'foo.js'), options: [{packageDir: packageDirMonoRepoWithNested}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: "'left-pad' should be listed in the project's dependencies. Run 'npm i -S left-pad' to add it", }], }), @@ -280,7 +265,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import react from "react";', filename: path.join(packageDirMonoRepoRoot, 'foo.js'), errors: [{ - ruleId: 'no-extraneous-dependencies', message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it", }], }), @@ -289,7 +273,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { filename: path.join(packageDirMonoRepoWithNested, 'foo.js'), options: [{packageDir: packageDirMonoRepoRoot}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it", }], }), @@ -298,7 +281,6 @@ ruleTester.run('no-extraneous-dependencies', rule, { filename: path.join(packageDirWithEmpty, 'index.js'), options: [{packageDir: packageDirWithEmpty}], errors: [{ - ruleId: 'no-extraneous-dependencies', message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it", }], }), @@ -319,14 +301,12 @@ ruleTester.run('no-extraneous-dependencies', rule, { test({ code: 'export { foo } from "not-a-dependency";', errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', }], }), test({ code: 'export * from "not-a-dependency";', errors: [{ - ruleId: 'no-extraneous-dependencies', message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', }], }), diff --git a/tests/src/rules/no-internal-modules.js b/tests/src/rules/no-internal-modules.js index 8ed1c623e..5058fcb34 100644 --- a/tests/src/rules/no-internal-modules.js +++ b/tests/src/rules/no-internal-modules.js @@ -7,6 +7,7 @@ const ruleTester = new RuleTester() ruleTester.run('no-internal-modules', rule, { valid: [ + // imports test({ code: 'import a from "./plugin2"', filename: testFilePath('./internal-modules/plugins/plugin.js'), @@ -57,9 +58,44 @@ ruleTester.run('no-internal-modules', rule, { allow: [ '**/index{.js,}' ], } ], }), + // exports + test({ + code: 'export {a} from "./internal.js"', + filename: testFilePath('./internal-modules/plugins/plugin2/index.js'), + }), + test({ + code: 'export * from "lodash.get"', + filename: testFilePath('./internal-modules/plugins/plugin2/index.js'), + }), + test({ + code: 'export {b} from "@org/package"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + }), + test({ + code: 'export {b} from "../../api/service"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + allow: [ '**/api/*' ], + } ], + }), + test({ + code: 'export * from "jquery/dist/jquery"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + allow: [ 'jquery/dist/*' ], + } ], + }), + test({ + code: 'export * from "./app/index.js";\nexport * from "./app/index"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + allow: [ '**/index{.js,}' ], + } ], + }), ], invalid: [ + // imports test({ code: 'import "./plugin2/index.js";\nimport "./plugin2/app/index"', filename: testFilePath('./internal-modules/plugins/plugin.js'), @@ -126,5 +162,72 @@ ruleTester.run('no-internal-modules', rule, { }, ], }), + // exports + test({ + code: 'export * from "./plugin2/index.js";\nexport * from "./plugin2/app/index"', + filename: testFilePath('./internal-modules/plugins/plugin.js'), + options: [ { + allow: [ '*/index.js' ], + } ], + errors: [ { + message: 'Reaching to "./plugin2/app/index" is not allowed.', + line: 2, + column: 15, + } ], + }), + test({ + code: 'export * from "./app/index.js"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + errors: [ { + message: 'Reaching to "./app/index.js" is not allowed.', + line: 1, + column: 15, + } ], + }), + test({ + code: 'export {b} from "./plugin2/internal"', + filename: testFilePath('./internal-modules/plugins/plugin.js'), + errors: [ { + message: 'Reaching to "./plugin2/internal" is not allowed.', + line: 1, + column: 17, + } ], + }), + test({ + code: 'export {a} from "../api/service/index"', + filename: testFilePath('./internal-modules/plugins/plugin.js'), + options: [ { + allow: [ '**/internal-modules/*' ], + } ], + errors: [ + { + message: 'Reaching to "../api/service/index" is not allowed.', + line: 1, + column: 17, + }, + ], + }), + test({ + code: 'export {b} from "@org/package/internal"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + errors: [ + { + message: 'Reaching to "@org/package/internal" is not allowed.', + line: 1, + column: 17, + }, + ], + }), + test({ + code: 'export {get} from "debug/node"', + filename: testFilePath('./internal-modules/plugins/plugin.js'), + errors: [ + { + message: 'Reaching to "debug/node" is not allowed.', + line: 1, + column: 19, + }, + ], + }), ], }) diff --git a/tests/src/rules/no-named-export.js b/tests/src/rules/no-named-export.js index c4ef9c9c7..bde92b9e4 100644 --- a/tests/src/rules/no-named-export.js +++ b/tests/src/rules/no-named-export.js @@ -35,10 +35,10 @@ ruleTester.run('no-named-export', rule, { export const bar = 'bar'; `, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }, { - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), @@ -47,7 +47,7 @@ ruleTester.run('no-named-export', rule, { export const foo = 'foo'; export default bar;`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), @@ -57,17 +57,17 @@ ruleTester.run('no-named-export', rule, { export function bar() {}; `, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }, { - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), test({ code: `export const foo = 'foo';`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), @@ -77,35 +77,35 @@ ruleTester.run('no-named-export', rule, { export { foo }; `, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), test({ code: `let foo, bar; export { foo, bar }`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), test({ code: `export const { foo, bar } = item;`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), test({ code: `export const { foo, bar: baz } = item;`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), test({ code: `export const { foo: { bar, baz } } = item;`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), @@ -116,31 +116,31 @@ ruleTester.run('no-named-export', rule, { export { item }; `, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }, { - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), test({ code: `export * from './foo';`, errors: [{ - ruleId: 'ExportAllDeclaration', + type: 'ExportAllDeclaration', message: 'Named exports are not allowed.', }], }), test({ code: `export const { foo } = { foo: "bar" };`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), test({ code: `export const { foo: { bar } } = { foo: { bar: "baz" } };`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), @@ -148,7 +148,7 @@ ruleTester.run('no-named-export', rule, { code: 'export { a, b } from "foo.js"', parser: require.resolve('babel-eslint'), errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), @@ -156,7 +156,7 @@ ruleTester.run('no-named-export', rule, { code: `export type UserId = number;`, parser: require.resolve('babel-eslint'), errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), @@ -164,7 +164,7 @@ ruleTester.run('no-named-export', rule, { code: 'export foo from "foo.js"', parser: require.resolve('babel-eslint'), errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), @@ -172,7 +172,7 @@ ruleTester.run('no-named-export', rule, { code: `export Memory, { MemoryValue } from './Memory'`, parser: require.resolve('babel-eslint'), errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Named exports are not allowed.', }], }), diff --git a/tests/src/rules/no-nodejs-modules.js b/tests/src/rules/no-nodejs-modules.js index b5e55fafc..4be050c63 100644 --- a/tests/src/rules/no-nodejs-modules.js +++ b/tests/src/rules/no-nodejs-modules.js @@ -6,7 +6,6 @@ const ruleTester = new RuleTester() , rule = require('rules/no-nodejs-modules') const error = message => ({ - ruleId: 'no-nodejs-modules', message, }) diff --git a/tests/src/rules/no-restricted-paths.js b/tests/src/rules/no-restricted-paths.js index 1c3edb3da..bd5ab2931 100644 --- a/tests/src/rules/no-restricted-paths.js +++ b/tests/src/rules/no-restricted-paths.js @@ -145,6 +145,23 @@ ruleTester.run('no-restricted-paths', rule, { column: 15, } ], }), + test({ + code: 'import b from "../two/a.js"', + filename: testFilePath('./restricted-paths/server/one/a.js'), + options: [ { + zones: [ { + target: './tests/files/restricted-paths/server/one', + from: './tests/files/restricted-paths/server', + except: ['./one'], + message: 'Custom message', + } ], + } ], + errors: [ { + message: 'Unexpected path "../two/a.js" imported in restricted zone. Custom message', + line: 1, + column: 15, + } ], + }), test({ code: 'import b from "../two/a.js"', filename: testFilePath('./restricted-paths/server/one/a.js'), diff --git a/tests/src/rules/no-self-import.js b/tests/src/rules/no-self-import.js index f8549b49e..281d67107 100644 --- a/tests/src/rules/no-self-import.js +++ b/tests/src/rules/no-self-import.js @@ -6,7 +6,6 @@ const ruleTester = new RuleTester() , rule = require('rules/no-self-import') const error = { - ruleId: 'no-self-import', message: 'Module imports itself.', } diff --git a/tests/src/rules/no-unassigned-import.js b/tests/src/rules/no-unassigned-import.js index 414bfca90..d4fca8f45 100644 --- a/tests/src/rules/no-unassigned-import.js +++ b/tests/src/rules/no-unassigned-import.js @@ -7,7 +7,6 @@ const ruleTester = new RuleTester() , rule = require('rules/no-unassigned-import') const error = { - ruleId: 'no-unassigned-import', message: 'Imported module should be assigned', } diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index ac15fd915..ef2d3e66c 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -10,7 +10,7 @@ const ruleTester = new RuleTester() , jsxRuleTester = new RuleTester(jsxConfig) , rule = require('rules/no-unused-modules') -const error = message => ({ ruleId: 'no-unused-modules', message }) +const error = message => ({ message }) const missingExportsOptions = [{ missingExports: true, @@ -52,6 +52,10 @@ ruleTester.run('no-unused-modules', rule, { 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();'}), ], invalid: [ test({ @@ -427,6 +431,42 @@ ruleTester.run('no-unused-modules', rule, { ], }) +// Test that import and export in the same file both counts as usage +ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `export const a = 5;export const b = 't1'`, + filename: testFilePath('./no-unused-modules/import-export-1.js'), + }), + ], + invalid: [], +}) + +describe('renameDefault', () => { + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: 'export { default as Component } from "./Component"', + filename: testFilePath('./no-unused-modules/renameDefault/components.js')}), + test({ options: unusedExportsOptions, + code: 'export default function Component() {}', + filename: testFilePath('./no-unused-modules/renameDefault/Component.js')}), + ], + invalid: [], + }) + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: 'export { default as ComponentA } from "./ComponentA";export { default as ComponentB } from "./ComponentB";', + filename: testFilePath('./no-unused-modules/renameDefault-2/components.js')}), + test({ options: unusedExportsOptions, + code: 'export default function ComponentA() {};', + filename: testFilePath('./no-unused-modules/renameDefault-2/ComponentA.js')}), + ], + invalid: [], + }) +}) + describe('test behaviour for new file', () => { before(() => { fs.writeFileSync(testFilePath('./no-unused-modules/file-added-0.js'), '', {encoding: 'utf8'}) @@ -665,38 +705,6 @@ describe('do not report unused export for files mentioned in package.json', () = }) }) -describe('correctly report flow types', () => { - ruleTester.run('no-unused-modules', rule, { - valid: [ - test({ - options: unusedExportsOptions, - code: 'import { type FooType } from "./flow-2";', - parser: require.resolve('babel-eslint'), - filename: testFilePath('./no-unused-modules/flow-0.js'), - }), - test({ - options: unusedExportsOptions, - code: `// @flow strict - export type FooType = string;`, - parser: require.resolve('babel-eslint'), - filename: testFilePath('./no-unused-modules/flow-2.js'), - }), - ], - invalid: [ - test({ - options: unusedExportsOptions, - code: `// @flow strict - export type Bar = string;`, - parser: require.resolve('babel-eslint'), - filename: testFilePath('./no-unused-modules/flow-1.js'), - errors: [ - error(`exported declaration 'Bar' not used within other modules`), - ], - }), - ], - }) -}) - describe('Avoid errors if re-export all from umd compiled library', () => { ruleTester.run('no-unused-modules', rule, { valid: [ diff --git a/tests/src/rules/no-useless-path-segments.js b/tests/src/rules/no-useless-path-segments.js index 366e75354..2a864d0b6 100644 --- a/tests/src/rules/no-useless-path-segments.js +++ b/tests/src/rules/no-useless-path-segments.js @@ -40,41 +40,49 @@ function runResolverTests(resolver) { // CommonJS modules test({ code: 'require("./../files/malformed.js")', + output: 'require("../files/malformed.js")', options: [{ commonjs: true }], errors: [ 'Useless path segments for "./../files/malformed.js", should be "../files/malformed.js"'], }), test({ code: 'require("./../files/malformed")', + output: 'require("../files/malformed")', options: [{ commonjs: true }], errors: [ 'Useless path segments for "./../files/malformed", should be "../files/malformed"'], }), test({ code: 'require("../files/malformed.js")', + output: 'require("./malformed.js")', options: [{ commonjs: true }], errors: [ 'Useless path segments for "../files/malformed.js", should be "./malformed.js"'], }), test({ code: 'require("../files/malformed")', + output: 'require("./malformed")', options: [{ commonjs: true }], errors: [ 'Useless path segments for "../files/malformed", should be "./malformed"'], }), test({ code: 'require("./test-module/")', + output: 'require("./test-module")', options: [{ commonjs: true }], errors: [ 'Useless path segments for "./test-module/", should be "./test-module"'], }), test({ code: 'require("./")', + output: 'require(".")', options: [{ commonjs: true }], errors: [ 'Useless path segments for "./", should be "."'], }), test({ code: 'require("../")', + output: 'require("..")', options: [{ commonjs: true }], errors: [ 'Useless path segments for "../", should be ".."'], }), test({ code: 'require("./deep//a")', + output: 'require("./deep/a")', options: [{ commonjs: true }], errors: [ 'Useless path segments for "./deep//a", should be "./deep/a"'], }), @@ -82,41 +90,49 @@ function runResolverTests(resolver) { // CommonJS modules + noUselessIndex test({ code: 'require("./bar/index.js")', + output: 'require("./bar/")', options: [{ commonjs: true, noUselessIndex: true }], errors: ['Useless path segments for "./bar/index.js", should be "./bar/"'], // ./bar.js exists }), test({ code: 'require("./bar/index")', + output: 'require("./bar/")', options: [{ commonjs: true, noUselessIndex: true }], errors: ['Useless path segments for "./bar/index", should be "./bar/"'], // ./bar.js exists }), test({ code: 'require("./importPath/")', + output: 'require("./importPath")', options: [{ commonjs: true, noUselessIndex: true }], errors: ['Useless path segments for "./importPath/", should be "./importPath"'], // ./importPath.js does not exist }), test({ code: 'require("./importPath/index.js")', + output: 'require("./importPath")', options: [{ commonjs: true, noUselessIndex: true }], errors: ['Useless path segments for "./importPath/index.js", should be "./importPath"'], // ./importPath.js does not exist }), test({ code: 'require("./importType/index")', + output: 'require("./importType")', options: [{ commonjs: true, noUselessIndex: true }], errors: ['Useless path segments for "./importType/index", should be "./importType"'], // ./importPath.js does not exist }), test({ code: 'require("./index")', + output: 'require(".")', options: [{ commonjs: true, noUselessIndex: true }], errors: ['Useless path segments for "./index", should be "."'], }), test({ code: 'require("../index")', + output: 'require("..")', options: [{ commonjs: true, noUselessIndex: true }], errors: ['Useless path segments for "../index", should be ".."'], }), test({ code: 'require("../index.js")', + output: 'require("..")', options: [{ commonjs: true, noUselessIndex: true }], errors: ['Useless path segments for "../index.js", should be ".."'], }), @@ -124,90 +140,109 @@ function runResolverTests(resolver) { // ES modules test({ code: 'import "./../files/malformed.js"', + output: 'import "../files/malformed.js"', errors: [ 'Useless path segments for "./../files/malformed.js", should be "../files/malformed.js"'], }), test({ code: 'import "./../files/malformed"', + output: 'import "../files/malformed"', errors: [ 'Useless path segments for "./../files/malformed", should be "../files/malformed"'], }), test({ code: 'import "../files/malformed.js"', + output: 'import "./malformed.js"', errors: [ 'Useless path segments for "../files/malformed.js", should be "./malformed.js"'], }), test({ code: 'import "../files/malformed"', + output: 'import "./malformed"', errors: [ 'Useless path segments for "../files/malformed", should be "./malformed"'], }), test({ code: 'import "./test-module/"', + output: 'import "./test-module"', errors: [ 'Useless path segments for "./test-module/", should be "./test-module"'], }), test({ code: 'import "./"', + output: 'import "."', errors: [ 'Useless path segments for "./", should be "."'], }), test({ code: 'import "../"', + output: 'import ".."', errors: [ 'Useless path segments for "../", should be ".."'], }), test({ code: 'import "./deep//a"', + output: 'import "./deep/a"', errors: [ 'Useless path segments for "./deep//a", should be "./deep/a"'], }), // ES modules + noUselessIndex test({ code: 'import "./bar/index.js"', + output: 'import "./bar/"', options: [{ noUselessIndex: true }], errors: ['Useless path segments for "./bar/index.js", should be "./bar/"'], // ./bar.js exists }), test({ code: 'import "./bar/index"', + output: 'import "./bar/"', options: [{ noUselessIndex: true }], errors: ['Useless path segments for "./bar/index", should be "./bar/"'], // ./bar.js exists }), test({ code: 'import "./importPath/"', + output: 'import "./importPath"', options: [{ noUselessIndex: true }], errors: ['Useless path segments for "./importPath/", should be "./importPath"'], // ./importPath.js does not exist }), test({ code: 'import "./importPath/index.js"', + output: 'import "./importPath"', options: [{ noUselessIndex: true }], errors: ['Useless path segments for "./importPath/index.js", should be "./importPath"'], // ./importPath.js does not exist }), test({ code: 'import "./importPath/index"', + output: 'import "./importPath"', options: [{ noUselessIndex: true }], errors: ['Useless path segments for "./importPath/index", should be "./importPath"'], // ./importPath.js does not exist }), test({ code: 'import "./index"', + output: 'import "."', options: [{ noUselessIndex: true }], errors: ['Useless path segments for "./index", should be "."'], }), test({ code: 'import "../index"', + output: 'import ".."', options: [{ noUselessIndex: true }], errors: ['Useless path segments for "../index", should be ".."'], }), test({ code: 'import "../index.js"', + output: 'import ".."', options: [{ noUselessIndex: true }], errors: ['Useless path segments for "../index.js", should be ".."'], }), test({ code: 'import("./")', + output: 'import(".")', errors: [ 'Useless path segments for "./", should be "."'], parser: require.resolve('babel-eslint'), }), test({ code: 'import("../")', + output: 'import("..")', errors: [ 'Useless path segments for "../", should be ".."'], parser: require.resolve('babel-eslint'), }), test({ code: 'import("./deep//a")', + output: 'import("./deep/a")', errors: [ 'Useless path segments for "./deep//a", should be "./deep/a"'], parser: require.resolve('babel-eslint'), }), diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index c9fd4fa7a..f6e2dddba 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1,6 +1,8 @@ -import { test, getTSParsers } from '../utils' +import { test, getTSParsers, getNonDefaultParsers } from '../utils' import { RuleTester } from 'eslint' +import eslintPkg from 'eslint/package.json' +import semver from 'semver' const ruleTester = new RuleTester() , rule = require('rules/order') @@ -165,6 +167,22 @@ ruleTester.run('order', rule, { var index = require('./'); `, }), + // Export equals expressions should be on top alongside with ordinary import-statements. + ...getTSParsers().map(parser => ( + test({ + code: ` + import async, {foo1} from 'async'; + import relParent2, {foo2} from '../foo/bar'; + import sibling, {foo3} from './foo'; + var fs = require('fs'); + var util = require("util"); + var relParent1 = require('../foo'); + var relParent3 = require('../'); + var index = require('./'); + `, + parser, + }) + )), // Adding unknown import types (e.g. using a resolver alias via babel) to the groups. test({ code: ` @@ -305,6 +323,23 @@ ruleTester.run('order', rule, { ], }], }), + // Using pathGroups (a test case for https://github.com/benmosher/eslint-plugin-import/pull/1724) + test({ + code: ` + import fs from 'fs'; + import external from 'external'; + import externalTooPlease from './make-me-external'; + + import sibling from './sibling';`, + options: [{ + 'newlines-between': 'always', + pathGroupsExcludedImportTypes: [], + pathGroups: [ + { pattern: './make-me-external', group: 'external' }, + ], + groups: [['builtin', 'external'], 'internal', 'parent', 'sibling', 'index'], + }], + }), // Monorepo setup, using Webpack resolver, workspace folder name in external-module-folders test({ code: ` @@ -681,7 +716,6 @@ ruleTester.run('order', rule, { var async = require('async'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -696,7 +730,6 @@ ruleTester.run('order', rule, { var async = require('async'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -711,7 +744,6 @@ ruleTester.run('order', rule, { var async = require('async'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -726,7 +758,6 @@ ruleTester.run('order', rule, { /* comment1 */ var async = require('async'); /* comment2 */ `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -741,7 +772,6 @@ ruleTester.run('order', rule, { /* comment0 */ /* comment1 */ var async = require('async'); /* comment2 */ `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -756,7 +786,6 @@ ruleTester.run('order', rule, { `/* comment0 */ /* comment1 */ var async = require('async'); /* comment2 */` + `\r\n` , errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -776,7 +805,6 @@ ruleTester.run('order', rule, { comment3 */ `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -791,7 +819,6 @@ ruleTester.run('order', rule, { var {b} = require('async'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -808,7 +835,6 @@ ruleTester.run('order', rule, { var async = require('async'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -821,7 +847,6 @@ ruleTester.run('order', rule, { var fs = require('fs'); var async = require('async');` + '\n', errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -836,7 +861,6 @@ ruleTester.run('order', rule, { import async from 'async'; `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -851,7 +875,6 @@ ruleTester.run('order', rule, { var async = require('async'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], }), @@ -866,7 +889,6 @@ ruleTester.run('order', rule, { var parent = require('../parent'); `, errors: [{ - ruleId: 'order', message: '`async` import should occur before import of `../parent`', }], }), @@ -881,7 +903,6 @@ ruleTester.run('order', rule, { var sibling = require('./sibling'); `, errors: [{ - ruleId: 'order', message: '`../parent` import should occur before import of `./sibling`', }], }), @@ -896,25 +917,29 @@ ruleTester.run('order', rule, { var index = require('./'); `, errors: [{ - ruleId: 'order', message: '`./sibling` import should occur before import of `./`', }], }), // Multiple errors - test({ - code: ` - var sibling = require('./sibling'); - var async = require('async'); - var fs = require('fs'); - `, - errors: [{ - ruleId: 'order', - message: '`async` import should occur before import of `./sibling`', - }, { - ruleId: 'order', - message: '`fs` import should occur before import of `./sibling`', - }], - }), + ...semver.satisfies(eslintPkg.version, '< 3.0.0') ? [] : [ + test({ + code: ` + var sibling = require('./sibling'); + var async = require('async'); + var fs = require('fs'); + `, + output: ` + var async = require('async'); + var sibling = require('./sibling'); + var fs = require('fs'); + `, + errors: [{ + message: '`async` import should occur before import of `./sibling`', + }, { + message: '`fs` import should occur before import of `./sibling`', + }], + }), + ], // Uses 'after' wording if it creates less errors test({ code: ` @@ -934,7 +959,6 @@ ruleTester.run('order', rule, { var index = require('./'); `, errors: [{ - ruleId: 'order', message: '`./` import should occur after import of `bar`', }], }), @@ -950,7 +974,6 @@ ruleTester.run('order', rule, { `, options: [{groups: ['index', 'sibling', 'parent', 'external', 'builtin']}], errors: [{ - ruleId: 'order', message: '`./` import should occur before import of `fs`', }], }), @@ -961,7 +984,6 @@ ruleTester.run('order', rule, { var fs = require('fs'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `./foo`', }], })), @@ -972,7 +994,6 @@ ruleTester.run('order', rule, { var fs = require('fs'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `./foo`', }], })), @@ -985,7 +1006,6 @@ ruleTester.run('order', rule, { var fs = require('fs'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `./foo`', }], })), @@ -998,7 +1018,6 @@ ruleTester.run('order', rule, { .bar; `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `./foo`', }], })), @@ -1021,7 +1040,6 @@ ruleTester.run('order', rule, { ['sibling', 'parent', 'external'], ]}], errors: [{ - ruleId: 'order', message: '`path` import should occur before import of `./foo`', }], }), @@ -1041,7 +1059,6 @@ ruleTester.run('order', rule, { // missing 'builtin' ]}], errors: [{ - ruleId: 'order', message: '`async` import should occur before import of `path`', }], }), @@ -1057,7 +1074,6 @@ ruleTester.run('order', rule, { ['sibling', 'parent', 'UNKNOWN', 'internal'], ]}], errors: [{ - ruleId: 'order', message: 'Incorrect configuration of the rule: Unknown type `"UNKNOWN"`', }], }), @@ -1072,7 +1088,6 @@ ruleTester.run('order', rule, { ['sibling', 'parent', ['builtin'], 'internal'], ]}], errors: [{ - ruleId: 'order', message: 'Incorrect configuration of the rule: Unknown type `["builtin"]`', }], }), @@ -1087,7 +1102,6 @@ ruleTester.run('order', rule, { ['sibling', 'parent', 2, 'internal'], ]}], errors: [{ - ruleId: 'order', message: 'Incorrect configuration of the rule: Unknown type `2`', }], }), @@ -1102,7 +1116,6 @@ ruleTester.run('order', rule, { ['sibling', 'parent', 'parent', 'internal'], ]}], errors: [{ - ruleId: 'order', message: 'Incorrect configuration of the rule: `parent` is duplicated', }], }), @@ -1127,7 +1140,6 @@ ruleTester.run('order', rule, { var index = require('./'); `, errors: [{ - ruleId: 'order', message: '`./foo` import should occur before import of `fs`', }], }), @@ -1143,10 +1155,27 @@ ruleTester.run('order', rule, { var fs = require('fs'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur after import of `../foo/bar`', }], }), + ...getTSParsers().map(parser => ( + test({ + code: ` + var fs = require('fs'); + import async, {foo1} from 'async'; + import bar = require("../foo/bar"); + `, + output: ` + import async, {foo1} from 'async'; + import bar = require("../foo/bar"); + var fs = require('fs'); + `, + parser, + errors: [{ + message: '`fs` import should occur after import of `../foo/bar`', + }], + }) + )), // Default order using import with custom import alias test({ code: ` @@ -1510,7 +1539,6 @@ ruleTester.run('order', rule, { fn_call(); `, errors: [{ - ruleId: 'order', message: '`./local` import should occur after import of `global2`', }], }), @@ -1533,7 +1561,6 @@ ruleTester.run('order', rule, { fn_call(); `, errors: [{ - ruleId: 'order', message: '`./local` import should occur after import of `global2`', }], }), @@ -1584,7 +1611,6 @@ ruleTester.run('order', rule, { fn_call(); `, errors: [{ - ruleId: 'order', message: '`./local` import should occur after import of `global3`', }], })), @@ -1639,7 +1665,6 @@ ruleTester.run('order', rule, { ], }], errors: [{ - ruleId: 'order', message: '`~/components/Input` import should occur before import of `./helper`', }], }), @@ -1665,7 +1690,6 @@ ruleTester.run('order', rule, { ], }], errors: [{ - ruleId: 'order', message: '`./helper` import should occur after import of `async`', }], }), @@ -1689,7 +1713,6 @@ ruleTester.run('order', rule, { ], }], errors: [{ - ruleId: 'order', message: '`~/components/Input` import should occur before import of `lodash`', }], }), @@ -1723,7 +1746,6 @@ ruleTester.run('order', rule, { }], errors: [ { - ruleId: 'order', message: '`-/components/Export` import should occur before import of `$/components/Import`', }, ], @@ -1759,7 +1781,6 @@ ruleTester.run('order', rule, { }], errors: [ { - ruleId: 'order', message: '`~/components/Output` import should occur before import of `#/components/Input`', }, ], @@ -1773,7 +1794,6 @@ ruleTester.run('order', rule, { var fs = require('fs'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], })), @@ -1800,7 +1820,6 @@ ruleTester.run('order', rule, { http.createServer(express()); `, errors: [{ - ruleId: 'order', message: '`./config` import should occur after import of `express`', }], }), @@ -1812,7 +1831,6 @@ ruleTester.run('order', rule, { var fs = require('fs'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], })), @@ -1823,7 +1841,6 @@ ruleTester.run('order', rule, { var fs = require('fs')(a); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], })), @@ -1834,7 +1851,6 @@ ruleTester.run('order', rule, { var fs = require('fs'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], })), @@ -1846,7 +1862,6 @@ ruleTester.run('order', rule, { var fs = require('fs'); `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], })), @@ -1858,7 +1873,6 @@ ruleTester.run('order', rule, { import fs from 'fs'; `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], })), @@ -1870,7 +1884,6 @@ ruleTester.run('order', rule, { import fs from 'fs'; `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], })), @@ -1882,7 +1895,6 @@ ruleTester.run('order', rule, { import fs from 'fs'; `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], })), @@ -1894,7 +1906,6 @@ ruleTester.run('order', rule, { import fs from 'fs'; `, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], })), @@ -1909,7 +1920,6 @@ ruleTester.run('order', rule, { `, parser, errors: [{ - ruleId: 'order', message: '`fs` import should occur before import of `async`', }], })), @@ -1934,10 +1944,33 @@ ruleTester.run('order', rule, { alphabetize: {order: 'asc'}, }], errors: [{ - ruleID: 'order', 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: ` @@ -1959,7 +1992,6 @@ ruleTester.run('order', rule, { alphabetize: {order: 'desc'}, }], errors: [{ - ruleID: 'order', message: '`bar` import should occur before import of `Bar`', }], }), @@ -1982,7 +2014,6 @@ ruleTester.run('order', rule, { alphabetize: {order: 'asc', caseInsensitive: true}, }], errors: [{ - ruleID: 'order', message: '`Bar` import should occur before import of `foo`', }], }), @@ -2005,7 +2036,6 @@ ruleTester.run('order', rule, { alphabetize: {order: 'desc', caseInsensitive: true}, }], errors: [{ - ruleID: 'order', message: '`foo` import should occur before import of `Bar`', }], }), @@ -2024,25 +2054,170 @@ ruleTester.run('order', rule, { alphabetize: {order: 'asc'}, }], errors: [{ - ruleID: 'order', message: '`..` import should occur before import of `../a`', }], }), // Alphabetize with require - test({ - code: ` - const { cello } = require('./cello'); - import { int } from './int'; - const blah = require('./blah'); - import { hello } from './hello'; - `, - errors: [{ - ruleId: 'order', - message: '`./int` import should occur before import of `./cello`', - }, { - ruleId: 'order', - message: '`./hello` import should occur before import of `./cello`', - }], - }), + ...semver.satisfies(eslintPkg.version, '< 3.0.0') ? [] : [ + test({ + code: ` + const { cello } = require('./cello'); + import { int } from './int'; + const blah = require('./blah'); + import { hello } from './hello'; + `, + output: ` + import { int } from './int'; + const { cello } = require('./cello'); + const blah = require('./blah'); + import { hello } from './hello'; + `, + errors: [{ + message: '`./int` import should occur before import of `./cello`', + }, { + message: '`./hello` import should occur before import of `./cello`', + }], + }), + ], ].filter((t) => !!t), }) + + +context('TypeScript', function () { + getNonDefaultParsers() + .filter((parser) => parser !== require.resolve('typescript-eslint-parser')) + .forEach((parser) => { + const parserConfig = { + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + } + + ruleTester.run('order', rule, { + valid: [ + // #1667: typescript type import support + + // Option alphabetize: {order: 'asc'} + test( + { + code: ` + import c from 'Bar'; + import type { C } from 'Bar'; + import b from 'bar'; + import a from 'foo'; + import type { A } from 'foo'; + + import index from './'; + `, + parser, + options: [ + { + groups: ['external', 'index'], + alphabetize: { order: 'asc' }, + }, + ], + }, + parserConfig, + ), + // Option alphabetize: {order: 'desc'} + test( + { + code: ` + import a from 'foo'; + import type { A } from 'foo'; + import b from 'bar'; + import c from 'Bar'; + import type { C } from 'Bar'; + + import index from './'; + `, + parser, + options: [ + { + groups: ['external', 'index'], + alphabetize: { order: 'desc' }, + }, + ], + }, + parserConfig, + ), + ], + invalid: [ + // Option alphabetize: {order: 'asc'} + test( + { + code: ` + import b from 'bar'; + import c from 'Bar'; + import type { C } from 'Bar'; + import a from 'foo'; + import type { A } from 'foo'; + + import index from './'; + `, + output: ` + import c from 'Bar'; + import type { C } from 'Bar'; + import b from 'bar'; + import a from 'foo'; + import type { A } from 'foo'; + + import index from './'; + `, + parser, + options: [ + { + groups: ['external', 'index'], + alphabetize: { order: 'asc' }, + }, + ], + errors: [ + { + message: process.env.ESLINT_VERSION === '2' ? '`bar` import should occur after import of `Bar`' : /(`bar` import should occur after import of `Bar`)|(`Bar` import should occur before import of `bar`)/, + }, + ], + }, + parserConfig, + ), + // Option alphabetize: {order: 'desc'} + test( + { + code: ` + import a from 'foo'; + import type { A } from 'foo'; + import c from 'Bar'; + import type { C } from 'Bar'; + import b from 'bar'; + + import index from './'; + `, + output: ` + import a from 'foo'; + import type { A } from 'foo'; + import b from 'bar'; + import c from 'Bar'; + import type { C } from 'Bar'; + + import index from './'; + `, + parser, + options: [ + { + groups: ['external', 'index'], + alphabetize: { order: 'desc' }, + }, + ], + errors: [ + { + message: process.env.ESLINT_VERSION === '2' ? '`bar` import should occur before import of `Bar`' : /(`bar` import should occur before import of `Bar`)|(`Bar` import should occur after import of `bar`)/, + }, + ], + }, + parserConfig, + ), + ], + }) + }) +}) diff --git a/tests/src/rules/prefer-default-export.js b/tests/src/rules/prefer-default-export.js index 19aef41e0..9e38cea92 100644 --- a/tests/src/rules/prefer-default-export.js +++ b/tests/src/rules/prefer-default-export.js @@ -94,7 +94,7 @@ ruleTester.run('prefer-default-export', rule, { code: ` export function bar() {};`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Prefer default export.', }], }), @@ -102,7 +102,7 @@ ruleTester.run('prefer-default-export', rule, { code: ` export const foo = 'foo';`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Prefer default export.', }], }), @@ -111,7 +111,7 @@ ruleTester.run('prefer-default-export', rule, { const foo = 'foo'; export { foo };`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportSpecifier', message: 'Prefer default export.', }], }), @@ -119,7 +119,7 @@ ruleTester.run('prefer-default-export', rule, { code: ` export const { foo } = { foo: "bar" };`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Prefer default export.', }], }), @@ -127,7 +127,7 @@ ruleTester.run('prefer-default-export', rule, { code: ` export const { foo: { bar } } = { foo: { bar: "baz" } };`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Prefer default export.', }], }), @@ -135,7 +135,7 @@ ruleTester.run('prefer-default-export', rule, { code: ` export const [a] = ["foo"]`, errors: [{ - ruleId: 'ExportNamedDeclaration', + type: 'ExportNamedDeclaration', message: 'Prefer default export.', }], }), diff --git a/utils/resolve.js b/utils/resolve.js index 3138194a9..fc8f85de9 100644 --- a/utils/resolve.js +++ b/utils/resolve.js @@ -10,7 +10,7 @@ const path = require('path') const hashObject = require('./hash').hashObject , ModuleCache = require('./ModuleCache').default -const CASE_SENSITIVE_FS = !fs.existsSync(path.join(__dirname, 'reSOLVE.js')) +const CASE_SENSITIVE_FS = !fs.existsSync(path.join(__dirname.toUpperCase(), 'reSOLVE.js')) exports.CASE_SENSITIVE_FS = CASE_SENSITIVE_FS const ERROR_NAME = 'EslintPluginImportResolveError'