diff --git a/.babelrc b/.babelrc index 2cbf5c811c..604f307fee 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,7 @@ { "presets": [ "es2015-argon" ], "sourceMaps": "inline", + "retainLines": true, "env": { "test": { "plugins": [ diff --git a/.coveralls.yml b/.coveralls.yml index 77bcfb3743..b8ebe05a18 100644 --- a/.coveralls.yml +++ b/.coveralls.yml @@ -1,2 +1,2 @@ --- -repo_token: fW3moW39Z8pKOgqTnUMT68DnNCd2SM8Ly \ No newline at end of file +repo_token: fW3moW39Z8pKOgqTnUMT68DnNCd2SM8Ly diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..356666af5d --- /dev/null +++ b/.eslintrc @@ -0,0 +1,85 @@ +{ + "root": true, + "plugins": [ + "eslint-plugin", + "import", + ], + "extends": [ + "eslint:recommended", + "plugin:eslint-plugin/recommended", + "plugin:import/recommended", + ], + "env": { + "node": true, + "es6": true, + }, + "parserOptions": { + "sourceType": "module", + "ecmaVersion": 6, + }, + "rules": { + "comma-dangle": [2, "always-multiline"], + "comma-style": [2, "last"], + "curly": [2, "multi-line"], + "eol-last": [2, "always"], + "eqeqeq": [2, "allow-null"], + "func-call-spacing": 2, + "indent": [2, 2], + "max-len": [1, 99, 2], + "no-cond-assign": [2, "always"], + "no-return-assign": [2, "always"], + "no-shadow": 1, + "no-var": 2, + "object-curly-spacing": [2, "always"], + "one-var": [2, "never"], + "prefer-const": 2, + "quotes": [2, "single", { + "allowTemplateLiterals": true, + "avoidEscape": true, + }], + "semi": [2, "always"], + "eslint-plugin/consistent-output": [ + "error", + "always", + ], + "eslint-plugin/meta-property-ordering": "error", + "eslint-plugin/no-deprecated-context-methods": "error", + "eslint-plugin/no-deprecated-report-api": "off", + "eslint-plugin/prefer-replace-text": "error", + "eslint-plugin/report-message-format": "error", + "eslint-plugin/require-meta-schema": "error", + "eslint-plugin/require-meta-type": "error", + + // dog fooding + "import/no-extraneous-dependencies": "error", + "import/unambiguous": "off", + }, + + "settings": { + "import/resolver": { + "node": { + "paths": [ + "src", + ], + }, + }, + }, + + "overrides": [ + { + "files": "scripts/**", + "rules": { + "no-console": "off", + }, + }, + { + "files": [ + "resolvers/*/test/**/*", + ], + "env": { + "mocha": true, + "es6": false + }, + } + ], +} diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index 8c270e9533..0000000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,47 +0,0 @@ ---- -plugins: - - eslint-plugin - - import -extends: - - eslint:recommended - - plugin:eslint-plugin/recommended - - plugin:import/recommended - -env: - node: true - es6: true - -parserOptions: - sourceType: module - ecmaVersion: 6 - -rules: - max-len: [1, 99, 2] - semi: [2, "never"] - curly: [2, "multi-line"] - comma-dangle: [2, always-multiline] - eqeqeq: [2, "allow-null"] - no-shadow: 1 - quotes: - - 2 - - single - - allowTemplateLiterals: true - avoidEscape: true - - eslint-plugin/consistent-output: ["error", "always"] - eslint-plugin/meta-property-ordering: "error" - eslint-plugin/no-deprecated-context-methods: "error" - eslint-plugin/no-deprecated-report-api: "off" - eslint-plugin/prefer-replace-text: "error" - eslint-plugin/report-message-format: "error" - eslint-plugin/require-meta-schema: "error" - eslint-plugin/require-meta-type: "error" - - # dog fooding - import/no-extraneous-dependencies: "error" - import/unambiguous: "off" - -settings: - import/resolver: - node: - paths: [ src ] diff --git a/.github/workflows/node-4+.yml b/.github/workflows/node-4+.yml new file mode 100644 index 0000000000..02c11a0708 --- /dev/null +++ b/.github/workflows/node-4+.yml @@ -0,0 +1,87 @@ +name: 'Tests: node.js' + +on: [pull_request, push] + +jobs: + matrix: + runs-on: ubuntu-latest + outputs: + latest: ${{ steps.set-matrix.outputs.requireds }} + minors: ${{ steps.set-matrix.outputs.optionals }} + steps: + - uses: ljharb/actions/node/matrix@main + id: set-matrix + with: + versionsAsRoot: true + type: majors + preset: '>= 6' # preset: '>=4' # see https://github.com/benmosher/eslint-plugin-import/issues/2053 + + latest: + needs: [matrix] + name: 'latest majors' + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + node-version: ${{ fromJson(needs.matrix.outputs.latest) }} + eslint: + - 7 + - 6 + - 5 + - 4 + - 3 + - 2 + include: + - node-version: 'lts/*' + eslint: 7 + ts-parser: 2 + env: + TS_PARSER: 2 + exclude: + - node-version: 9 + eslint: 7 + - node-version: 8 + eslint: 7 + - node-version: 7 + eslint: 7 + - node-version: 7 + eslint: 6 + - node-version: 6 + eslint: 7 + - node-version: 6 + eslint: 6 + - node-version: 5 + eslint: 7 + - node-version: 5 + eslint: 6 + - node-version: 5 + eslint: 5 + - node-version: 4 + eslint: 7 + - node-version: 4 + eslint: 6 + - node-version: 4 + eslint: 5 + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/actions/node/install@main + continue-on-error: ${{ matrix.eslint == 4 && matrix.node-version == 4 }} + name: 'nvm install ${{ matrix.node-version }} && npm install, with eslint ${{ matrix.eslint }}' + env: + ESLINT_VERSION: ${{ matrix.eslint }} + TRAVIS_NODE_VERSION: ${{ matrix.node-version }} + with: + node-version: ${{ matrix.node-version }} + after_install: npm run copy-metafiles && ./tests/dep-time-travel.sh + skip-ls-check: true + - run: npm run tests-only + - run: npm run coveralls + + node: + name: 'node 4+' + needs: [latest] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml new file mode 100644 index 0000000000..cea20ec385 --- /dev/null +++ b/.github/workflows/node-pretest.yml @@ -0,0 +1,28 @@ +name: 'Tests: pretest/posttest' + +on: [pull_request, push] + +jobs: + # pretest: + # runs-on: ubuntu-latest + + # steps: + # - uses: actions/checkout@v2 + # - uses: ljharb/actions/node/install@main + # name: 'nvm install lts/* && npm install' + # with: + # node-version: 'lts/*' + # skip-ls-check: true + # - run: npm run pretest + + posttest: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/actions/node/install@main + name: 'nvm install lts/* && npm install' + with: + node-version: 'lts/*' + skip-ls-check: true + - run: npm run posttest diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml new file mode 100644 index 0000000000..2add24b49c --- /dev/null +++ b/.github/workflows/packages.yml @@ -0,0 +1,52 @@ +name: 'Tests: packages' + +on: [pull_request, push] + +jobs: + matrix: + runs-on: ubuntu-latest + outputs: + latest: ${{ steps.set-matrix.outputs.requireds }} + minors: ${{ steps.set-matrix.outputs.optionals }} + steps: + - uses: ljharb/actions/node/matrix@main + id: set-matrix + with: + type: 'majors' + preset: '>= 6' # preset: '>=4' # see https://github.com/benmosher/eslint-plugin-import/issues/2053 + versionsAsRoot: true + + tests: + needs: [matrix] + name: 'packages' + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + node-version: ${{ fromJson(needs.matrix.outputs.latest) }} + package: + - resolvers/node + - resolvers/webpack + # - memo-parser + # - utils + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/actions/node/install@main + name: 'nvm install ${{ matrix.node-version }} && npm install' + env: + ESLINT_VERSION: ${{ matrix.eslint }} + TRAVIS_NODE_VERSION: ${{ matrix.node-version }} + with: + node-version: ${{ matrix.node-version }} + after_install: npm run copy-metafiles && ./tests/dep-time-travel.sh && cd ${{ matrix.package }} && npm install + skip-ls-check: true + - run: cd ${{ matrix.package }} && npm run tests-only + + packages: + name: 'packages: all tests' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 0000000000..027aed0797 --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,15 @@ +name: Automatic Rebase + +on: [pull_request_target] + +jobs: + _: + name: "Automatic Rebase" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml new file mode 100644 index 0000000000..549d7b4823 --- /dev/null +++ b/.github/workflows/require-allow-edits.yml @@ -0,0 +1,12 @@ +name: Require “Allow Edits” + +on: [pull_request_target] + +jobs: + _: + name: "Require “Allow Edits”" + + runs-on: ubuntu-latest + + steps: + - uses: ljharb/require-allow-edits@main diff --git a/.gitignore b/.gitignore index a01de720c3..e1114fe9ac 100644 --- a/.gitignore +++ b/.gitignore @@ -25,9 +25,13 @@ resolvers/node/LICENSE resolvers/webpack/LICENSE utils/LICENSE memo-parser/.npmrc +memo-parser/.nycrc resolvers/node/.npmrc +resolvers/node/.nycrc resolvers/webpack/.npmrc +resolvers/webpack/.nycrc utils/.npmrc +utils/.nycrc # Dependency directory # Commenting this out is preferred by some people, see @@ -49,3 +53,6 @@ lib/ yarn.lock package-lock.json npm-shrinkwrap.json + +# macOS +.DS_Store diff --git a/.nycrc b/.nycrc new file mode 100644 index 0000000000..8147f38718 --- /dev/null +++ b/.nycrc @@ -0,0 +1,15 @@ +{ + "all": true, + "check-coverage": false, + "reporter": ["text-summary", "text", "html", "json"], + "require": [ + "babel-register" + ], + "sourceMap": true, + "instrument": false, + "exclude": [ + "coverage", + "test", + "tests" + ] +} diff --git a/.travis.yml b/.travis.yml index fda8f0a5a6..583a411972 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,53 +1,8 @@ language: node_js -node_js: - - '14' - - '13' - - '12' - - '10' - - '8' - - '6' - - '4' - -os: linux - -env: - - ESLINT_VERSION=^7.0.0-0 - - ESLINT_VERSION=6 - - ESLINT_VERSION=5 - - ESLINT_VERSION=4 - - ESLINT_VERSION=3 - - ESLINT_VERSION=2 # osx backlog is often deep, so to be polite we can just hit these highlights matrix: include: - - env: LINT=true - node_js: lts/* - - env: PACKAGE=resolvers/node - node_js: 14 - - env: PACKAGE=resolvers/node - node_js: 12 - - env: PACKAGE=resolvers/node - node_js: 10 - - env: PACKAGE=resolvers/node - node_js: 8 - - env: PACKAGE=resolvers/node - 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 - node_js: 10 - - env: PACKAGE=resolvers/webpack - node_js: 8 - - env: PACKAGE=resolvers/webpack - node_js: 6 - - env: PACKAGE=resolvers/webpack - node_js: 4 - - os: osx env: ESLINT_VERSION=5 node_js: 14 @@ -67,37 +22,18 @@ matrix: env: ESLINT_VERSION=2 node_js: 4 - exclude: - - node_js: '4' - 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: - # issues with TypeScript deps in this version intersection - - node_js: '4' - env: ESLINT_VERSION=4 before_install: - 'nvm install-latest-npm' - 'npm install' - 'npm run copy-metafiles' - - 'if [ -n "${PACKAGE-}" ]; then cd "${PACKAGE}"; fi' install: - 'npm install' - 'if [ -n "${ESLINT_VERSION}" ]; then ./tests/dep-time-travel.sh; fi' script: - - 'if [ -n "${LINT-}" ]; then npm run posttest ; else npm run tests-only ; fi' + - npm run tests-only after_success: - npm run coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d583358ce..95416f1af7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,101 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] +## [2.23.4] - 2021-05-29 + +### Fixed +- [`no-import-module-exports`]: Don't crash if packages have no entrypoint ([#2099], thanks [@eps1lon]) +- [`no-extraneous-dependencies`]: fix package name algorithm ([#2097], thanks [@paztis]) + +## [2.23.3] - 2021-05-21 + +### Fixed +- [`no-restricted-paths`]: fix false positive matches ([#2090], thanks [@malykhinvi]) +- [`no-cycle`]: ignore imports where imported file only imports types of importing file ([#2083], thanks [@cherryblossom000]) +- [`no-cycle`]: fix false negative when file imports a type after importing a value in Flow ([#2083], thanks [@cherryblossom000]) +- [`order`]: restore default behavior unless `type` is in groups ([#2087], thanks [@grit96]) + +### Changed +- [Docs] Add `no-relative-packages` to list of to the list of rules ([#2075], thanks [@arvigeus]) + +## [2.23.2] - 2021-05-15 + +### Changed +- [meta] add `safe-publish-latest`; use `prepublishOnly` script for npm 7+ + +## [2.23.1] - 2021-05-14 + +### Fixed +- [`newline-after-import`]: fix crash with `export {}` syntax ([#2063], [#2056], thanks [@ljharb]) +- `ExportMap`: do not crash when tsconfig lacks `.compilerOptions` ([#2067], thanks [@ljharb]) +- [`order`]: fix alphabetical sorting ([#2071], thanks [@grit96]) + +## [2.23.0] - 2021-05-13 + +### Added +- [`no-commonjs`]: Also detect require calls with expressionless template literals: ``` require(`x`) ``` ([#1958], thanks [@FloEdelmann]) +- [`no-internal-modules`]: Add `forbid` option ([#1846], thanks [@guillaumewuip]) +- add [`no-relative-packages`] ([#1860], [#966], thanks [@tapayne88] [@panrafal]) +- add [`no-import-module-exports`] rule: report import declarations with CommonJS exports ([#804], thanks [@kentcdodds] and [@ttmarek]) +- [`no-unused-modules`]: Support destructuring assignment for `export`. ([#1997], thanks [@s-h-a-d-o-w]) +- [`order`]: support type imports ([#2021], thanks [@grit96]) +- [`order`]: Add `warnOnUnassignedImports` option to enable warnings for out of order unassigned imports ([#1990], thanks [@hayes]) + +### Fixed +- [`export`]/TypeScript: properly detect export specifiers as children of a TS module block ([#1889], thanks [@andreubotella]) +- [`order`]: ignore non-module-level requires ([#1940], thanks [@golopot]) +- [`no-webpack-loader-syntax`]/TypeScript: avoid crash on missing name ([#1947], thanks [@leonardodino]) +- [`no-extraneous-dependencies`]: Add package.json cache ([#1948], thanks [@fa93hws]) +- [`prefer-default-export`]: handle empty array destructuring ([#1965], thanks [@ljharb]) +- [`no-unused-modules`]: make type imports mark a module as used (fixes #1924) ([#1974], thanks [@cherryblossom000]) +- [`no-cycle`]: fix perf regression ([#1944], thanks [@Blasz]) +- [`first`]: fix handling of `import = require` ([#1963], thanks [@MatthiasKunnen]) +- [`no-cycle`]/[`extensions`]: fix isExternalModule usage ([#1696], thanks [@paztis]) +- [`extensions`]/[`no-cycle`]/[`no-extraneous-dependencies`]: Correct module real path resolution ([#1696], thanks [@paztis]) +- [`no-named-default`]: ignore Flow import type and typeof ([#1983], thanks [@christianvuerings]) +- [`no-extraneous-dependencies`]: Exclude flow `typeof` imports ([#1534], thanks [@devongovett]) +- [`newline-after-import`]: respect decorator annotations ([#1985], thanks [@lilling]) +- [`no-restricted-paths`]: enhance performance for zones with `except` paths ([#2022], thanks [@malykhinvi]) +- [`no-unresolved`]: check import() ([#2026], thanks [@aladdin-add]) + +### Changed +- [Generic Import Callback] Make callback for all imports once in rules ([#1237], thanks [@ljqx]) +- [Docs] [`no-named-as-default`]: add semicolon ([#1897], thanks [@bicstone]) +- [Docs] `no-extraneous-dependencies`: correct peerDependencies option default to `true` ([#1993], thanks [@dwardu]) +- [Docs] `order`: Document options required to match ordering example ([#1992], thanks [@silviogutierrez]) +- [Tests] `no-unresolved`: add tests for `import()` ([#2012], thanks [@davidbonnet]) +- [Docs] Add import/recommended ruleset to README ([#2034], thanks [@edemaine]) + +## [2.22.1] - 2020-09-27 +### Fixed +- [`default`]/TypeScript: avoid crash on `export =` with a MemberExpression ([#1841], thanks [@ljharb]) +- [`extensions`]/importType: Fix @/abc being treated as scoped module ([#1854], thanks [@3nuc]) +- allow using rest operator in named export ([#1878], thanks [@foray1010]) +- [`dynamic-import-chunkname`]: allow single quotes to match Webpack support ([#1848], thanks [@straub]) + +### Changed +- [`export`]: add tests for a name collision with `export * from` ([#1704], thanks @tomprats) + +## [2.22.0] - 2020-06-26 +### Added +- [`no-unused-modules`]: consider exported TypeScript interfaces, types and enums ([#1819], thanks [@nicolashenry]) +- [`no-cycle`]: allow `maxDepth` option to be `"∞"` (thanks [@ljharb]) + +### Fixed +- [`order`]/TypeScript: properly support `import = object` expressions ([#1823], thanks [@manuth]) +- [`no-extraneous-dependencies`]/TypeScript: do not error when importing type from dev dependencies ([#1820], thanks [@fernandopasik]) +- [`default`]: avoid crash with `export =` ([#1822], thanks [@AndrewLeedham]) +- [`order`]/[`newline-after-import`]: ignore TypeScript's "export import object" ([#1830], thanks [@be5invis]) +- [`dynamic-import-chunkname`]/TypeScript: supports `@typescript-eslint/parser` ([#1833], thanks [@noelebrun]) +- [`order`]/TypeScript: ignore ordering of object imports ([#1831], thanks [@manuth]) +- [`namespace`]: do not report on shadowed import names ([#518], thanks [@ljharb]) +- [`export`]: avoid warning on `export * as` non-conflicts ([#1834], thanks [@ljharb]) + +### Changed +- [`no-extraneous-dependencies`]: add tests for importing types ([#1824], thanks [@taye]) +- [docs] [`no-default-export`]: Fix docs url ([#1836], thanks [@beatrizrezener]) +- [docs] [`imports-first`]: deprecation info and link to `first` docs ([#1835], thanks [@beatrizrezener]) + ## [2.21.2] - 2020-06-09 ### Fixed - [`order`]: avoid a crash on TypeScript’s `export import` syntax ([#1808], thanks [@ljharb]) @@ -30,7 +125,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`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]) +- [`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]) @@ -40,7 +135,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### 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]) +- [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]) @@ -65,7 +160,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`no-duplicates`]: allow duplicate imports if one is a namespace and the other not ([#1612], thanks [@sveyret]) - Add some missing rule meta schemas and types ([#1620], thanks [@bmish]) - [`named`]: for importing from a module which re-exports named exports from a `node_modules` module ([#1569], [#1447], thanks [@redbugz], [@kentcdodds]) -- [`order`]: Fix alphabetize for mixed requires and imports ([#5625], thanks [@wschurman]) +- [`order`]: Fix alphabetize for mixed requires and imports ([#1626], thanks [@wschurman]) ### Changed - [`import/external-module-folders` setting] behavior is more strict now: it will only match complete path segments ([#1605], thanks [@skozin]) @@ -188,7 +283,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - aliased internal modules that look like core modules ([#1297], thanks [@echenley]) - [`namespace`]: add check for null ExportMap ([#1235], [#1144], thanks [@ljqx]) - [ExportMap] fix condition for checking if block comment ([#1234], [#1233], thanks [@ljqx]) -- Fix overwriting of dynamic import() CallExpression ([`no-cycle`], [`no-relative-parent-import`], [`no-unresolved`], [`no-useless-path-segments`]) ([#1218], [#1166], [#1035], thanks [@vikr01]) +- Fix overwriting of dynamic import() CallExpression ([`no-cycle`], [`no-relative-parent-imports`], [`no-unresolved`], [`no-useless-path-segments`]) ([#1218], [#1166], [#1035], thanks [@vikr01]) - [`export`]: false positives for TypeScript type + value export ([#1319], thanks [@bradzacher]) - [`export`]: Support TypeScript namespaces ([#1320], [#1300], thanks [@bradzacher]) @@ -681,6 +776,7 @@ for info on changes for earlier releases. [`no-duplicates`]: ./docs/rules/no-duplicates.md [`no-dynamic-require`]: ./docs/rules/no-dynamic-require.md [`no-extraneous-dependencies`]: ./docs/rules/no-extraneous-dependencies.md +[`no-import-module-exports`]: ./docs/rules/no-import-module-exports.md [`no-internal-modules`]: ./docs/rules/no-internal-modules.md [`no-mutable-exports`]: ./docs/rules/no-mutable-exports.md [`no-named-as-default-member`]: ./docs/rules/no-named-as-default-member.md @@ -689,6 +785,8 @@ for info on changes for earlier releases. [`no-named-export`]: ./docs/rules/no-named-export.md [`no-namespace`]: ./docs/rules/no-namespace.md [`no-nodejs-modules`]: ./docs/rules/no-nodejs-modules.md +[`no-relative-packages`]: ./docs/rules/no-relative-packages.md +[`no-relative-parent-imports`]: ./docs/rules/no-relative-parent-imports.md [`no-restricted-paths`]: ./docs/rules/no-restricted-paths.md [`no-self-import`]: ./docs/rules/no-self-import.md [`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md @@ -702,8 +800,46 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#2099]: https://github.com/benmosher/eslint-plugin-import/pull/2099 +[#2097]: https://github.com/benmosher/eslint-plugin-import/pull/2097 +[#2090]: https://github.com/benmosher/eslint-plugin-import/pull/2090 +[#2087]: https://github.com/benmosher/eslint-plugin-import/pull/2087 +[#2083]: https://github.com/benmosher/eslint-plugin-import/pull/2083 +[#2075]: https://github.com/benmosher/eslint-plugin-import/pull/2075 +[#2071]: https://github.com/benmosher/eslint-plugin-import/pull/2071 +[#2034]: https://github.com/benmosher/eslint-plugin-import/pull/2034 +[#2026]: https://github.com/benmosher/eslint-plugin-import/pull/2026 +[#2022]: https://github.com/benmosher/eslint-plugin-import/pull/2022 +[#2021]: https://github.com/benmosher/eslint-plugin-import/pull/2021 +[#2012]: https://github.com/benmosher/eslint-plugin-import/pull/2012 +[#1997]: https://github.com/benmosher/eslint-plugin-import/pull/1997 +[#1993]: https://github.com/benmosher/eslint-plugin-import/pull/1993 +[#1990]: https://github.com/benmosher/eslint-plugin-import/pull/1990 +[#1985]: https://github.com/benmosher/eslint-plugin-import/pull/1985 +[#1983]: https://github.com/benmosher/eslint-plugin-import/pull/1983 +[#1974]: https://github.com/benmosher/eslint-plugin-import/pull/1974 +[#1958]: https://github.com/benmosher/eslint-plugin-import/pull/1958 +[#1948]: https://github.com/benmosher/eslint-plugin-import/pull/1948 +[#1947]: https://github.com/benmosher/eslint-plugin-import/pull/1947 +[#1944]: https://github.com/benmosher/eslint-plugin-import/pull/1944 +[#1940]: https://github.com/benmosher/eslint-plugin-import/pull/1940 +[#1897]: https://github.com/benmosher/eslint-plugin-import/pull/1897 +[#1889]: https://github.com/benmosher/eslint-plugin-import/pull/1889 +[#1878]: https://github.com/benmosher/eslint-plugin-import/pull/1878 +[#1860]: https://github.com/benmosher/eslint-plugin-import/pull/1860 +[#1848]: https://github.com/benmosher/eslint-plugin-import/pull/1848 +[#1846]: https://github.com/benmosher/eslint-plugin-import/pull/1846 +[#1836]: https://github.com/benmosher/eslint-plugin-import/pull/1836 +[#1835]: https://github.com/benmosher/eslint-plugin-import/pull/1835 +[#1833]: https://github.com/benmosher/eslint-plugin-import/pull/1833 +[#1831]: https://github.com/benmosher/eslint-plugin-import/pull/1831 +[#1830]: https://github.com/benmosher/eslint-plugin-import/pull/1830 +[#1824]: https://github.com/benmosher/eslint-plugin-import/pull/1824 +[#1823]: https://github.com/benmosher/eslint-plugin-import/pull/1823 +[#1822]: https://github.com/benmosher/eslint-plugin-import/pull/1822 +[#1820]: https://github.com/benmosher/eslint-plugin-import/pull/1820 +[#1819]: https://github.com/benmosher/eslint-plugin-import/pull/1819 [#1802]: https://github.com/benmosher/eslint-plugin-import/pull/1802 -[#1801]: https://github.com/benmosher/eslint-plugin-import/issues/1801 [#1788]: https://github.com/benmosher/eslint-plugin-import/pull/1788 [#1786]: https://github.com/benmosher/eslint-plugin-import/pull/1786 [#1785]: https://github.com/benmosher/eslint-plugin-import/pull/1785 @@ -717,9 +853,8 @@ for info on changes for earlier releases. [#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 +[#1696]: https://github.com/benmosher/eslint-plugin-import/pull/1696 [#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 @@ -729,17 +864,12 @@ for info on changes for earlier releases. [#1664]: https://github.com/benmosher/eslint-plugin-import/pull/1664 [#1658]: https://github.com/benmosher/eslint-plugin-import/pull/1658 [#1651]: https://github.com/benmosher/eslint-plugin-import/pull/1651 -[#1635]: https://github.com/benmosher/eslint-plugin-import/issues/1635 -[#1631]: https://github.com/benmosher/eslint-plugin-import/issues/1631 -[#1625]: https://github.com/benmosher/eslint-plugin-import/pull/1625 +[#1626]: https://github.com/benmosher/eslint-plugin-import/pull/1626 [#1620]: https://github.com/benmosher/eslint-plugin-import/pull/1620 [#1619]: https://github.com/benmosher/eslint-plugin-import/pull/1619 -[#1616]: https://github.com/benmosher/eslint-plugin-import/issues/1616 -[#1613]: https://github.com/benmosher/eslint-plugin-import/issues/1613 [#1612]: https://github.com/benmosher/eslint-plugin-import/pull/1612 [#1611]: https://github.com/benmosher/eslint-plugin-import/pull/1611 [#1605]: https://github.com/benmosher/eslint-plugin-import/pull/1605 -[#1589]: https://github.com/benmosher/eslint-plugin-import/issues/1589 [#1586]: https://github.com/benmosher/eslint-plugin-import/pull/1586 [#1572]: https://github.com/benmosher/eslint-plugin-import/pull/1572 [#1569]: https://github.com/benmosher/eslint-plugin-import/pull/1569 @@ -747,6 +877,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 +[#1534]: https://github.com/benmosher/eslint-plugin-import/pull/1534 [#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 @@ -757,6 +888,7 @@ for info on changes for earlier releases. [#1495]: https://github.com/benmosher/eslint-plugin-import/pull/1495 [#1494]: https://github.com/benmosher/eslint-plugin-import/pull/1494 [#1493]: https://github.com/benmosher/eslint-plugin-import/pull/1493 +[#1491]: https://github.com/benmosher/eslint-plugin-import/pull/1491 [#1472]: https://github.com/benmosher/eslint-plugin-import/pull/1472 [#1470]: https://github.com/benmosher/eslint-plugin-import/pull/1470 [#1447]: https://github.com/benmosher/eslint-plugin-import/pull/1447 @@ -804,6 +936,7 @@ for info on changes for earlier releases. [#1253]: https://github.com/benmosher/eslint-plugin-import/pull/1253 [#1248]: https://github.com/benmosher/eslint-plugin-import/pull/1248 [#1238]: https://github.com/benmosher/eslint-plugin-import/pull/1238 +[#1237]: https://github.com/benmosher/eslint-plugin-import/pull/1237 [#1235]: https://github.com/benmosher/eslint-plugin-import/pull/1235 [#1234]: https://github.com/benmosher/eslint-plugin-import/pull/1234 [#1232]: https://github.com/benmosher/eslint-plugin-import/pull/1232 @@ -828,6 +961,7 @@ for info on changes for earlier releases. [#1068]: https://github.com/benmosher/eslint-plugin-import/pull/1068 [#1049]: https://github.com/benmosher/eslint-plugin-import/pull/1049 [#1046]: https://github.com/benmosher/eslint-plugin-import/pull/1046 +[#966]: https://github.com/benmosher/eslint-plugin-import/pull/966 [#944]: https://github.com/benmosher/eslint-plugin-import/pull/944 [#912]: https://github.com/benmosher/eslint-plugin-import/pull/912 [#908]: https://github.com/benmosher/eslint-plugin-import/pull/908 @@ -837,6 +971,7 @@ for info on changes for earlier releases. [#871]: https://github.com/benmosher/eslint-plugin-import/pull/871 [#858]: https://github.com/benmosher/eslint-plugin-import/pull/858 [#843]: https://github.com/benmosher/eslint-plugin-import/pull/843 +[#804]: https://github.com/benmosher/eslint-plugin-import/pull/804 [#797]: https://github.com/benmosher/eslint-plugin-import/pull/797 [#794]: https://github.com/benmosher/eslint-plugin-import/pull/794 [#744]: https://github.com/benmosher/eslint-plugin-import/pull/744 @@ -861,6 +996,7 @@ for info on changes for earlier releases. [#555]: https://github.com/benmosher/eslint-plugin-import/pull/555 [#538]: https://github.com/benmosher/eslint-plugin-import/pull/538 [#527]: https://github.com/benmosher/eslint-plugin-import/pull/527 +[#518]: https://github.com/benmosher/eslint-plugin-import/pull/518 [#509]: https://github.com/benmosher/eslint-plugin-import/pull/509 [#508]: https://github.com/benmosher/eslint-plugin-import/pull/508 [#503]: https://github.com/benmosher/eslint-plugin-import/pull/503 @@ -903,10 +1039,27 @@ for info on changes for earlier releases. [#211]: https://github.com/benmosher/eslint-plugin-import/pull/211 [#164]: https://github.com/benmosher/eslint-plugin-import/pull/164 [#157]: https://github.com/benmosher/eslint-plugin-import/pull/157 +[#2067]: https://github.com/benmosher/eslint-plugin-import/issues/2067 +[#2056]: https://github.com/benmosher/eslint-plugin-import/issues/2056 +[#2063]: https://github.com/benmosher/eslint-plugin-import/issues/2063 +[#1965]: https://github.com/benmosher/eslint-plugin-import/issues/1965 +[#1924]: https://github.com/benmosher/eslint-plugin-import/issues/1924 +[#1854]: https://github.com/benmosher/eslint-plugin-import/issues/1854 +[#1841]: https://github.com/benmosher/eslint-plugin-import/issues/1841 +[#1834]: https://github.com/benmosher/eslint-plugin-import/issues/1834 [#1814]: https://github.com/benmosher/eslint-plugin-import/issues/1814 [#1811]: https://github.com/benmosher/eslint-plugin-import/issues/1811 [#1808]: https://github.com/benmosher/eslint-plugin-import/issues/1808 [#1805]: https://github.com/benmosher/eslint-plugin-import/issues/1805 +[#1801]: https://github.com/benmosher/eslint-plugin-import/issues/1801 +[#1722]: https://github.com/benmosher/eslint-plugin-import/issues/1722 +[#1704]: https://github.com/benmosher/eslint-plugin-import/issues/1704 +[#1702]: https://github.com/benmosher/eslint-plugin-import/issues/1702 +[#1635]: https://github.com/benmosher/eslint-plugin-import/issues/1635 +[#1631]: https://github.com/benmosher/eslint-plugin-import/issues/1631 +[#1616]: https://github.com/benmosher/eslint-plugin-import/issues/1616 +[#1613]: https://github.com/benmosher/eslint-plugin-import/issues/1613 +[#1589]: https://github.com/benmosher/eslint-plugin-import/issues/1589 [#1565]: https://github.com/benmosher/eslint-plugin-import/issues/1565 [#1366]: https://github.com/benmosher/eslint-plugin-import/issues/1366 [#1334]: https://github.com/benmosher/eslint-plugin-import/issues/1334 @@ -992,7 +1145,14 @@ for info on changes for earlier releases. [#119]: https://github.com/benmosher/eslint-plugin-import/issues/119 [#89]: https://github.com/benmosher/eslint-plugin-import/issues/89 -[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.21.2...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.23.4...HEAD +[2.23.4]: https://github.com/benmosher/eslint-plugin-import/compare/v2.23.3...v2.23.4 +[2.23.3]: https://github.com/benmosher/eslint-plugin-import/compare/v2.23.2...v2.23.3 +[2.23.2]: https://github.com/benmosher/eslint-plugin-import/compare/v2.23.1...v2.23.2 +[2.23.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.23.0...v2.23.1 +[2.23.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.22.1...v2.23.0 +[2.22.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.22.0...v2.22.1 +[2.22.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.21.1...v2.22.0 [2.21.2]: https://github.com/benmosher/eslint-plugin-import/compare/v2.21.1...v2.21.2 [2.21.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.21.0...v2.21.1 [2.21.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.20.2...v2.21.0 @@ -1063,156 +1223,193 @@ for info on changes for earlier releases. [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 +[@1pete]: https://github.com/1pete +[@3nuc]: https://github.com/3nuc +[@aamulumi]: https://github.com/aamulumi +[@adamborowski]: https://github.com/adamborowski +[@adjerbetian]: https://github.com/adjerbetian +[@ai]: https://github.com/ai +[@aladdin-add]: https://github.com/aladdin-add +[@alex-page]: https://github.com/alex-page +[@alexgorbatchev]: https://github.com/alexgorbatchev +[@andreubotella]: https://github.com/andreubotella +[@AndrewLeedham]: https://github.com/AndrewLeedham +[@aravindet]: https://github.com/aravindet +[@arvigeus]: https://github.com/arvigeus +[@asapach]: https://github.com/asapach +[@astorije]: https://github.com/astorije +[@atikenny]: https://github.com/atikenny +[@atos1990]: https://github.com/atos1990 +[@barbogast]: https://github.com/barbogast +[@be5invis]: https://github.com/be5invis +[@beatrizrezener]: https://github.com/beatrizrezener +[@benmosher]: https://github.com/benmosher +[@benmunro]: https://github.com/benmunro +[@bicstone]: https://github.com/bicstone +[@Blasz]: https://github.com/Blasz +[@bmish]: https://github.com/bmish +[@borisyankov]: https://github.com/borisyankov +[@bradennapier]: https://github.com/bradennapier +[@bradzacher]: https://github.com/bradzacher +[@brendo]: https://github.com/brendo +[@brettz9]: https://github.com/brettz9 +[@charlessuh]: https://github.com/charlessuh +[@cherryblossom000]: https://github.com/cherryblossom000 +[@chrislloyd]: https://github.com/chrislloyd +[@christianvuerings]: https://github.com/christianvuerings +[@christophercurrie]: https://github.com/christophercurrie +[@danny-andrews]: https://github.com/dany-andrews +[@darkartur]: https://github.com/darkartur +[@davidbonnet]: https://github.com/davidbonnet +[@dbrewer5]: https://github.com/dbrewer5 +[@devongovett]: https://github.com/devongovett +[@dmnd]: https://github.com/dmnd +[@duncanbeevers]: https://github.com/duncanbeevers +[@dwardu]: https://github.com/dwardu +[@echenley]: https://github.com/echenley +[@edemaine]: https://github.com/edemaine +[@eelyafi]: https://github.com/eelyafi +[@Ephem]: https://github.com/Ephem +[@ephys]: https://github.com/ephys +[@eps1lon]: https://github.com/eps1lon +[@ernestostifano]: https://github.com/ernestostifano +[@fa93hws]: https://github.com/fa93hws +[@fengkfengk]: https://github.com/fengkfengk +[@fernandopasik]: https://github.com/fernandopasik +[@feychenie]: https://github.com/feychenie +[@fisker]: https://github.com/fisker +[@FloEdelmann]: https://github.com/FloEdelmann +[@fooloomanzoo]: https://github.com/fooloomanzoo +[@foray1010]: https://github.com/foray1010 +[@forivall]: https://github.com/forivall +[@fsmaia]: https://github.com/fsmaia +[@fson]: https://github.com/fson +[@futpib]: https://github.com/futpib +[@gajus]: https://github.com/gajus [@gausie]: https://github.com/gausie -[@singles]: https://github.com/singles +[@gavriguy]: https://github.com/gavriguy +[@giodamelio]: https://github.com/giodamelio +[@golopot]: https://github.com/golopot +[@graingert]: https://github.com/graingert +[@grit96]: https://github.com/grit96 +[@guillaumewuip]: https://github.com/guillaumewuip +[@hayes]: https://github.com/hayes +[@hulkish]: https://github.com/hulkish +[@Hypnosphi]: https://github.com/Hypnosphi +[@isiahmeadows]: https://github.com/isiahmeadows +[@IvanGoncharov]: https://github.com/IvanGoncharov +[@ivo-stefchev]: https://github.com/ivo-stefchev +[@jakubsta]: https://github.com/jakubsta +[@jeffshaver]: https://github.com/jeffshaver +[@jf248]: https://github.com/jf248 [@jfmengels]: https://github.com/jfmengels -[@lo1tuma]: https://github.com/lo1tuma -[@dmnd]: https://github.com/dmnd -[@lemonmade]: https://github.com/lemonmade [@jimbolla]: https://github.com/jimbolla -[@jquense]: https://github.com/jquense +[@jkimbo]: https://github.com/jkimbo +[@joaovieira]: https://github.com/joaovieira +[@johndevedu]: https://github.com/johndevedu [@jonboiser]: https://github.com/jonboiser -[@taion]: https://github.com/taion -[@strawbrary]: https://github.com/strawbrary -[@SimenB]: https://github.com/SimenB [@josh]: https://github.com/josh -[@borisyankov]: https://github.com/borisyankov -[@gavriguy]: https://github.com/gavriguy -[@jkimbo]: https://github.com/jkimbo -[@le0nik]: https://github.com/le0nik -[@scottnonnenberg]: https://github.com/scottnonnenberg -[@sindresorhus]: https://github.com/sindresorhus -[@ljharb]: https://github.com/ljharb -[@rhettlivingston]: https://github.com/rhettlivingston -[@zloirock]: https://github.com/zloirock -[@rhys-vdw]: https://github.com/rhys-vdw -[@wKich]: https://github.com/wKich -[@tizmagik]: https://github.com/tizmagik -[@knpwrs]: https://github.com/knpwrs -[@spalger]: https://github.com/spalger -[@preco21]: https://github.com/preco21 -[@skyrpex]: https://github.com/skyrpex -[@fson]: https://github.com/fson -[@ntdb]: https://github.com/ntdb -[@jakubsta]: https://github.com/jakubsta -[@wtgtybhertgeghgtwtg]: https://github.com/wtgtybhertgeghgtwtg -[@duncanbeevers]: https://github.com/duncanbeevers -[@giodamelio]: https://github.com/giodamelio -[@ntdb]: https://github.com/ntdb -[@ramasilveyra]: https://github.com/ramasilveyra -[@sompylasar]: https://github.com/sompylasar -[@kevin940726]: https://github.com/kevin940726 -[@eelyafi]: https://github.com/eelyafi -[@mastilver]: https://github.com/mastilver +[@JounQin]: https://github.com/JounQin +[@jquense]: https://github.com/jquense [@jseminck]: https://github.com/jseminck -[@laysent]: https://github.com/laysent +[@julien1619]: https://github.com/julien1619 +[@justinanastos]: https://github.com/justinanastos [@k15a]: https://github.com/k15a -[@mplewis]: https://github.com/mplewis -[@rosswarren]: https://github.com/rosswarren -[@alexgorbatchev]: https://github.com/alexgorbatchev -[@tihonove]: https://github.com/tihonove -[@robertrossmann]: https://github.com/robertrossmann -[@isiahmeadows]: https://github.com/isiahmeadows -[@graingert]: https://github.com/graingert -[@danny-andrews]: https://github.com/dany-andrews -[@fengkfengk]: https://github.com/fengkfengk -[@futpib]: https://github.com/futpib +[@kentcdodds]: https://github.com/kentcdodds +[@kevin940726]: https://github.com/kevin940726 +[@kgregory]: https://github.com/kgregory +[@kirill-konshin]: https://github.com/kirill-konshin +[@kiwka]: https://github.com/kiwka [@klimashkin]: https://github.com/klimashkin +[@kmui2]: https://github.com/kmui2 +[@knpwrs]: https://github.com/knpwrs +[@laysent]: https://github.com/laysent +[@le0nik]: https://github.com/le0nik +[@lemonmade]: https://github.com/lemonmade +[@lencioni]: https://github.com/lencioni +[@leonardodino]: https://github.com/leonardodino +[@Librazy]: https://github.com/Librazy +[@lilling]: https://github.com/lilling +[@ljharb]: https://github.com/ljharb +[@ljqx]: https://github.com/ljqx +[@lo1tuma]: https://github.com/lo1tuma +[@loganfsmyth]: https://github.com/loganfsmyth +[@luczsoma]: https://github.com/luczsoma [@lukeapage]: https://github.com/lukeapage +[@lydell]: https://github.com/lydell +[@Mairu]: https://github.com/Mairu +[@malykhinvi]: https://github.com/malykhinvi [@manovotny]: https://github.com/manovotny +[@manuth]: https://github.com/manuth +[@marcusdarmstrong]: https://github.com/marcusdarmstrong +[@mastilver]: https://github.com/mastilver +[@mathieudutour]: https://github.com/mathieudutour +[@MatthiasKunnen]: https://github.com/MatthiasKunnen [@mattijsbliek]: https://github.com/mattijsbliek -[@hulkish]: https://github.com/hulkish -[@chrislloyd]: https://github.com/chrislloyd -[@ai]: https://github.com/ai -[@syymza]: https://github.com/syymza -[@justinanastos]: https://github.com/justinanastos -[@1pete]: https://github.com/1pete -[@gajus]: https://github.com/gajus -[@jf248]: https://github.com/jf248 -[@aravindet]: https://github.com/aravindet -[@pzhine]: https://github.com/pzhine -[@st-sloth]: https://github.com/st-sloth -[@ljqx]: https://github.com/ljqx -[@kirill-konshin]: https://github.com/kirill-konshin -[@asapach]: https://github.com/asapach -[@sergei-startsev]: https://github.com/sergei-startsev -[@ephys]: https://github.com/ephys -[@lydell]: https://github.com/lydell -[@jeffshaver]: https://github.com/jeffshaver -[@timkraut]: https://github.com/timkraut -[@TakeScoop]: https://github.com/TakeScoop -[@rfermann]: https://github.com/rfermann -[@bradennapier]: https://github.com/bradennapier -[@schmod]: https://github.com/schmod -[@echenley]: https://github.com/echenley -[@vikr01]: https://github.com/vikr01 -[@bradzacher]: https://github.com/bradzacher -[@feychenie]: https://github.com/feychenie -[@kiwka]: https://github.com/kiwka -[@loganfsmyth]: https://github.com/loganfsmyth -[@johndevedu]: https://github.com/johndevedu -[@charlessuh]: https://github.com/charlessuh -[@kgregory]: https://github.com/kgregory -[@christophercurrie]: https://github.com/christophercurrie -[@alex-page]: https://github.com/alex-page -[@benmosher]: https://github.com/benmosher -[@fooloomanzoo]: https://github.com/fooloomanzoo -[@sheepsteak]: https://github.com/sheepsteak -[@sharmilajesupaul]: https://github.com/sharmilajesupaul -[@lencioni]: https://github.com/lencioni -[@JounQin]: https://github.com/JounQin -[@atikenny]: https://github.com/atikenny -[@schmidsi]: https://github.com/schmidsi -[@TrevorBurnham]: https://github.com/TrevorBurnham -[@benmunro]: https://github.com/benmunro -[@tihonove]: https://github.com/tihonove -[@brendo]: https://github.com/brendo -[@saschanaz]: https://github.com/saschanaz -[@brettz9]: https://github.com/brettz9 -[@Taranys]: https://github.com/Taranys +[@Maxim-Mazurok]: https://github.com/Maxim-Mazurok [@maxmalov]: https://github.com/maxmalov -[@marcusdarmstrong]: https://github.com/marcusdarmstrong -[@Mairu]: https://github.com/Mairu -[@aamulumi]: https://github.com/aamulumi +[@MikeyBeLike]: https://github.com/MikeyBeLike +[@mplewis]: https://github.com/mplewis +[@nickofthyme]: https://github.com/nickofthyme +[@nicolashenry]: https://github.com/nicolashenry +[@noelebrun]: https://github.com/noelebrun +[@ntdb]: https://github.com/ntdb +[@panrafal]: https://github.com/panrafal +[@paztis]: https://github.com/paztis [@pcorpet]: https://github.com/pcorpet -[@stropho]: https://github.com/stropho -[@luczsoma]: https://github.com/luczsoma -[@christophercurrie]: https://github.com/christophercurrie -[@randallreedjr]: https://github.com/randallreedjr [@Pessimistress]: https://github.com/Pessimistress -[@stekycz]: https://github.com/stekycz -[@dbrewer5]: https://github.com/dbrewer5 +[@preco21]: https://github.com/preco21 +[@pzhine]: https://github.com/pzhine +[@ramasilveyra]: https://github.com/ramasilveyra +[@randallreedjr]: https://github.com/randallreedjr +[@redbugz]: https://github.com/redbugz +[@rfermann]: https://github.com/rfermann +[@rhettlivingston]: https://github.com/rhettlivingston +[@rhys-vdw]: https://github.com/rhys-vdw +[@richardxia]: https://github.com/richardxia +[@robertrossmann]: https://github.com/robertrossmann +[@rosswarren]: https://github.com/rosswarren [@rsolomon]: https://github.com/rsolomon -[@joaovieira]: https://github.com/joaovieira -[@ivo-stefchev]: https://github.com/ivo-stefchev +[@s-h-a-d-o-w]: https://github.com/s-h-a-d-o-w +[@saschanaz]: https://github.com/saschanaz +[@schmidsi]: https://github.com/schmidsi +[@schmod]: https://github.com/schmod +[@scottnonnenberg]: https://github.com/scottnonnenberg +[@sergei-startsev]: https://github.com/sergei-startsev +[@sharmilajesupaul]: https://github.com/sharmilajesupaul +[@sheepsteak]: https://github.com/sheepsteak +[@silviogutierrez]: https://github.com/silviogutierrez +[@SimenB]: https://github.com/SimenB +[@sindresorhus]: https://github.com/sindresorhus +[@singles]: https://github.com/singles [@skozin]: https://github.com/skozin -[@yordis]: https://github.com/yordis +[@skyrpex]: https://github.com/skyrpex +[@sompylasar]: https://github.com/sompylasar +[@spalger]: https://github.com/spalger +[@st-sloth]: https://github.com/st-sloth +[@stekycz]: https://github.com/stekycz +[@straub]: https://github.com/straub +[@strawbrary]: https://github.com/strawbrary +[@stropho]: https://github.com/stropho [@sveyret]: https://github.com/sveyret -[@bmish]: https://github.com/bmish -[@redbugz]: https://github.com/redbugz -[@kentcdodds]: https://github.com/kentcdodds -[@IvanGoncharov]: https://github.com/IvanGoncharov -[@wschurman]: https://github.com/wschurman -[@fisker]: https://github.com/fisker -[@richardxia]: https://github.com/richardxia +[@swernerx]: https://github.com/swernerx +[@syymza]: https://github.com/syymza +[@taion]: https://github.com/taion +[@TakeScoop]: https://github.com/TakeScoop +[@tapayne88]: https://github.com/tapayne88 +[@Taranys]: https://github.com/Taranys +[@taye]: https://github.com/taye [@TheCrueltySage]: https://github.com/TheCrueltySage -[@ernestostifano]: https://github.com/ernestostifano -[@forivall]: https://github.com/forivall +[@tihonove]: https://github.com/tihonove +[@timkraut]: https://github.com/timkraut +[@tizmagik]: https://github.com/tizmagik +[@tomprats]: https://github.com/tomprats +[@TrevorBurnham]: https://github.com/TrevorBurnham +[@ttmarek]: https://github.com/ttmarek +[@vikr01]: https://github.com/vikr01 +[@wKich]: https://github.com/wKich +[@wschurman]: https://github.com/wschurman +[@wtgtybhertgeghgtwtg]: https://github.com/wtgtybhertgeghgtwtg [@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 +[@yordis]: https://github.com/yordis +[@zloirock]: https://github.com/zloirock \ No newline at end of file diff --git a/README.md b/README.md index e08e72ffa2..d7d50eaf51 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a * Forbid a module from importing a module with a dependency path back to itself ([`no-cycle`]) * Prevent unnecessary path segments in import and require statements ([`no-useless-path-segments`]) * Forbid importing modules from parent directories ([`no-relative-parent-imports`]) +* Prevent importing packages through relative paths ([`no-relative-packages`]) [`no-unresolved`]: ./docs/rules/no-unresolved.md [`named`]: ./docs/rules/named.md @@ -41,6 +42,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a [`no-cycle`]: ./docs/rules/no-cycle.md [`no-useless-path-segments`]: ./docs/rules/no-useless-path-segments.md [`no-relative-parent-imports`]: ./docs/rules/no-relative-parent-imports.md +[`no-relative-packages`]: ./docs/rules/no-relative-packages.md ### Helpful warnings @@ -67,11 +69,13 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a * Report CommonJS `require` calls and `module.exports` or `exports.*`. ([`no-commonjs`]) * Report AMD `require` and `define` calls. ([`no-amd`]) * No Node.js builtin modules. ([`no-nodejs-modules`]) +* Forbid imports with CommonJS exports ([`no-import-module-exports`]) [`unambiguous`]: ./docs/rules/unambiguous.md [`no-commonjs`]: ./docs/rules/no-commonjs.md [`no-amd`]: ./docs/rules/no-amd.md [`no-nodejs-modules`]: ./docs/rules/no-nodejs-modules.md +[`no-import-module-exports`]: ./docs/rules/no-import-module-exports.md ### Style guide @@ -136,6 +140,8 @@ in your `.eslintrc.(yml|json|js)`, or extend one of the canned configs: --- extends: - eslint:recommended + - plugin:import/recommended + # alternatively, 'recommended' is the combination of these two rule sets: - plugin:import/errors - plugin:import/warnings @@ -161,8 +167,7 @@ Make sure you have installed [`@typescript-eslint/parser`] which is used in the ```yaml extends: - eslint:recommended - - plugin:import/errors - - plugin:import/warnings + - plugin:import/recommended - plugin:import/typescript # this line does the trick ``` diff --git a/appveyor.yml b/appveyor.yml index b79315b7be..de79234eb4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,6 +16,8 @@ image: Visual Studio 2019 matrix: fast_finish: false exclude: + - configuration: WSL + nodejs_version: "10" - configuration: WSL nodejs_version: "8" diff --git a/config/electron.js b/config/electron.js index 6fab4e8b9e..f98ff0614b 100644 --- a/config/electron.js +++ b/config/electron.js @@ -5,4 +5,4 @@ module.exports = { settings: { 'import/core-modules': ['electron'], }, -} +}; diff --git a/config/errors.js b/config/errors.js index d99a9dacf0..127c29a0cc 100644 --- a/config/errors.js +++ b/config/errors.js @@ -5,10 +5,10 @@ */ module.exports = { plugins: ['import'], - rules: { 'import/no-unresolved': 2 - , 'import/named': 2 - , 'import/namespace': 2 - , 'import/default': 2 - , 'import/export': 2, - }, -} + rules: { 'import/no-unresolved': 2, + 'import/named': 2, + 'import/namespace': 2, + 'import/default': 2, + 'import/export': 2, + }, +}; diff --git a/config/react-native.js b/config/react-native.js index fbc8652c9f..a1aa0ee565 100644 --- a/config/react-native.js +++ b/config/react-native.js @@ -10,4 +10,4 @@ module.exports = { }, }, }, -} +}; diff --git a/config/react.js b/config/react.js index fe1b5f2ec1..68555512d7 100644 --- a/config/react.js +++ b/config/react.js @@ -15,4 +15,4 @@ module.exports = { ecmaFeatures: { jsx: true }, }, -} +}; diff --git a/config/recommended.js b/config/recommended.js index 9970918933..8e7ca9fd05 100644 --- a/config/recommended.js +++ b/config/recommended.js @@ -25,4 +25,4 @@ module.exports = { sourceType: 'module', ecmaVersion: 2018, }, -} +}; diff --git a/config/stage-0.js b/config/stage-0.js index 25ad75feb1..42419123f0 100644 --- a/config/stage-0.js +++ b/config/stage-0.js @@ -9,4 +9,4 @@ module.exports = { rules: { 'import/no-deprecated': 1, }, -} +}; diff --git a/config/typescript.js b/config/typescript.js index 705faaf372..01b59f06b9 100644 --- a/config/typescript.js +++ b/config/typescript.js @@ -2,7 +2,7 @@ * Adds `.jsx`, `.ts` and `.tsx` as an extension, and enables JSX/TSX parsing. */ -var allExtensions = ['.ts', '.tsx', '.d.ts', '.js', '.jsx'] +const allExtensions = ['.ts', '.tsx', '.d.ts', '.js', '.jsx']; module.exports = { @@ -25,4 +25,4 @@ module.exports = { // TypeScript compilation already ensures that named imports exist in the referenced module 'import/named': 'off', }, -} +}; diff --git a/config/warnings.js b/config/warnings.js index c05eb0722a..5d74143b28 100644 --- a/config/warnings.js +++ b/config/warnings.js @@ -9,4 +9,4 @@ module.exports = { 'import/no-named-as-default-member': 1, 'import/no-duplicates': 1, }, -} +}; diff --git a/docs/rules/dynamic-import-chunkname.md b/docs/rules/dynamic-import-chunkname.md index 4bcc5a98b1..d29c06bbaa 100644 --- a/docs/rules/dynamic-import-chunkname.md +++ b/docs/rules/dynamic-import-chunkname.md @@ -39,12 +39,6 @@ import( 'someModule', ); -// using single quotes instead of double quotes -import( - /* webpackChunkName: 'someModule' */ - 'someModule', -); - // invalid syntax for webpack comment import( /* totally not webpackChunkName: "someModule" */ @@ -78,6 +72,12 @@ The following patterns are valid: /* webpackChunkName: "someModule", webpackPrefetch: true */ 'someModule', ); + + // using single quotes instead of double quotes + import( + /* webpackChunkName: 'someModule' */ + 'someModule', + ); ``` ## When Not To Use It diff --git a/docs/rules/imports-first.md b/docs/rules/imports-first.md new file mode 100644 index 0000000000..b7f20754af --- /dev/null +++ b/docs/rules/imports-first.md @@ -0,0 +1,3 @@ +# imports-first + +This rule was **deprecated** in eslint-plugin-import v2.0.0. Please use the corresponding rule [`first`](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/first.md). diff --git a/docs/rules/no-cycle.md b/docs/rules/no-cycle.md index 6329bb272e..7d54e81ff8 100644 --- a/docs/rules/no-cycle.md +++ b/docs/rules/no-cycle.md @@ -2,7 +2,7 @@ Ensures that there is no resolvable path back to this module via its dependencies. -This includes cycles of depth 1 (imported module imports me) to `Infinity`, if the +This includes cycles of depth 1 (imported module imports me) to `"∞"` (or `Infinity`), if the [`maxDepth`](#maxdepth) option is not set. ```js diff --git a/docs/rules/no-extraneous-dependencies.md b/docs/rules/no-extraneous-dependencies.md index 295590ccd0..cdc0a913fe 100644 --- a/docs/rules/no-extraneous-dependencies.md +++ b/docs/rules/no-extraneous-dependencies.md @@ -1,7 +1,7 @@ # import/no-extraneous-dependencies: Forbid the use of extraneous packages Forbid the import of external modules that are not declared in the `package.json`'s `dependencies`, `devDependencies`, `optionalDependencies`, `peerDependencies`, or `bundledDependencies`. -The closest parent `package.json` will be used. If no `package.json` is found, the rule will not lint anything. This behaviour can be changed with the rule option `packageDir`. +The closest parent `package.json` will be used. If no `package.json` is found, the rule will not lint anything. This behavior can be changed with the rule option `packageDir`. Modules have to be installed for this rule to work. @@ -13,7 +13,7 @@ This rule supports the following options: `optionalDependencies`: If set to `false`, then the rule will show an error when `optionalDependencies` are imported. Defaults to `true`. -`peerDependencies`: If set to `false`, then the rule will show an error when `peerDependencies` are imported. Defaults to `false`. +`peerDependencies`: If set to `false`, then the rule will show an error when `peerDependencies` are imported. Defaults to `true`. `bundledDependencies`: If set to `false`, then the rule will show an error when `bundledDependencies` are imported. Defaults to `true`. diff --git a/docs/rules/no-import-module-exports.md b/docs/rules/no-import-module-exports.md new file mode 100644 index 0000000000..8131fd5f78 --- /dev/null +++ b/docs/rules/no-import-module-exports.md @@ -0,0 +1,74 @@ +# no-import-module-exports + +Reports the use of import declarations with CommonJS exports in any module +except for the [main module](https://docs.npmjs.com/files/package.json#main). + +If you have multiple entry points or are using `js:next` this rule includes an +`exceptions` option which you can use to exclude those files from the rule. + +## Options + +#### `exceptions` + - An array of globs. The rule will be omitted from any file that matches a glob + in the options array. For example, the following setting will omit the rule + in the `some-file.js` file. + +```json +"import/no-import-module-exports": ["error", { + "exceptions": ["**/*/some-file.js"] +}] +``` + +## Rule Details + +### Fail + +```js +import { stuff } from 'starwars' +module.exports = thing + +import * as allThings from 'starwars' +exports.bar = thing + +import thing from 'other-thing' +exports.foo = bar + +import thing from 'starwars' +const baz = module.exports = thing +console.log(baz) +``` + +### Pass +Given the following package.json: + +```json +{ + "main": "lib/index.js", +} +``` + +```js +import thing from 'other-thing' +export default thing + +const thing = require('thing') +module.exports = thing + +const thing = require('thing') +exports.foo = bar + +import thing from 'otherthing' +console.log(thing.module.exports) + +// in lib/index.js +import foo from 'path'; +module.exports = foo; + +// in some-file.js +// eslint import/no-import-module-exports: ["error", {"exceptions": ["**/*/some-file.js"]}] +import foo from 'path'; +module.exports = foo; +``` + +### Further Reading + - [webpack issue #4039](https://github.com/webpack/webpack/issues/4039) diff --git a/docs/rules/no-internal-modules.md b/docs/rules/no-internal-modules.md index 7bbb2edd16..d957e26f36 100644 --- a/docs/rules/no-internal-modules.md +++ b/docs/rules/no-internal-modules.md @@ -4,7 +4,10 @@ Use this rule to prevent importing the submodules of other modules. ## Rule Details -This rule has one option, `allow` which is an array of [minimatch/glob patterns](https://github.com/isaacs/node-glob#glob-primer) patterns that whitelist paths and import statements that can be imported with reaching. +This rule has two mutally exclusive options that are arrays of [minimatch/glob patterns](https://github.com/isaacs/node-glob#glob-primer) patterns: + +- `allow` that include paths and import statements that can be imported with reaching. +- `forbid` that exclude paths and import statements that can be imported with reaching. ### Examples @@ -33,7 +36,7 @@ And the .eslintrc file: ... "rules": { "import/no-internal-modules": [ "error", { - "allow": [ "**/actions/*", "source-map-support/*" ] + "allow": [ "**/actions/*", "source-map-support/*" ], } ] } } @@ -68,3 +71,62 @@ import getUser from '../actions/getUser'; export * from 'source-map-support/register'; export { settings } from '../app'; ``` + +Given the following folder structure: + +``` +my-project +├── actions +│ └── getUser.js +│ └── updateUser.js +├── reducer +│ └── index.js +│ └── user.js +├── redux +│ └── index.js +│ └── configureStore.js +└── app +│ └── index.js +│ └── settings.js +└── entry.js +``` + +And the .eslintrc file: +``` +{ + ... + "rules": { + "import/no-internal-modules": [ "error", { + "forbid": [ "**/actions/*", "source-map-support/*" ], + } ] + } +} +``` + +The following patterns are considered problems: + +```js +/** + * in my-project/entry.js + */ + +import 'source-map-support/register'; +import getUser from '../actions/getUser'; + +export * from 'source-map-support/register'; +export getUser from '../actions/getUser'; +``` + +The following patterns are NOT considered problems: + +```js +/** + * in my-project/entry.js + */ + +import 'source-map-support'; +import { getUser } from '../actions'; + +export * from 'source-map-support'; +export { getUser } from '../actions'; +``` diff --git a/docs/rules/no-named-as-default.md b/docs/rules/no-named-as-default.md index 0a92b7b517..0421413833 100644 --- a/docs/rules/no-named-as-default.md +++ b/docs/rules/no-named-as-default.md @@ -31,7 +31,7 @@ For post-ES2015 `export` extensions, this also prevents exporting the default fr ```js // valid: -export foo from './foo.js' +export foo from './foo.js'; // message: Using exported name 'bar' as identifier for default export. export bar from './foo.js'; diff --git a/docs/rules/no-named-default.md b/docs/rules/no-named-default.md index 86fb41d615..bb8b13bca4 100644 --- a/docs/rules/no-named-default.md +++ b/docs/rules/no-named-default.md @@ -4,6 +4,10 @@ Reports use of a default export as a locally named import. Rationale: the syntax exists to import default exports expressively, let's use it. +Note that type imports, as used by [Flow], are always ignored. + +[Flow]: https://flow.org/ + ## Rule Details Given: diff --git a/docs/rules/no-relative-packages.md b/docs/rules/no-relative-packages.md new file mode 100644 index 0000000000..d5a0684932 --- /dev/null +++ b/docs/rules/no-relative-packages.md @@ -0,0 +1,66 @@ +# import/no-relative-packages + +Use this rule to prevent importing packages through relative paths. + +It's useful in Yarn/Lerna workspaces, were it's possible to import a sibling +package using `../package` relative path, while direct `package` is the correct one. + + +### Examples + +Given the following folder structure: + +``` +my-project +├── packages +│ ├── foo +│ │ ├── index.js +│ │ └── package.json +│ └── bar +│ ├── index.js +│ └── package.json +└── entry.js +``` + +And the .eslintrc file: +``` +{ + ... + "rules": { + "import/no-relative-packages": "error" + } +} +``` + +The following patterns are considered problems: + +```js +/** + * in my-project/packages/foo.js + */ + +import bar from '../bar'; // Import sibling package using relative path +import entry from '../../entry.js'; // Import from parent package using relative path + +/** + * in my-project/entry.js + */ + +import bar from './packages/bar'; // Import child package using relative path +``` + +The following patterns are NOT considered problems: + +```js +/** + * in my-project/packages/foo.js + */ + +import bar from 'bar'; // Import sibling package using package name + +/** + * in my-project/entry.js + */ + +import bar from 'bar'; // Import sibling package using package name +``` diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md index 8c234202f8..4c04333ad3 100644 --- a/docs/rules/no-unused-modules.md +++ b/docs/rules/no-unused-modules.md @@ -12,7 +12,7 @@ Note: dynamic imports are currently not supported. In order for this plugin to work, one of the options `missingExports` or `unusedExports` must be enabled (see "Options" section below). In the future, these options will be enabled by default (see https://github.com/benmosher/eslint-plugin-import/issues/1324) -Example: +Example: ``` "rules: { ...otherRules, @@ -27,13 +27,13 @@ This rule takes the following option: - **`missingExports`**: if `true`, files without any exports are reported (defaults to `false`) - **`unusedExports`**: if `true`, exports without any static usage within other modules are reported (defaults to `false`) - `src`: an array with files/paths to be analyzed. It only applies to unused exports. Defaults to `process.cwd()`, if not provided -- `ignoreExports`: an array with files/paths for which unused exports will not be reported (e.g module entry points in a published package) +- `ignoreExports`: an array with files/paths for which unused exports will not be reported (e.g module entry points in a published package) ### Example for missing exports #### The following will be reported ```js -const class MyClass { /*...*/ } +const class MyClass { /*...*/ } function makeClass() { return new MyClass(...arguments) } ``` @@ -41,10 +41,10 @@ function makeClass() { return new MyClass(...arguments) } #### The following will not be reported ```js -export default function () { /*...*/ } +export default function () { /*...*/ } ``` ```js -export const foo = function () { /*...*/ } +export const foo = function () { /*...*/ } ``` ```js export { foo, bar } @@ -61,7 +61,7 @@ import { f } from 'file-b' import * as fileC from 'file-c' export { default, i0 } from 'file-d' // both will be reported -export const j = 99 // will be reported +export const j = 99 // will be reported ``` and file-d: ```js diff --git a/docs/rules/order.md b/docs/rules/order.md index 3aa41bbf50..848c91ddef 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -2,7 +2,8 @@ Enforce a convention in the order of `require()` / `import` statements. +(fixable) The `--fix` option on the [command line] automatically fixes problems reported by this rule. -The order is as shown in the following example: + +With the [`groups`](#groups-array) option set to `["builtin", "external", "internal", "parent", "sibling", "index", "object"]` the order is as shown in the following example: ```js // 1. node "builtin" modules @@ -22,6 +23,10 @@ import bar from './bar'; import baz from './bar/baz'; // 6. "index" of the current directory import main from './'; +// 7. "object"-imports (only available in TypeScript) +import log = console.log; +// 8. "type" imports (only available in Flow and TypeScript) +import type { Foo } from 'foo'; ``` Unassigned imports are ignored, as the order they are imported in may be important. @@ -77,12 +82,15 @@ This rule supports the following options: ### `groups: [array]`: -How groups are defined, and the order to respect. `groups` must be an array of `string` or [`string`]. The only allowed `string`s are: `"builtin"`, `"external"`, `"internal"`, `"unknown"`, `"parent"`, `"sibling"`, `"index"`. The enforced order is the same as the order of each element in a group. Omitted types are implicitly grouped together as the last element. Example: +How groups are defined, and the order to respect. `groups` must be an array of `string` or [`string`]. The only allowed `string`s are: +`"builtin"`, `"external"`, `"internal"`, `"unknown"`, `"parent"`, `"sibling"`, `"index"`, `"object"`, `"type"`. +The enforced order is the same as the order of each element in a group. Omitted types are implicitly grouped together as the last element. Example: ```js [ 'builtin', // Built-in types are first ['sibling', 'parent'], // Then sibling and parent types. They can be mingled together 'index', // Then the index file + 'object', // Then the rest: internal and external type ] ``` @@ -91,7 +99,7 @@ The default value is `["builtin", "external", "parent", "sibling", "index"]`. You can set the options like this: ```js -"import/order": ["error", {"groups": ["index", "sibling", "parent", "internal", "external", "builtin"]}] +"import/order": ["error", {"groups": ["index", "sibling", "parent", "internal", "external", "builtin", "object", "type"]}] ``` ### `pathGroups: [array of objects]`: @@ -248,6 +256,33 @@ import React, { PureComponent } from 'react'; import { compose, apply } from 'xcompose'; ``` +### `warnOnUnassignedImports: true|false`: + +* default: `false` + +Warns when unassigned imports are out of order. These warning will not be fixed +with `--fix` because unassigned imports are used for side-effects and changing the +import of order of modules with side effects can not be done automatically in a +way that is safe. + +This will fail the rule check: + +```js +/* eslint import/order: ["error", {"warnOnUnassignedImports": true}] */ +import fs from 'fs'; +import './styles.css'; +import path from 'path'; +``` + +While this will pass: + +```js +/* eslint import/order: ["error", {"warnOnUnassignedImports": true}] */ +import fs from 'fs'; +import path from 'path'; +import './styles.css'; +``` + ## Related - [`import/external-module-folders`] setting diff --git a/memo-parser/index.js b/memo-parser/index.js index b64f854219..de558ffa3e 100644 --- a/memo-parser/index.js +++ b/memo-parser/index.js @@ -1,10 +1,10 @@ -'use strict' +'use strict'; -const crypto = require('crypto') - , moduleRequire = require('eslint-module-utils/module-require').default - , hashObject = require('eslint-module-utils/hash').hashObject +const crypto = require('crypto'); +const moduleRequire = require('eslint-module-utils/module-require').default; +const hashObject = require('eslint-module-utils/hash').hashObject; -const cache = new Map() +const cache = new Map(); // must match ESLint default options or we'll miss the cache every time const parserOptions = { @@ -14,28 +14,28 @@ const parserOptions = { tokens: true, comment: true, attachComment: true, -} +}; exports.parse = function parse(content, options) { - options = Object.assign({}, options, parserOptions) + options = Object.assign({}, options, parserOptions); if (!options.filePath) { - throw new Error('no file path provided!') + throw new Error('no file path provided!'); } - const keyHash = crypto.createHash('sha256') - keyHash.update(content) - hashObject(options, keyHash) + const keyHash = crypto.createHash('sha256'); + keyHash.update(content); + hashObject(options, keyHash); - const key = keyHash.digest('hex') + const key = keyHash.digest('hex'); - let ast = cache.get(key) - if (ast != null) return ast + let ast = cache.get(key); + if (ast != null) return ast; - const realParser = moduleRequire(options.parser) + const realParser = moduleRequire(options.parser); - ast = realParser.parse(content, options) - cache.set(key, ast) + ast = realParser.parse(content, options); + cache.set(key, ast); - return ast -} + return ast; +}; diff --git a/memo-parser/package.json b/memo-parser/package.json index 69996d33eb..9aa7647b8d 100644 --- a/memo-parser/package.json +++ b/memo-parser/package.json @@ -29,6 +29,6 @@ "eslint": ">=3.5.0" }, "dependencies": { - "eslint-module-utils": "^2.5.0" + "eslint-module-utils": "^2.6.1" } } diff --git a/package.json b/package.json index 9b42324f66..eedf4e3504 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.21.2", + "version": "2.23.4", "description": "Import with sanity.", "engines": { "node": ">=4" @@ -30,7 +30,8 @@ "test": "npm run tests-only", "test-compiled": "npm run prepublish && BABEL_ENV=testCompiled mocha --compilers js:babel-register tests/src", "test-all": "node --require babel-register ./scripts/testAll", - "prepublish": "not-in-publish || npm run build", + "prepublishOnly": "safe-publish-latest && npm run build", + "prepublish": "not-in-publish || npm run prepublishOnly", "coveralls": "nyc report --reporter lcovonly && coveralls < ./coverage/lcov.info" }, "repository": { @@ -55,31 +56,31 @@ "devDependencies": { "@eslint/import-test-order-redirect-scoped": "file:./tests/files/order-redirect-scoped", "@test-scope/some-module": "file:./tests/files/symlinked-module", - "@typescript-eslint/parser": "^2.23.0", - "array.prototype.flatmap": "^1.2.3", + "@typescript-eslint/parser": "^2.23.0 || ^3.3.0", + "array.prototype.flatmap": "^1.2.4", "babel-cli": "^6.26.0", "babel-core": "^6.26.3", - "babel-eslint": "^8.2.6", + "babel-eslint": "=8.0.3 || ^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", + "chai": "^4.3.4", + "coveralls": "^3.1.0", "cross-env": "^4.0.0", "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0", "eslint-import-resolver-node": "file:./resolvers/node", - "eslint-import-resolver-typescript": "^1.0.2", + "eslint-import-resolver-typescript": "^1.0.2 || ^1.1.1", "eslint-import-resolver-webpack": "file:./resolvers/webpack", "eslint-import-test-order-redirect": "file:./tests/files/order-redirect", "eslint-module-utils": "file:./utils", - "eslint-plugin-eslint-plugin": "^2.2.1", + "eslint-plugin-eslint-plugin": "^2.3.0", "eslint-plugin-import": "2.x", - "eslint-plugin-json": "^2.1.1", + "eslint-plugin-json": "^2.1.2", "fs-copy-file-sync": "^1.1.1", - "glob": "^7.1.6", + "glob": "^7.1.7", "in-publish": "^2.0.1", "linklocal": "^2.8.2", "lodash.isarray": "^4.0.0", @@ -88,38 +89,30 @@ "nyc": "^11.9.0", "redux": "^3.7.2", "rimraf": "^2.7.1", + "safe-publish-latest": "^1.1.4", "semver": "^6.3.0", "sinon": "^2.4.1", - "typescript": "~3.9.5", - "typescript-eslint-parser": "^22.0.0" + "typescript": "^2.8.1 || ~3.9.5", + "typescript-eslint-parser": "^15 || ^22.0.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0" }, "dependencies": { - "array-includes": "^3.1.1", - "array.prototype.flat": "^1.2.3", - "contains-path": "^0.1.0", + "array-includes": "^3.1.3", + "array.prototype.flat": "^1.2.4", "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.3", - "eslint-module-utils": "^2.6.0", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-module-utils": "^2.6.1", + "find-up": "^2.0.0", "has": "^1.0.3", + "is-core-module": "^2.4.0", "minimatch": "^3.0.4", - "object.values": "^1.1.1", - "read-pkg-up": "^2.0.0", - "resolve": "^1.17.0", + "object.values": "^1.1.3", + "pkg-up": "^2.0.0", + "read-pkg-up": "^3.0.0", + "resolve": "^1.20.0", "tsconfig-paths": "^3.9.0" - }, - "nyc": { - "require": [ - "babel-register" - ], - "sourceMap": false, - "instrument": false, - "include": [ - "src/", - "resolvers/" - ] } } diff --git a/resolvers/node/CHANGELOG.md b/resolvers/node/CHANGELOG.md index 1418844082..8fa31bed7d 100644 --- a/resolvers/node/CHANGELOG.md +++ b/resolvers/node/CHANGELOG.md @@ -4,9 +4,15 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## Unreleased + +## v0.3.4 - 2020-06-16 ### Added - add `.node` extension ([#1663]) +## v0.3.3 - 2020-01-10 +### Changed +- [meta] copy LICENSE file to all npm packages on prepublish ([#1595], thanks [@opichals]) + ## v0.3.2 - 2018-01-05 ### Added - `.mjs` extension detected by default to support `experimental-modules` ([#939]) @@ -45,6 +51,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [#438]: https://github.com/benmosher/eslint-plugin-import/pull/438 [#1663]: https://github.com/benmosher/eslint-plugin-import/issues/1663 +[#1595]: https://github.com/benmosher/eslint-plugin-import/pull/1595 [#939]: https://github.com/benmosher/eslint-plugin-import/issues/939 [#531]: https://github.com/benmosher/eslint-plugin-import/issues/531 [#437]: https://github.com/benmosher/eslint-plugin-import/issues/437 @@ -53,3 +60,4 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [@lukeapage]: https://github.com/lukeapage [@SkeLLLa]: https://github.com/SkeLLLa [@ljharb]: https://github.com/ljharb +[@opichals]: https://github.com/opichals diff --git a/resolvers/node/index.js b/resolvers/node/index.js index bf2aab3820..85550ddf7a 100644 --- a/resolvers/node/index.js +++ b/resolvers/node/index.js @@ -1,47 +1,49 @@ -var resolve = require('resolve') - , path = require('path') +'use strict'; -var log = require('debug')('eslint-plugin-import:resolver:node') +const resolve = require('resolve'); +const path = require('path'); -exports.interfaceVersion = 2 +const log = require('debug')('eslint-plugin-import:resolver:node'); + +exports.interfaceVersion = 2; exports.resolve = function (source, file, config) { - log('Resolving:', source, 'from:', file) - var resolvedPath + log('Resolving:', source, 'from:', file); + let resolvedPath; if (resolve.isCore(source)) { - log('resolved to core') - return { found: true, path: null } + log('resolved to core'); + return { found: true, path: null }; } try { - resolvedPath = resolve.sync(source, opts(file, config)) - log('Resolved to:', resolvedPath) - return { found: true, path: resolvedPath } + resolvedPath = resolve.sync(source, opts(file, config)); + log('Resolved to:', resolvedPath); + return { found: true, path: resolvedPath }; } catch (err) { - log('resolve threw error:', err) - return { found: false } + log('resolve threw error:', err); + return { found: false }; } -} +}; function opts(file, config) { return Object.assign({ - // more closely matches Node (#333) - // plus 'mjs' for native modules! (#939) - extensions: ['.mjs', '.js', '.json', '.node'], - }, - config, - { - // path.resolve will handle paths relative to CWD - basedir: path.dirname(path.resolve(file)), - packageFilter: packageFilter, - - }) + // more closely matches Node (#333) + // plus 'mjs' for native modules! (#939) + extensions: ['.mjs', '.js', '.json', '.node'], + }, + config, + { + // path.resolve will handle paths relative to CWD + basedir: path.dirname(path.resolve(file)), + packageFilter: packageFilter, + + }); } function packageFilter(pkg) { if (pkg['jsnext:main']) { - pkg['main'] = pkg['jsnext:main'] + pkg['main'] = pkg['jsnext:main']; } - return pkg + return pkg; } diff --git a/resolvers/node/package.json b/resolvers/node/package.json index 03eadafbf6..336e8fb0e9 100644 --- a/resolvers/node/package.json +++ b/resolvers/node/package.json @@ -1,6 +1,6 @@ { "name": "eslint-import-resolver-node", - "version": "0.3.3", + "version": "0.3.4", "description": "Node default behavior import resolution plugin for eslint-plugin-import.", "main": "index.js", "files": [ @@ -30,18 +30,13 @@ }, "homepage": "https://github.com/benmosher/eslint-plugin-import", "dependencies": { - "debug": "^2.6.9", - "resolve": "^1.13.1" + "debug": "^3.2.7", + "resolve": "^1.20.0" }, "devDependencies": { "chai": "^3.5.0", - "coveralls": "^3.0.0", + "coveralls": "^3.1.0", "mocha": "^3.5.3", - "nyc": "^11.7.1" - }, - "nyc": { - "exclude": [ - "test/" - ] + "nyc": "^11.9.0" } } diff --git a/resolvers/node/test/.eslintrc b/resolvers/node/test/.eslintrc deleted file mode 100644 index 5a1ff85fa5..0000000000 --- a/resolvers/node/test/.eslintrc +++ /dev/null @@ -1,6 +0,0 @@ ---- -env: - mocha: true - es6: false -rules: - quotes: 0 diff --git a/resolvers/node/test/native.js b/resolvers/node/test/native.js index 8212295922..134f23bbce 100644 --- a/resolvers/node/test/native.js +++ b/resolvers/node/test/native.js @@ -1 +1 @@ -exports.natively = function () { return "but where do we feature?" } \ No newline at end of file +exports.natively = function () { return 'but where do we feature?'; }; diff --git a/resolvers/node/test/paths.js b/resolvers/node/test/paths.js index d50366d13c..1c42b46167 100644 --- a/resolvers/node/test/paths.js +++ b/resolvers/node/test/paths.js @@ -1,60 +1,60 @@ -var expect = require('chai').expect +const expect = require('chai').expect; -var path = require('path') -var node = require('../index.js') +const path = require('path'); +const node = require('../index.js'); -describe("paths", function () { - it("handles base path relative to CWD", function () { +describe('paths', function () { + it('handles base path relative to CWD', function () { expect(node.resolve('../', './test/file.js')) .to.have.property('path') - .equal(path.resolve(__dirname, '../index.js')) - }) -}) + .equal(path.resolve(__dirname, '../index.js')); + }); +}); -describe("core", function () { - it("returns found, but null path, for core Node modules", function () { - var resolved = node.resolve('fs', "./test/file.js") - expect(resolved).has.property("found", true) - expect(resolved).has.property("path", null) - }) -}) +describe('core', function () { + it('returns found, but null path, for core Node modules', function () { + const resolved = node.resolve('fs', './test/file.js'); + expect(resolved).has.property('found', true); + expect(resolved).has.property('path', null); + }); +}); -describe("default options", function () { +describe('default options', function () { - it("finds .json files", function () { + it('finds .json files', function () { expect(node.resolve('./data', './test/file.js')) .to.have.property('path') - .equal(path.resolve(__dirname, './data.json')) - }) + .equal(path.resolve(__dirname, './data.json')); + }); it("ignores .json files if 'extensions' is redefined", function () { expect(node.resolve('./data', './test/file.js', { extensions: ['.js'] })) - .to.have.property('found', false) - }) + .to.have.property('found', false); + }); - it("finds mjs modules, with precedence over .js", function () { + it('finds mjs modules, with precedence over .js', function () { expect(node.resolve('./native', './test/file.js')) .to.have.property('path') - .equal(path.resolve(__dirname, './native.mjs')) - }) + .equal(path.resolve(__dirname, './native.mjs')); + }); - it("finds .node modules, with lowest precedence", function () { + it('finds .node modules, with lowest precedence', function () { expect(node.resolve('./native.node', './test/file.js')) .to.have.property('path') - .equal(path.resolve(__dirname, './native.node')) - }) + .equal(path.resolve(__dirname, './native.node')); + }); - it("finds .node modules", function () { + it('finds .node modules', function () { expect(node.resolve('./dot-node', './test/file.js')) .to.have.property('path') - .equal(path.resolve(__dirname, './dot-node.node')) - }) + .equal(path.resolve(__dirname, './dot-node.node')); + }); - it("still finds .js if explicit", function () { + it('still finds .js if explicit', function () { expect(node.resolve('./native.js', './test/file.js')) .to.have.property('path') - .equal(path.resolve(__dirname, './native.js')) - }) -}) + .equal(path.resolve(__dirname, './native.js')); + }); +}); diff --git a/resolvers/webpack/.eslintrc b/resolvers/webpack/.eslintrc new file mode 100644 index 0000000000..544167c4bb --- /dev/null +++ b/resolvers/webpack/.eslintrc @@ -0,0 +1,9 @@ +{ + "rules": { + "import/no-extraneous-dependencies": 1, + "no-console": 1, + }, + "env": { + "es6": true, + }, +} diff --git a/resolvers/webpack/.eslintrc.yml b/resolvers/webpack/.eslintrc.yml deleted file mode 100644 index febeb09cfa..0000000000 --- a/resolvers/webpack/.eslintrc.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -rules: - import/no-extraneous-dependencies: 1 - no-console: 1 diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index e06203823d..edc67627ff 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -5,9 +5,34 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +## 0.13.1 - 2021-05-13 + +### Added +- add support for webpack5 'externals function' ([#2023], thanks [@jet2jet]) + +### Changed +- Add warning about async Webpack configs ([#1962], thanks [@ogonkov]) +- Replace `node-libs-browser` with `is-core-module` ([#1967], thanks [@andersk]) +- [meta] add "engines" field to document existing requirements +- [Refactor] use `is-regex` instead of `instanceof RegExp` +- [Refactor] use `Array.isArray` instead of `instanceof Array` +- [deps] update `debug`, `interpret`, `is-core-module`, `lodash`, `resolve` + +## 0.13.0 - 2020-09-27 + +### Breaking +- [Breaking] Allow to resolve config path relative to working directory (#1276) + +## 0.12.2 - 2020-06-16 + ### Fixed - [fix] provide config fallback ([#1705], thanks [@migueloller]) +## 0.12.1 - 2020-01-10 + +### Changed +- [meta] copy LICENSE file to all npm packages on prepublish ([#1595], thanks [@opichals]) + ## 0.12.0 - 2019-12-07 ### Added @@ -125,7 +150,11 @@ 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]). +[#2023]: https://github.com/benmosher/eslint-plugin-import/pull/2023 +[#1967]: https://github.com/benmosher/eslint-plugin-import/pull/1967 +[#1962]: https://github.com/benmosher/eslint-plugin-import/pull/1962 [#1705]: https://github.com/benmosher/eslint-plugin-import/pull/1705 +[#1595]: https://github.com/benmosher/eslint-plugin-import/pull/1595 [#1503]: https://github.com/benmosher/eslint-plugin-import/pull/1503 [#1297]: https://github.com/benmosher/eslint-plugin-import/pull/1297 [#1261]: https://github.com/benmosher/eslint-plugin-import/pull/1261 @@ -179,3 +208,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [@echenley]: https://github.com/echenley [@Aghassi]: https://github.com/Aghassi [@migueloller]: https://github.com/migueloller +[@opichals]: https://github.com/opichals +[@andersk]: https://github.com/andersk +[@ogonkov]: https://github.com/ogonkov +[@jet2jet]: https://github.com/jet2jet \ No newline at end of file diff --git a/resolvers/webpack/README.md b/resolvers/webpack/README.md index 4fc3b0ffff..cdb9222fae 100644 --- a/resolvers/webpack/README.md +++ b/resolvers/webpack/README.md @@ -4,6 +4,10 @@ Webpack-literate module resolution plugin for [`eslint-plugin-import`](https://www.npmjs.com/package/eslint-plugin-import). +> :boom: Only "synchronous" Webpack configs are supported at the moment. +> If your config returns a `Promise`, this will cause problems. +> Consider splitting your asynchronous configuration to a separate config. + Published separately to allow pegging to a specific version in case of breaking changes. @@ -42,7 +46,7 @@ settings: config: 'webpack.dev.config.js' ``` -or with explicit config file name: +or with explicit config file index: ```yaml --- @@ -53,6 +57,16 @@ settings: config-index: 1 # take the config at index 1 ``` +or with explicit config file path relative to your projects's working directory: + +```yaml +--- +settings: + import/resolver: + webpack: + config: './configs/webpack.dev.config.js' +``` + or with explicit config object: ```yaml diff --git a/resolvers/webpack/config.js b/resolvers/webpack/config.js index d15f082daa..894787d1f3 100644 --- a/resolvers/webpack/config.js +++ b/resolvers/webpack/config.js @@ -5,4 +5,4 @@ module.exports = { settings: { 'import/resolver': 'webpack', }, -} +}; diff --git a/resolvers/webpack/index.js b/resolvers/webpack/index.js index 20c594847b..fb03fc4d19 100644 --- a/resolvers/webpack/index.js +++ b/resolvers/webpack/index.js @@ -1,18 +1,21 @@ -var findRoot = require('find-root') - , path = require('path') - , get = require('lodash/get') - , isEqual = require('lodash/isEqual') - , find = require('array-find') - , interpret = require('interpret') - , fs = require('fs') - , coreLibs = require('node-libs-browser') - , resolve = require('resolve') - , semver = require('semver') - , has = require('has') - -var log = require('debug')('eslint-plugin-import:resolver:webpack') - -exports.interfaceVersion = 2 +'use strict'; + +const findRoot = require('find-root'); +const path = require('path'); +const get = require('lodash/get'); +const isEqual = require('lodash/isEqual'); +const find = require('array-find'); +const interpret = require('interpret'); +const fs = require('fs'); +const isCore = require('is-core-module'); +const resolve = require('resolve'); +const semver = require('semver'); +const has = require('has'); +const isRegex = require('is-regex'); + +const log = require('debug')('eslint-plugin-import:resolver:webpack'); + +exports.interfaceVersion = 2; /** * Find the full path to 'source', given 'file' as a full reference path. @@ -35,178 +38,183 @@ exports.interfaceVersion = 2 exports.resolve = function (source, file, settings) { // strip loaders - var finalBang = source.lastIndexOf('!') + const finalBang = source.lastIndexOf('!'); if (finalBang >= 0) { - source = source.slice(finalBang + 1) + source = source.slice(finalBang + 1); } // strip resource query - var finalQuestionMark = source.lastIndexOf('?') + const finalQuestionMark = source.lastIndexOf('?'); if (finalQuestionMark >= 0) { - source = source.slice(0, finalQuestionMark) + source = source.slice(0, finalQuestionMark); } - var webpackConfig + let webpackConfig; - var configPath = get(settings, 'config') - /** + const _configPath = get(settings, 'config'); + /** * Attempt to set the current working directory. * If none is passed, default to the `cwd` where the config is located. */ - , cwd = get(settings, 'cwd') - , configIndex = get(settings, 'config-index') - , env = get(settings, 'env') - , argv = get(settings, 'argv', {}) - , packageDir + const cwd = get(settings, 'cwd'); + const configIndex = get(settings, 'config-index'); + const env = get(settings, 'env'); + const argv = get(settings, 'argv', {}); + let packageDir; - log('Config path from settings:', configPath) + let configPath = typeof _configPath === 'string' && _configPath.startsWith('.') + ? path.resolve(_configPath) + : _configPath; + + log('Config path from settings:', configPath); // see if we've got a config path, a config object, an array of config objects or a config function if (!configPath || typeof configPath === 'string') { - // see if we've got an absolute path - if (!configPath || !path.isAbsolute(configPath)) { - // if not, find ancestral package.json and use its directory as base for the path - packageDir = findRoot(path.resolve(file)) - if (!packageDir) throw new Error('package not found above ' + file) - } + // see if we've got an absolute path + if (!configPath || !path.isAbsolute(configPath)) { + // if not, find ancestral package.json and use its directory as base for the path + packageDir = findRoot(path.resolve(file)); + if (!packageDir) throw new Error('package not found above ' + file); + } - configPath = findConfigPath(configPath, packageDir) + configPath = findConfigPath(configPath, packageDir); - log('Config path resolved to:', configPath) - if (configPath) { - try { - webpackConfig = require(configPath) - } catch(e) { - console.log('Error resolving webpackConfig', e) - throw e - } - } else { - log('No config path found relative to', file, '; using {}') - webpackConfig = {} + log('Config path resolved to:', configPath); + if (configPath) { + try { + webpackConfig = require(configPath); + } catch(e) { + console.log('Error resolving webpackConfig', e); + throw e; } + } else { + log('No config path found relative to', file, '; using {}'); + webpackConfig = {}; + } - if (webpackConfig && webpackConfig.default) { - log('Using ES6 module "default" key instead of module.exports.') - webpackConfig = webpackConfig.default - } + if (webpackConfig && webpackConfig.default) { + log('Using ES6 module "default" key instead of module.exports.'); + webpackConfig = webpackConfig.default; + } } else { - webpackConfig = configPath - configPath = null + webpackConfig = configPath; + configPath = null; } if (typeof webpackConfig === 'function') { - webpackConfig = webpackConfig(env, argv) + webpackConfig = webpackConfig(env, argv); } if (Array.isArray(webpackConfig)) { webpackConfig = webpackConfig.map(cfg => { if (typeof cfg === 'function') { - return cfg(env, argv) + return cfg(env, argv); } - return cfg - }) + return cfg; + }); if (typeof configIndex !== 'undefined' && webpackConfig.length > configIndex) { - webpackConfig = webpackConfig[configIndex] + webpackConfig = webpackConfig[configIndex]; } else { webpackConfig = find(webpackConfig, function findFirstWithResolve(config) { - return !!config.resolve - }) + return !!config.resolve; + }); } } + if (typeof webpackConfig.then === 'function') { + webpackConfig = {}; + + console.warn('Webpack config returns a `Promise`; that signature is not supported at the moment. Using empty object instead.'); + } + if (webpackConfig == null) { - webpackConfig = {} + webpackConfig = {}; - console.warn('No webpack configuration with a "resolve" field found. Using empty object instead') + console.warn('No webpack configuration with a "resolve" field found. Using empty object instead.'); } - log('Using config: ', webpackConfig) + log('Using config: ', webpackConfig); + + const resolveSync = getResolveSync(configPath, webpackConfig, cwd); // externals - if (findExternal(source, webpackConfig.externals, path.dirname(file))) { - return { found: true, path: null } + if (findExternal(source, webpackConfig.externals, path.dirname(file), resolveSync)) { + return { found: true, path: null }; } // otherwise, resolve "normally" - var resolveSync = getResolveSync(configPath, webpackConfig, cwd) try { - return { found: true, path: resolveSync(path.dirname(file), source) } + return { found: true, path: resolveSync(path.dirname(file), source) }; } catch (err) { - if (source in coreLibs) { - return { found: true, path: coreLibs[source] } + if (isCore(source)) { + return { found: true, path: null }; } - log('Error during module resolution:', err) - return { found: false } + log('Error during module resolution:', err); + return { found: false }; } -} +}; -var MAX_CACHE = 10 -var _cache = [] +const MAX_CACHE = 10; +const _cache = []; function getResolveSync(configPath, webpackConfig, cwd) { - var cacheKey = { configPath: configPath, webpackConfig: webpackConfig } - var cached = find(_cache, function (entry) { return isEqual(entry.key, cacheKey) }) + const cacheKey = { configPath: configPath, webpackConfig: webpackConfig }; + let cached = find(_cache, function (entry) { return isEqual(entry.key, cacheKey); }); if (!cached) { cached = { key: cacheKey, value: createResolveSync(configPath, webpackConfig, cwd), - } + }; // put in front and pop last item if (_cache.unshift(cached) > MAX_CACHE) { - _cache.pop() + _cache.pop(); } } - return cached.value + return cached.value; } function createResolveSync(configPath, webpackConfig, cwd) { - var webpackRequire - , basedir = null + let webpackRequire; + let basedir = null; if (typeof configPath === 'string') { // This can be changed via the settings passed in when defining the resolver - basedir = cwd || configPath - log(`Attempting to load webpack path from ${basedir}`) + basedir = cwd || configPath; + log(`Attempting to load webpack path from ${basedir}`); } try { // Attempt to resolve webpack from the given `basedir` - var webpackFilename = resolve.sync('webpack', { basedir, preserveSymlinks: false }) - var webpackResolveOpts = { basedir: path.dirname(webpackFilename), preserveSymlinks: false } + const webpackFilename = resolve.sync('webpack', { basedir, preserveSymlinks: false }); + const webpackResolveOpts = { basedir: path.dirname(webpackFilename), preserveSymlinks: false }; webpackRequire = function (id) { - return require(resolve.sync(id, webpackResolveOpts)) - } + return require(resolve.sync(id, webpackResolveOpts)); + }; } catch (e) { // Something has gone wrong (or we're in a test). Use our own bundled // enhanced-resolve. - log('Using bundled enhanced-resolve.') - webpackRequire = require + log('Using bundled enhanced-resolve.'); + webpackRequire = require; } - var enhancedResolvePackage = webpackRequire('enhanced-resolve/package.json') - var enhancedResolveVersion = enhancedResolvePackage.version - log('enhanced-resolve version:', enhancedResolveVersion) + const enhancedResolvePackage = webpackRequire('enhanced-resolve/package.json'); + const enhancedResolveVersion = enhancedResolvePackage.version; + log('enhanced-resolve version:', enhancedResolveVersion); - var resolveConfig = webpackConfig.resolve || {} + const resolveConfig = webpackConfig.resolve || {}; if (semver.major(enhancedResolveVersion) >= 2) { - return createWebpack2ResolveSync(webpackRequire, resolveConfig) + return createWebpack2ResolveSync(webpackRequire, resolveConfig); } - return createWebpack1ResolveSync(webpackRequire, resolveConfig, webpackConfig.plugins) -} - -function createWebpack2ResolveSync(webpackRequire, resolveConfig) { - var EnhancedResolve = webpackRequire('enhanced-resolve') - - return EnhancedResolve.create.sync(Object.assign({}, webpack2DefaultResolveConfig, resolveConfig)) + return createWebpack1ResolveSync(webpackRequire, resolveConfig, webpackConfig.plugins); } /** @@ -214,36 +222,46 @@ function createWebpack2ResolveSync(webpackRequire, resolveConfig) { * https://github.com/webpack/webpack/blob/v2.1.0-beta.20/lib/WebpackOptionsDefaulter.js#L72-L87 * @type {Object} */ -var webpack2DefaultResolveConfig = { +const webpack2DefaultResolveConfig = { unsafeCache: true, // Probably a no-op, since how can we cache anything at all here? modules: ['node_modules'], extensions: ['.js', '.json'], aliasFields: ['browser'], mainFields: ['browser', 'module', 'main'], +}; + +function createWebpack2ResolveSync(webpackRequire, resolveConfig) { + const EnhancedResolve = webpackRequire('enhanced-resolve'); + + return EnhancedResolve.create.sync(Object.assign({}, webpack2DefaultResolveConfig, resolveConfig)); } +/** + * webpack 1 defaults: http://webpack.github.io/docs/configuration.html#resolve-packagemains + * @type {Array} + */ +const webpack1DefaultMains = [ + 'webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main', +]; + // adapted from tests & // https://github.com/webpack/webpack/blob/v1.13.0/lib/WebpackOptionsApply.js#L322 function createWebpack1ResolveSync(webpackRequire, resolveConfig, plugins) { - var Resolver = webpackRequire('enhanced-resolve/lib/Resolver') - var SyncNodeJsInputFileSystem = webpackRequire('enhanced-resolve/lib/SyncNodeJsInputFileSystem') - - var ModuleAliasPlugin = webpackRequire('enhanced-resolve/lib/ModuleAliasPlugin') - var ModulesInDirectoriesPlugin = - webpackRequire('enhanced-resolve/lib/ModulesInDirectoriesPlugin') - var ModulesInRootPlugin = webpackRequire('enhanced-resolve/lib/ModulesInRootPlugin') - var ModuleAsFilePlugin = webpackRequire('enhanced-resolve/lib/ModuleAsFilePlugin') - var ModuleAsDirectoryPlugin = webpackRequire('enhanced-resolve/lib/ModuleAsDirectoryPlugin') - var DirectoryDescriptionFilePlugin = - webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFilePlugin') - var DirectoryDefaultFilePlugin = - webpackRequire('enhanced-resolve/lib/DirectoryDefaultFilePlugin') - var FileAppendPlugin = webpackRequire('enhanced-resolve/lib/FileAppendPlugin') - var ResultSymlinkPlugin = webpackRequire('enhanced-resolve/lib/ResultSymlinkPlugin') - var DirectoryDescriptionFileFieldAliasPlugin = - webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFileFieldAliasPlugin') - - var resolver = new Resolver(new SyncNodeJsInputFileSystem()) + const Resolver = webpackRequire('enhanced-resolve/lib/Resolver'); + const SyncNodeJsInputFileSystem = webpackRequire('enhanced-resolve/lib/SyncNodeJsInputFileSystem'); + + const ModuleAliasPlugin = webpackRequire('enhanced-resolve/lib/ModuleAliasPlugin'); + const ModulesInDirectoriesPlugin = webpackRequire('enhanced-resolve/lib/ModulesInDirectoriesPlugin'); + const ModulesInRootPlugin = webpackRequire('enhanced-resolve/lib/ModulesInRootPlugin'); + const ModuleAsFilePlugin = webpackRequire('enhanced-resolve/lib/ModuleAsFilePlugin'); + const ModuleAsDirectoryPlugin = webpackRequire('enhanced-resolve/lib/ModuleAsDirectoryPlugin'); + const DirectoryDescriptionFilePlugin = webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFilePlugin'); + const DirectoryDefaultFilePlugin = webpackRequire('enhanced-resolve/lib/DirectoryDefaultFilePlugin'); + const FileAppendPlugin = webpackRequire('enhanced-resolve/lib/FileAppendPlugin'); + const ResultSymlinkPlugin = webpackRequire('enhanced-resolve/lib/ResultSymlinkPlugin'); + const DirectoryDescriptionFileFieldAliasPlugin = webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFileFieldAliasPlugin'); + + const resolver = new Resolver(new SyncNodeJsInputFileSystem()); resolver.apply( resolveConfig.packageAlias @@ -265,10 +283,10 @@ function createWebpack1ResolveSync(webpackRequire, resolveConfig, plugins) { new DirectoryDefaultFilePlugin(['index']), new FileAppendPlugin(resolveConfig.extensions || ['', '.webpack.js', '.web.js', '.js']), new ResultSymlinkPlugin() - ) + ); - var resolvePlugins = [] + const resolvePlugins = []; // support webpack.ResolverPlugin if (plugins) { @@ -278,16 +296,16 @@ function createWebpack1ResolveSync(webpackRequire, resolveConfig, plugins) { plugin.constructor.name === 'ResolverPlugin' && Array.isArray(plugin.plugins) ) { - resolvePlugins.push.apply(resolvePlugins, plugin.plugins) + resolvePlugins.push.apply(resolvePlugins, plugin.plugins); } - }) + }); } - resolver.apply.apply(resolver, resolvePlugins) + resolver.apply.apply(resolver, resolvePlugins); return function() { - return resolver.resolveSync.apply(resolver, arguments) - } + return resolver.resolveSync.apply(resolver, arguments); + }; } /* eslint-disable */ @@ -306,106 +324,130 @@ function makeRootPlugin(ModulesInRootPlugin, name, root) { } /* eslint-enable */ -function findExternal(source, externals, context) { - if (!externals) return false +function findExternal(source, externals, context, resolveSync) { + if (!externals) return false; // string match - if (typeof externals === 'string') return (source === externals) + if (typeof externals === 'string') return (source === externals); // array: recurse - if (externals instanceof Array) { - return externals.some(function (e) { return findExternal(source, e, context) }) + if (Array.isArray(externals)) { + return externals.some(function (e) { return findExternal(source, e, context, resolveSync); }); } - if (externals instanceof RegExp) { - return externals.test(source) + if (isRegex(externals)) { + return externals.test(source); } if (typeof externals === 'function') { - var functionExternalFound = false - externals.call(null, context, source, function(err, value) { + let functionExternalFound = false; + const callback = function (err, value) { if (err) { - functionExternalFound = false + functionExternalFound = false; } else { - functionExternalFound = findExternal(source, value, context) + functionExternalFound = findExternal(source, value, context, resolveSync); + } + }; + // - for prior webpack 5, 'externals function' uses 3 arguments + // - for webpack 5, the count of arguments is less than 3 + if (externals.length === 3) { + externals.call(null, context, source, callback); + } else { + const ctx = { + context, + request: source, + contextInfo: { + issuer: '', + issuerLayer: null, + compiler: '', + }, + getResolve: () => (resolveContext, requestToResolve, cb) => { + if (cb) { + try { + cb(null, resolveSync(resolveContext, requestToResolve)); + } catch (e) { + cb(e); + } + } else { + log('getResolve without callback not supported'); + return Promise.reject(new Error('Not supported')); + } + }, + }; + const result = externals.call(null, ctx, callback); + // todo handling Promise object (using synchronous-promise package?) + if (result && typeof result.then === 'function') { + log('Asynchronous functions for externals not supported'); } - }) - return functionExternalFound + } + return functionExternalFound; } // else, vanilla object - for (var key in externals) { - if (!has(externals, key)) continue - if (source === key) return true + for (const key in externals) { + if (!has(externals, key)) continue; + if (source === key) return true; } - return false + return false; } -/** - * webpack 1 defaults: http://webpack.github.io/docs/configuration.html#resolve-packagemains - * @type {Array} - */ -var webpack1DefaultMains = [ - 'webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main', -] - function findConfigPath(configPath, packageDir) { - var extensions = Object.keys(interpret.extensions).sort(function(a, b) { - return a === '.js' ? -1 : b === '.js' ? 1 : a.length - b.length - }) - , extension + const extensions = Object.keys(interpret.extensions).sort(function(a, b) { + return a === '.js' ? -1 : b === '.js' ? 1 : a.length - b.length; + }); + let extension; if (configPath) { // extensions is not reused below, so safe to mutate it here. - extensions.reverse() + extensions.reverse(); extensions.forEach(function (maybeExtension) { if (extension) { - return + return; } if (configPath.substr(-maybeExtension.length) === maybeExtension) { - extension = maybeExtension + extension = maybeExtension; } - }) + }); // see if we've got an absolute path if (!path.isAbsolute(configPath)) { - configPath = path.join(packageDir, configPath) + configPath = path.join(packageDir, configPath); } } else { extensions.forEach(function (maybeExtension) { if (extension) { - return + return; } - var maybePath = path.resolve( + const maybePath = path.resolve( path.join(packageDir, 'webpack.config' + maybeExtension) - ) + ); if (fs.existsSync(maybePath)) { - configPath = maybePath - extension = maybeExtension + configPath = maybePath; + extension = maybeExtension; } - }) + }); } - registerCompiler(interpret.extensions[extension]) - return configPath + registerCompiler(interpret.extensions[extension]); + return configPath; } function registerCompiler(moduleDescriptor) { if(moduleDescriptor) { if(typeof moduleDescriptor === 'string') { - require(moduleDescriptor) + require(moduleDescriptor); } else if(!Array.isArray(moduleDescriptor)) { - moduleDescriptor.register(require(moduleDescriptor.module)) + moduleDescriptor.register(require(moduleDescriptor.module)); } else { - for(var i = 0; i < moduleDescriptor.length; i++) { + for(let i = 0; i < moduleDescriptor.length; i++) { try { - registerCompiler(moduleDescriptor[i]) - break + registerCompiler(moduleDescriptor[i]); + break; } catch(e) { - log('Failed to register compiler for moduleDescriptor[]:', i, moduleDescriptor) + log('Failed to register compiler for moduleDescriptor[]:', i, moduleDescriptor); } } } diff --git a/resolvers/webpack/package.json b/resolvers/webpack/package.json index 72959fa886..fc8c42e6da 100644 --- a/resolvers/webpack/package.json +++ b/resolvers/webpack/package.json @@ -1,6 +1,6 @@ { "name": "eslint-import-resolver-webpack", - "version": "0.12.1", + "version": "0.13.1", "description": "Resolve paths to dependencies, given a webpack.config.js. Plugin for eslint-plugin-import.", "main": "index.js", "scripts": { @@ -33,14 +33,15 @@ "homepage": "https://github.com/benmosher/eslint-plugin-import/tree/master/resolvers/webpack", "dependencies": { "array-find": "^1.0.0", - "debug": "^2.6.9", + "debug": "^3.2.7", "enhanced-resolve": "^0.9.1", "find-root": "^1.1.0", "has": "^1.0.3", - "interpret": "^1.2.0", - "lodash": "^4.17.15", - "node-libs-browser": "^1.0.0 || ^2.0.0", - "resolve": "^1.13.1", + "interpret": "^1.4.0", + "is-core-module": "^2.4.0", + "is-regex": "^1.1.3", + "lodash": "^4.17.21", + "resolve": "^1.20.0", "semver": "^5.7.1" }, "peerDependencies": { @@ -52,13 +53,12 @@ "babel-preset-es2015-argon": "latest", "babel-register": "^6.26.0", "chai": "^3.5.0", - "coveralls": "^3.0.0", + "coveralls": "^3.1.0", "mocha": "^3.5.3", - "nyc": "^11.7.1" + "nyc": "^11.9.0", + "webpack": "https://gist.github.com/ljharb/9cdb687f3806f8e6cb8a365d0b7840eb" }, - "nyc": { - "exclude": [ - "test/" - ] + "engines": { + "node": "^16 || ^15 || ^14 || ^13 || ^12 || ^11 || ^10 || ^9 || ^8 || ^7 || ^6" } } diff --git a/resolvers/webpack/test/.eslintrc b/resolvers/webpack/test/.eslintrc deleted file mode 100644 index 2ad1adee92..0000000000 --- a/resolvers/webpack/test/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ ---- -env: - mocha: true -rules: - quotes: 0 diff --git a/resolvers/webpack/test/alias.js b/resolvers/webpack/test/alias.js index f8cd210e42..06aad44699 100644 --- a/resolvers/webpack/test/alias.js +++ b/resolvers/webpack/test/alias.js @@ -1,137 +1,139 @@ -var chai = require('chai') - , expect = chai.expect - , path = require('path') +'use strict'; -var webpack = require('../index') +const chai = require('chai'); +const expect = chai.expect; +const path = require('path'); -var file = path.join(__dirname, 'files', 'dummy.js') +const webpack = require('../index'); -describe("resolve.alias", function () { - var resolved - before(function () { resolved = webpack.resolve('foo', file) }) +const file = path.join(__dirname, 'files', 'dummy.js'); - it("is found", function () { expect(resolved).to.have.property('found', true) }) +describe('resolve.alias', function () { + let resolved; + before(function () { resolved = webpack.resolve('foo', file); }); - it("is correct", function () { + it('is found', function () { expect(resolved).to.have.property('found', true); }); + + it('is correct', function () { expect(resolved).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js')) - }) -}) + .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js')); + }); +}); // todo: reimplement with resolver function / config -// describe.skip("webpack alias spec", function () { +// describe.skip('webpack alias spec', function () { // // from table: http://webpack.github.io/docs/configuration.html#resolve-alias // function tableLine(alias, xyz, xyzFile) { // describe(JSON.stringify(alias), function () { -// it("xyz: " + xyz, function () { -// expect(resolveAlias('xyz', alias)).to.equal(xyz) -// }) -// it("xyz/file: " + (xyzFile.name || xyzFile), function () { +// it('xyz: ' + xyz, function () { +// expect(resolveAlias('xyz', alias)).to.equal(xyz); +// }); +// it('xyz/file: ' + (xyzFile.name || xyzFile), function () { // if (xyzFile === Error) { -// expect(resolveAlias.bind(null, 'xyz/file', alias)).to.throw(xyzFile) +// expect(resolveAlias.bind(null, 'xyz/file', alias)).to.throw(xyzFile); // } else { -// expect(resolveAlias('xyz/file', alias)).to.equal(xyzFile) +// expect(resolveAlias('xyz/file', alias)).to.equal(xyzFile); // } -// }) -// }) +// }); +// }); // } // tableLine( {} -// , 'xyz', 'xyz/file' ) +// , 'xyz', 'xyz/file' ); -// tableLine( { xyz: "/absolute/path/to/file.js" } -// , '/absolute/path/to/file.js', 'xyz/file' ) +// tableLine( { xyz: '/absolute/path/to/file.js' } +// , '/absolute/path/to/file.js', 'xyz/file' ); -// tableLine( { xyz$: "/absolute/path/to/file.js" } -// , "/absolute/path/to/file.js", Error ) +// tableLine( { xyz$: '/absolute/path/to/file.js' } +// , '/absolute/path/to/file.js', Error ); -// tableLine( { xyz: "./dir/file.js" } -// , './dir/file.js', 'xyz/file' ) +// tableLine( { xyz: './dir/file.js' } +// , './dir/file.js', 'xyz/file' ); -// tableLine( { xyz$: "./dir/file.js" } -// , './dir/file.js', Error ) +// tableLine( { xyz$: './dir/file.js' } +// , './dir/file.js', Error ); -// tableLine( { xyz: "/some/dir" } -// , '/some/dir', '/some/dir/file' ) +// tableLine( { xyz: '/some/dir' } +// , '/some/dir', '/some/dir/file' ); -// tableLine( { xyz$: "/some/dir" } -// , '/some/dir', 'xyz/file' ) +// tableLine( { xyz$: '/some/dir' } +// , '/some/dir', 'xyz/file' ); -// tableLine( { xyz: "./dir" } -// , './dir', './dir/file' ) +// tableLine( { xyz: './dir' } +// , './dir', './dir/file' ); -// tableLine( { xyz: "modu" } -// , 'modu', 'modu/file' ) +// tableLine( { xyz: 'modu' } +// , 'modu', 'modu/file' ); -// tableLine( { xyz$: "modu" } -// , 'modu', 'xyz/file' ) +// tableLine( { xyz$: 'modu' } +// , 'modu', 'xyz/file' ); -// tableLine( { xyz: "modu/some/file.js" } -// , 'modu/some/file.js', Error ) +// tableLine( { xyz: 'modu/some/file.js' } +// , 'modu/some/file.js', Error ); -// tableLine( { xyz: "modu/dir" } -// , 'modu/dir', 'modu/dir/file' ) +// tableLine( { xyz: 'modu/dir' } +// , 'modu/dir', 'modu/dir/file' ); -// tableLine( { xyz: "xyz/dir" } -// , 'xyz/dir', 'xyz/dir/file' ) +// tableLine( { xyz: 'xyz/dir' } +// , 'xyz/dir', 'xyz/dir/file' ); -// tableLine( { xyz$: "xyz/dir" } -// , 'xyz/dir', 'xyz/file' ) -// }) +// tableLine( { xyz$: 'xyz/dir' } +// , 'xyz/dir', 'xyz/file' ); +// }); -// describe.skip("nested module names", function () { +// describe.skip('nested module names', function () { // // from table: http://webpack.github.io/docs/configuration.html#resolve-alias // function nestedName(alias, xyz, xyzFile) { // describe(JSON.stringify(alias), function () { -// it("top/xyz: " + xyz, function () { -// expect(resolveAlias('top/xyz', alias)).to.equal(xyz) -// }) -// it("top/xyz/file: " + (xyzFile.name || xyzFile), function () { +// it('top/xyz: ' + xyz, function () { +// expect(resolveAlias('top/xyz', alias)).to.equal(xyz); +// }); +// it('top/xyz/file: ' + (xyzFile.name || xyzFile), function () { // if (xyzFile === Error) { -// expect(resolveAlias.bind(null, 'top/xyz/file', alias)).to.throw(xyzFile) +// expect(resolveAlias.bind(null, 'top/xyz/file', alias)).to.throw(xyzFile); // } else { -// expect(resolveAlias('top/xyz/file', alias)).to.equal(xyzFile) +// expect(resolveAlias('top/xyz/file', alias)).to.equal(xyzFile); // } -// }) -// }) +// }); +// }); // } -// nestedName( { 'top/xyz': "/absolute/path/to/file.js" } -// , '/absolute/path/to/file.js', 'top/xyz/file' ) +// nestedName( { 'top/xyz': '/absolute/path/to/file.js' } +// , '/absolute/path/to/file.js', 'top/xyz/file' ); -// nestedName( { 'top/xyz$': "/absolute/path/to/file.js" } -// , "/absolute/path/to/file.js", Error ) +// nestedName( { 'top/xyz$': '/absolute/path/to/file.js' } +// , '/absolute/path/to/file.js', Error ); -// nestedName( { 'top/xyz': "./dir/file.js" } -// , './dir/file.js', 'top/xyz/file' ) +// nestedName( { 'top/xyz': './dir/file.js' } +// , './dir/file.js', 'top/xyz/file' ); -// nestedName( { 'top/xyz$': "./dir/file.js" } -// , './dir/file.js', Error ) +// nestedName( { 'top/xyz$': './dir/file.js' } +// , './dir/file.js', Error ); -// nestedName( { 'top/xyz': "/some/dir" } -// , '/some/dir', '/some/dir/file' ) +// nestedName( { 'top/xyz': '/some/dir' } +// , '/some/dir', '/some/dir/file' ); -// nestedName( { 'top/xyz$': "/some/dir" } -// , '/some/dir', 'top/xyz/file' ) +// nestedName( { 'top/xyz$': '/some/dir' } +// , '/some/dir', 'top/xyz/file' ); -// nestedName( { 'top/xyz': "./dir" } -// , './dir', './dir/file' ) +// nestedName( { 'top/xyz': './dir' } +// , './dir', './dir/file' ); -// nestedName( { 'top/xyz': "modu" } -// , 'modu', 'modu/file' ) +// nestedName( { 'top/xyz': 'modu' } +// , 'modu', 'modu/file' ); -// nestedName( { 'top/xyz$': "modu" } -// , 'modu', 'top/xyz/file' ) +// nestedName( { 'top/xyz$': 'modu' } +// , 'modu', 'top/xyz/file' ); -// nestedName( { 'top/xyz': "modu/some/file.js" } -// , 'modu/some/file.js', Error ) +// nestedName( { 'top/xyz': 'modu/some/file.js' } +// , 'modu/some/file.js', Error ); -// nestedName( { 'top/xyz': "modu/dir" } -// , 'modu/dir', 'modu/dir/file' ) +// nestedName( { 'top/xyz': 'modu/dir' } +// , 'modu/dir', 'modu/dir/file' ); -// nestedName( { 'top/xyz': "top/xyz/dir" } -// , 'top/xyz/dir', 'top/xyz/dir/file' ) +// nestedName( { 'top/xyz': 'top/xyz/dir' } +// , 'top/xyz/dir', 'top/xyz/dir/file' ); -// nestedName( { 'top/xyz$': "top/xyz/dir" } -// , 'top/xyz/dir', 'top/xyz/file' ) -// }) +// nestedName( { 'top/xyz$': 'top/xyz/dir' } +// , 'top/xyz/dir', 'top/xyz/file' ); +// }); diff --git a/resolvers/webpack/test/config-extensions/webpack.config.babel.js b/resolvers/webpack/test/config-extensions/webpack.config.babel.js index 41dc6c8e89..a63434f9bb 100644 --- a/resolvers/webpack/test/config-extensions/webpack.config.babel.js +++ b/resolvers/webpack/test/config-extensions/webpack.config.babel.js @@ -1,4 +1,4 @@ -import path from 'path' +import path from 'path'; export default { resolve: { @@ -20,4 +20,4 @@ export default { { 'jquery': 'jQuery' }, 'bootstrap', ], -} +}; diff --git a/resolvers/webpack/test/config.js b/resolvers/webpack/test/config.js index ff0c0bd669..069c2e3942 100644 --- a/resolvers/webpack/test/config.js +++ b/resolvers/webpack/test/config.js @@ -1,138 +1,158 @@ -var chai = require('chai') - , expect = chai.expect - , path = require('path') +'use strict'; -var resolve = require('../index').resolve +const chai = require('chai'); +const expect = chai.expect; +const path = require('path'); -var file = path.join(__dirname, 'files', 'src', 'jsx', 'dummy.js') -var extensionFile = path.join(__dirname, 'config-extensions', 'src', 'dummy.js') +const resolve = require('../index').resolve; -var absoluteSettings = { +const file = path.join(__dirname, 'files', 'src', 'jsx', 'dummy.js'); +const extensionFile = path.join(__dirname, 'config-extensions', 'src', 'dummy.js'); + +const absoluteSettings = { config: path.join(__dirname, 'files', 'some', 'absolute.path.webpack.config.js'), -} +}; -describe("config", function () { - it("finds webpack.config.js in parent directories", function () { +describe('config', function () { + it('finds webpack.config.js in parent directories', function () { expect(resolve('main-module', file)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - }) + .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + }); - it("finds absolute webpack.config.js files", function () { + it('finds absolute webpack.config.js files', function () { expect(resolve('foo', file, absoluteSettings)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js')) - }) + .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js')); + }); - it("finds compile-to-js configs", function () { - var settings = { + it('finds compile-to-js configs', function () { + const settings = { config: path.join(__dirname, './files/webpack.config.babel.js'), - } + }; expect(resolve('main-module', file, settings)) .to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - }) + .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + }); - it("finds compile-to-js config in parent directories", function () { + it('finds compile-to-js config in parent directories', function () { expect(resolve('main-module', extensionFile)) .to.have.property('path') - .and.equal(path.join(__dirname, 'config-extensions', 'src', 'main-module.js')) - }) + .and.equal(path.join(__dirname, 'config-extensions', 'src', 'main-module.js')); + }); - it("finds the first config with a resolve section", function () { - var settings = { + it('finds the first config with a resolve section', function () { + const settings = { config: path.join(__dirname, './files/webpack.config.multiple.js'), - } + }; expect(resolve('main-module', file, settings)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - }) + .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + }); - it("finds the config at option config-index", function () { - var settings = { + it('finds the config at option config-index', function () { + const settings = { config: path.join(__dirname, './files/webpack.config.multiple.js'), 'config-index': 2, - } + }; expect(resolve('foo', file, settings)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js')) - }) + .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js')); + }); it("doesn't swallow config load errors (#435)", function () { - var settings = { + const settings = { config: path.join(__dirname, './files/webpack.config.garbage.js'), - } - expect(function () { resolve('foo', file, settings) }).to.throw(Error) - }) + }; + expect(function () { resolve('foo', file, settings); }).to.throw(Error); + }); - it("finds config object when config is an object", function () { - var settings = { + it('finds config object when config is an object', function () { + const settings = { config: require(path.join(__dirname, 'files', 'some', 'absolute.path.webpack.config.js')), - } + }; + expect(resolve('foo', file, settings)).to.have.property('path') + .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js')); + }); + + it('finds config object when config uses a path relative to working dir', function () { + const settings = { + config: './test/files/some/absolute.path.webpack.config.js', + }; expect(resolve('foo', file, settings)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js')) - }) + .and.equal(path.join(__dirname, 'files', 'some', 'absolutely', 'goofy', 'path', 'foo.js')); + }); - it("finds the first config with a resolve section when config is an array of config objects", function () { - var settings = { + it('finds the first config with a resolve section when config is an array of config objects', function () { + const settings = { config: require(path.join(__dirname, './files/webpack.config.multiple.js')), - } + }; expect(resolve('main-module', file, settings)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - }) + .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + }); - it("finds the config at option config-index when config is an array of config objects", function () { - var settings = { + it('finds the config at option config-index when config is an array of config objects', function () { + const settings = { config: require(path.join(__dirname, './files/webpack.config.multiple.js')), 'config-index': 2, - } + }; expect(resolve('foo', file, settings)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js')) - }) + .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js')); + }); it('finds the config at option env when config is a function', function() { - var settings = { + const settings = { config: require(path.join(__dirname, './files/webpack.function.config.js')), env: { dummy: true, }, - } + }; expect(resolve('bar', file, settings)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js')) - }) + .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js')); + }); it('finds the config at option env when config is an array of functions', function() { - var settings = { + const settings = { config: require(path.join(__dirname, './files/webpack.function.config.multiple.js')), env: { dummy: true, }, - } + }; expect(resolve('bar', file, settings)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js')) - }) + .and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'bar.js')); + }); it('passes argv to config when it is a function', function() { - var settings = { + const settings = { config: require(path.join(__dirname, './files/webpack.function.config.js')), argv: { mode: 'test', }, - } + }; expect(resolve('baz', file, settings)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'some', 'bar', 'bar.js')) - }) + .and.equal(path.join(__dirname, 'files', 'some', 'bar', 'bar.js')); + }); it('passes a default empty argv object to config when it is a function', function() { - var settings = { + const settings = { config: require(path.join(__dirname, './files/webpack.function.config.js')), argv: undefined, - } + }; + + expect(function () { resolve('baz', file, settings); }).to.not.throw(Error); + }); + + it('prevents async config using', function() { + const settings = { + config: require(path.join(__dirname, './files/webpack.config.async.js')), + }; + const result = resolve('foo', file, settings); - expect(function () { resolve('baz', file, settings) }).to.not.throw(Error) - }) -}) + expect(result).not.to.have.property('path'); + expect(result).to.have.property('found').to.be.false; + }); +}); diff --git a/resolvers/webpack/test/custom-extensions/webpack.config.js b/resolvers/webpack/test/custom-extensions/webpack.config.js index ee3444c00f..b8d39ed7c0 100644 --- a/resolvers/webpack/test/custom-extensions/webpack.config.js +++ b/resolvers/webpack/test/custom-extensions/webpack.config.js @@ -1,3 +1,3 @@ module.exports = { resolve: { extensions: ['.js', '.coffee'] }, -} +}; diff --git a/resolvers/webpack/test/example.js b/resolvers/webpack/test/example.js index 375f6b5a1e..cd9ece0156 100644 --- a/resolvers/webpack/test/example.js +++ b/resolvers/webpack/test/example.js @@ -1,9 +1,11 @@ -var path = require('path') +'use strict'; -var resolve = require('../index').resolve +const path = require('path'); -var file = path.join(__dirname, 'files', 'src', 'dummy.js') +const resolve = require('../index').resolve; -var webpackDir = path.join(__dirname, "different-package-location") +const file = path.join(__dirname, 'files', 'src', 'dummy.js'); -console.log(resolve('main-module', file, { config: "webpack.config.js", cwd: webpackDir})) +const webpackDir = path.join(__dirname, 'different-package-location'); + +console.log(resolve('main-module', file, { config: 'webpack.config.js', cwd: webpackDir })); diff --git a/resolvers/webpack/test/extensions.js b/resolvers/webpack/test/extensions.js index 94e9bd3942..c028f5c913 100644 --- a/resolvers/webpack/test/extensions.js +++ b/resolvers/webpack/test/extensions.js @@ -1,32 +1,34 @@ -var chai = require('chai') - , expect = chai.expect - , path = require('path') +'use strict'; -var resolve = require('../index').resolve +const chai = require('chai'); +const expect = chai.expect; +const path = require('path'); +const resolve = require('../index').resolve; -var file = path.join(__dirname, 'files', 'dummy.js') - , extensions = path.join(__dirname, 'custom-extensions', 'dummy.js') -describe("extensions", function () { - it("respects the defaults", function () { +const file = path.join(__dirname, 'files', 'dummy.js'); +const extensions = path.join(__dirname, 'custom-extensions', 'dummy.js'); + +describe('extensions', function () { + it('respects the defaults', function () { expect(resolve('./foo', file)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'foo.web.js')) - }) + .and.equal(path.join(__dirname, 'files', 'foo.web.js')); + }); - describe("resolve.extensions set", function () { - it("works", function () { + describe('resolve.extensions set', function () { + it('works', function () { expect(resolve('./foo', extensions)).to.have.property('path') - .and.equal(path.join(__dirname, 'custom-extensions', 'foo.js')) - }) + .and.equal(path.join(__dirname, 'custom-extensions', 'foo.js')); + }); - it("replaces defaults", function () { - expect(resolve('./baz', extensions)).to.have.property('found', false) - }) + it('replaces defaults', function () { + expect(resolve('./baz', extensions)).to.have.property('found', false); + }); - it("finds .coffee", function () { + it('finds .coffee', function () { expect(resolve('./bar', extensions)).to.have.property('path') - .and.equal(path.join(__dirname, 'custom-extensions', 'bar.coffee')) - }) - }) -}) + .and.equal(path.join(__dirname, 'custom-extensions', 'bar.coffee')); + }); + }); +}); diff --git a/resolvers/webpack/test/externals.js b/resolvers/webpack/test/externals.js index e2e61fbe19..27dec2033d 100644 --- a/resolvers/webpack/test/externals.js +++ b/resolvers/webpack/test/externals.js @@ -1,33 +1,68 @@ -var chai = require('chai') - , expect = chai.expect - , path = require('path') - -var webpack = require('../index') - -var file = path.join(__dirname, 'files', 'dummy.js') - -describe("externals", function () { - it("works on just a string", function () { - var resolved = webpack.resolve('bootstrap', file) - expect(resolved).to.have.property('found', true) - expect(resolved).to.have.property('path', null) - }) - - it("works on object-map", function () { - var resolved = webpack.resolve('jquery', file) - expect(resolved).to.have.property('found', true) - expect(resolved).to.have.property('path', null) - }) - - it("works on a function", function () { - var resolved = webpack.resolve('underscore', file) - expect(resolved).to.have.property('found', true) - expect(resolved).to.have.property('path', null) - }) - - it("returns null for core modules", function () { - var resolved = webpack.resolve('fs', file) - expect(resolved).to.have.property('found', true) - expect(resolved).to.have.property('path', null) - }) -}) +'use strict'; + +const chai = require('chai'); +const expect = chai.expect; +const path = require('path'); +const semver = require('semver'); + +const webpack = require('../index'); + +const file = path.join(__dirname, 'files', 'dummy.js'); + +describe('externals', function () { + const settingsWebpack5 = { + config: require(path.join(__dirname, './files/webpack.config.webpack5.js')), + }; + + it('works on just a string', function () { + const resolved = webpack.resolve('bootstrap', file); + expect(resolved).to.have.property('found', true); + expect(resolved).to.have.property('path', null); + }); + + it('works on object-map', function () { + const resolved = webpack.resolve('jquery', file); + expect(resolved).to.have.property('found', true); + expect(resolved).to.have.property('path', null); + }); + + it('works on a function', function () { + const resolved = webpack.resolve('underscore', file); + expect(resolved).to.have.property('found', true); + expect(resolved).to.have.property('path', null); + }); + + it('returns null for core modules', function () { + const resolved = webpack.resolve('fs', file); + expect(resolved).to.have.property('found', true); + expect(resolved).to.have.property('path', null); + }); + + it('works on a function (synchronous) for webpack 5', function () { + const resolved = webpack.resolve('underscore', file, settingsWebpack5); + expect(resolved).to.have.property('found', true); + expect(resolved).to.have.property('path', null); + }); + + it('works on a function (synchronous) which uses getResolve for webpack 5', function () { + const resolved = webpack.resolve('graphql', file, settingsWebpack5); + expect(resolved).to.have.property('found', true); + expect(resolved).to.have.property('path', null); + }); + + (semver.satisfies(process.version, '> 6') ? describe : describe.skip)('async function in webpack 5', function () { + const settingsWebpack5Async = () => ({ + config: require(path.join(__dirname, './files/webpack.config.webpack5.async-externals.js')), + }); + + it('prevents using an asynchronous function for webpack 5', function () { + const resolved = webpack.resolve('underscore', file, settingsWebpack5Async()); + expect(resolved).to.have.property('found', false); + }); + + it('prevents using a function which uses Promise returned by getResolve for webpack 5', function () { + const resolved = webpack.resolve('graphql', file, settingsWebpack5Async()); + expect(resolved).to.have.property('found', false); + }); + }); +}); diff --git a/resolvers/webpack/test/fallback.js b/resolvers/webpack/test/fallback.js index ad4b2b4c1d..87c15eecd7 100644 --- a/resolvers/webpack/test/fallback.js +++ b/resolvers/webpack/test/fallback.js @@ -1,28 +1,30 @@ -var chai = require('chai') - , expect = chai.expect - , path = require('path') +'use strict'; -var resolve = require('../index').resolve +const chai = require('chai'); +const expect = chai.expect; +const path = require('path'); +const resolve = require('../index').resolve; -var file = path.join(__dirname, 'files', 'src', 'dummy.js') -describe("fallback", function () { - it("works", function () { +const file = path.join(__dirname, 'files', 'src', 'dummy.js'); + +describe('fallback', function () { + it('works', function () { expect(resolve('fb-module', file)).property('path') - .to.equal(path.join(__dirname, 'files', 'fallback', 'fb-module.js')) - }) - it("really works", function () { + .to.equal(path.join(__dirname, 'files', 'fallback', 'fb-module.js')); + }); + it('really works', function () { expect(resolve('jsx/some-fb-file', file)).property('path') - .to.equal(path.join(__dirname, 'files', 'fallback', 'jsx', 'some-fb-file.js')) - }) - it("prefer root", function () { + .to.equal(path.join(__dirname, 'files', 'fallback', 'jsx', 'some-fb-file.js')); + }); + it('prefer root', function () { expect(resolve('jsx/some-file', file)).property('path') - .to.equal(path.join(__dirname, 'files', 'src', 'jsx', 'some-file.js')) - }) - it("supports definition as an array", function () { - expect(resolve('fb-module', file, { config: "webpack.array-root.config.js" })) + .to.equal(path.join(__dirname, 'files', 'src', 'jsx', 'some-file.js')); + }); + it('supports definition as an array', function () { + expect(resolve('fb-module', file, { config: 'webpack.array-root.config.js' })) .property('path') - .to.equal(path.join(__dirname, 'files', 'fallback', 'fb-module.js')) - }) -}) + .to.equal(path.join(__dirname, 'files', 'fallback', 'fb-module.js')); + }); +}); diff --git a/resolvers/webpack/test/files/webpack.config.async.js b/resolvers/webpack/test/files/webpack.config.async.js new file mode 100644 index 0000000000..9b7aaa7f4d --- /dev/null +++ b/resolvers/webpack/test/files/webpack.config.async.js @@ -0,0 +1,7 @@ +const config = require('./webpack.config.js') + +module.exports = function() { + return new Promise(function(resolve) { + resolve(config) + }) +} diff --git a/resolvers/webpack/test/files/webpack.config.webpack5.async-externals.js b/resolvers/webpack/test/files/webpack.config.webpack5.async-externals.js new file mode 100644 index 0000000000..ba2902b83b --- /dev/null +++ b/resolvers/webpack/test/files/webpack.config.webpack5.async-externals.js @@ -0,0 +1,21 @@ +module.exports = { + externals: [ + { 'jquery': 'jQuery' }, + 'bootstrap', + async function ({ request },) { + if (request === 'underscore') { + return 'underscore' + } + }, + function ({ request, getResolve }, callback) { + if (request === 'graphql') { + const resolve = getResolve() + // dummy call (some-module should be resolved on __dirname) + resolve(__dirname, 'some-module').then( + function () { callback(null, 'graphql') }, + function (e) { callback(e) } + ) + } + }, + ], +} diff --git a/resolvers/webpack/test/files/webpack.config.webpack5.js b/resolvers/webpack/test/files/webpack.config.webpack5.js new file mode 100644 index 0000000000..88a12567a1 --- /dev/null +++ b/resolvers/webpack/test/files/webpack.config.webpack5.js @@ -0,0 +1,27 @@ +module.exports = { + externals: [ + { 'jquery': 'jQuery' }, + 'bootstrap', + function ({ request }, callback) { + if (request === 'underscore') { + return callback(null, 'underscore') + } + callback() + }, + function ({ request, getResolve }, callback) { + if (request === 'graphql') { + const resolve = getResolve() + // dummy call (some-module should be resolved on __dirname) + resolve(__dirname, 'some-module', function (err, value) { + if (err) { + callback(err) + } else { + callback(null, 'graphql') + } + }) + } else { + callback() + } + }, + ], +} diff --git a/resolvers/webpack/test/loaders.js b/resolvers/webpack/test/loaders.js index 1d588c77cd..6b5604592d 100644 --- a/resolvers/webpack/test/loaders.js +++ b/resolvers/webpack/test/loaders.js @@ -1,35 +1,34 @@ -var chai = require('chai') - , expect = chai.expect - , path = require('path') +'use strict'; -var resolve = require('../index').resolve +const chai = require('chai'); +const expect = chai.expect; +const path = require('path'); +const resolve = require('../index').resolve; -var file = path.join(__dirname, 'files', 'dummy.js') -describe("inline loader syntax", function () { +const file = path.join(__dirname, 'files', 'dummy.js'); - it("strips bang-loaders", function () { +describe('inline loader syntax', function () { + it('strips bang-loaders', function () { expect(resolve('css-loader!./src/main-module', file)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - }) + .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + }); - it("strips loader query string", function () { + it('strips loader query string', function () { expect(resolve('some-loader?param=value!./src/main-module', file)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - }) + .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + }); - it("strips resource query string", function () { + it('strips resource query string', function () { expect(resolve('./src/main-module?otherParam=otherValue', file)) .to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - }) + .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + }); - it("strips everything", function () { + it('strips everything', function () { expect(resolve('some-loader?param=value!./src/main-module?otherParam=otherValue', file)) .to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - }) - -}) - + .and.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + }); +}); diff --git a/resolvers/webpack/test/modules.js b/resolvers/webpack/test/modules.js index 753ceffc00..066e52a6f7 100644 --- a/resolvers/webpack/test/modules.js +++ b/resolvers/webpack/test/modules.js @@ -1,21 +1,23 @@ -var chai = require('chai') - , expect = chai.expect - , path = require('path') +'use strict'; -var resolve = require('../index').resolve +const chai = require('chai'); +const expect = chai.expect; +const path = require('path'); -var file = path.join(__dirname, 'files', 'dummy.js') +const resolve = require('../index').resolve; -describe("resolve.moduleDirectories", function () { +const file = path.join(__dirname, 'files', 'dummy.js'); - it("finds a node module", function () { +describe('resolve.moduleDirectories', function () { + + it('finds a node module', function () { expect(resolve('some-module', file)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'node_modules', 'some-module', 'index.js')) - }) + .and.equal(path.join(__dirname, 'files', 'node_modules', 'some-module', 'index.js')); + }); - it("finds a bower module", function () { + it('finds a bower module', function () { expect(resolve('typeahead.js', file)).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js')) - }) + .and.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js')); + }); -}) +}); diff --git a/resolvers/webpack/test/package-mains/webpack.alt.config.js b/resolvers/webpack/test/package-mains/webpack.alt.config.js index c31e49a05b..b955d9d378 100644 --- a/resolvers/webpack/test/package-mains/webpack.alt.config.js +++ b/resolvers/webpack/test/package-mains/webpack.alt.config.js @@ -1,3 +1,3 @@ exports.resolve = { - packageMains: ["main"], // override -} + packageMains: ['main'], // override +}; diff --git a/resolvers/webpack/test/packageMains.js b/resolvers/webpack/test/packageMains.js index 63f5b68931..ed9c79a398 100644 --- a/resolvers/webpack/test/packageMains.js +++ b/resolvers/webpack/test/packageMains.js @@ -1,47 +1,49 @@ -var chai = require('chai') - , expect = chai.expect - , path = require('path') +'use strict'; -var webpack = require('../') +const chai = require('chai'); +const expect = chai.expect; +const path = require('path'); -var file = path.join(__dirname, 'package-mains', 'dummy.js') +const webpack = require('../'); +const file = path.join(__dirname, 'package-mains', 'dummy.js'); -describe("packageMains", function () { - it("captures module", function () { +describe('packageMains', function () { + + it('captures module', function () { expect(webpack.resolve('./module', file)).property('path') - .to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js')) - }) + .to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js')); + }); - it("captures jsnext", function () { + it('captures jsnext', function () { expect(webpack.resolve('./jsnext', file)).property('path') - .to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js')) - }) + .to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js')); + }); - it("captures webpack", function () { + it('captures webpack', function () { expect(webpack.resolve('./webpack', file)).property('path') - .to.equal(path.join(__dirname, 'package-mains', 'webpack', 'webpack.js')) - }) + .to.equal(path.join(__dirname, 'package-mains', 'webpack', 'webpack.js')); + }); - it("captures jam (array path)", function () { + it('captures jam (array path)', function () { expect(webpack.resolve('./jam', file)).property('path') - .to.equal(path.join(__dirname, 'package-mains', 'jam', 'jam.js')) - }) + .to.equal(path.join(__dirname, 'package-mains', 'jam', 'jam.js')); + }); - it("uses configured packageMains, if provided", function () { + it('uses configured packageMains, if provided', function () { expect(webpack.resolve('./webpack', file, { config: 'webpack.alt.config.js' })).property('path') - .to.equal(path.join(__dirname, 'package-mains', 'webpack', 'index.js')) - }) + .to.equal(path.join(__dirname, 'package-mains', 'webpack', 'index.js')); + }); - it("always defers to module, regardless of config", function () { + it('always defers to module, regardless of config', function () { expect(webpack.resolve('./module', file, { config: 'webpack.alt.config.js' })).property('path') - .to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js')) - }) + .to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js')); + }); - it("always defers to jsnext:main, regardless of config", function () { + it('always defers to jsnext:main, regardless of config', function () { expect(webpack.resolve('./jsnext', file, { config: 'webpack.alt.config.js' })).property('path') - .to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js')) - }) + .to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js')); + }); -}) +}); diff --git a/resolvers/webpack/test/plugins.js b/resolvers/webpack/test/plugins.js index f37b945183..b964e7c30e 100644 --- a/resolvers/webpack/test/plugins.js +++ b/resolvers/webpack/test/plugins.js @@ -1,29 +1,31 @@ -var chai = require('chai') - , expect = chai.expect - , path = require('path') +'use strict'; -var webpack = require('../index') +const chai = require('chai'); +const expect = chai.expect; +const path = require('path'); -var file = path.join(__dirname, 'files', 'dummy.js') +const webpack = require('../index'); -describe("plugins", function () { - var resolved, aliasResolved +const file = path.join(__dirname, 'files', 'dummy.js'); + +describe('plugins', function () { + let resolved; let aliasResolved; before(function () { - resolved = webpack.resolve('./some/bar', file) - aliasResolved = webpack.resolve('some-alias/bar', file) - }) + resolved = webpack.resolve('./some/bar', file); + aliasResolved = webpack.resolve('some-alias/bar', file); + }); - it("work", function () { - expect(resolved).to.have.property('found', true) - }) + it('work', function () { + expect(resolved).to.have.property('found', true); + }); - it("is correct", function () { + it('is correct', function () { expect(resolved).to.have.property('path') - .and.equal(path.join(__dirname, 'files', 'some', 'bar', 'bar.js')) - }) + .and.equal(path.join(__dirname, 'files', 'some', 'bar', 'bar.js')); + }); - it("work with alias", function () { - expect(aliasResolved).to.have.property('found', true) - }) -}) + it('work with alias', function () { + expect(aliasResolved).to.have.property('found', true); + }); +}); diff --git a/resolvers/webpack/test/root.js b/resolvers/webpack/test/root.js index 4365720091..154dbeef95 100644 --- a/resolvers/webpack/test/root.js +++ b/resolvers/webpack/test/root.js @@ -1,45 +1,47 @@ -var chai = require('chai') - , expect = chai.expect - , path = require('path') +'use strict'; -var resolve = require('../index').resolve +const chai = require('chai'); +const expect = chai.expect; +const path = require('path'); +const resolve = require('../index').resolve; -var file = path.join(__dirname, 'files', 'src', 'dummy.js') -var webpackDir = path.join(__dirname, "different-package-location") -describe("root", function () { - it("works", function () { +const file = path.join(__dirname, 'files', 'src', 'dummy.js'); +const webpackDir = path.join(__dirname, 'different-package-location'); + +describe('root', function () { + it('works', function () { expect(resolve('main-module', file)).property('path') - .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - }) - it("really works", function () { + .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + }); + it('really works', function () { expect(resolve('jsx/some-file', file)).property('path') - .to.equal(path.join(__dirname, 'files', 'src', 'jsx', 'some-file.js')) - }) - it("supports definition as an array", function () { - expect(resolve('main-module', file, { config: "webpack.array-root.config.js" })) + .to.equal(path.join(__dirname, 'files', 'src', 'jsx', 'some-file.js')); + }); + it('supports definition as an array', function () { + expect(resolve('main-module', file, { config: 'webpack.array-root.config.js' })) .property('path') - .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - expect(resolve('typeahead', file, { config: "webpack.array-root.config.js" })) + .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + expect(resolve('typeahead', file, { config: 'webpack.array-root.config.js' })) .property('path') - .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js')) - }) - it("supports definition as a function", function () { - expect(resolve('main-module', file, { config: "webpack.function.config.js" })) + .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js')); + }); + it('supports definition as a function', function () { + expect(resolve('main-module', file, { config: 'webpack.function.config.js' })) .property('path') - .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - expect(resolve('typeahead', file, { config: "webpack.function.config.js" })) + .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + expect(resolve('typeahead', file, { config: 'webpack.function.config.js' })) .property('path') - .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js')) - }) - it("supports passing a different directory to load webpack from", function () { + .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js')); + }); + it('supports passing a different directory to load webpack from', function () { // Webpack should still be able to resolve the config here - expect(resolve('main-module', file, { config: "webpack.config.js", cwd: webpackDir})) + expect(resolve('main-module', file, { config: 'webpack.config.js', cwd: webpackDir })) .property('path') - .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js')) - expect(resolve('typeahead', file, { config: "webpack.config.js", cwd: webpackDir})) + .to.equal(path.join(__dirname, 'files', 'src', 'main-module.js')); + expect(resolve('typeahead', file, { config: 'webpack.config.js', cwd: webpackDir })) .property('path') - .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js')) - }) -}) + .to.equal(path.join(__dirname, 'files', 'bower_components', 'typeahead.js')); + }); +}); diff --git a/scripts/copyMetafiles.js b/scripts/copyMetafiles.js index 441e421e8d..d14964f1c7 100644 --- a/scripts/copyMetafiles.js +++ b/scripts/copyMetafiles.js @@ -1,22 +1,23 @@ -import path from 'path' -import copyFileSync from 'fs-copy-file-sync' -import resolverDirectories from './resolverDirectories' +import path from 'path'; +import copyFileSync from 'fs-copy-file-sync'; +import resolverDirectories from './resolverDirectories'; -let files = [ - 'LICENSE', - '.npmrc', -] +const files = [ + 'LICENSE', + '.npmrc', + '.nycrc', +]; -let directories = [ - 'memo-parser', - ...resolverDirectories, - 'utils', -] +const 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}`) - } +for (const directory of directories) { + for (const file of files) { + const destination = path.join(directory, file); + copyFileSync(file, destination); + console.log(`${file} -> ${destination}`); + } } diff --git a/scripts/resolverDirectories.js b/scripts/resolverDirectories.js index eea0620d36..f0c03a3ccf 100644 --- a/scripts/resolverDirectories.js +++ b/scripts/resolverDirectories.js @@ -1,3 +1,3 @@ -import glob from 'glob' +import glob from 'glob'; -export default glob.sync('./resolvers/*/') +export default glob.sync('./resolvers/*/'); diff --git a/scripts/testAll.js b/scripts/testAll.js index 358ef3e89e..fc30b1ac7d 100644 --- a/scripts/testAll.js +++ b/scripts/testAll.js @@ -1,20 +1,20 @@ -import { spawnSync } from 'child_process' -import npmWhich from 'npm-which' -import resolverDirectories from './resolverDirectories' +import { spawnSync } from 'child_process'; +import npmWhich from 'npm-which'; +import resolverDirectories from './resolverDirectories'; -let npmPath = npmWhich(__dirname).sync('npm') -let spawnOptions = { - stdio: 'inherit', -} +const npmPath = npmWhich(__dirname).sync('npm'); +const spawnOptions = { + stdio: 'inherit', +}; spawnSync( + npmPath, + ['test'], + Object.assign({ cwd: __dirname }, spawnOptions)); + +for (const resolverDir of resolverDirectories) { + spawnSync( npmPath, ['test'], - Object.assign({ cwd: __dirname }, spawnOptions)) - -for (let resolverDir of resolverDirectories) { - spawnSync( - npmPath, - ['test'], - Object.assign({ cwd: resolverDir }, spawnOptions)) + Object.assign({ cwd: resolverDir }, spawnOptions)); } diff --git a/src/ExportMap.js b/src/ExportMap.js index c8f03cf4f6..76b07f9dc9 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -1,58 +1,59 @@ -import fs from 'fs' +import fs from 'fs'; -import doctrine from 'doctrine' +import doctrine from 'doctrine'; -import debug from 'debug' +import debug from 'debug'; -import { SourceCode } from 'eslint' +import { SourceCode } from 'eslint'; -import parse from 'eslint-module-utils/parse' -import resolve from 'eslint-module-utils/resolve' -import isIgnored, { hasValidExtension } from 'eslint-module-utils/ignore' +import parse from 'eslint-module-utils/parse'; +import resolve from 'eslint-module-utils/resolve'; +import isIgnored, { hasValidExtension } from 'eslint-module-utils/ignore'; -import { hashObject } from 'eslint-module-utils/hash' -import * as unambiguous from 'eslint-module-utils/unambiguous' +import { hashObject } from 'eslint-module-utils/hash'; +import * as unambiguous from 'eslint-module-utils/unambiguous'; -import { tsConfigLoader } from 'tsconfig-paths/lib/tsconfig-loader' +import { tsConfigLoader } from 'tsconfig-paths/lib/tsconfig-loader'; -import includes from 'array-includes' +import includes from 'array-includes'; -let parseConfigFileTextToJson +let parseConfigFileTextToJson; -const log = debug('eslint-plugin-import:ExportMap') +const log = debug('eslint-plugin-import:ExportMap'); -const exportCache = new Map() +const exportCache = new Map(); +const tsConfigCache = new Map(); export default class ExportMap { constructor(path) { - this.path = path - this.namespace = new Map() + this.path = path; + this.namespace = new Map(); // todo: restructure to key on path, value is resolver + map of names - this.reexports = new Map() + this.reexports = new Map(); /** * star-exports * @type {Set} of () => ExportMap */ - this.dependencies = new Set() + this.dependencies = new Set(); /** * dependencies of this module that are not explicitly re-exported * @type {Map} from path = () => ExportMap */ - this.imports = new Map() - this.errors = [] + this.imports = new Map(); + this.errors = []; } - get hasDefault() { return this.get('default') != null } // stronger than this.has + get hasDefault() { return this.get('default') != null; } // stronger than this.has get size() { - let size = this.namespace.size + this.reexports.size + let size = this.namespace.size + this.reexports.size; this.dependencies.forEach(dep => { - const d = dep() + const d = dep(); // CJS / ignored dependencies won't exist (#717) - if (d == null) return - size += d.size - }) - return size + if (d == null) return; + size += d.size; + }); + return size; } /** @@ -63,125 +64,125 @@ export default class ExportMap { * @return {Boolean} true if `name` is exported by this module. */ has(name) { - if (this.namespace.has(name)) return true - if (this.reexports.has(name)) return true + if (this.namespace.has(name)) return true; + if (this.reexports.has(name)) return true; // default exports must be explicitly re-exported (#328) if (name !== 'default') { - for (let dep of this.dependencies) { - let innerMap = dep() + for (const dep of this.dependencies) { + const innerMap = dep(); // todo: report as unresolved? - if (!innerMap) continue + if (!innerMap) continue; - if (innerMap.has(name)) return true + if (innerMap.has(name)) return true; } } - return false + return false; } /** * ensure that imported name fully resolves. - * @param {[type]} name [description] - * @return {Boolean} [description] + * @param {string} name + * @return {{ found: boolean, path: ExportMap[] }} */ hasDeep(name) { - if (this.namespace.has(name)) return { found: true, path: [this] } + if (this.namespace.has(name)) return { found: true, path: [this] }; if (this.reexports.has(name)) { - const reexports = this.reexports.get(name) - , imported = reexports.getImport() + const reexports = this.reexports.get(name); + const imported = reexports.getImport(); // if import is ignored, return explicit 'null' - if (imported == null) return { found: true, path: [this] } + if (imported == null) return { found: true, path: [this] }; // safeguard against cycles, only if name matches if (imported.path === this.path && reexports.local === name) { - return { found: false, path: [this] } + return { found: false, path: [this] }; } - const deep = imported.hasDeep(reexports.local) - deep.path.unshift(this) + const deep = imported.hasDeep(reexports.local); + deep.path.unshift(this); - return deep + return deep; } // default exports must be explicitly re-exported (#328) if (name !== 'default') { - for (let dep of this.dependencies) { - let innerMap = dep() - if (innerMap == null) return { found: true, path: [this] } + for (const dep of this.dependencies) { + const innerMap = dep(); + if (innerMap == null) return { found: true, path: [this] }; // todo: report as unresolved? - if (!innerMap) continue + if (!innerMap) continue; // safeguard against cycles - if (innerMap.path === this.path) continue + if (innerMap.path === this.path) continue; - let innerValue = innerMap.hasDeep(name) + const innerValue = innerMap.hasDeep(name); if (innerValue.found) { - innerValue.path.unshift(this) - return innerValue + innerValue.path.unshift(this); + return innerValue; } } } - return { found: false, path: [this] } + return { found: false, path: [this] }; } get(name) { - if (this.namespace.has(name)) return this.namespace.get(name) + if (this.namespace.has(name)) return this.namespace.get(name); if (this.reexports.has(name)) { - const reexports = this.reexports.get(name) - , imported = reexports.getImport() + const reexports = this.reexports.get(name); + const imported = reexports.getImport(); // if import is ignored, return explicit 'null' - if (imported == null) return null + if (imported == null) return null; // safeguard against cycles, only if name matches - if (imported.path === this.path && reexports.local === name) return undefined + if (imported.path === this.path && reexports.local === name) return undefined; - return imported.get(reexports.local) + return imported.get(reexports.local); } // default exports must be explicitly re-exported (#328) if (name !== 'default') { - for (let dep of this.dependencies) { - let innerMap = dep() + for (const dep of this.dependencies) { + const innerMap = dep(); // todo: report as unresolved? - if (!innerMap) continue + if (!innerMap) continue; // safeguard against cycles - if (innerMap.path === this.path) continue + if (innerMap.path === this.path) continue; - let innerValue = innerMap.get(name) - if (innerValue !== undefined) return innerValue + const innerValue = innerMap.get(name); + if (innerValue !== undefined) return innerValue; } } - return undefined + return undefined; } forEach(callback, thisArg) { this.namespace.forEach((v, n) => - callback.call(thisArg, v, n, this)) + callback.call(thisArg, v, n, this)); this.reexports.forEach((reexports, name) => { - const reexported = reexports.getImport() + const reexported = reexports.getImport(); // can't look up meta for ignored re-exports (#348) - callback.call(thisArg, reexported && reexported.get(reexports.local), name, this) - }) + callback.call(thisArg, reexported && reexported.get(reexports.local), name, this); + }); this.dependencies.forEach(dep => { - const d = dep() + const d = dep(); // CJS / ignored dependencies won't exist (#717) - if (d == null) return + if (d == null) return; d.forEach((v, n) => - n !== 'default' && callback.call(thisArg, v, n, this)) - }) + n !== 'default' && callback.call(thisArg, v, n, this)); + }); } // todo: keys, values, entries? @@ -191,9 +192,9 @@ export default class ExportMap { node: declaration.source, message: `Parse errors in imported module '${declaration.source.value}': ` + `${this.errors - .map(e => `${e.message} (${e.lineNumber}:${e.column})`) - .join(', ')}`, - }) + .map(e => `${e.message} (${e.lineNumber}:${e.column})`) + .join(', ')}`, + }); } } @@ -201,64 +202,64 @@ export default class ExportMap { * parse docs from the first node that has leading comments */ function captureDoc(source, docStyleParsers, ...nodes) { - const metadata = {} + const metadata = {}; // 'some' short-circuits on first 'true' nodes.some(n => { try { - let leadingComments + let leadingComments; // n.leadingComments is legacy `attachComments` behavior if ('leadingComments' in n) { - leadingComments = n.leadingComments + leadingComments = n.leadingComments; } else if (n.range) { - leadingComments = source.getCommentsBefore(n) + leadingComments = source.getCommentsBefore(n); } - if (!leadingComments || leadingComments.length === 0) return false + if (!leadingComments || leadingComments.length === 0) return false; - for (let name in docStyleParsers) { - const doc = docStyleParsers[name](leadingComments) + for (const name in docStyleParsers) { + const doc = docStyleParsers[name](leadingComments); if (doc) { - metadata.doc = doc + metadata.doc = doc; } } - return true + return true; } catch (err) { - return false + return false; } - }) + }); - return metadata + return metadata; } const availableDocStyleParsers = { jsdoc: captureJsDoc, tomdoc: captureTomDoc, -} +}; /** * parse JSDoc from leading comments - * @param {...[type]} comments [description] - * @return {{doc: object}} + * @param {object[]} comments + * @return {{ doc: object }} */ function captureJsDoc(comments) { - let doc + let doc; // capture XSDoc comments.forEach(comment => { // skip non-block comments - if (comment.type !== 'Block') return + if (comment.type !== 'Block') return; try { - doc = doctrine.parse(comment.value, { unwrap: true }) + doc = doctrine.parse(comment.value, { unwrap: true }); } catch (err) { /* don't care, for now? maybe add to `errors?` */ } - }) + }); - return doc + return doc; } /** @@ -266,15 +267,15 @@ function captureJsDoc(comments) { */ function captureTomDoc(comments) { // collect lines up to first paragraph break - const lines = [] + const lines = []; for (let i = 0; i < comments.length; i++) { - const comment = comments[i] - if (comment.value.match(/^\s*$/)) break - lines.push(comment.value.trim()) + const comment = comments[i]; + if (comment.value.match(/^\s*$/)) break; + lines.push(comment.value.trim()); } // return doctrine-like object - const statusMatch = lines.join(' ').match(/^(Public|Internal|Deprecated):\s*(.+)/) + const statusMatch = lines.join(' ').match(/^(Public|Internal|Deprecated):\s*(.+)/); if (statusMatch) { return { description: statusMatch[2], @@ -282,287 +283,311 @@ function captureTomDoc(comments) { title: statusMatch[1].toLowerCase(), description: statusMatch[2], }], - } + }; } } +const supportedImportTypes = new Set(['ImportDefaultSpecifier', 'ImportNamespaceSpecifier']); + ExportMap.get = function (source, context) { - const path = resolve(source, context) - if (path == null) return null + const path = resolve(source, context); + if (path == null) return null; - return ExportMap.for(childContext(path, context)) -} + return ExportMap.for(childContext(path, context)); +}; ExportMap.for = function (context) { - const { path } = context + const { path } = context; - const cacheKey = hashObject(context).digest('hex') - let exportMap = exportCache.get(cacheKey) + const cacheKey = hashObject(context).digest('hex'); + let exportMap = exportCache.get(cacheKey); // return cached ignore - if (exportMap === null) return null + if (exportMap === null) return null; - const stats = fs.statSync(path) + const stats = fs.statSync(path); if (exportMap != null) { // date equality check if (exportMap.mtime - stats.mtime === 0) { - return exportMap + return exportMap; } // future: check content equality? } // check valid extensions first if (!hasValidExtension(path, context)) { - exportCache.set(cacheKey, null) - return null + exportCache.set(cacheKey, null); + return null; } // check for and cache ignore if (isIgnored(path, context)) { - log('ignored path due to ignore settings:', path) - exportCache.set(cacheKey, null) - return null + log('ignored path due to ignore settings:', path); + exportCache.set(cacheKey, null); + return null; } - const content = fs.readFileSync(path, { encoding: 'utf8' }) + const content = fs.readFileSync(path, { encoding: 'utf8' }); // check for and cache unambiguous modules if (!unambiguous.test(content)) { - log('ignored path due to unambiguous regex:', path) - exportCache.set(cacheKey, null) - return null + log('ignored path due to unambiguous regex:', path); + exportCache.set(cacheKey, null); + return null; } - log('cache miss', cacheKey, 'for path', path) - exportMap = ExportMap.parse(path, content, context) + log('cache miss', cacheKey, 'for path', path); + exportMap = ExportMap.parse(path, content, context); // ambiguous modules return null - if (exportMap == null) return null + if (exportMap == null) return null; - exportMap.mtime = stats.mtime + exportMap.mtime = stats.mtime; - exportCache.set(cacheKey, exportMap) - return exportMap -} + exportCache.set(cacheKey, exportMap); + return exportMap; +}; ExportMap.parse = function (path, content, context) { - var m = new ExportMap(path) + const m = new ExportMap(path); + let ast; try { - var ast = parse(path, content, context) + ast = parse(path, content, context); } catch (err) { - log('parse error:', path, err) - m.errors.push(err) - return m // can't continue + log('parse error:', path, err); + m.errors.push(err); + return m; // can't continue } - if (!unambiguous.isModule(ast)) return null + if (!unambiguous.isModule(ast)) return null; - const docstyle = (context.settings && context.settings['import/docstyle']) || ['jsdoc'] - const docStyleParsers = {} + const docstyle = (context.settings && context.settings['import/docstyle']) || ['jsdoc']; + const docStyleParsers = {}; docstyle.forEach(style => { - docStyleParsers[style] = availableDocStyleParsers[style] - }) + docStyleParsers[style] = availableDocStyleParsers[style]; + }); // attempt to collect module doc if (ast.comments) { ast.comments.some(c => { - if (c.type !== 'Block') return false + if (c.type !== 'Block') return false; try { - const doc = doctrine.parse(c.value, { unwrap: true }) + const doc = doctrine.parse(c.value, { unwrap: true }); if (doc.tags.some(t => t.title === 'module')) { - m.doc = doc - return true + m.doc = doc; + return true; } } catch (err) { /* ignore */ } - return false - }) + return false; + }); } - const namespaces = new Map() + const namespaces = new Map(); function remotePath(value) { - return resolve.relative(value, path, context.settings) + return resolve.relative(value, path, context.settings); } function resolveImport(value) { - const rp = remotePath(value) - if (rp == null) return null - return ExportMap.for(childContext(rp, context)) + const rp = remotePath(value); + if (rp == null) return null; + return ExportMap.for(childContext(rp, context)); } function getNamespace(identifier) { - if (!namespaces.has(identifier.name)) return + if (!namespaces.has(identifier.name)) return; return function () { - return resolveImport(namespaces.get(identifier.name)) - } + return resolveImport(namespaces.get(identifier.name)); + }; } function addNamespace(object, identifier) { - const nsfn = getNamespace(identifier) + const nsfn = getNamespace(identifier); if (nsfn) { - Object.defineProperty(object, 'namespace', { get: nsfn }) + Object.defineProperty(object, 'namespace', { get: nsfn }); } - return object + return object; } - function captureDependency(declaration) { - if (declaration.source == null) return null - if (declaration.importKind === 'type') return null // skip Flow type imports - const importedSpecifiers = new Set() - const supportedTypes = new Set(['ImportDefaultSpecifier', 'ImportNamespaceSpecifier']) - let hasImportedType = false - if (declaration.specifiers) { - declaration.specifiers.forEach(specifier => { - const isType = specifier.importKind === 'type' - hasImportedType = hasImportedType || isType - - if (supportedTypes.has(specifier.type) && !isType) { - importedSpecifiers.add(specifier.type) - } - if (specifier.type === 'ImportSpecifier' && !isType) { - importedSpecifiers.add(specifier.imported.name) - } - }) - } + function captureDependency({ source }, isOnlyImportingTypes, importedSpecifiers = new Set()) { + if (source == null) return null; + + const p = remotePath(source.value); + if (p == null) return null; - // only Flow types were imported - if (hasImportedType && importedSpecifiers.size === 0) return null - - const p = remotePath(declaration.source.value) - if (p == null) return null - const existing = m.imports.get(p) - if (existing != null) return existing.getter - - const getter = thunkFor(p, context) - m.imports.set(p, { - getter, - source: { // capturing actual node reference holds full AST in memory! - value: declaration.source.value, - loc: declaration.source.loc, - }, + const declarationMetadata = { + // capturing actual node reference holds full AST in memory! + source: { value: source.value, loc: source.loc }, + isOnlyImportingTypes, importedSpecifiers, - }) - return getter + }; + + const existing = m.imports.get(p); + if (existing != null) { + existing.declarations.add(declarationMetadata); + return existing.getter; + } + + const getter = thunkFor(p, context); + m.imports.set(p, { getter, declarations: new Set([declarationMetadata]) }); + return getter; } - const source = makeSourceCode(content, ast) + const source = makeSourceCode(content, ast); - function isEsModuleInterop() { + function readTsConfig() { const tsConfigInfo = tsConfigLoader({ - cwd: context.parserOptions && context.parserOptions.tsconfigRootDir || process.cwd(), + 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 jsonText = fs.readFileSync(tsConfigInfo.tsConfigPath).toString(); if (!parseConfigFileTextToJson) { // this is because projects not using TypeScript won't have typescript installed - ({parseConfigFileTextToJson} = require('typescript')) + ({ parseConfigFileTextToJson } = require('typescript')); } - const tsConfig = parseConfigFileTextToJson(tsConfigInfo.tsConfigPath, jsonText).config - return tsConfig.compilerOptions.esModuleInterop + return parseConfigFileTextToJson(tsConfigInfo.tsConfigPath, jsonText).config; } } catch (e) { - return false + // Catch any errors + } + + return null; + } + + function isEsModuleInterop() { + const cacheKey = hashObject({ + tsconfigRootDir: context.parserOptions && context.parserOptions.tsconfigRootDir, + }).digest('hex'); + let tsConfig = tsConfigCache.get(cacheKey); + if (typeof tsConfig === 'undefined') { + tsConfig = readTsConfig(); + tsConfigCache.set(cacheKey, tsConfig); } + + return tsConfig && tsConfig.compilerOptions ? tsConfig.compilerOptions.esModuleInterop : false; } ast.body.forEach(function (n) { if (n.type === 'ExportDefaultDeclaration') { - const exportMeta = captureDoc(source, docStyleParsers, n) + const exportMeta = captureDoc(source, docStyleParsers, n); if (n.declaration.type === 'Identifier') { - addNamespace(exportMeta, n.declaration) + addNamespace(exportMeta, n.declaration); } - m.namespace.set('default', exportMeta) - return + m.namespace.set('default', exportMeta); + return; } if (n.type === 'ExportAllDeclaration') { - const getter = captureDependency(n) - if (getter) m.dependencies.add(getter) - return + const getter = captureDependency(n, n.exportKind === 'type'); + if (getter) m.dependencies.add(getter); + return; } // capture namespaces in case of later export if (n.type === 'ImportDeclaration') { - captureDependency(n) - let ns - if (n.specifiers.some(s => s.type === 'ImportNamespaceSpecifier' && (ns = s))) { - namespaces.set(ns.local.name, n.source.value) + // import type { Foo } (TS and Flow) + const declarationIsType = n.importKind === 'type'; + // import './foo' or import {} from './foo' (both 0 specifiers) is a side effect and + // shouldn't be considered to be just importing types + let specifiersOnlyImportingTypes = n.specifiers.length; + const importedSpecifiers = new Set(); + n.specifiers.forEach(specifier => { + if (supportedImportTypes.has(specifier.type)) { + importedSpecifiers.add(specifier.type); + } + if (specifier.type === 'ImportSpecifier') { + importedSpecifiers.add(specifier.imported.name); + } + + // import { type Foo } (Flow) + specifiersOnlyImportingTypes = + specifiersOnlyImportingTypes && specifier.importKind === 'type'; + }); + captureDependency(n, declarationIsType || specifiersOnlyImportingTypes, importedSpecifiers); + + const ns = n.specifiers.find(s => s.type === 'ImportNamespaceSpecifier'); + if (ns) { + namespaces.set(ns.local.name, n.source.value); } - return + return; } if (n.type === 'ExportNamedDeclaration') { // capture declaration if (n.declaration != null) { switch (n.declaration.type) { - case 'FunctionDeclaration': - case 'ClassDeclaration': - case 'TypeAlias': // flowtype with babel-eslint parser - case 'InterfaceDeclaration': - case 'DeclareFunction': - case 'TSDeclareFunction': - case 'TSEnumDeclaration': - case 'TSTypeAliasDeclaration': - case 'TSInterfaceDeclaration': - case 'TSAbstractClassDeclaration': - case 'TSModuleDeclaration': - m.namespace.set(n.declaration.id.name, captureDoc(source, docStyleParsers, n)) - break - case 'VariableDeclaration': - n.declaration.declarations.forEach((d) => - recursivePatternCapture(d.id, - id => m.namespace.set(id.name, captureDoc(source, docStyleParsers, d, n)))) - break + case 'FunctionDeclaration': + case 'ClassDeclaration': + case 'TypeAlias': // flowtype with babel-eslint parser + case 'InterfaceDeclaration': + case 'DeclareFunction': + case 'TSDeclareFunction': + case 'TSEnumDeclaration': + case 'TSTypeAliasDeclaration': + case 'TSInterfaceDeclaration': + case 'TSAbstractClassDeclaration': + case 'TSModuleDeclaration': + m.namespace.set(n.declaration.id.name, captureDoc(source, docStyleParsers, n)); + break; + case 'VariableDeclaration': + n.declaration.declarations.forEach((d) => + recursivePatternCapture(d.id, + id => m.namespace.set(id.name, captureDoc(source, docStyleParsers, d, n)))); + break; } } - const nsource = n.source && n.source.value + const nsource = n.source && n.source.value; n.specifiers.forEach((s) => { - const exportMeta = {} - let local + const exportMeta = {}; + let local; switch (s.type) { - case 'ExportDefaultSpecifier': - if (!n.source) return - local = 'default' - break - case 'ExportNamespaceSpecifier': - m.namespace.set(s.exported.name, Object.defineProperty(exportMeta, 'namespace', { - get() { return resolveImport(nsource) }, - })) - return - case 'ExportSpecifier': - if (!n.source) { - m.namespace.set(s.exported.name, addNamespace(exportMeta, s.local)) - return - } - // else falls through - default: - local = s.local.name - break + case 'ExportDefaultSpecifier': + if (!n.source) return; + local = 'default'; + break; + case 'ExportNamespaceSpecifier': + m.namespace.set(s.exported.name, Object.defineProperty(exportMeta, 'namespace', { + get() { return resolveImport(nsource); }, + })); + return; + case 'ExportSpecifier': + if (!n.source) { + m.namespace.set(s.exported.name, addNamespace(exportMeta, s.local)); + return; + } + // else falls through + default: + local = s.local.name; + break; } // todo: JSDoc - m.reexports.set(s.exported.name, { local, getImport: () => resolveImport(nsource) }) - }) + m.reexports.set(s.exported.name, { local, getImport: () => resolveImport(nsource) }); + }); } - const isEsModuleInteropTrue = isEsModuleInterop() + const isEsModuleInteropTrue = isEsModuleInterop(); - const exports = ['TSExportAssignment'] + const exports = ['TSExportAssignment']; if (isEsModuleInteropTrue) { - exports.push('TSNamespaceExportDeclaration') + exports.push('TSNamespaceExportDeclaration'); } // This doesn't declare anything, but changes what's being exported. if (includes(exports, n.type)) { - const exportedName = n.expression && n.expression.name || n.id.name + const exportedName = n.type === 'TSNamespaceExportDeclaration' + ? n.id.name + : (n.expression && n.expression.name || (n.expression.id && n.expression.id.name) || null); const declTypes = [ 'VariableDeclaration', 'ClassDeclaration', @@ -572,29 +597,29 @@ ExportMap.parse = function (path, content, context) { '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 + m.namespace.set('default', captureDoc(source, docStyleParsers, n)); + return; } if (isEsModuleInteropTrue) { - m.namespace.set('default', {}) + 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)) + 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 + moduleBlockNode; if (!namespaceDecl) { // TypeScript can check this for us; we needn't @@ -604,24 +629,24 @@ ExportMap.parse = function (path, content, context) { id.name, captureDoc(source, docStyleParsers, decl, namespaceDecl, moduleBlockNode) )) - ) + ); } else { m.namespace.set( namespaceDecl.id.name, - captureDoc(source, docStyleParsers, moduleBlockNode)) + captureDoc(source, docStyleParsers, moduleBlockNode)); } - }) + }); } } else { // Export as default - m.namespace.set('default', captureDoc(source, docStyleParsers, decl)) + m.namespace.set('default', captureDoc(source, docStyleParsers, decl)); } - }) + }); } - }) + }); - return m -} + return m; +}; /** * The creation of this closure is isolated from other scopes @@ -629,7 +654,7 @@ ExportMap.parse = function (path, content, context) { * caused memory leaks. See #1266. */ function thunkFor(p, context) { - return () => ExportMap.for(childContext(p, context)) + return () => ExportMap.for(childContext(p, context)); } @@ -642,26 +667,34 @@ function thunkFor(p, context) { */ export function recursivePatternCapture(pattern, callback) { switch (pattern.type) { - case 'Identifier': // base case - callback(pattern) - break - - case 'ObjectPattern': - pattern.properties.forEach(p => { - recursivePatternCapture(p.value, callback) - }) - break - - case 'ArrayPattern': - pattern.elements.forEach((element) => { - if (element == null) return - recursivePatternCapture(element, callback) - }) - break - - case 'AssignmentPattern': - callback(pattern.left) - break + case 'Identifier': // base case + callback(pattern); + break; + + case 'ObjectPattern': + pattern.properties.forEach(p => { + if (p.type === 'ExperimentalRestProperty' || p.type === 'RestElement') { + callback(p.argument); + return; + } + recursivePatternCapture(p.value, callback); + }); + break; + + case 'ArrayPattern': + pattern.elements.forEach((element) => { + if (element == null) return; + if (element.type === 'ExperimentalRestProperty' || element.type === 'RestElement') { + callback(element.argument); + return; + } + recursivePatternCapture(element, callback); + }); + break; + + case 'AssignmentPattern': + callback(pattern.left); + break; } } @@ -669,13 +702,13 @@ export function recursivePatternCapture(pattern, callback) { * don't hold full context object in memory, just grab what we need. */ function childContext(path, context) { - const { settings, parserOptions, parserPath } = context + const { settings, parserOptions, parserPath } = context; return { settings, parserOptions, parserPath, path, - } + }; } @@ -685,9 +718,9 @@ function childContext(path, context) { function makeSourceCode(text, ast) { if (SourceCode.length > 1) { // ESLint 3 - return new SourceCode(text, ast) + return new SourceCode(text, ast); } else { // ESLint 4, 5 - return new SourceCode({ text, ast }) + return new SourceCode({ text, ast }); } } diff --git a/src/core/importType.js b/src/core/importType.js index ff2d10b60f..ecea976f4a 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -1,101 +1,110 @@ -import coreModules from 'resolve/lib/core' +import { isAbsolute as nodeIsAbsolute, relative, resolve as nodeResolve } from 'path'; +import isCoreModule from 'is-core-module'; -import resolve from 'eslint-module-utils/resolve' +import resolve from 'eslint-module-utils/resolve'; +import { getContextPackagePath } from './packagePath'; function baseModule(name) { if (isScoped(name)) { - const [scope, pkg] = name.split('/') - return `${scope}/${pkg}` + const [scope, pkg] = name.split('/'); + return `${scope}/${pkg}`; } - const [pkg] = name.split('/') - return pkg + const [pkg] = name.split('/'); + return pkg; } export function isAbsolute(name) { - return name && name.startsWith('/') + return nodeIsAbsolute(name); } // path is defined only when a resolver resolves to a non-standard path export function isBuiltIn(name, settings, path) { - if (path || !name) return false - const base = baseModule(name) - const extras = (settings && settings['import/core-modules']) || [] - return coreModules[base] || extras.indexOf(base) > -1 + if (path || !name) return false; + const base = baseModule(name); + const extras = (settings && settings['import/core-modules']) || []; + return isCoreModule(base) || extras.indexOf(base) > -1; } -function isExternalPath(path, name, settings) { - const folders = (settings && settings['import/external-module-folders']) || ['node_modules'] - return !path || folders.some(folder => isSubpath(folder, path)) +export function isExternalModule(name, settings, path, context) { + if (arguments.length < 4) { + throw new TypeError('isExternalModule: name, settings, path, and context are all required'); + } + return isModule(name) && isExternalPath(name, settings, path, getContextPackagePath(context)); +} + +export function isExternalModuleMain(name, settings, path, context) { + return isModuleMain(name) && isExternalPath(name, settings, path, getContextPackagePath(context)); } -function isSubpath(subpath, path) { - const normPath = path.replace(/\\/g, '/') - const normSubpath = subpath.replace(/\\/g, '/').replace(/\/$/, '') - if (normSubpath.length === 0) { - return false +function isExternalPath(name, settings, path, packagePath) { + const internalScope = (settings && settings['import/internal-regex']); + if (internalScope && new RegExp(internalScope).test(name)) { + return false; + } + + if (!path || relative(packagePath, path).startsWith('..')) { + return true; } - const left = normPath.indexOf(normSubpath) - const right = left + normSubpath.length - return left !== -1 && - (left === 0 || normSubpath[0] !== '/' && normPath[left - 1] === '/') && - (right >= normPath.length || normPath[right] === '/') + + const folders = (settings && settings['import/external-module-folders']) || ['node_modules']; + return folders.some((folder) => { + const folderPath = nodeResolve(packagePath, folder); + const relativePath = relative(folderPath, path); + return !relativePath.startsWith('..'); + }); } -const externalModuleRegExp = /^\w/ -export function isExternalModule(name, settings, path) { - return externalModuleRegExp.test(name) && isExternalPath(path, name, settings) +const moduleRegExp = /^\w/; +function isModule(name) { + return name && moduleRegExp.test(name); } -const externalModuleMainRegExp = /^[\w]((?!\/).)*$/ -export function isExternalModuleMain(name, settings, path) { - return externalModuleMainRegExp.test(name) && isExternalPath(path, name, settings) +const moduleMainRegExp = /^[\w]((?!\/).)*$/; +function isModuleMain(name) { + return name && moduleMainRegExp.test(name); } -const scopedRegExp = /^@[^/]*\/?[^/]+/ +const scopedRegExp = /^@[^/]*\/?[^/]+/; export function isScoped(name) { - return name && scopedRegExp.test(name) + return name && scopedRegExp.test(name); } -const scopedMainRegExp = /^@[^/]+\/?[^/]+$/ +const scopedMainRegExp = /^@[^/]+\/?[^/]+$/; export function isScopedMain(name) { - return name && scopedMainRegExp.test(name) -} - -function isInternalModule(name, settings, path) { - const internalScope = (settings && settings['import/internal-regex']) - const matchesScopedOrExternalRegExp = scopedRegExp.test(name) || externalModuleRegExp.test(name) - return (matchesScopedOrExternalRegExp && (internalScope && new RegExp(internalScope).test(name) || !isExternalPath(path, name, settings))) + return name && scopedMainRegExp.test(name); } function isRelativeToParent(name) { - return/^\.\.$|^\.\.[\\/]/.test(name) + return/^\.\.$|^\.\.[\\/]/.test(name); } -const indexFiles = ['.', './', './index', './index.js'] +const indexFiles = ['.', './', './index', './index.js']; function isIndex(name) { - return indexFiles.indexOf(name) !== -1 + return indexFiles.indexOf(name) !== -1; } function isRelativeToSibling(name) { - return /^\.[\\/]/.test(name) + return /^\.[\\/]/.test(name); } -function typeTest(name, settings, path) { - if (isAbsolute(name, settings, path)) { return 'absolute' } - if (isBuiltIn(name, settings, path)) { return 'builtin' } - if (isInternalModule(name, settings, path)) { return 'internal' } - if (isExternalModule(name, settings, path)) { return 'external' } - if (isScoped(name, settings, path)) { return 'external' } - if (isRelativeToParent(name, settings, path)) { return 'parent' } - if (isIndex(name, settings, path)) { return 'index' } - if (isRelativeToSibling(name, settings, path)) { return 'sibling' } - return 'unknown' +function typeTest(name, context, path) { + const { settings } = context; + if (isAbsolute(name, settings, path)) { return 'absolute'; } + if (isBuiltIn(name, settings, path)) { return 'builtin'; } + if (isModule(name, settings, path) || isScoped(name, settings, path)) { + const packagePath = getContextPackagePath(context); + return isExternalPath(name, settings, path, packagePath) ? 'external' : 'internal'; + } + if (isRelativeToParent(name, settings, path)) { return 'parent'; } + if (isIndex(name, settings, path)) { return 'index'; } + if (isRelativeToSibling(name, settings, path)) { return 'sibling'; } + return 'unknown'; } export function isScopedModule(name) { - return name.indexOf('@') === 0 + return name.indexOf('@') === 0 && !name.startsWith('@/'); } export default function resolveImportType(name, context) { - return typeTest(name, context.settings, resolve(name, context)) + return typeTest(name, context, resolve(name, context)); } diff --git a/src/core/packagePath.js b/src/core/packagePath.js new file mode 100644 index 0000000000..e95b066668 --- /dev/null +++ b/src/core/packagePath.js @@ -0,0 +1,18 @@ +import { dirname } from 'path'; +import findUp from 'find-up'; +import readPkgUp from 'read-pkg-up'; + + +export function getContextPackagePath(context) { + return getFilePackagePath(context.getFilename()); +} + +export function getFilePackagePath(filePath) { + const fp = findUp.sync('package.json', { cwd: filePath }); + return dirname(fp); +} + +export function getFilePackageName(filePath) { + const { pkg } = readPkgUp.sync({ cwd: filePath, normalize: false }); + return pkg && pkg.name; +} diff --git a/src/core/staticRequire.js b/src/core/staticRequire.js index 45ed79d79b..502d39317d 100644 --- a/src/core/staticRequire.js +++ b/src/core/staticRequire.js @@ -6,5 +6,5 @@ export default function isStaticRequire(node) { node.callee.name === 'require' && node.arguments.length === 1 && node.arguments[0].type === 'Literal' && - typeof node.arguments[0].value === 'string' + typeof node.arguments[0].value === 'string'; } diff --git a/src/docsUrl.js b/src/docsUrl.js index 3c01c49adf..ff277251b4 100644 --- a/src/docsUrl.js +++ b/src/docsUrl.js @@ -1,7 +1,7 @@ -import pkg from '../package.json' +import pkg from '../package.json'; -const repoUrl = 'https://github.com/benmosher/eslint-plugin-import' +const repoUrl = 'https://github.com/benmosher/eslint-plugin-import'; export default function docsUrl(ruleName, commitish = `v${pkg.version}`) { - return `${repoUrl}/blob/${commitish}/docs/rules/${ruleName}.md` + return `${repoUrl}/blob/${commitish}/docs/rules/${ruleName}.md`; } diff --git a/src/importDeclaration.js b/src/importDeclaration.js index 69af65d978..0d5e1870a7 100644 --- a/src/importDeclaration.js +++ b/src/importDeclaration.js @@ -1,4 +1,4 @@ export default function importDeclaration(context) { - var ancestors = context.getAncestors() - return ancestors[ancestors.length - 1] + const ancestors = context.getAncestors(); + return ancestors[ancestors.length - 1]; } diff --git a/src/index.js b/src/index.js index d0a98b7cb9..7fa3710d64 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ export const rules = { 'no-restricted-paths': require('./rules/no-restricted-paths'), 'no-internal-modules': require('./rules/no-internal-modules'), 'group-exports': require('./rules/group-exports'), + 'no-relative-packages': require('./rules/no-relative-packages'), 'no-relative-parent-imports': require('./rules/no-relative-parent-imports'), 'no-self-import': require('./rules/no-self-import'), @@ -39,6 +40,7 @@ export const rules = { 'no-unassigned-import': require('./rules/no-unassigned-import'), 'no-useless-path-segments': require('./rules/no-useless-path-segments'), 'dynamic-import-chunkname': require('./rules/dynamic-import-chunkname'), + 'no-import-module-exports': require('./rules/no-import-module-exports'), // export 'exports-last': require('./rules/exports-last'), @@ -48,7 +50,7 @@ export const rules = { // deprecated aliases to rules 'imports-first': require('./rules/imports-first'), -} +}; export const configs = { 'recommended': require('../config/recommended'), @@ -64,4 +66,4 @@ export const configs = { 'react-native': require('../config/react-native'), 'electron': require('../config/electron'), 'typescript': require('../config/typescript'), -} +}; diff --git a/src/rules/default.js b/src/rules/default.js index 09efa0d880..797519a64c 100644 --- a/src/rules/default.js +++ b/src/rules/default.js @@ -1,5 +1,5 @@ -import Exports from '../ExportMap' -import docsUrl from '../docsUrl' +import Exports from '../ExportMap'; +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -16,25 +16,25 @@ module.exports = { const defaultSpecifier = node.specifiers.find( specifier => specifier.type === specifierType - ) + ); - if (!defaultSpecifier) return - var imports = Exports.get(node.source.value, context) - if (imports == null) return + if (!defaultSpecifier) return; + const imports = Exports.get(node.source.value, context); + if (imports == null) return; if (imports.errors.length) { - imports.reportErrors(context, node) + imports.reportErrors(context, node); } else if (imports.get('default') === undefined) { context.report({ node: defaultSpecifier, message: `No default export found in imported module "${node.source.value}".`, - }) + }); } } return { 'ImportDeclaration': checkDefault.bind(null, 'ImportDefaultSpecifier'), 'ExportNamedDeclaration': checkDefault.bind(null, 'ExportDefaultSpecifier'), - } + }; }, -} +}; diff --git a/src/rules/dynamic-import-chunkname.js b/src/rules/dynamic-import-chunkname.js index 40b99239ab..7a21ec62d9 100644 --- a/src/rules/dynamic-import-chunkname.js +++ b/src/rules/dynamic-import-chunkname.js @@ -1,5 +1,5 @@ -import vm from 'vm' -import docsUrl from '../docsUrl' +import vm from 'vm'; +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -25,88 +25,95 @@ module.exports = { }, create: function (context) { - const config = context.options[0] - const { importFunctions = [] } = config || {} - const { webpackChunknameFormat = '[0-9a-zA-Z-_/.]+' } = config || {} + const config = context.options[0]; + const { importFunctions = [] } = config || {}; + const { webpackChunknameFormat = '[0-9a-zA-Z-_/.]+' } = config || {}; - const paddedCommentRegex = /^ (\S[\s\S]+\S) $/ - const commentStyleRegex = /^( \w+: ("[^"]*"|\d+|false|true),?)+ $/ - const chunkSubstrFormat = ` webpackChunkName: "${webpackChunknameFormat}",? ` - const chunkSubstrRegex = new RegExp(chunkSubstrFormat) + const paddedCommentRegex = /^ (\S[\s\S]+\S) $/; + const commentStyleRegex = /^( \w+: (["'][^"']*["']|\d+|false|true),?)+ $/; + const chunkSubstrFormat = ` webpackChunkName: ["']${webpackChunknameFormat}["'],? `; + const chunkSubstrRegex = new RegExp(chunkSubstrFormat); - return { - CallExpression(node) { - if (node.callee.type !== 'Import' && importFunctions.indexOf(node.callee.name) < 0) { - return - } + function run(node, arg) { + const sourceCode = context.getSourceCode(); + const leadingComments = sourceCode.getCommentsBefore + ? sourceCode.getCommentsBefore(arg) // This method is available in ESLint >= 4. + : sourceCode.getComments(arg).leading; // This method is deprecated in ESLint 7. - const sourceCode = context.getSourceCode() - const arg = node.arguments[0] - const leadingComments = sourceCode.getCommentsBefore - ? sourceCode.getCommentsBefore(arg) // This method is available in ESLint >= 4. - : sourceCode.getComments(arg).leading // This method is deprecated in ESLint 7. + if (!leadingComments || leadingComments.length === 0) { + context.report({ + node, + message: 'dynamic imports require a leading comment with the webpack chunkname', + }); + return; + } - if (!leadingComments || leadingComments.length === 0) { + let isChunknamePresent = false; + + for (const comment of leadingComments) { + if (comment.type !== 'Block') { context.report({ node, - message: 'dynamic imports require a leading comment with the webpack chunkname', - }) - return + message: 'dynamic imports require a /* foo */ style comment, not a // foo comment', + }); + return; } - let isChunknamePresent = false - - for (const comment of leadingComments) { - if (comment.type !== 'Block') { - context.report({ - node, - message: 'dynamic imports require a /* foo */ style comment, not a // foo comment', - }) - return - } - - if (!paddedCommentRegex.test(comment.value)) { - context.report({ - node, - message: `dynamic imports require a block comment padded with spaces - /* foo */`, - }) - return - } - - try { - // just like webpack itself does - vm.runInNewContext(`(function(){return {${comment.value}}})()`) - } - catch (error) { - context.report({ - node, - message: `dynamic imports require a "webpack" comment with valid syntax`, - }) - return - } - - if (!commentStyleRegex.test(comment.value)) { - context.report({ - node, - message: - `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`, - }) - return - } + if (!paddedCommentRegex.test(comment.value)) { + context.report({ + node, + message: `dynamic imports require a block comment padded with spaces - /* foo */`, + }); + return; + } - if (chunkSubstrRegex.test(comment.value)) { - isChunknamePresent = true - } + try { + // just like webpack itself does + vm.runInNewContext(`(function(){return {${comment.value}}})()`); + } + catch (error) { + context.report({ + node, + message: `dynamic imports require a "webpack" comment with valid syntax`, + }); + return; } - if (!isChunknamePresent) { + if (!commentStyleRegex.test(comment.value)) { context.report({ node, message: `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`, - }) + }); + return; } - }, + + if (chunkSubstrRegex.test(comment.value)) { + isChunknamePresent = true; + } + } + + if (!isChunknamePresent) { + context.report({ + node, + message: + `dynamic imports require a leading comment in the form /*${chunkSubstrFormat}*/`, + }); + } } + + return { + ImportExpression(node) { + run(node, node.source); + }, + + CallExpression(node) { + if (node.callee.type !== 'Import' && importFunctions.indexOf(node.callee.name) < 0) { + return; + } + + run(node, node.arguments[0]); + }, + }; }, -} +}; diff --git a/src/rules/export.js b/src/rules/export.js index f131374df3..386211baaf 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -1,6 +1,6 @@ -import ExportMap, { recursivePatternCapture } from '../ExportMap' -import docsUrl from '../docsUrl' -import includes from 'array-includes' +import ExportMap, { recursivePatternCapture } from '../ExportMap'; +import docsUrl from '../docsUrl'; +import includes from 'array-includes'; /* Notes on TypeScript namespaces aka TSModuleDeclaration: @@ -21,8 +21,8 @@ ambient namespaces: - have no other restrictions */ -const rootProgram = 'root' -const tsTypePrefix = 'type:' +const rootProgram = 'root'; +const tsTypePrefix = 'type:'; /** * Detect function overloads like: @@ -35,14 +35,14 @@ const tsTypePrefix = 'type:' * @returns {boolean} */ function isTypescriptFunctionOverloads(nodes) { - const types = new Set(Array.from(nodes, node => node.parent.type)) + const types = new Set(Array.from(nodes, node => node.parent.type)); return ( types.has('TSDeclareFunction') && ( types.size === 1 || (types.size === 2 && types.has('FunctionDeclaration')) ) - ) + ); } module.exports = { @@ -55,111 +55,122 @@ module.exports = { }, create: function (context) { - const namespace = new Map([[rootProgram, new Map()]]) + const namespace = new Map([[rootProgram, new Map()]]); function addNamed(name, node, parent, isType) { if (!namespace.has(parent)) { - namespace.set(parent, new Map()) + namespace.set(parent, new Map()); } - const named = namespace.get(parent) + const named = namespace.get(parent); - const key = isType ? `${tsTypePrefix}${name}` : name - let nodes = named.get(key) + const key = isType ? `${tsTypePrefix}${name}` : name; + let nodes = named.get(key); if (nodes == null) { - nodes = new Set() - named.set(key, nodes) + nodes = new Set(); + named.set(key, nodes); } - nodes.add(node) + nodes.add(node); } function getParent(node) { if (node.parent && node.parent.type === 'TSModuleBlock') { - return node.parent.parent + return node.parent.parent; } // just in case somehow a non-ts namespace export declaration isn't directly // parented to the root Program node - return rootProgram + return rootProgram; } return { 'ExportDefaultDeclaration': (node) => addNamed('default', node, getParent(node)), - 'ExportSpecifier': (node) => addNamed(node.exported.name, node.exported, getParent(node)), + 'ExportSpecifier': (node) => addNamed( + node.exported.name, + node.exported, + getParent(node.parent) + ), 'ExportNamedDeclaration': function (node) { - if (node.declaration == null) return + if (node.declaration == null) return; - const parent = getParent(node) + const parent = getParent(node); // support for old TypeScript versions - const isTypeVariableDecl = node.declaration.kind === 'type' + const isTypeVariableDecl = node.declaration.kind === 'type'; if (node.declaration.id != null) { if (includes([ 'TSTypeAliasDeclaration', 'TSInterfaceDeclaration', ], node.declaration.type)) { - addNamed(node.declaration.id.name, node.declaration.id, parent, true) + addNamed(node.declaration.id.name, node.declaration.id, parent, true); } else { - addNamed(node.declaration.id.name, node.declaration.id, parent, isTypeVariableDecl) + addNamed(node.declaration.id.name, node.declaration.id, parent, isTypeVariableDecl); } } if (node.declaration.declarations != null) { - for (let declaration of node.declaration.declarations) { + for (const declaration of node.declaration.declarations) { recursivePatternCapture(declaration.id, v => - addNamed(v.name, v, parent, isTypeVariableDecl)) + addNamed(v.name, v, parent, isTypeVariableDecl)); } } }, 'ExportAllDeclaration': function (node) { - if (node.source == null) return // not sure if this is ever true + if (node.source == null) return; // not sure if this is ever true - const remoteExports = ExportMap.get(node.source.value, context) - if (remoteExports == null) return + // `export * as X from 'path'` does not conflict + if (node.exported && node.exported.name) return; + + const remoteExports = ExportMap.get(node.source.value, context); + if (remoteExports == null) return; if (remoteExports.errors.length) { - remoteExports.reportErrors(context, node) - return + remoteExports.reportErrors(context, node); + return; } - const parent = getParent(node) + const parent = getParent(node); - let any = false - remoteExports.forEach((v, name) => - name !== 'default' && - (any = true) && // poor man's filter - addNamed(name, node, parent)) + let any = false; + remoteExports.forEach((v, name) => { + if (name !== 'default') { + any = true; // poor man's filter + addNamed(name, node, parent); + } + }); if (!any) { - context.report(node.source, - `No named exports found in module '${node.source.value}'.`) + context.report( + node.source, + `No named exports found in module '${node.source.value}'.` + ); } }, 'Program:exit': function () { - for (let [, named] of namespace) { - for (let [name, nodes] of named) { - if (nodes.size <= 1) continue + for (const [, named] of namespace) { + for (const [name, nodes] of named) { + if (nodes.size <= 1) continue; - if (isTypescriptFunctionOverloads(nodes)) continue + if (isTypescriptFunctionOverloads(nodes)) continue; - for (let node of nodes) { + for (const node of nodes) { if (name === 'default') { - context.report(node, 'Multiple default exports.') + context.report(node, 'Multiple default exports.'); } else { context.report( node, `Multiple exports of name '${name.replace(tsTypePrefix, '')}'.` - ) + ); } } } } }, - } + }; }, -} +}; diff --git a/src/rules/exports-last.js b/src/rules/exports-last.js index 65dd8a30fa..ea044f32b6 100644 --- a/src/rules/exports-last.js +++ b/src/rules/exports-last.js @@ -1,9 +1,9 @@ -import docsUrl from '../docsUrl' +import docsUrl from '../docsUrl'; function isNonExportStatement({ type }) { return type !== 'ExportDefaultDeclaration' && type !== 'ExportNamedDeclaration' && - type !== 'ExportAllDeclaration' + type !== 'ExportAllDeclaration'; } module.exports = { @@ -20,10 +20,10 @@ module.exports = { Program: function ({ body }) { const lastNonExportStatementIndex = body.reduce(function findLastIndex(acc, item, index) { if (isNonExportStatement(item)) { - return index + return index; } - return acc - }, -1) + return acc; + }, -1); if (lastNonExportStatementIndex !== -1) { body.slice(0, lastNonExportStatementIndex).forEach(function checkNonExport(node) { @@ -31,11 +31,11 @@ module.exports = { context.report({ node, message: 'Export statements should appear at the end of the file', - }) + }); } - }) + }); } }, - } + }; }, -} +}; diff --git a/src/rules/extensions.js b/src/rules/extensions.js index fd9d177adf..bd47afa99d 100644 --- a/src/rules/extensions.js +++ b/src/rules/extensions.js @@ -1,61 +1,62 @@ -import path from 'path' +import path from 'path'; -import resolve from 'eslint-module-utils/resolve' -import { isBuiltIn, isExternalModule, isScoped, isScopedModule } from '../core/importType' -import docsUrl from '../docsUrl' +import resolve from 'eslint-module-utils/resolve'; +import { isBuiltIn, isExternalModule, isScoped, isScopedModule } from '../core/importType'; +import moduleVisitor from 'eslint-module-utils/moduleVisitor'; +import docsUrl from '../docsUrl'; -const enumValues = { enum: [ 'always', 'ignorePackages', 'never' ] } +const enumValues = { enum: [ 'always', 'ignorePackages', 'never' ] }; const patternProperties = { type: 'object', patternProperties: { '.*': enumValues }, -} +}; const properties = { type: 'object', properties: { 'pattern': patternProperties, 'ignorePackages': { type: 'boolean' }, }, -} +}; function buildProperties(context) { - const result = { - defaultConfig: 'never', - pattern: {}, - ignorePackages: false, - } - - context.options.forEach(obj => { + const result = { + defaultConfig: 'never', + pattern: {}, + ignorePackages: false, + }; - // If this is a string, set defaultConfig to its value - if (typeof obj === 'string') { - result.defaultConfig = obj - return - } + context.options.forEach(obj => { - // If this is not the new structure, transfer all props to result.pattern - if (obj.pattern === undefined && obj.ignorePackages === undefined) { - Object.assign(result.pattern, obj) - return - } + // If this is a string, set defaultConfig to its value + if (typeof obj === 'string') { + result.defaultConfig = obj; + return; + } - // If pattern is provided, transfer all props - if (obj.pattern !== undefined) { - Object.assign(result.pattern, obj.pattern) - } + // If this is not the new structure, transfer all props to result.pattern + if (obj.pattern === undefined && obj.ignorePackages === undefined) { + Object.assign(result.pattern, obj); + return; + } - // If ignorePackages is provided, transfer it to result - if (obj.ignorePackages !== undefined) { - result.ignorePackages = obj.ignorePackages - } - }) + // If pattern is provided, transfer all props + if (obj.pattern !== undefined) { + Object.assign(result.pattern, obj.pattern); + } - if (result.defaultConfig === 'ignorePackages') { - result.defaultConfig = 'always' - result.ignorePackages = true + // If ignorePackages is provided, transfer it to result + if (obj.ignorePackages !== undefined) { + result.ignorePackages = obj.ignorePackages; } + }); + + if (result.defaultConfig === 'ignorePackages') { + result.defaultConfig = 'always'; + result.ignorePackages = true; + } - return result + return result; } module.exports = { @@ -104,86 +105,85 @@ module.exports = { create: function (context) { - const props = buildProperties(context) + const props = buildProperties(context); function getModifier(extension) { - return props.pattern[extension] || props.defaultConfig + return props.pattern[extension] || props.defaultConfig; } function isUseOfExtensionRequired(extension, isPackage) { - return getModifier(extension) === 'always' && (!props.ignorePackages || !isPackage) + return getModifier(extension) === 'always' && (!props.ignorePackages || !isPackage); } function isUseOfExtensionForbidden(extension) { - return getModifier(extension) === 'never' + return getModifier(extension) === 'never'; } function isResolvableWithoutExtension(file) { - const extension = path.extname(file) - const fileWithoutExtension = file.slice(0, -extension.length) - const resolvedFileWithoutExtension = resolve(fileWithoutExtension, context) + const extension = path.extname(file); + const fileWithoutExtension = file.slice(0, -extension.length); + const resolvedFileWithoutExtension = resolve(fileWithoutExtension, context); - return resolvedFileWithoutExtension === resolve(file, context) + return resolvedFileWithoutExtension === resolve(file, context); } function isExternalRootModule(file) { - const slashCount = file.split('/').length - 1 + const slashCount = file.split('/').length - 1; - if (isScopedModule(file) && slashCount <= 1) return true - if (isExternalModule(file, context, resolve(file, context)) && !slashCount) return true - return false + if (slashCount === 0) return true; + if (isScopedModule(file) && slashCount <= 1) return true; + return false; } - function checkFileExtension(node) { - const { source } = node - + function checkFileExtension(source) { // bail if the declaration doesn't have a source, e.g. "export { foo };" - if (!source) return - - const importPathWithQueryString = source.value + if (!source) return; + + const importPathWithQueryString = source.value; // don't enforce anything on builtins - if (isBuiltIn(importPathWithQueryString, context.settings)) return + if (isBuiltIn(importPathWithQueryString, context.settings)) return; - const importPath = importPathWithQueryString.replace(/\?(.*)$/, '') + const importPath = importPathWithQueryString.replace(/\?(.*)$/, ''); // don't enforce in root external packages as they may have names with `.js`. // Like `import Decimal from decimal.js`) - if (isExternalRootModule(importPath)) return + if (isExternalRootModule(importPath)) return; - const resolvedPath = resolve(importPath, context) + const resolvedPath = resolve(importPath, context); // get extension from resolved path, if possible. // for unresolved, use source value. - const extension = path.extname(resolvedPath || importPath).substring(1) + const extension = path.extname(resolvedPath || importPath).substring(1); // determine if this is a module - const isPackage = isExternalModule(importPath, context.settings) - || isScoped(importPath) + const isPackage = isExternalModule( + importPath, + context.settings, + resolve(importPath, context), + context + ) || isScoped(importPath); if (!extension || !importPath.endsWith(`.${extension}`)) { - const extensionRequired = isUseOfExtensionRequired(extension, isPackage) - const extensionForbidden = isUseOfExtensionForbidden(extension) + const extensionRequired = isUseOfExtensionRequired(extension, isPackage); + const extensionForbidden = isUseOfExtensionForbidden(extension); if (extensionRequired && !extensionForbidden) { context.report({ node: source, message: `Missing file extension ${extension ? `"${extension}" ` : ''}for "${importPathWithQueryString}"`, - }) + }); } } else if (extension) { if (isUseOfExtensionForbidden(extension) && isResolvableWithoutExtension(importPath)) { context.report({ node: source, message: `Unexpected use of file extension "${extension}" for "${importPathWithQueryString}"`, - }) + }); } } } - return { - ImportDeclaration: checkFileExtension, - ExportNamedDeclaration: checkFileExtension, - } + return moduleVisitor(checkFileExtension, { commonjs: true }); }, -} +}; diff --git a/src/rules/first.js b/src/rules/first.js index c1422cdb0b..a3b7f24e03 100644 --- a/src/rules/first.js +++ b/src/rules/first.js @@ -1,4 +1,10 @@ -import docsUrl from '../docsUrl' +import docsUrl from '../docsUrl'; + +function getImportValue(node) { + return node.type === 'ImportDeclaration' + ? node.source.value + : node.moduleReference.expression.value; +} module.exports = { meta: { @@ -19,114 +25,114 @@ module.exports = { function isPossibleDirective (node) { return node.type === 'ExpressionStatement' && node.expression.type === 'Literal' && - typeof node.expression.value === 'string' + typeof node.expression.value === 'string'; } return { 'Program': function (n) { - const body = n.body - , absoluteFirst = context.options[0] === 'absolute-first' - , message = 'Import in body of module; reorder to top.' - , sourceCode = context.getSourceCode() - , originSourceCode = sourceCode.getText() - let nonImportCount = 0 - , anyExpressions = false - , anyRelative = false - , lastLegalImp = null - , errorInfos = [] - , shouldSort = true - , lastSortNodesIndex = 0 + const body = n.body; + const absoluteFirst = context.options[0] === 'absolute-first'; + const message = 'Import in body of module; reorder to top.'; + const sourceCode = context.getSourceCode(); + const originSourceCode = sourceCode.getText(); + let nonImportCount = 0; + let anyExpressions = false; + let anyRelative = false; + let lastLegalImp = null; + const errorInfos = []; + let shouldSort = true; + let lastSortNodesIndex = 0; body.forEach(function (node, index){ if (!anyExpressions && isPossibleDirective(node)) { - return + return; } - anyExpressions = true + anyExpressions = true; - if (node.type === 'ImportDeclaration') { + if (node.type === 'ImportDeclaration' || node.type === 'TSImportEqualsDeclaration') { if (absoluteFirst) { - if (/^\./.test(node.source.value)) { - anyRelative = true + if (/^\./.test(getImportValue(node))) { + anyRelative = true; } else if (anyRelative) { context.report({ - node: node.source, + node: node.type === 'ImportDeclaration' ? node.source : node.moduleReference, message: 'Absolute imports should come before relative imports.', - }) + }); } } if (nonImportCount > 0) { - for (let variable of context.getDeclaredVariables(node)) { - if (!shouldSort) break - const references = variable.references + for (const variable of context.getDeclaredVariables(node)) { + if (!shouldSort) break; + const references = variable.references; if (references.length) { - for (let reference of references) { + for (const reference of references) { if (reference.identifier.range[0] < node.range[1]) { - shouldSort = false - break + shouldSort = false; + break; } } } } - shouldSort && (lastSortNodesIndex = errorInfos.length) + shouldSort && (lastSortNodesIndex = errorInfos.length); errorInfos.push({ node, range: [body[index - 1].range[1], node.range[1]], - }) + }); } else { - lastLegalImp = node + lastLegalImp = node; } } else { - nonImportCount++ + nonImportCount++; } - }) - if (!errorInfos.length) return + }); + if (!errorInfos.length) return; errorInfos.forEach(function (errorInfo, index) { - const node = errorInfo.node - , infos = { - node, - message, - } + const node = errorInfo.node; + const infos = { + node, + message, + }; if (index < lastSortNodesIndex) { infos.fix = function (fixer) { - return fixer.insertTextAfter(node, '') - } + return fixer.insertTextAfter(node, ''); + }; } else if (index === lastSortNodesIndex) { - const sortNodes = errorInfos.slice(0, lastSortNodesIndex + 1) + const sortNodes = errorInfos.slice(0, lastSortNodesIndex + 1); infos.fix = function (fixer) { const removeFixers = sortNodes.map(function (_errorInfo) { - return fixer.removeRange(_errorInfo.range) - }) - , range = [0, removeFixers[removeFixers.length - 1].range[1]] + return fixer.removeRange(_errorInfo.range); + }); + const range = [0, removeFixers[removeFixers.length - 1].range[1]]; let insertSourceCode = sortNodes.map(function (_errorInfo) { - const nodeSourceCode = String.prototype.slice.apply( - originSourceCode, _errorInfo.range - ) - if (/\S/.test(nodeSourceCode[0])) { - return '\n' + nodeSourceCode - } - return nodeSourceCode - }).join('') - , insertFixer = null - , replaceSourceCode = '' + const nodeSourceCode = String.prototype.slice.apply( + originSourceCode, _errorInfo.range + ); + if (/\S/.test(nodeSourceCode[0])) { + return '\n' + nodeSourceCode; + } + return nodeSourceCode; + }).join(''); + let insertFixer = null; + let replaceSourceCode = ''; if (!lastLegalImp) { - insertSourceCode = - insertSourceCode.trim() + insertSourceCode.match(/^(\s+)/)[0] + insertSourceCode = + insertSourceCode.trim() + insertSourceCode.match(/^(\s+)/)[0]; } insertFixer = lastLegalImp ? - fixer.insertTextAfter(lastLegalImp, insertSourceCode) : - fixer.insertTextBefore(body[0], insertSourceCode) - const fixers = [insertFixer].concat(removeFixers) + fixer.insertTextAfter(lastLegalImp, insertSourceCode) : + fixer.insertTextBefore(body[0], insertSourceCode); + const fixers = [insertFixer].concat(removeFixers); fixers.forEach(function (computedFixer, i) { replaceSourceCode += (originSourceCode.slice( fixers[i - 1] ? fixers[i - 1].range[1] : 0, computedFixer.range[0] - ) + computedFixer.text) - }) - return fixer.replaceTextRange(range, replaceSourceCode) - } + ) + computedFixer.text); + }); + return fixer.replaceTextRange(range, replaceSourceCode); + }; } - context.report(infos) - }) + context.report(infos); + }); }, - } + }; }, -} +}; diff --git a/src/rules/group-exports.js b/src/rules/group-exports.js index 8abeb3d231..e9fc432977 100644 --- a/src/rules/group-exports.js +++ b/src/rules/group-exports.js @@ -1,18 +1,18 @@ -import docsUrl from '../docsUrl' -import values from 'object.values' -import flat from 'array.prototype.flat' +import docsUrl from '../docsUrl'; +import values from 'object.values'; +import flat from 'array.prototype.flat'; const meta = { type: 'suggestion', docs: { url: docsUrl('group-exports'), }, -} +}; /* eslint-disable max-len */ const errors = { ExportNamedDeclaration: 'Multiple named export declarations; consolidate all named exports into a single export declaration', AssignmentExpression: 'Multiple CommonJS exports; consolidate all exports into a single assignment to `module.exports`', -} +}; /* eslint-enable max-len */ /** @@ -28,20 +28,20 @@ const errors = { * @private */ function accessorChain(node) { - const chain = [] + const chain = []; do { - chain.unshift(node.property.name) + chain.unshift(node.property.name); if (node.object.type === 'Identifier') { - chain.unshift(node.object.name) - break + chain.unshift(node.object.name); + break; } - node = node.object - } while (node.type === 'MemberExpression') + node = node.object; + } while (node.type === 'MemberExpression'); - return chain + return chain; } function create(context) { @@ -57,39 +57,39 @@ function create(context) { commonjs: { set: new Set(), }, - } + }; return { ExportNamedDeclaration(node) { - let target = node.exportKind === 'type' ? nodes.types : nodes.modules + const target = node.exportKind === 'type' ? nodes.types : nodes.modules; if (!node.source) { - target.set.add(node) + target.set.add(node); } else if (Array.isArray(target.sources[node.source.value])) { - target.sources[node.source.value].push(node) + target.sources[node.source.value].push(node); } else { - target.sources[node.source.value] = [node] + target.sources[node.source.value] = [node]; } }, AssignmentExpression(node) { if (node.left.type !== 'MemberExpression') { - return + return; } - const chain = accessorChain(node.left) + const chain = accessorChain(node.left); // Assignments to module.exports // 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.set.add(node) - return + nodes.commonjs.set.add(node); + return; } // Assignments to exports (exports.* = *) if (chain[0] === 'exports' && chain.length === 2) { - nodes.commonjs.set.add(node) - return + nodes.commonjs.set.add(node); + return; } }, @@ -100,8 +100,8 @@ function create(context) { context.report({ node, message: errors[node.type], - }) - }) + }); + }); } // Report multiple `aggregated exports` from the same module (ES2015 modules) @@ -111,8 +111,8 @@ function create(context) { context.report({ node, message: errors[node.type], - }) - }) + }); + }); // Report multiple `export type` declarations (FLOW ES2015 modules) if (nodes.types.set.size > 1) { @@ -120,8 +120,8 @@ function create(context) { context.report({ node, message: errors[node.type], - }) - }) + }); + }); } // Report multiple `aggregated type exports` from the same module (FLOW ES2015 modules) @@ -131,8 +131,8 @@ function create(context) { context.report({ node, message: errors[node.type], - }) - }) + }); + }); // Report multiple `module.exports` assignments (CommonJS) if (nodes.commonjs.set.size > 1) { @@ -140,14 +140,14 @@ function create(context) { context.report({ node, message: errors[node.type], - }) - }) + }); + }); } }, - } + }; } module.exports = { meta, create, -} +}; diff --git a/src/rules/imports-first.js b/src/rules/imports-first.js index 7ed9accc46..ba8af48f00 100644 --- a/src/rules/imports-first.js +++ b/src/rules/imports-first.js @@ -1,12 +1,12 @@ -import docsUrl from '../docsUrl' +import docsUrl from '../docsUrl'; -const first = require('./first') +const first = require('./first'); const newMeta = Object.assign({}, first.meta, { deprecated: true, docs: { url: docsUrl('imports-first', '7b25c1cb95ee18acc1531002fd343e1e6031f9ed'), }, -}) +}); -module.exports = Object.assign({}, first, { meta: newMeta }) +module.exports = Object.assign({}, first, { meta: newMeta }); diff --git a/src/rules/max-dependencies.js b/src/rules/max-dependencies.js index 7e1fdb1011..c8e1b3ab13 100644 --- a/src/rules/max-dependencies.js +++ b/src/rules/max-dependencies.js @@ -1,18 +1,18 @@ -import isStaticRequire from '../core/staticRequire' -import docsUrl from '../docsUrl' +import moduleVisitor from 'eslint-module-utils/moduleVisitor'; +import docsUrl from '../docsUrl'; -const DEFAULT_MAX = 10 +const DEFAULT_MAX = 10; const countDependencies = (dependencies, lastNode, context) => { - const {max} = context.options[0] || { max: DEFAULT_MAX } + const { max } = context.options[0] || { max: DEFAULT_MAX }; if (dependencies.size > max) { context.report( lastNode, `Maximum number of dependencies (${max}) exceeded.` - ) + ); } -} +}; module.exports = { meta: { @@ -33,26 +33,16 @@ module.exports = { }, create: context => { - const dependencies = new Set() // keep track of dependencies - let lastNode // keep track of the last node to report on - - return { - ImportDeclaration(node) { - dependencies.add(node.source.value) - lastNode = node.source - }, - - CallExpression(node) { - if (isStaticRequire(node)) { - const [ requirePath ] = node.arguments - dependencies.add(requirePath.value) - lastNode = node - } - }, + const dependencies = new Set(); // keep track of dependencies + let lastNode; // keep track of the last node to report on + return Object.assign({ 'Program:exit': function () { - countDependencies(dependencies, lastNode, context) + countDependencies(dependencies, lastNode, context); }, - } + }, moduleVisitor((source) => { + dependencies.add(source.value); + lastNode = source; + }, { commonjs: true })); }, -} +}; diff --git a/src/rules/named.js b/src/rules/named.js index 6853229b45..c4ce5ea915 100644 --- a/src/rules/named.js +++ b/src/rules/named.js @@ -1,6 +1,6 @@ -import * as path from 'path' -import Exports from '../ExportMap' -import docsUrl from '../docsUrl' +import * as path from 'path'; +import Exports from '../ExportMap'; +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -16,57 +16,57 @@ module.exports = { // ignore local exports and type imports/exports if (node.source == null || node.importKind === 'type' || node.importKind === 'typeof' || node.exportKind === 'type') { - return + return; } if (!node.specifiers - .some(function (im) { return im.type === type })) { - return // no named imports/exports + .some(function (im) { return im.type === type; })) { + return; // no named imports/exports } - const imports = Exports.get(node.source.value, context) - if (imports == null) return + const imports = Exports.get(node.source.value, context); + if (imports == null) return; if (imports.errors.length) { - imports.reportErrors(context, node) - return + imports.reportErrors(context, node); + return; } node.specifiers.forEach(function (im) { - if (im.type !== type) return + if (im.type !== type) return; // ignore type imports - if (im.importKind === 'type' || im.importKind === 'typeof') return + if (im.importKind === 'type' || im.importKind === 'typeof') return; - const deepLookup = imports.hasDeep(im[key].name) + const deepLookup = imports.hasDeep(im[key].name); if (!deepLookup.found) { if (deepLookup.path.length > 1) { const deepPath = deepLookup.path .map(i => path.relative(path.dirname(context.getFilename()), i.path)) - .join(' -> ') + .join(' -> '); context.report(im[key], - `${im[key].name} not found via ${deepPath}`) + `${im[key].name} not found via ${deepPath}`); } else { context.report(im[key], - im[key].name + ' not found in \'' + node.source.value + '\'') + im[key].name + ' not found in \'' + node.source.value + '\''); } } - }) + }); } return { 'ImportDeclaration': checkSpecifiers.bind( null - , 'imported' - , 'ImportSpecifier' - ), + , 'imported' + , 'ImportSpecifier' + ), 'ExportNamedDeclaration': checkSpecifiers.bind( null - , 'local' - , 'ExportSpecifier' - ), - } + , 'local' + , 'ExportSpecifier' + ), + }; }, -} +}; diff --git a/src/rules/namespace.js b/src/rules/namespace.js index dd840b86f6..a23cfeac88 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -1,7 +1,7 @@ -import declaredScope from 'eslint-module-utils/declaredScope' -import Exports from '../ExportMap' -import importDeclaration from '../importDeclaration' -import docsUrl from '../docsUrl' +import declaredScope from 'eslint-module-utils/declaredScope'; +import Exports from '../ExportMap'; +import importDeclaration from '../importDeclaration'; +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -12,17 +12,15 @@ module.exports = { schema: [ { - 'type': 'object', - 'properties': { - 'allowComputed': { - 'description': - 'If `false`, will report computed (and thus, un-lintable) references ' + - 'to namespace members.', - 'type': 'boolean', - 'default': false, + type: 'object', + properties: { + allowComputed: { + description: 'If `false`, will report computed (and thus, un-lintable) references to namespace members.', + type: 'boolean', + default: false, }, }, - 'additionalProperties': false, + additionalProperties: false, }, ], }, @@ -32,134 +30,139 @@ module.exports = { // read options const { allowComputed = false, - } = context.options[0] || {} + } = context.options[0] || {}; - const namespaces = new Map() + const namespaces = new Map(); function makeMessage(last, namepath) { - return `'${last.name}' not found in` + - (namepath.length > 1 ? ' deeply ' : ' ') + - `imported namespace '${namepath.join('.')}'.` + return `'${last.name}' not found in ${namepath.length > 1 ? 'deeply ' : ''}imported namespace '${namepath.join('.')}'.`; } return { - // pick up all imports at body entry time, to properly respect hoisting - Program: function ({ body }) { + Program({ body }) { function processBodyStatement(declaration) { - if (declaration.type !== 'ImportDeclaration') return + if (declaration.type !== 'ImportDeclaration') return; - if (declaration.specifiers.length === 0) return + if (declaration.specifiers.length === 0) return; - const imports = Exports.get(declaration.source.value, context) - if (imports == null) return null + const imports = Exports.get(declaration.source.value, context); + if (imports == null) return null; if (imports.errors.length) { - imports.reportErrors(context, declaration) - return + imports.reportErrors(context, declaration); + return; } for (const specifier of declaration.specifiers) { switch (specifier.type) { - case 'ImportNamespaceSpecifier': - if (!imports.size) { - context.report(specifier, - `No exported names found in module '${declaration.source.value}'.`) - } - namespaces.set(specifier.local.name, imports) - break - case 'ImportDefaultSpecifier': - case 'ImportSpecifier': { - const meta = imports.get( - // default to 'default' for default http://i.imgur.com/nj6qAWy.jpg - specifier.imported ? specifier.imported.name : 'default') - if (!meta || !meta.namespace) break - namespaces.set(specifier.local.name, meta.namespace) - break + case 'ImportNamespaceSpecifier': + if (!imports.size) { + context.report( + specifier, + `No exported names found in module '${declaration.source.value}'.` + ); } + namespaces.set(specifier.local.name, imports); + break; + case 'ImportDefaultSpecifier': + case 'ImportSpecifier': { + const meta = imports.get( + // default to 'default' for default http://i.imgur.com/nj6qAWy.jpg + specifier.imported ? specifier.imported.name : 'default' + ); + if (!meta || !meta.namespace) { break; } + namespaces.set(specifier.local.name, meta.namespace); + break; + } } } } - body.forEach(processBodyStatement) + body.forEach(processBodyStatement); }, // same as above, but does not add names to local map - ExportNamespaceSpecifier: function (namespace) { - var declaration = importDeclaration(context) + ExportNamespaceSpecifier(namespace) { + const declaration = importDeclaration(context); - var imports = Exports.get(declaration.source.value, context) - if (imports == null) return null + const imports = Exports.get(declaration.source.value, context); + if (imports == null) return null; if (imports.errors.length) { - imports.reportErrors(context, declaration) - return + imports.reportErrors(context, declaration); + return; } if (!imports.size) { - context.report(namespace, - `No exported names found in module '${declaration.source.value}'.`) + context.report( + namespace, + `No exported names found in module '${declaration.source.value}'.` + ); } }, // todo: check for possible redefinition - MemberExpression: function (dereference) { - if (dereference.object.type !== 'Identifier') return - if (!namespaces.has(dereference.object.name)) return + MemberExpression(dereference) { + if (dereference.object.type !== 'Identifier') return; + if (!namespaces.has(dereference.object.name)) return; + if (declaredScope(context, dereference.object.name) !== 'module') return; - if (dereference.parent.type === 'AssignmentExpression' && - dereference.parent.left === dereference) { - context.report(dereference.parent, - `Assignment to member of namespace '${dereference.object.name}'.`) + if (dereference.parent.type === 'AssignmentExpression' && dereference.parent.left === dereference) { + context.report( + dereference.parent, + `Assignment to member of namespace '${dereference.object.name}'.` + ); } // go deep - var namespace = namespaces.get(dereference.object.name) - var namepath = [dereference.object.name] + let namespace = namespaces.get(dereference.object.name); + const namepath = [dereference.object.name]; // while property is namespace and parent is member expression, keep validating - while (namespace instanceof Exports && - dereference.type === 'MemberExpression') { + while (namespace instanceof Exports && dereference.type === 'MemberExpression') { if (dereference.computed) { if (!allowComputed) { - context.report(dereference.property, - 'Unable to validate computed reference to imported namespace \'' + - dereference.object.name + '\'.') + context.report( + dereference.property, + `Unable to validate computed reference to imported namespace '${dereference.object.name}'.` + ); } - return + return; } if (!namespace.has(dereference.property.name)) { context.report( dereference.property, - makeMessage(dereference.property, namepath)) - break + makeMessage(dereference.property, namepath) + ); + break; } - const exported = namespace.get(dereference.property.name) - if (exported == null) return + const exported = namespace.get(dereference.property.name); + if (exported == null) return; // stash and pop - namepath.push(dereference.property.name) - namespace = exported.namespace - dereference = dereference.parent + namepath.push(dereference.property.name); + namespace = exported.namespace; + dereference = dereference.parent; } }, - VariableDeclarator: function ({ id, init }) { - if (init == null) return - if (init.type !== 'Identifier') return - if (!namespaces.has(init.name)) return + VariableDeclarator({ id, init }) { + if (init == null) return; + if (init.type !== 'Identifier') return; + if (!namespaces.has(init.name)) return; // check for redefinition in intermediate scopes - if (declaredScope(context, init.name) !== 'module') return + if (declaredScope(context, init.name) !== 'module') return; // DFS traverse child namespaces function testKey(pattern, namespace, path = [init.name]) { - if (!(namespace instanceof Exports)) return + if (!(namespace instanceof Exports)) return; - if (pattern.type !== 'ObjectPattern') return + if (pattern.type !== 'ObjectPattern') return; for (const property of pattern.properties) { if ( @@ -167,48 +170,48 @@ module.exports = { || property.type === 'RestElement' || !property.key ) { - continue + continue; } if (property.key.type !== 'Identifier') { context.report({ node: property, message: 'Only destructure top-level names.', - }) - continue + }); + continue; } if (!namespace.has(property.key.name)) { context.report({ node: property, message: makeMessage(property.key, path), - }) - continue + }); + continue; } - path.push(property.key.name) - const dependencyExportMap = namespace.get(property.key.name) + path.push(property.key.name); + const dependencyExportMap = namespace.get(property.key.name); // could be null when ignored or ambiguous if (dependencyExportMap !== null) { - testKey(property.value, dependencyExportMap.namespace, path) + testKey(property.value, dependencyExportMap.namespace, path); } - path.pop() + path.pop(); } } - testKey(id, namespaces.get(init.name)) + testKey(id, namespaces.get(init.name)); }, - JSXMemberExpression: function({object, property}) { - if (!namespaces.has(object.name)) return - var namespace = namespaces.get(object.name) - if (!namespace.has(property.name)) { - context.report({ - node: property, - message: makeMessage(property, [object.name]), - }) - } + JSXMemberExpression({ object, property }) { + if (!namespaces.has(object.name)) return; + const namespace = namespaces.get(object.name); + if (!namespace.has(property.name)) { + context.report({ + node: property, + message: makeMessage(property, [object.name]), + }); + } }, - } + }; }, -} +}; diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js index 8255b189cc..f9a817846b 100644 --- a/src/rules/newline-after-import.js +++ b/src/rules/newline-after-import.js @@ -3,48 +3,53 @@ * @author Radek Benkel */ -import isStaticRequire from '../core/staticRequire' -import docsUrl from '../docsUrl' +import isStaticRequire from '../core/staticRequire'; +import docsUrl from '../docsUrl'; -import debug from 'debug' -const log = debug('eslint-plugin-import:rules:newline-after-import') +import debug from 'debug'; +const log = debug('eslint-plugin-import:rules:newline-after-import'); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ function containsNodeOrEqual(outerNode, innerNode) { - return outerNode.range[0] <= innerNode.range[0] && outerNode.range[1] >= innerNode.range[1] + return outerNode.range[0] <= innerNode.range[0] && outerNode.range[1] >= innerNode.range[1]; } function getScopeBody(scope) { - if (scope.block.type === 'SwitchStatement') { - log('SwitchStatement scopes not supported') - return null - } + if (scope.block.type === 'SwitchStatement') { + log('SwitchStatement scopes not supported'); + return null; + } - const { body } = scope.block - if (body && body.type === 'BlockStatement') { - return body.body - } + const { body } = scope.block; + if (body && body.type === 'BlockStatement') { + return body.body; + } - return body + return body; } function findNodeIndexInScopeBody(body, nodeToFind) { - return body.findIndex((node) => containsNodeOrEqual(node, nodeToFind)) + return body.findIndex((node) => containsNodeOrEqual(node, nodeToFind)); } function getLineDifference(node, nextNode) { - return nextNode.loc.start.line - node.loc.end.line + return nextNode.loc.start.line - node.loc.end.line; } function isClassWithDecorator(node) { - return node.type === 'ClassDeclaration' && node.decorators && node.decorators.length + return node.type === 'ClassDeclaration' && node.decorators && node.decorators.length; } function isExportDefaultClass(node) { - return node.type === 'ExportDefaultDeclaration' && node.declaration.type === 'ClassDeclaration' + return node.type === 'ExportDefaultDeclaration' && node.declaration.type === 'ClassDeclaration'; +} + +function isExportNameClass(node) { + + return node.type === 'ExportNamedDeclaration' && node.declaration && node.declaration.type === 'ClassDeclaration'; } module.exports = { @@ -68,29 +73,29 @@ module.exports = { ], }, create: function (context) { - let level = 0 - const requireCalls = [] + let level = 0; + const requireCalls = []; function checkForNewLine(node, nextNode, type) { - if (isExportDefaultClass(nextNode)) { - let classNode = nextNode.declaration + if (isExportDefaultClass(nextNode) || isExportNameClass(nextNode)) { + const classNode = nextNode.declaration; if (isClassWithDecorator(classNode)) { - nextNode = classNode.decorators[0] + nextNode = classNode.decorators[0]; } } else if (isClassWithDecorator(nextNode)) { - nextNode = nextNode.decorators[0] + nextNode = nextNode.decorators[0]; } - const options = context.options[0] || { count: 1 } - const lineDifference = getLineDifference(node, nextNode) - const EXPECTED_LINE_DIFFERENCE = options.count + 1 + const options = context.options[0] || { count: 1 }; + const lineDifference = getLineDifference(node, nextNode); + const EXPECTED_LINE_DIFFERENCE = options.count + 1; if (lineDifference < EXPECTED_LINE_DIFFERENCE) { - let column = node.loc.start.column + let column = node.loc.start.column; if (node.loc.start.line !== node.loc.end.line) { - column = 0 + column = 0; } context.report({ @@ -104,25 +109,30 @@ after ${type} statement not followed by another ${type}.`, node, '\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference) ), - }) + }); } } function incrementLevel() { - level++ + level++; } function decrementLevel() { - level-- + level--; } function checkImport(node) { - const { parent } = node - const nodePosition = parent.body.indexOf(node) - const nextNode = parent.body[nodePosition + 1] + const { parent } = node; + const nodePosition = parent.body.indexOf(node); + const nextNode = parent.body[nodePosition + 1]; + + // skip "export import"s + if (node.type === 'TSImportEqualsDeclaration' && node.isExport) { + return; + } - if (nextNode && nextNode.type !== 'ImportDeclaration' && nextNode.type !== 'TSImportEqualsDeclaration') { - checkForNewLine(node, nextNode, 'import') - } + if (nextNode && nextNode.type !== 'ImportDeclaration' && (nextNode.type !== 'TSImportEqualsDeclaration' || nextNode.isExport)) { + checkForNewLine(node, nextNode, 'import'); + } } return { @@ -130,32 +140,32 @@ after ${type} statement not followed by another ${type}.`, TSImportEqualsDeclaration: checkImport, CallExpression: function(node) { if (isStaticRequire(node) && level === 0) { - requireCalls.push(node) + requireCalls.push(node); } }, 'Program:exit': function () { - log('exit processing for', context.getFilename()) - const scopeBody = getScopeBody(context.getScope()) - log('got scope:', scopeBody) + log('exit processing for', context.getFilename()); + const scopeBody = getScopeBody(context.getScope()); + log('got scope:', scopeBody); requireCalls.forEach(function (node, index) { - const nodePosition = findNodeIndexInScopeBody(scopeBody, node) - log('node position in scope:', nodePosition) + const nodePosition = findNodeIndexInScopeBody(scopeBody, node); + log('node position in scope:', nodePosition); - const statementWithRequireCall = scopeBody[nodePosition] - const nextStatement = scopeBody[nodePosition + 1] - const nextRequireCall = requireCalls[index + 1] + const statementWithRequireCall = scopeBody[nodePosition]; + const nextStatement = scopeBody[nodePosition + 1]; + const nextRequireCall = requireCalls[index + 1]; if (nextRequireCall && containsNodeOrEqual(statementWithRequireCall, nextRequireCall)) { - return + return; } if (nextStatement && (!nextRequireCall || !containsNodeOrEqual(nextStatement, nextRequireCall))) { - checkForNewLine(statementWithRequireCall, nextStatement, 'require') + checkForNewLine(statementWithRequireCall, nextStatement, 'require'); } - }) + }); }, FunctionDeclaration: incrementLevel, FunctionExpression: incrementLevel, @@ -169,6 +179,6 @@ after ${type} statement not followed by another ${type}.`, 'BlockStatement:exit': decrementLevel, 'ObjectExpression:exit': decrementLevel, 'Decorator:exit': decrementLevel, - } + }; }, -} +}; diff --git a/src/rules/no-absolute-path.js b/src/rules/no-absolute-path.js index 2cc0f6ae0f..cc81c5c4be 100644 --- a/src/rules/no-absolute-path.js +++ b/src/rules/no-absolute-path.js @@ -1,6 +1,6 @@ -import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor' -import { isAbsolute } from '../core/importType' -import docsUrl from '../docsUrl' +import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; +import { isAbsolute } from '../core/importType'; +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -14,11 +14,11 @@ module.exports = { create: function (context) { function reportIfAbsolute(source) { if (typeof source.value === 'string' && isAbsolute(source.value)) { - context.report(source, 'Do not import modules using an absolute path') + context.report(source, 'Do not import modules using an absolute path'); } } - const options = Object.assign({ esmodule: true, commonjs: true }, context.options[0]) - return moduleVisitor(reportIfAbsolute, options) + const options = Object.assign({ esmodule: true, commonjs: true }, context.options[0]); + return moduleVisitor(reportIfAbsolute, options); }, -} +}; diff --git a/src/rules/no-amd.js b/src/rules/no-amd.js index a6a460bcf8..7a0771bd57 100644 --- a/src/rules/no-amd.js +++ b/src/rules/no-amd.js @@ -3,7 +3,7 @@ * @author Jamund Ferguson */ -import docsUrl from '../docsUrl' +import docsUrl from '../docsUrl'; //------------------------------------------------------------------------------ // Rule Definition @@ -21,23 +21,23 @@ module.exports = { create: function (context) { return { 'CallExpression': function (node) { - if (context.getScope().type !== 'module') return + if (context.getScope().type !== 'module') return; - if (node.callee.type !== 'Identifier') return + if (node.callee.type !== 'Identifier') return; if (node.callee.name !== 'require' && - node.callee.name !== 'define') return + node.callee.name !== 'define') return; // todo: capture define((require, module, exports) => {}) form? - if (node.arguments.length !== 2) return + if (node.arguments.length !== 2) return; - const modules = node.arguments[0] - if (modules.type !== 'ArrayExpression') return + const modules = node.arguments[0]; + if (modules.type !== 'ArrayExpression') return; // todo: check second arg type? (identifier or callback) - context.report(node, `Expected imports instead of AMD ${node.callee.name}().`) + context.report(node, `Expected imports instead of AMD ${node.callee.name}().`); }, - } + }; }, -} +}; diff --git a/src/rules/no-anonymous-default-export.js b/src/rules/no-anonymous-default-export.js index 1557404507..8ea3365861 100644 --- a/src/rules/no-anonymous-default-export.js +++ b/src/rules/no-anonymous-default-export.js @@ -3,8 +3,8 @@ * @author Duncan Beevers */ -import docsUrl from '../docsUrl' -import has from 'has' +import docsUrl from '../docsUrl'; +import has from 'has'; const defs = { ArrayExpression: { @@ -50,7 +50,7 @@ const defs = { description: 'If `false`, will report default export of a literal', message: 'Assign literal to a variable before exporting as module default', }, -} +}; const schemaProperties = Object.keys(defs) .map((key) => defs[key]) @@ -58,17 +58,17 @@ const schemaProperties = Object.keys(defs) acc[def.option] = { description: def.description, type: 'boolean', - } + }; - return acc - }, {}) + return acc; + }, {}); const defaults = Object.keys(defs) .map((key) => defs[key]) .reduce((acc, def) => { - acc[def.option] = has(def, 'default') ? def.default : false - return acc - }, {}) + acc[def.option] = has(def, 'default') ? def.default : false; + return acc; + }, {}); module.exports = { meta: { @@ -87,18 +87,18 @@ module.exports = { }, create: function (context) { - const options = Object.assign({}, defaults, context.options[0]) + const options = Object.assign({}, defaults, context.options[0]); return { 'ExportDefaultDeclaration': (node) => { - const def = defs[node.declaration.type] + const def = defs[node.declaration.type]; // Recognized node type and allowed by configuration, // and has no forbid check, or forbid check return value is truthy if (def && !options[def.option] && (!def.forbid || def.forbid(node))) { - context.report({ node, message: def.message }) + context.report({ node, message: def.message }); } }, - } + }; }, -} +}; diff --git a/src/rules/no-commonjs.js b/src/rules/no-commonjs.js index 456f030f42..08d29a0cdb 100644 --- a/src/rules/no-commonjs.js +++ b/src/rules/no-commonjs.js @@ -3,34 +3,34 @@ * @author Jamund Ferguson */ -import docsUrl from '../docsUrl' +import docsUrl from '../docsUrl'; -const EXPORT_MESSAGE = 'Expected "export" or "export default"' - , IMPORT_MESSAGE = 'Expected "import" instead of "require()"' +const EXPORT_MESSAGE = 'Expected "export" or "export default"'; +const IMPORT_MESSAGE = 'Expected "import" instead of "require()"'; function normalizeLegacyOptions(options) { if (options.indexOf('allow-primitive-modules') >= 0) { - return { allowPrimitiveModules: true } + return { allowPrimitiveModules: true }; } - return options[0] || {} + return options[0] || {}; } function allowPrimitive(node, options) { - if (!options.allowPrimitiveModules) return false - if (node.parent.type !== 'AssignmentExpression') return false - return (node.parent.right.type !== 'ObjectExpression') + if (!options.allowPrimitiveModules) return false; + if (node.parent.type !== 'AssignmentExpression') return false; + return (node.parent.right.type !== 'ObjectExpression'); } function allowRequire(node, options) { - return options.allowRequire + return options.allowRequire; } function allowConditionalRequire(node, options) { - return options.allowConditionalRequire !== false + return options.allowConditionalRequire !== false; } function validateScope(scope) { - return scope.variableScope.type === 'module' + return scope.variableScope.type === 'module'; } // https://github.com/estree/estree/blob/master/es5.md @@ -40,16 +40,21 @@ function isConditional(node) { || node.type === 'TryStatement' || node.type === 'LogicalExpression' || node.type === 'ConditionalExpression' - ) return true - if (node.parent) return isConditional(node.parent) - return false + ) return true; + if (node.parent) return isConditional(node.parent); + return false; +} + +function isLiteralString(node) { + return (node.type === 'Literal' && typeof node.value === 'string') || + (node.type === 'TemplateLiteral' && node.expressions.length === 0); } //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -const schemaString = { enum: ['allow-primitive-modules'] } +const schemaString = { enum: ['allow-primitive-modules'] }; const schemaObject = { type: 'object', properties: { @@ -58,7 +63,7 @@ const schemaObject = { allowConditionalRequire: { 'type': 'boolean' }, }, additionalProperties: false, -} +}; module.exports = { meta: { @@ -84,7 +89,7 @@ module.exports = { }, create: function (context) { - const options = normalizeLegacyOptions(context.options) + const options = normalizeLegacyOptions(context.options); return { @@ -92,44 +97,41 @@ module.exports = { // module.exports if (node.object.name === 'module' && node.property.name === 'exports') { - if (allowPrimitive(node, options)) return - context.report({ node, message: EXPORT_MESSAGE }) + if (allowPrimitive(node, options)) return; + context.report({ node, message: EXPORT_MESSAGE }); } // exports. if (node.object.name === 'exports') { const isInScope = context.getScope() .variables - .some(variable => variable.name === 'exports') + .some(variable => variable.name === 'exports'); if (! isInScope) { - context.report({ node, message: EXPORT_MESSAGE }) + context.report({ node, message: EXPORT_MESSAGE }); } } }, 'CallExpression': function (call) { - if (!validateScope(context.getScope())) return + if (!validateScope(context.getScope())) return; - if (call.callee.type !== 'Identifier') return - if (call.callee.name !== 'require') return + if (call.callee.type !== 'Identifier') return; + if (call.callee.name !== 'require') return; - if (call.arguments.length !== 1) return - var module = call.arguments[0] + if (call.arguments.length !== 1) return; + if (!isLiteralString(call.arguments[0])) return; - if (module.type !== 'Literal') return - if (typeof module.value !== 'string') return + if (allowRequire(call, options)) return; - if (allowRequire(call, options)) return - - if (allowConditionalRequire(call, options) && isConditional(call.parent)) return + if (allowConditionalRequire(call, options) && isConditional(call.parent)) return; // keeping it simple: all 1-string-arg `require` calls are reported context.report({ node: call.callee, message: IMPORT_MESSAGE, - }) + }); }, - } + }; }, -} +}; diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index 8f39246b5c..9d9a28cd66 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -3,10 +3,11 @@ * @author Ben Mosher */ -import Exports from '../ExportMap' -import { isExternalModule } from '../core/importType' -import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor' -import docsUrl from '../docsUrl' +import resolve from 'eslint-module-utils/resolve'; +import Exports from '../ExportMap'; +import { isExternalModule } from '../core/importType'; +import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; +import docsUrl from '../docsUrl'; // todo: cache cycles / deep relationships for faster repeat evaluation module.exports = { @@ -14,10 +15,18 @@ module.exports = { type: 'suggestion', docs: { url: docsUrl('no-cycle') }, schema: [makeOptionsSchema({ - maxDepth:{ - description: 'maximum dependency depth to traverse', - type: 'integer', - minimum: 1, + maxDepth: { + oneOf: [ + { + description: 'maximum dependency depth to traverse', + type: 'integer', + minimum: 1, + }, + { + enum: ['∞'], + type: 'string', + }, + ], }, ignoreExternal: { description: 'ignore external modules', @@ -28,69 +37,94 @@ module.exports = { }, create: function (context) { - const myPath = context.getFilename() - if (myPath === '') return {} // can't cycle-check a non-file + const myPath = context.getFilename(); + if (myPath === '') return {}; // can't cycle-check a non-file - const options = context.options[0] || {} - const maxDepth = options.maxDepth || Infinity - const ignoreModule = (name) => options.ignoreExternal ? isExternalModule(name) : false + const options = context.options[0] || {}; + const maxDepth = typeof options.maxDepth === 'number' ? options.maxDepth : Infinity; + const ignoreModule = (name) => options.ignoreExternal && isExternalModule( + name, + context.settings, + resolve(name, context), + context + ); function checkSourceValue(sourceNode, importer) { if (ignoreModule(sourceNode.value)) { - return // ignore external modules + return; // ignore external modules } - const imported = Exports.get(sourceNode.value, context) - - if (importer.importKind === 'type') { - return // no Flow import resolution + if ( + importer.type === 'ImportDeclaration' && ( + // import type { Foo } (TS and Flow) + importer.importKind === 'type' || + // import { type Foo } (Flow) + importer.specifiers.every(({ importKind }) => importKind === 'type') + ) + ) { + return; // ignore type imports } + const imported = Exports.get(sourceNode.value, context); + if (imported == null) { - return // no-unresolved territory + return; // no-unresolved territory } if (imported.path === myPath) { - return // no-self-import territory + return; // no-self-import territory } - const untraversed = [{mget: () => imported, route:[]}] - const traversed = new Set() - function detectCycle({mget, route}) { - const m = mget() - if (m == null) return - if (traversed.has(m.path)) return - traversed.add(m.path) - - for (let [path, { getter, source }] of m.imports) { - if (path === myPath) return true - if (traversed.has(path)) continue - if (ignoreModule(source.value)) continue + const untraversed = [{ mget: () => imported, route:[] }]; + const traversed = new Set(); + function detectCycle({ mget, route }) { + const m = mget(); + if (m == null) return; + if (traversed.has(m.path)) return; + traversed.add(m.path); + + for (const [path, { getter, declarations }] of m.imports) { + if (traversed.has(path)) continue; + const toTraverse = [...declarations].filter(({ source, isOnlyImportingTypes }) => + !ignoreModule(source.value) && + // Ignore only type imports + !isOnlyImportingTypes + ); + /* + Only report as a cycle if there are any import declarations that are considered by + the rule. For example: + + a.ts: + import { foo } from './b' // should not be reported as a cycle + + b.ts: + import type { Bar } from './a' + */ + if (path === myPath && toTraverse.length > 0) return true; if (route.length + 1 < maxDepth) { - untraversed.push({ - mget: getter, - route: route.concat(source), - }) + for (const { source } of toTraverse) { + untraversed.push({ mget: getter, route: route.concat(source) }); + } } } } while (untraversed.length > 0) { - const next = untraversed.shift() // bfs! + const next = untraversed.shift(); // bfs! if (detectCycle(next)) { const message = (next.route.length > 0 ? `Dependency cycle via ${routeString(next.route)}` - : 'Dependency cycle detected.') - context.report(importer, message) - return + : 'Dependency cycle detected.'); + context.report(importer, message); + return; } } } - return moduleVisitor(checkSourceValue, context.options[0]) + return moduleVisitor(checkSourceValue, context.options[0]); }, -} +}; function routeString(route) { - return route.map(s => `${s.value}:${s.loc.start.line}`).join('=>') + return route.map(s => `${s.value}:${s.loc.start.line}`).join('=>'); } diff --git a/src/rules/no-default-export.js b/src/rules/no-default-export.js index 0a46fd35f7..cb7c0bb724 100644 --- a/src/rules/no-default-export.js +++ b/src/rules/no-default-export.js @@ -1,37 +1,41 @@ +import docsUrl from '../docsUrl'; + module.exports = { meta: { type: 'suggestion', - docs: {}, + docs: { + url: docsUrl('no-default-export'), + }, schema: [], }, create(context) { // ignore non-modules if (context.parserOptions.sourceType !== 'module') { - return {} + return {}; } - const preferNamed = 'Prefer named exports.' - const noAliasDefault = ({local}) => + const preferNamed = 'Prefer named exports.'; + const noAliasDefault = ({ local }) => `Do not alias \`${local.name}\` as \`default\`. Just export ` + - `\`${local.name}\` itself instead.` + `\`${local.name}\` itself instead.`; return { ExportDefaultDeclaration(node) { - context.report({node, message: preferNamed}) + context.report({ node, message: preferNamed }); }, ExportNamedDeclaration(node) { node.specifiers.forEach(specifier => { if (specifier.type === 'ExportDefaultSpecifier' && specifier.exported.name === 'default') { - context.report({node, message: preferNamed}) + context.report({ node, message: preferNamed }); } else if (specifier.type === 'ExportSpecifier' && specifier.exported.name === 'default') { - context.report({node, message: noAliasDefault(specifier)}) + context.report({ node, message: noAliasDefault(specifier) }); } - }) + }); }, - } + }; }, -} +}; diff --git a/src/rules/no-deprecated.js b/src/rules/no-deprecated.js index fc01d9dd10..628759bd42 100644 --- a/src/rules/no-deprecated.js +++ b/src/rules/no-deprecated.js @@ -1,18 +1,15 @@ -import declaredScope from 'eslint-module-utils/declaredScope' -import Exports from '../ExportMap' -import docsUrl from '../docsUrl' +import declaredScope from 'eslint-module-utils/declaredScope'; +import Exports from '../ExportMap'; +import docsUrl from '../docsUrl'; function message(deprecation) { - return 'Deprecated' + (deprecation.description ? ': ' + deprecation.description : '.') + return 'Deprecated' + (deprecation.description ? ': ' + deprecation.description : '.'); } function getDeprecation(metadata) { - if (!metadata || !metadata.doc) return + if (!metadata || !metadata.doc) return; - let deprecation - if (metadata.doc.tags.some(t => t.title === 'deprecated' && (deprecation = t))) { - return deprecation - } + return metadata.doc.tags.find(t => t.title === 'deprecated'); } module.exports = { @@ -25,66 +22,65 @@ module.exports = { }, create: function (context) { - const deprecated = new Map() - , namespaces = new Map() + const deprecated = new Map(); + const namespaces = new Map(); function checkSpecifiers(node) { - if (node.type !== 'ImportDeclaration') return - if (node.source == null) return // local export, ignore + if (node.type !== 'ImportDeclaration') return; + if (node.source == null) return; // local export, ignore - const imports = Exports.get(node.source.value, context) - if (imports == null) return + const imports = Exports.get(node.source.value, context); + if (imports == null) return; - let moduleDeprecation - if (imports.doc && - imports.doc.tags.some(t => t.title === 'deprecated' && (moduleDeprecation = t))) { - context.report({ node, message: message(moduleDeprecation) }) + const moduleDeprecation = imports.doc && imports.doc.tags.find(t => t.title === 'deprecated'); + if (moduleDeprecation) { + context.report({ node, message: message(moduleDeprecation) }); } if (imports.errors.length) { - imports.reportErrors(context, node) - return + imports.reportErrors(context, node); + return; } node.specifiers.forEach(function (im) { - let imported, local + let imported; let local; switch (im.type) { - case 'ImportNamespaceSpecifier':{ - if (!imports.size) return - namespaces.set(im.local.name, imports) - return - } + case 'ImportNamespaceSpecifier':{ + if (!imports.size) return; + namespaces.set(im.local.name, imports); + return; + } - case 'ImportDefaultSpecifier': - imported = 'default' - local = im.local.name - break + case 'ImportDefaultSpecifier': + imported = 'default'; + local = im.local.name; + break; - case 'ImportSpecifier': - imported = im.imported.name - local = im.local.name - break + case 'ImportSpecifier': + imported = im.imported.name; + local = im.local.name; + break; - default: return // can't handle this one + default: return; // can't handle this one } // unknown thing can't be deprecated - const exported = imports.get(imported) - if (exported == null) return + const exported = imports.get(imported); + if (exported == null) return; // capture import of deep namespace - if (exported.namespace) namespaces.set(local, exported.namespace) + if (exported.namespace) namespaces.set(local, exported.namespace); - const deprecation = getDeprecation(imports.get(imported)) - if (!deprecation) return + const deprecation = getDeprecation(imports.get(imported)); + if (!deprecation) return; - context.report({ node: im, message: message(deprecation) }) + context.report({ node: im, message: message(deprecation) }); - deprecated.set(local, deprecation) + deprecated.set(local, deprecation); - }) + }); } return { @@ -92,52 +88,52 @@ module.exports = { 'Identifier': function (node) { if (node.parent.type === 'MemberExpression' && node.parent.property === node) { - return // handled by MemberExpression + return; // handled by MemberExpression } // ignore specifier identifiers - if (node.parent.type.slice(0, 6) === 'Import') return + if (node.parent.type.slice(0, 6) === 'Import') return; - if (!deprecated.has(node.name)) return + if (!deprecated.has(node.name)) return; - if (declaredScope(context, node.name) !== 'module') return + if (declaredScope(context, node.name) !== 'module') return; context.report({ node, message: message(deprecated.get(node.name)), - }) + }); }, 'MemberExpression': function (dereference) { - if (dereference.object.type !== 'Identifier') return - if (!namespaces.has(dereference.object.name)) return + if (dereference.object.type !== 'Identifier') return; + if (!namespaces.has(dereference.object.name)) return; - if (declaredScope(context, dereference.object.name) !== 'module') return + if (declaredScope(context, dereference.object.name) !== 'module') return; // go deep - var namespace = namespaces.get(dereference.object.name) - var namepath = [dereference.object.name] + let namespace = namespaces.get(dereference.object.name); + const namepath = [dereference.object.name]; // while property is namespace and parent is member expression, keep validating while (namespace instanceof Exports && dereference.type === 'MemberExpression') { // ignore computed parts for now - if (dereference.computed) return + if (dereference.computed) return; - const metadata = namespace.get(dereference.property.name) + const metadata = namespace.get(dereference.property.name); - if (!metadata) break - const deprecation = getDeprecation(metadata) + if (!metadata) break; + const deprecation = getDeprecation(metadata); if (deprecation) { - context.report({ node: dereference.property, message: message(deprecation) }) + context.report({ node: dereference.property, message: message(deprecation) }); } // stash and pop - namepath.push(dereference.property.name) - namespace = metadata.namespace - dereference = dereference.parent + namepath.push(dereference.property.name); + namespace = metadata.namespace; + dereference = dereference.parent; } }, - } + }; }, -} +}; diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index ce586cd674..1bf6f38245 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -1,25 +1,25 @@ -import resolve from 'eslint-module-utils/resolve' -import docsUrl from '../docsUrl' +import resolve from 'eslint-module-utils/resolve'; +import docsUrl from '../docsUrl'; function checkImports(imported, context) { for (const [module, nodes] of imported.entries()) { if (nodes.length > 1) { - const message = `'${module}' imported multiple times.` - const [first, ...rest] = nodes - const sourceCode = context.getSourceCode() - const fix = getFix(first, rest, sourceCode) + const message = `'${module}' imported multiple times.`; + const [first, ...rest] = nodes; + const sourceCode = context.getSourceCode(); + const fix = getFix(first, rest, sourceCode); context.report({ node: first.source, message, fix, // Attach the autofix (if any) to the first import. - }) + }); for (const node of rest) { context.report({ node: node.source, message, - }) + }); } } } @@ -33,7 +33,7 @@ function getFix(first, rest, sourceCode) { // `sourceCode.getCommentsBefore` was added in 4.0, so that's an easy thing to // check for. if (typeof sourceCode.getCommentsBefore !== 'function') { - return undefined + return undefined; } // Adjusting the first import might make it multiline, which could break @@ -41,17 +41,17 @@ function getFix(first, rest, sourceCode) { // import has comments. Also, if the first import is `import * as ns from // './foo'` there's nothing we can do. if (hasProblematicComments(first, sourceCode) || hasNamespace(first)) { - return undefined + return undefined; } const defaultImportNames = new Set( [first, ...rest].map(getDefaultImportName).filter(Boolean) - ) + ); // Bail if there are multiple different default import names – it's up to the // user to choose which one to keep. if (defaultImportNames.size > 1) { - return undefined + return undefined; } // Leave it to the user to handle comments. Also skip `import * as ns from @@ -59,16 +59,16 @@ function getFix(first, rest, sourceCode) { const restWithoutComments = rest.filter(node => !( hasProblematicComments(node, sourceCode) || hasNamespace(node) - )) + )); const specifiers = restWithoutComments .map(node => { - const tokens = sourceCode.getTokens(node) - const openBrace = tokens.find(token => isPunctuator(token, '{')) - const closeBrace = tokens.find(token => isPunctuator(token, '}')) + const tokens = sourceCode.getTokens(node); + const openBrace = tokens.find(token => isPunctuator(token, '{')); + const closeBrace = tokens.find(token => isPunctuator(token, '}')); if (openBrace == null || closeBrace == null) { - return undefined + return undefined; } return { @@ -76,35 +76,35 @@ function getFix(first, rest, sourceCode) { text: sourceCode.text.slice(openBrace.range[1], closeBrace.range[0]), hasTrailingComma: isPunctuator(sourceCode.getTokenBefore(closeBrace), ','), isEmpty: !hasSpecifiers(node), - } + }; }) - .filter(Boolean) + .filter(Boolean); const unnecessaryImports = restWithoutComments.filter(node => !hasSpecifiers(node) && !hasNamespace(node) && !specifiers.some(specifier => specifier.importNode === node) - ) + ); - const shouldAddDefault = getDefaultImportName(first) == null && defaultImportNames.size === 1 - const shouldAddSpecifiers = specifiers.length > 0 - const shouldRemoveUnnecessary = unnecessaryImports.length > 0 + const shouldAddDefault = getDefaultImportName(first) == null && defaultImportNames.size === 1; + const shouldAddSpecifiers = specifiers.length > 0; + const shouldRemoveUnnecessary = unnecessaryImports.length > 0; if (!(shouldAddDefault || shouldAddSpecifiers || shouldRemoveUnnecessary)) { - return undefined + return undefined; } return fixer => { - const tokens = sourceCode.getTokens(first) - const openBrace = tokens.find(token => isPunctuator(token, '{')) - const closeBrace = tokens.find(token => isPunctuator(token, '}')) - const firstToken = sourceCode.getFirstToken(first) - const [defaultImportName] = defaultImportNames + const tokens = sourceCode.getTokens(first); + const openBrace = tokens.find(token => isPunctuator(token, '{')); + const closeBrace = tokens.find(token => isPunctuator(token, '}')); + const firstToken = sourceCode.getFirstToken(first); + const [defaultImportName] = defaultImportNames; const firstHasTrailingComma = closeBrace != null && - isPunctuator(sourceCode.getTokenBefore(closeBrace), ',') - const firstIsEmpty = !hasSpecifiers(first) + isPunctuator(sourceCode.getTokenBefore(closeBrace), ','); + const firstIsEmpty = !hasSpecifiers(first); const [specifiersText] = specifiers.reduce( ([result, needsComma], specifier) => { @@ -113,80 +113,80 @@ function getFix(first, rest, sourceCode) { ? `${result},${specifier.text}` : `${result}${specifier.text}`, specifier.isEmpty ? needsComma : true, - ] + ]; }, ['', !firstHasTrailingComma && !firstIsEmpty] - ) + ); - const fixes = [] + const fixes = []; if (shouldAddDefault && openBrace == null && shouldAddSpecifiers) { // `import './foo'` → `import def, {...} from './foo'` fixes.push( fixer.insertTextAfter(firstToken, ` ${defaultImportName}, {${specifiersText}} from`) - ) + ); } else if (shouldAddDefault && openBrace == null && !shouldAddSpecifiers) { // `import './foo'` → `import def from './foo'` - fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName} from`)) + fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName} from`)); } else if (shouldAddDefault && openBrace != null && closeBrace != null) { // `import {...} from './foo'` → `import def, {...} from './foo'` - fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName},`)) + fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName},`)); if (shouldAddSpecifiers) { // `import def, {...} from './foo'` → `import def, {..., ...} from './foo'` - fixes.push(fixer.insertTextBefore(closeBrace, specifiersText)) + fixes.push(fixer.insertTextBefore(closeBrace, specifiersText)); } } else if (!shouldAddDefault && openBrace == null && shouldAddSpecifiers) { if (first.specifiers.length === 0) { // `import './foo'` → `import {...} from './foo'` - fixes.push(fixer.insertTextAfter(firstToken, ` {${specifiersText}} from`)) + fixes.push(fixer.insertTextAfter(firstToken, ` {${specifiersText}} from`)); } else { // `import def from './foo'` → `import def, {...} from './foo'` - fixes.push(fixer.insertTextAfter(first.specifiers[0], `, {${specifiersText}}`)) + fixes.push(fixer.insertTextAfter(first.specifiers[0], `, {${specifiersText}}`)); } } else if (!shouldAddDefault && openBrace != null && closeBrace != null) { // `import {...} './foo'` → `import {..., ...} from './foo'` - fixes.push(fixer.insertTextBefore(closeBrace, specifiersText)) + fixes.push(fixer.insertTextBefore(closeBrace, specifiersText)); } // Remove imports whose specifiers have been moved into the first import. for (const specifier of specifiers) { - fixes.push(fixer.remove(specifier.importNode)) + fixes.push(fixer.remove(specifier.importNode)); } // Remove imports whose default import has been moved to the first import, // and side-effect-only imports that are unnecessary due to the first // import. for (const node of unnecessaryImports) { - fixes.push(fixer.remove(node)) + fixes.push(fixer.remove(node)); } - return fixes - } + return fixes; + }; } function isPunctuator(node, value) { - return node.type === 'Punctuator' && node.value === value + return node.type === 'Punctuator' && node.value === value; } // Get the name of the default import of `node`, if any. function getDefaultImportName(node) { const defaultSpecifier = node.specifiers - .find(specifier => specifier.type === 'ImportDefaultSpecifier') - return defaultSpecifier != null ? defaultSpecifier.local.name : undefined + .find(specifier => specifier.type === 'ImportDefaultSpecifier'); + return defaultSpecifier != null ? defaultSpecifier.local.name : undefined; } // Checks whether `node` has a namespace import. function hasNamespace(node) { const specifiers = node.specifiers - .filter(specifier => specifier.type === 'ImportNamespaceSpecifier') - return specifiers.length > 0 + .filter(specifier => specifier.type === 'ImportNamespaceSpecifier'); + return specifiers.length > 0; } // Checks whether `node` has any non-default specifiers. function hasSpecifiers(node) { const specifiers = node.specifiers - .filter(specifier => specifier.type === 'ImportSpecifier') - return specifiers.length > 0 + .filter(specifier => specifier.type === 'ImportSpecifier'); + return specifiers.length > 0; } // It's not obvious what the user wants to do with comments associated with @@ -196,36 +196,36 @@ function hasProblematicComments(node, sourceCode) { hasCommentBefore(node, sourceCode) || hasCommentAfter(node, sourceCode) || hasCommentInsideNonSpecifiers(node, sourceCode) - ) + ); } // Checks whether `node` has a comment (that ends) on the previous line or on // the same line as `node` (starts). function hasCommentBefore(node, sourceCode) { return sourceCode.getCommentsBefore(node) - .some(comment => comment.loc.end.line >= node.loc.start.line - 1) + .some(comment => comment.loc.end.line >= node.loc.start.line - 1); } // Checks whether `node` has a comment (that starts) on the same line as `node` // (ends). function hasCommentAfter(node, sourceCode) { return sourceCode.getCommentsAfter(node) - .some(comment => comment.loc.start.line === node.loc.end.line) + .some(comment => comment.loc.start.line === node.loc.end.line); } // Checks whether `node` has any comments _inside,_ except inside the `{...}` // part (if any). function hasCommentInsideNonSpecifiers(node, sourceCode) { - const tokens = sourceCode.getTokens(node) - const openBraceIndex = tokens.findIndex(token => isPunctuator(token, '{')) - const closeBraceIndex = tokens.findIndex(token => isPunctuator(token, '}')) + const tokens = sourceCode.getTokens(node); + const openBraceIndex = tokens.findIndex(token => isPunctuator(token, '{')); + const closeBraceIndex = tokens.findIndex(token => isPunctuator(token, '}')); // Slice away the first token, since we're no looking for comments _before_ // `node` (only inside). If there's a `{...}` part, look for comments before // the `{`, but not before the `}` (hence the `+1`s). const someTokens = openBraceIndex >= 0 && closeBraceIndex >= 0 ? tokens.slice(1, openBraceIndex + 1).concat(tokens.slice(closeBraceIndex + 1)) - : tokens.slice(1) - return someTokens.some(token => sourceCode.getCommentsBefore(token).length > 0) + : tokens.slice(1); + return someTokens.some(token => sourceCode.getCommentsBefore(token).length > 0); } module.exports = { @@ -251,38 +251,38 @@ module.exports = { create: function (context) { // Prepare the resolver from options. const considerQueryStringOption = context.options[0] && - context.options[0]['considerQueryString'] - const defaultResolver = sourcePath => resolve(sourcePath, context) || sourcePath + context.options[0]['considerQueryString']; + const defaultResolver = sourcePath => resolve(sourcePath, context) || sourcePath; const resolver = considerQueryStringOption ? (sourcePath => { - const parts = sourcePath.match(/^([^?]*)\?(.*)$/) + const parts = sourcePath.match(/^([^?]*)\?(.*)$/); if (!parts) { - return defaultResolver(sourcePath) + return defaultResolver(sourcePath); } - return defaultResolver(parts[1]) + '?' + parts[2] - }) : defaultResolver + return defaultResolver(parts[1]) + '?' + parts[2]; + }) : defaultResolver; - const imported = new Map() - const nsImported = new Map() - const typesImported = new Map() + const imported = new Map(); + const nsImported = new Map(); + const typesImported = new Map(); return { 'ImportDeclaration': function (n) { // resolved path will cover aliased duplicates - const resolvedPath = resolver(n.source.value) + const resolvedPath = resolver(n.source.value); const importMap = n.importKind === 'type' ? typesImported : - (hasNamespace(n) ? nsImported : imported) + (hasNamespace(n) ? nsImported : imported); if (importMap.has(resolvedPath)) { - importMap.get(resolvedPath).push(n) + importMap.get(resolvedPath).push(n); } else { - importMap.set(resolvedPath, [n]) + importMap.set(resolvedPath, [n]); } }, 'Program:exit': function () { - checkImports(imported, context) - checkImports(nsImported, context) - checkImports(typesImported, context) + checkImports(imported, context); + checkImports(nsImported, context); + checkImports(typesImported, context); }, - } + }; }, -} +}; diff --git a/src/rules/no-dynamic-require.js b/src/rules/no-dynamic-require.js index 9e7af8e283..0c14df0893 100644 --- a/src/rules/no-dynamic-require.js +++ b/src/rules/no-dynamic-require.js @@ -1,16 +1,16 @@ -import docsUrl from '../docsUrl' +import docsUrl from '../docsUrl'; function isRequire(node) { return node && node.callee && node.callee.type === 'Identifier' && node.callee.name === 'require' && - node.arguments.length >= 1 + node.arguments.length >= 1; } function isStaticValue(arg) { return arg.type === 'Literal' || - (arg.type === 'TemplateLiteral' && arg.expressions.length === 0) + (arg.type === 'TemplateLiteral' && arg.expressions.length === 0); } module.exports = { @@ -29,9 +29,9 @@ module.exports = { context.report({ node, message: 'Calls to require() should use string literals', - }) + }); } }, - } + }; }, -} +}; diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 03c45526c0..8a6af2f617 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -1,18 +1,21 @@ -import path from 'path' -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 docsUrl from '../docsUrl' +import path from 'path'; +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 { getFilePackageName } from '../core/packagePath'; +import docsUrl from '../docsUrl'; + +const depFieldCache = new Map(); function hasKeys(obj = {}) { - return Object.keys(obj).length > 0 + return Object.keys(obj).length > 0; } function arrayOrKeys(arrayOrObject) { - return Array.isArray(arrayOrObject) ? arrayOrObject : Object.keys(arrayOrObject) + return Array.isArray(arrayOrObject) ? arrayOrObject : Object.keys(arrayOrObject); } function extractDepFields(pkg) { @@ -24,11 +27,11 @@ function extractDepFields(pkg) { // BundledDeps should be in the form of an array, but object notation is also supported by // `npm`, so we convert it to an array if it is an object bundledDependencies: arrayOrKeys(pkg.bundleDependencies || pkg.bundledDependencies || []), - } + }; } function getDependencies(context, packageDir) { - let paths = [] + let paths = []; try { const packageContent = { dependencies: {}, @@ -36,34 +39,39 @@ function getDependencies(context, packageDir) { optionalDependencies: {}, peerDependencies: {}, bundledDependencies: [], - } + }; if (packageDir && packageDir.length > 0) { if (!Array.isArray(packageDir)) { - paths = [path.resolve(packageDir)] + paths = [path.resolve(packageDir)]; } else { - paths = packageDir.map(dir => path.resolve(dir)) + paths = packageDir.map(dir => path.resolve(dir)); } } if (paths.length > 0) { // use rule config to find package.json paths.forEach(dir => { - const _packageContent = extractDepFields( - JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')) - ) + const packageJsonPath = path.join(dir, 'package.json'); + if (!depFieldCache.has(packageJsonPath)) { + const depFields = extractDepFields( + JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) + ); + depFieldCache.set(packageJsonPath, depFields); + } + const _packageContent = depFieldCache.get(packageJsonPath); Object.keys(packageContent).forEach(depsKey => Object.assign(packageContent[depsKey], _packageContent[depsKey]) - ) - }) + ); + }); } else { // use closest package.json Object.assign( packageContent, extractDepFields( - readPkgUp.sync({cwd: context.getFilename(), normalize: false}).pkg + readPkgUp.sync({ cwd: context.getFilename(), normalize: false }).pkg ) - ) + ); } if (![ @@ -73,97 +81,149 @@ function getDependencies(context, packageDir) { packageContent.peerDependencies, packageContent.bundledDependencies, ].some(hasKeys)) { - return null + return null; } - return packageContent + return packageContent; } catch (e) { if (paths.length > 0 && e.code === 'ENOENT') { context.report({ message: 'The package.json file could not be found.', loc: { line: 0, column: 0 }, - }) + }); } if (e.name === 'JSONError' || e instanceof SyntaxError) { context.report({ message: 'The package.json file could not be parsed: ' + e.message, loc: { line: 0, column: 0 }, - }) + }); } - return null + return null; } } function missingErrorMessage(packageName) { return `'${packageName}' should be listed in the project's dependencies. ` + - `Run 'npm i -S ${packageName}' to add it` + `Run 'npm i -S ${packageName}' to add it`; } function devDepErrorMessage(packageName) { - return `'${packageName}' should be listed in the project's dependencies, not devDependencies.` + return `'${packageName}' should be listed in the project's dependencies, not devDependencies.`; } function optDepErrorMessage(packageName) { return `'${packageName}' should be listed in the project's dependencies, ` + - `not optionalDependencies.` + `not optionalDependencies.`; +} + +function getModuleOriginalName(name) { + const [first, second] = name.split('/'); + return first.startsWith('@') ? `${first}/${second}` : first; +} + +function getModuleRealName(resolved) { + return getFilePackageName(resolved); +} + +function checkDependencyDeclaration(deps, packageName) { + // in case of sub package.json inside a module + // check the dependencies on all hierarchy + const packageHierarchy = []; + const packageNameParts = packageName.split('/'); + packageNameParts.forEach((namePart, index) => { + if (!namePart.startsWith('@')) { + const ancestor = packageNameParts.slice(0, index + 1).join('/'); + packageHierarchy.push(ancestor); + } + }); + + return packageHierarchy.reduce((result, ancestorName) => { + return { + isInDeps: result.isInDeps || deps.dependencies[ancestorName] !== undefined, + isInDevDeps: result.isInDevDeps || deps.devDependencies[ancestorName] !== undefined, + isInOptDeps: result.isInOptDeps || deps.optionalDependencies[ancestorName] !== undefined, + isInPeerDeps: result.isInPeerDeps || deps.peerDependencies[ancestorName] !== undefined, + isInBundledDeps: + result.isInBundledDeps || deps.bundledDependencies.indexOf(ancestorName) !== -1, + }; + }, { + isInDeps: false, + isInDevDeps: false, + isInOptDeps: false, + isInPeerDeps: false, + isInBundledDeps: false, + }); } function reportIfMissing(context, deps, depsOptions, node, name) { // Do not report when importing types - if (node.importKind === 'type') { - return + if (node.importKind === 'type' || (node.parent && node.parent.importKind === 'type') || node.importKind === 'typeof') { + return; } if (importType(name, context) !== 'external') { - return + return; + } + + const resolved = resolve(name, context); + if (!resolved) { return; } + + const importPackageName = getModuleOriginalName(name); + const importPackageNameDeclaration = checkDependencyDeclaration(deps, importPackageName); + + if (importPackageNameDeclaration.isInDeps || + (depsOptions.allowDevDeps && importPackageNameDeclaration.isInDevDeps) || + (depsOptions.allowPeerDeps && importPackageNameDeclaration.isInPeerDeps) || + (depsOptions.allowOptDeps && importPackageNameDeclaration.isInOptDeps) || + (depsOptions.allowBundledDeps && importPackageNameDeclaration.isInBundledDeps) + ) { + return; } - const resolved = resolve(name, context) - if (!resolved) { return } - - const splitName = name.split('/') - const packageName = splitName[0][0] === '@' - ? splitName.slice(0, 2).join('/') - : splitName[0] - const isInDeps = deps.dependencies[packageName] !== undefined - const isInDevDeps = deps.devDependencies[packageName] !== undefined - const isInOptDeps = deps.optionalDependencies[packageName] !== undefined - const isInPeerDeps = deps.peerDependencies[packageName] !== undefined - const isInBundledDeps = deps.bundledDependencies.indexOf(packageName) !== -1 - - if (isInDeps || - (depsOptions.allowDevDeps && isInDevDeps) || - (depsOptions.allowPeerDeps && isInPeerDeps) || - (depsOptions.allowOptDeps && isInOptDeps) || - (depsOptions.allowBundledDeps && isInBundledDeps) + // test the real name from the resolved package.json + // if not aliased imports (alias/react for example), importPackageName can be misinterpreted + const realPackageName = getModuleRealName(resolved); + const realPackageNameDeclaration = checkDependencyDeclaration(deps, realPackageName); + + if (realPackageNameDeclaration.isInDeps || + (depsOptions.allowDevDeps && realPackageNameDeclaration.isInDevDeps) || + (depsOptions.allowPeerDeps && realPackageNameDeclaration.isInPeerDeps) || + (depsOptions.allowOptDeps && realPackageNameDeclaration.isInOptDeps) || + (depsOptions.allowBundledDeps && realPackageNameDeclaration.isInBundledDeps) ) { - return + return; } - if (isInDevDeps && !depsOptions.allowDevDeps) { - context.report(node, devDepErrorMessage(packageName)) - return + if (( + importPackageNameDeclaration.isInDevDeps || + realPackageNameDeclaration.isInDevDeps + ) && !depsOptions.allowDevDeps) { + context.report(node, devDepErrorMessage(realPackageName)); + return; } - if (isInOptDeps && !depsOptions.allowOptDeps) { - context.report(node, optDepErrorMessage(packageName)) - return + if (( + importPackageNameDeclaration.isInOptDeps || + realPackageNameDeclaration.isInOptDeps + ) && !depsOptions.allowOptDeps) { + context.report(node, optDepErrorMessage(realPackageName)); + return; } - context.report(node, missingErrorMessage(packageName)) + context.report(node, missingErrorMessage(realPackageName)); } function testConfig(config, filename) { // Simplest configuration first, either a boolean or nothing. if (typeof config === 'boolean' || typeof config === 'undefined') { - return config + return config; } // Array of globs. return config.some(c => ( minimatch(filename, c) || minimatch(filename, path.join(process.cwd(), c)) - )) + )); } module.exports = { @@ -189,19 +249,19 @@ module.exports = { }, create: function (context) { - const options = context.options[0] || {} - const filename = context.getFilename() - const deps = getDependencies(context, options.packageDir) || extractDepFields({}) + const options = context.options[0] || {}; + const filename = context.getFilename(); + const deps = getDependencies(context, options.packageDir) || extractDepFields({}); const depsOptions = { allowDevDeps: testConfig(options.devDependencies, filename) !== false, allowOptDeps: testConfig(options.optionalDependencies, filename) !== false, allowPeerDeps: testConfig(options.peerDependencies, filename) !== false, allowBundledDeps: testConfig(options.bundledDependencies, filename) !== false, - } + }; - return moduleVisitor(node => { - reportIfMissing(context, deps, depsOptions, node, node.value) - }, {commonjs: true}) + return moduleVisitor((source, node) => { + reportIfMissing(context, deps, depsOptions, node, source.value); + }, { commonjs: true }); }, -} +}; diff --git a/src/rules/no-import-module-exports.js b/src/rules/no-import-module-exports.js new file mode 100644 index 0000000000..8ce5f4c9ac --- /dev/null +++ b/src/rules/no-import-module-exports.js @@ -0,0 +1,72 @@ +import minimatch from 'minimatch'; +import path from 'path'; +import pkgUp from 'pkg-up'; + +function getEntryPoint(context) { + const pkgPath = pkgUp.sync(context.getFilename()); + try { + return require.resolve(path.dirname(pkgPath)); + } catch (error) { + // Assume the package has no entrypoint (e.g. CLI packages) + // in which case require.resolve would throw. + return null; + } +} + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Disallow import statements with module.exports', + category: 'Best Practices', + recommended: true, + }, + fixable: 'code', + schema: [ + { + 'type': 'object', + 'properties': { + 'exceptions': { 'type': 'array' }, + }, + 'additionalProperties': false, + }, + ], + }, + create(context) { + const importDeclarations = []; + const entryPoint = getEntryPoint(context); + const options = context.options[0] || {}; + let alreadyReported = false; + + function report(node) { + const fileName = context.getFilename(); + const isEntryPoint = entryPoint === fileName; + const isIdentifier = node.object.type === 'Identifier'; + const hasKeywords = (/^(module|exports)$/).test(node.object.name); + const isException = options.exceptions && + options.exceptions.some(glob => minimatch(fileName, glob)); + + if (isIdentifier && hasKeywords && !isEntryPoint && !isException) { + importDeclarations.forEach(importDeclaration => { + context.report({ + node: importDeclaration, + message: `Cannot use import declarations in modules that export using ` + + `CommonJS (module.exports = 'foo' or exports.bar = 'hi')`, + }); + }); + alreadyReported = true; + } + } + + return { + ImportDeclaration(node) { + importDeclarations.push(node); + }, + MemberExpression(node) { + if (!alreadyReported) { + report(node); + } + }, + }; + }, +}; diff --git a/src/rules/no-internal-modules.js b/src/rules/no-internal-modules.js index bd13ab07d0..a33f23b475 100644 --- a/src/rules/no-internal-modules.js +++ b/src/rules/no-internal-modules.js @@ -1,9 +1,9 @@ -import minimatch from 'minimatch' +import minimatch from 'minimatch'; -import resolve from 'eslint-module-utils/resolve' -import importType from '../core/importType' -import isStaticRequire from '../core/staticRequire' -import docsUrl from '../docsUrl' +import resolve from 'eslint-module-utils/resolve'; +import importType from '../core/importType'; +import moduleVisitor from 'eslint-module-utils/moduleVisitor'; +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -14,97 +14,127 @@ module.exports = { schema: [ { - type: 'object', - properties: { - allow: { - type: 'array', - items: { - type: 'string', + oneOf: [ + { + type: 'object', + properties: { + allow: { + type: 'array', + items: { + type: 'string', + }, + }, }, + additionalProperties: false, }, - }, - additionalProperties: false, + { + type: 'object', + properties: { + forbid: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + additionalProperties: false, + }, + ], }, ], }, create: function noReachingInside(context) { - const options = context.options[0] || {} - const allowRegexps = (options.allow || []).map(p => minimatch.makeRe(p)) - - // test if reaching to this destination is allowed - function reachingAllowed(importPath) { - return allowRegexps.some(re => re.test(importPath)) - } + const options = context.options[0] || {}; + const allowRegexps = (options.allow || []).map(p => minimatch.makeRe(p)); + const forbidRegexps = (options.forbid || []).map(p => minimatch.makeRe(p)); // minimatch patterns are expected to use / path separators, like import // statements, so normalize paths to use the same function normalizeSep(somePath) { - return somePath.split('\\').join('/') + return somePath.split('\\').join('/'); } - // find a directory that is being reached into, but which shouldn't be - function isReachViolation(importPath) { - const steps = normalizeSep(importPath) + function toSteps(somePath) { + return normalizeSep(somePath) .split('/') .reduce((acc, step) => { if (!step || step === '.') { - return acc + return acc; } else if (step === '..') { - return acc.slice(0, -1) + return acc.slice(0, -1); } else { - return acc.concat(step) + return acc.concat(step); } - }, []) + }, []); + } - const nonScopeSteps = steps.filter(step => step.indexOf('@') !== 0) - if (nonScopeSteps.length <= 1) return false + // test if reaching to this destination is allowed + function reachingAllowed(importPath) { + return allowRegexps.some(re => re.test(importPath)); + } + + // test if reaching to this destination is forbidden + function reachingForbidden(importPath) { + return forbidRegexps.some(re => re.test(importPath)); + } + + function isAllowViolation(importPath) { + const steps = toSteps(importPath); + + const nonScopeSteps = steps.filter(step => step.indexOf('@') !== 0); + if (nonScopeSteps.length <= 1) return false; // before trying to resolve, see if the raw import (with relative // segments resolved) matches an allowed pattern - const justSteps = steps.join('/') - if (reachingAllowed(justSteps) || reachingAllowed(`/${justSteps}`)) return false + const justSteps = steps.join('/'); + if (reachingAllowed(justSteps) || reachingAllowed(`/${justSteps}`)) return false; // if the import statement doesn't match directly, try to match the // resolved path if the import is resolvable - const resolved = resolve(importPath, context) - if (!resolved || reachingAllowed(normalizeSep(resolved))) return false + const resolved = resolve(importPath, context); + if (!resolved || reachingAllowed(normalizeSep(resolved))) return false; // this import was not allowed by the allowed paths, and reaches // so it is a violation - return true + return true; } + function isForbidViolation(importPath) { + const steps = toSteps(importPath); + + // before trying to resolve, see if the raw import (with relative + // segments resolved) matches a forbidden pattern + const justSteps = steps.join('/'); + + if (reachingForbidden(justSteps) || reachingForbidden(`/${justSteps}`)) return true; + + // if the import statement doesn't match directly, try to match the + // resolved path if the import is resolvable + const resolved = resolve(importPath, context); + if (resolved && reachingForbidden(normalizeSep(resolved))) return true; + + // this import was not forbidden by the forbidden paths so it is not a violation + return false; + } + + // find a directory that is being reached into, but which shouldn't be + const isReachViolation = options.forbid ? isForbidViolation : isAllowViolation; + function checkImportForReaching(importPath, node) { - const potentialViolationTypes = ['parent', 'index', 'sibling', 'external', 'internal'] + const potentialViolationTypes = ['parent', 'index', 'sibling', 'external', 'internal']; if (potentialViolationTypes.indexOf(importType(importPath, context)) !== -1 && isReachViolation(importPath) ) { context.report({ node, message: `Reaching to "${importPath}" is not allowed.`, - }) + }); } } - return { - ImportDeclaration(node) { - checkImportForReaching(node.source.value, node.source) - }, - ExportAllDeclaration(node) { - checkImportForReaching(node.source.value, node.source) - }, - ExportNamedDeclaration(node) { - if (node.source) { - checkImportForReaching(node.source.value, node.source) - } - }, - CallExpression(node) { - if (isStaticRequire(node)) { - const [ firstArgument ] = node.arguments - checkImportForReaching(firstArgument.value, firstArgument) - } - }, - } + return moduleVisitor((source) => { + checkImportForReaching(source.value, source); + }, { commonjs: true }); }, -} +}; diff --git a/src/rules/no-mutable-exports.js b/src/rules/no-mutable-exports.js index 7e94bfbe9b..a1635bb7ae 100644 --- a/src/rules/no-mutable-exports.js +++ b/src/rules/no-mutable-exports.js @@ -1,4 +1,4 @@ -import docsUrl from '../docsUrl' +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -11,18 +11,18 @@ module.exports = { create: function (context) { function checkDeclaration(node) { - const {kind} = node + const { kind } = node; if (kind === 'var' || kind === 'let') { - context.report(node, `Exporting mutable '${kind}' binding, use 'const' instead.`) + context.report(node, `Exporting mutable '${kind}' binding, use 'const' instead.`); } } - function checkDeclarationsInScope({variables}, name) { - for (let variable of variables) { + function checkDeclarationsInScope({ variables }, name) { + for (const variable of variables) { if (variable.name === name) { - for (let def of variable.defs) { + for (const def of variable.defs) { if (def.type === 'Variable' && def.parent) { - checkDeclaration(def.parent) + checkDeclaration(def.parent); } } } @@ -30,21 +30,21 @@ module.exports = { } function handleExportDefault(node) { - const scope = context.getScope() + const scope = context.getScope(); if (node.declaration.name) { - checkDeclarationsInScope(scope, node.declaration.name) + checkDeclarationsInScope(scope, node.declaration.name); } } function handleExportNamed(node) { - const scope = context.getScope() + const scope = context.getScope(); if (node.declaration) { - checkDeclaration(node.declaration) + checkDeclaration(node.declaration); } else if (!node.source) { - for (let specifier of node.specifiers) { - checkDeclarationsInScope(scope, specifier.local.name) + for (const specifier of node.specifiers) { + checkDeclarationsInScope(scope, specifier.local.name); } } } @@ -52,6 +52,6 @@ module.exports = { return { 'ExportDefaultDeclaration': handleExportDefault, 'ExportNamedDeclaration': handleExportNamed, - } + }; }, -} +}; diff --git a/src/rules/no-named-as-default-member.js b/src/rules/no-named-as-default-member.js index b6e50cd713..09bb5e34a3 100644 --- a/src/rules/no-named-as-default-member.js +++ b/src/rules/no-named-as-default-member.js @@ -4,9 +4,9 @@ * @copyright 2016 Desmond Brand. All rights reserved. * See LICENSE in root directory for full license. */ -import Exports from '../ExportMap' -import importDeclaration from '../importDeclaration' -import docsUrl from '../docsUrl' +import Exports from '../ExportMap'; +import importDeclaration from '../importDeclaration'; +import docsUrl from '../docsUrl'; //------------------------------------------------------------------------------ // Rule Definition @@ -23,35 +23,35 @@ module.exports = { create: function(context) { - const fileImports = new Map() - const allPropertyLookups = new Map() + const fileImports = new Map(); + const allPropertyLookups = new Map(); function handleImportDefault(node) { - const declaration = importDeclaration(context) - const exportMap = Exports.get(declaration.source.value, context) - if (exportMap == null) return + const declaration = importDeclaration(context); + const exportMap = Exports.get(declaration.source.value, context); + if (exportMap == null) return; if (exportMap.errors.length) { - exportMap.reportErrors(context, declaration) - return + exportMap.reportErrors(context, declaration); + return; } fileImports.set(node.local.name, { exportMap, sourcePath: declaration.source.value, - }) + }); } function storePropertyLookup(objectName, propName, node) { - const lookups = allPropertyLookups.get(objectName) || [] - lookups.push({node, propName}) - allPropertyLookups.set(objectName, lookups) + const lookups = allPropertyLookups.get(objectName) || []; + lookups.push({ node, propName }); + allPropertyLookups.set(objectName, lookups); } function handlePropLookup(node) { - const objectName = node.object.name - const propName = node.property.name - storePropertyLookup(objectName, propName, node) + const objectName = node.object.name; + const propName = node.property.name; + storePropertyLookup(objectName, propName, node); } function handleDestructuringAssignment(node) { @@ -59,25 +59,25 @@ module.exports = { node.id.type === 'ObjectPattern' && node.init != null && node.init.type === 'Identifier' - ) - if (!isDestructure) return + ); + if (!isDestructure) return; - const objectName = node.init.name + const objectName = node.init.name; for (const { key } of node.id.properties) { - if (key == null) continue // true for rest properties - storePropertyLookup(objectName, key.name, key) + if (key == null) continue; // true for rest properties + storePropertyLookup(objectName, key.name, key); } } function handleProgramExit() { allPropertyLookups.forEach((lookups, objectName) => { - const fileImport = fileImports.get(objectName) - if (fileImport == null) return + const fileImport = fileImports.get(objectName); + if (fileImport == null) return; - for (const {propName, node} of lookups) { + for (const { propName, node } of lookups) { // the default import can have a "default" property - if (propName === 'default') continue - if (!fileImport.exportMap.namespace.has(propName)) continue + if (propName === 'default') continue; + if (!fileImport.exportMap.namespace.has(propName)) continue; context.report({ node, @@ -87,9 +87,9 @@ module.exports = { `\`import {${propName}} from '${fileImport.sourcePath}'\` ` + 'instead.' ), - }) + }); } - }) + }); } return { @@ -97,6 +97,6 @@ module.exports = { 'MemberExpression': handlePropLookup, 'VariableDeclarator': handleDestructuringAssignment, 'Program:exit': handleProgramExit, - } + }; }, -} +}; diff --git a/src/rules/no-named-as-default.js b/src/rules/no-named-as-default.js index b4c64a34c9..7313e61268 100644 --- a/src/rules/no-named-as-default.js +++ b/src/rules/no-named-as-default.js @@ -1,6 +1,6 @@ -import Exports from '../ExportMap' -import importDeclaration from '../importDeclaration' -import docsUrl from '../docsUrl' +import Exports from '../ExportMap'; +import importDeclaration from '../importDeclaration'; +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -14,16 +14,16 @@ module.exports = { create: function (context) { function checkDefault(nameKey, defaultSpecifier) { // #566: default is a valid specifier - if (defaultSpecifier[nameKey].name === 'default') return + if (defaultSpecifier[nameKey].name === 'default') return; - var declaration = importDeclaration(context) + const declaration = importDeclaration(context); - var imports = Exports.get(declaration.source.value, context) - if (imports == null) return + const imports = Exports.get(declaration.source.value, context); + if (imports == null) return; if (imports.errors.length) { - imports.reportErrors(context, declaration) - return + imports.reportErrors(context, declaration); + return; } if (imports.has('default') && @@ -31,13 +31,13 @@ module.exports = { context.report(defaultSpecifier, 'Using exported name \'' + defaultSpecifier[nameKey].name + - '\' as identifier for default export.') + '\' as identifier for default export.'); } } return { 'ImportDefaultSpecifier': checkDefault.bind(null, 'local'), 'ExportDefaultSpecifier': checkDefault.bind(null, 'exported'), - } + }; }, -} +}; diff --git a/src/rules/no-named-default.js b/src/rules/no-named-default.js index f0b74a2ffb..d1c15d62e0 100644 --- a/src/rules/no-named-default.js +++ b/src/rules/no-named-default.js @@ -1,4 +1,4 @@ -import docsUrl from '../docsUrl' +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -13,13 +13,17 @@ module.exports = { return { 'ImportDeclaration': function (node) { node.specifiers.forEach(function (im) { + if (im.importKind === 'type' || im.importKind === 'typeof') { + return; + } + if (im.type === 'ImportSpecifier' && im.imported.name === 'default') { context.report({ node: im.local, - message: `Use default import syntax to import '${im.local.name}'.` }) + message: `Use default import syntax to import '${im.local.name}'.` }); } - }) + }); }, - } + }; }, -} +}; diff --git a/src/rules/no-named-export.js b/src/rules/no-named-export.js index e7d4b08351..bb586ead00 100644 --- a/src/rules/no-named-export.js +++ b/src/rules/no-named-export.js @@ -1,4 +1,4 @@ -import docsUrl from '../docsUrl' +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -10,26 +10,26 @@ module.exports = { create(context) { // ignore non-modules if (context.parserOptions.sourceType !== 'module') { - return {} + return {}; } - const message = 'Named exports are not allowed.' + const message = 'Named exports are not allowed.'; return { ExportAllDeclaration(node) { - context.report({node, message}) + context.report({ node, message }); }, ExportNamedDeclaration(node) { if (node.specifiers.length === 0) { - return context.report({node, message}) + return context.report({ node, message }); } - const someNamed = node.specifiers.some(specifier => specifier.exported.name !== 'default') + const someNamed = node.specifiers.some(specifier => specifier.exported.name !== 'default'); if (someNamed) { - context.report({node, message}) + context.report({ node, message }); } }, - } + }; }, -} +}; diff --git a/src/rules/no-namespace.js b/src/rules/no-namespace.js index 0b63132508..ca51823a0b 100644 --- a/src/rules/no-namespace.js +++ b/src/rules/no-namespace.js @@ -3,7 +3,7 @@ * @author Radek Benkel */ -import docsUrl from '../docsUrl' +import docsUrl from '../docsUrl'; //------------------------------------------------------------------------------ // Rule Definition @@ -23,69 +23,69 @@ module.exports = { create: function (context) { return { 'ImportNamespaceSpecifier': function (node) { - const scopeVariables = context.getScope().variables + const scopeVariables = context.getScope().variables; const namespaceVariable = scopeVariables.find((variable) => variable.defs[0].node === node - ) - const namespaceReferences = namespaceVariable.references - const namespaceIdentifiers = namespaceReferences.map(reference => reference.identifier) - const canFix = namespaceIdentifiers.length > 0 && !usesNamespaceAsObject(namespaceIdentifiers) + ); + const namespaceReferences = namespaceVariable.references; + const namespaceIdentifiers = namespaceReferences.map(reference => reference.identifier); + const canFix = namespaceIdentifiers.length > 0 && !usesNamespaceAsObject(namespaceIdentifiers); context.report({ node, message: `Unexpected namespace import.`, fix: canFix && (fixer => { - const scopeManager = context.getSourceCode().scopeManager - const fixes = [] + const scopeManager = context.getSourceCode().scopeManager; + const fixes = []; // Pass 1: Collect variable names that are already in scope for each reference we want // to transform, so that we can be sure that we choose non-conflicting import names - const importNameConflicts = {} + const importNameConflicts = {}; namespaceIdentifiers.forEach((identifier) => { - const parent = identifier.parent + const parent = identifier.parent; if (parent && parent.type === 'MemberExpression') { - const importName = getMemberPropertyName(parent) - const localConflicts = getVariableNamesInScope(scopeManager, parent) + const importName = getMemberPropertyName(parent); + const localConflicts = getVariableNamesInScope(scopeManager, parent); if (!importNameConflicts[importName]) { - importNameConflicts[importName] = localConflicts + importNameConflicts[importName] = localConflicts; } else { - localConflicts.forEach((c) => importNameConflicts[importName].add(c)) + localConflicts.forEach((c) => importNameConflicts[importName].add(c)); } } - }) + }); // Choose new names for each import - const importNames = Object.keys(importNameConflicts) + const importNames = Object.keys(importNameConflicts); const importLocalNames = generateLocalNames( importNames, importNameConflicts, namespaceVariable.name - ) + ); // Replace the ImportNamespaceSpecifier with a list of ImportSpecifiers const namedImportSpecifiers = importNames.map((importName) => importName === importLocalNames[importName] ? importName : `${importName} as ${importLocalNames[importName]}` - ) - fixes.push(fixer.replaceText(node, `{ ${namedImportSpecifiers.join(', ')} }`)) + ); + fixes.push(fixer.replaceText(node, `{ ${namedImportSpecifiers.join(', ')} }`)); // Pass 2: Replace references to the namespace with references to the named imports namespaceIdentifiers.forEach((identifier) => { - const parent = identifier.parent + const parent = identifier.parent; if (parent && parent.type === 'MemberExpression') { - const importName = getMemberPropertyName(parent) - fixes.push(fixer.replaceText(parent, importLocalNames[importName])) + const importName = getMemberPropertyName(parent); + fixes.push(fixer.replaceText(parent, importLocalNames[importName])); } - }) + }); - return fixes + return fixes; }), - }) + }); }, - } + }; }, -} +}; /** * @param {Identifier[]} namespaceIdentifiers @@ -93,14 +93,14 @@ module.exports = { */ function usesNamespaceAsObject(namespaceIdentifiers) { return !namespaceIdentifiers.every((identifier) => { - const parent = identifier.parent + const parent = identifier.parent; // `namespace.x` or `namespace['x']` return ( parent && parent.type === 'MemberExpression' && (parent.property.type === 'Identifier' || parent.property.type === 'Literal') - ) - }) + ); + }); } /** @@ -110,7 +110,7 @@ function usesNamespaceAsObject(namespaceIdentifiers) { function getMemberPropertyName(memberExpression) { return memberExpression.property.type === 'Identifier' ? memberExpression.property.name - : memberExpression.property.value + : memberExpression.property.value; } /** @@ -119,16 +119,16 @@ function getMemberPropertyName(memberExpression) { * @return {Set} */ function getVariableNamesInScope(scopeManager, node) { - let currentNode = node - let scope = scopeManager.acquire(currentNode) + let currentNode = node; + let scope = scopeManager.acquire(currentNode); while (scope == null) { - currentNode = currentNode.parent - scope = scopeManager.acquire(currentNode, true) + currentNode = currentNode.parent; + scope = scopeManager.acquire(currentNode, true); } return new Set([ ...scope.variables.map(variable => variable.name), ...scope.upper.variables.map(variable => variable.name), - ]) + ]); } /** @@ -138,22 +138,22 @@ function getVariableNamesInScope(scopeManager, node) { * @param {*} namespaceName */ function generateLocalNames(names, nameConflicts, namespaceName) { - const localNames = {} + const localNames = {}; names.forEach((name) => { - let localName + let localName; if (!nameConflicts[name].has(name)) { - localName = name + localName = name; } else if (!nameConflicts[name].has(`${namespaceName}_${name}`)) { - localName = `${namespaceName}_${name}` + localName = `${namespaceName}_${name}`; } else { for (let i = 1; i < Infinity; i++) { if (!nameConflicts[name].has(`${namespaceName}_${name}_${i}`)) { - localName = `${namespaceName}_${name}_${i}` - break + localName = `${namespaceName}_${name}_${i}`; + break; } } } - localNames[name] = localName - }) - return localNames + localNames[name] = localName; + }); + return localNames; } diff --git a/src/rules/no-nodejs-modules.js b/src/rules/no-nodejs-modules.js index fb9bc23e28..cbfb384d32 100644 --- a/src/rules/no-nodejs-modules.js +++ b/src/rules/no-nodejs-modules.js @@ -1,10 +1,10 @@ -import importType from '../core/importType' -import isStaticRequire from '../core/staticRequire' -import docsUrl from '../docsUrl' +import importType from '../core/importType'; +import moduleVisitor from 'eslint-module-utils/moduleVisitor'; +import docsUrl from '../docsUrl'; function reportIfMissing(context, node, allowed, name) { if (allowed.indexOf(name) === -1 && importType(name, context) === 'builtin') { - context.report(node, 'Do not import Node.js builtin module "' + name + '"') + context.report(node, 'Do not import Node.js builtin module "' + name + '"'); } } @@ -32,18 +32,11 @@ module.exports = { }, create: function (context) { - const options = context.options[0] || {} - const allowed = options.allow || [] + const options = context.options[0] || {}; + const allowed = options.allow || []; - return { - ImportDeclaration: function handleImports(node) { - reportIfMissing(context, node, allowed, node.source.value) - }, - CallExpression: function handleRequires(node) { - if (isStaticRequire(node)) { - reportIfMissing(context, node, allowed, node.arguments[0].value) - } - }, - } + return moduleVisitor((source, node) => { + reportIfMissing(context, node, allowed, source.value); + }, { commonjs: true }); }, -} +}; diff --git a/src/rules/no-relative-packages.js b/src/rules/no-relative-packages.js new file mode 100644 index 0000000000..a654c08393 --- /dev/null +++ b/src/rules/no-relative-packages.js @@ -0,0 +1,61 @@ +import path from 'path'; +import readPkgUp from 'read-pkg-up'; + +import resolve from 'eslint-module-utils/resolve'; +import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; +import importType from '../core/importType'; +import docsUrl from '../docsUrl'; + +function findNamedPackage(filePath) { + const found = readPkgUp.sync({ cwd: filePath, normalize: false }); + if (found.pkg && !found.pkg.name) { + return findNamedPackage(path.join(found.path, '../..')); + } + return found; +} + +function checkImportForRelativePackage(context, importPath, node) { + const potentialViolationTypes = ['parent', 'index', 'sibling']; + if (potentialViolationTypes.indexOf(importType(importPath, context)) === -1) { + return; + } + + const resolvedImport = resolve(importPath, context); + const resolvedContext = context.getFilename(); + + if (!resolvedImport || !resolvedContext) { + return; + } + + const importPkg = findNamedPackage(resolvedImport); + const contextPkg = findNamedPackage(resolvedContext); + + if (importPkg.pkg && contextPkg.pkg && importPkg.pkg.name !== contextPkg.pkg.name) { + const importBaseName = path.basename(importPath); + const importRoot = path.dirname(importPkg.path); + const properPath = path.relative(importRoot, resolvedImport); + const properImport = path.join( + importPkg.pkg.name, + path.dirname(properPath), + importBaseName === path.basename(importRoot) ? '' : importBaseName + ); + context.report({ + node, + message: `Relative import from another package is not allowed. Use \`${properImport}\` instead of \`${importPath}\``, + }); + } +} + +module.exports = { + meta: { + type: 'suggestion', + docs: { + url: docsUrl('no-relative-packages'), + }, + schema: [makeOptionsSchema()], + }, + + create(context) { + return moduleVisitor((source) => checkImportForRelativePackage(context, source.value, source), context.options[0]); + }, +}; diff --git a/src/rules/no-relative-parent-imports.js b/src/rules/no-relative-parent-imports.js index 544525755e..9826da826b 100644 --- a/src/rules/no-relative-parent-imports.js +++ b/src/rules/no-relative-parent-imports.js @@ -1,9 +1,9 @@ -import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor' -import docsUrl from '../docsUrl' -import { basename, dirname, relative } from 'path' -import resolve from 'eslint-module-utils/resolve' +import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; +import docsUrl from '../docsUrl'; +import { basename, dirname, relative } from 'path'; +import resolve from 'eslint-module-utils/resolve'; -import importType from '../core/importType' +import importType from '../core/importType'; module.exports = { meta: { @@ -15,23 +15,23 @@ module.exports = { }, create: function noRelativePackages(context) { - const myPath = context.getFilename() - if (myPath === '') return {} // can't check a non-file + const myPath = context.getFilename(); + if (myPath === '') return {}; // can't check a non-file function checkSourceValue(sourceNode) { - const depPath = sourceNode.value + const depPath = sourceNode.value; if (importType(depPath, context) === 'external') { // ignore packages - return + return; } - const absDepPath = resolve(depPath, context) + const absDepPath = resolve(depPath, context); if (!absDepPath) { // unable to resolve path - return + return; } - const relDepPath = relative(dirname(myPath), absDepPath) + const relDepPath = relative(dirname(myPath), absDepPath); if (importType(relDepPath, context) === 'parent') { context.report({ @@ -40,10 +40,10 @@ module.exports = { `Please either pass what you're importing through at runtime ` + `(dependency injection), move \`${basename(myPath)}\` to same ` + `directory as \`${depPath}\` or consider making \`${depPath}\` a package.`, - }) + }); } } - return moduleVisitor(checkSourceValue, context.options[0]) + return moduleVisitor(checkSourceValue, context.options[0]); }, -} +}; diff --git a/src/rules/no-restricted-paths.js b/src/rules/no-restricted-paths.js index a94b11ec16..6409ff57ac 100644 --- a/src/rules/no-restricted-paths.js +++ b/src/rules/no-restricted-paths.js @@ -1,10 +1,14 @@ -import containsPath from 'contains-path' -import path from 'path' +import path from 'path'; -import resolve from 'eslint-module-utils/resolve' -import isStaticRequire from '../core/staticRequire' -import docsUrl from '../docsUrl' -import importType from '../core/importType' +import resolve from 'eslint-module-utils/resolve'; +import moduleVisitor from 'eslint-module-utils/moduleVisitor'; +import docsUrl from '../docsUrl'; +import importType from '../core/importType'; + +const containsPath = (filepath, target) => { + const relative = path.relative(target, filepath); + return relative === '' || !relative.startsWith('..'); +}; module.exports = { meta: { @@ -45,81 +49,80 @@ module.exports = { }, create: function noRestrictedPaths(context) { - const options = context.options[0] || {} - const restrictedPaths = options.zones || [] - const basePath = options.basePath || process.cwd() - const currentFilename = context.getFilename() + const options = context.options[0] || {}; + const restrictedPaths = options.zones || []; + const basePath = options.basePath || process.cwd(); + const currentFilename = context.getFilename(); const matchingZones = restrictedPaths.filter((zone) => { - const targetPath = path.resolve(basePath, zone.target) + const targetPath = path.resolve(basePath, zone.target); - return containsPath(currentFilename, targetPath) - }) + return containsPath(currentFilename, targetPath); + }); function isValidExceptionPath(absoluteFromPath, absoluteExceptionPath) { - const relativeExceptionPath = path.relative(absoluteFromPath, absoluteExceptionPath) + const relativeExceptionPath = path.relative(absoluteFromPath, absoluteExceptionPath); - return importType(relativeExceptionPath, context) !== 'parent' + return importType(relativeExceptionPath, context) !== 'parent'; } function reportInvalidExceptionPath(node) { context.report({ node, message: 'Restricted path exceptions must be descendants of the configured `from` path for that zone.', - }) + }); } + const zoneExceptions = matchingZones.map((zone) => { + const exceptionPaths = zone.except || []; + const absoluteFrom = path.resolve(basePath, zone.from); + const absoluteExceptionPaths = exceptionPaths.map((exceptionPath) => path.resolve(absoluteFrom, exceptionPath)); + const hasValidExceptionPaths = absoluteExceptionPaths + .every((absoluteExceptionPath) => isValidExceptionPath(absoluteFrom, absoluteExceptionPath)); + + return { + absoluteExceptionPaths, + hasValidExceptionPaths, + }; + }); + function checkForRestrictedImportPath(importPath, node) { - const absoluteImportPath = resolve(importPath, context) + const absoluteImportPath = resolve(importPath, context); - if (!absoluteImportPath) { - return + if (!absoluteImportPath) { + return; + } + + matchingZones.forEach((zone, index) => { + const absoluteFrom = path.resolve(basePath, zone.from); + + if (!containsPath(absoluteImportPath, absoluteFrom)) { + return; } - matchingZones.forEach((zone) => { - const exceptionPaths = zone.except || [] - const absoluteFrom = path.resolve(basePath, zone.from) - - if (!containsPath(absoluteImportPath, absoluteFrom)) { - return - } - - const absoluteExceptionPaths = exceptionPaths.map((exceptionPath) => - path.resolve(absoluteFrom, exceptionPath) - ) - const hasValidExceptionPaths = absoluteExceptionPaths - .every((absoluteExceptionPath) => isValidExceptionPath(absoluteFrom, absoluteExceptionPath)) - - if (!hasValidExceptionPaths) { - reportInvalidExceptionPath(node) - return - } - - const pathIsExcepted = absoluteExceptionPaths - .some((absoluteExceptionPath) => containsPath(absoluteImportPath, absoluteExceptionPath)) - - if (pathIsExcepted) { - return - } - - context.report({ - node, - message: `Unexpected path "{{importPath}}" imported in restricted zone.${zone.message ? ` ${zone.message}` : ''}`, - data: { importPath }, - }) - }) - } + const { hasValidExceptionPaths, absoluteExceptionPaths } = zoneExceptions[index]; - return { - ImportDeclaration(node) { - checkForRestrictedImportPath(node.source.value, node.source) - }, - CallExpression(node) { - if (isStaticRequire(node)) { - const [ firstArgument ] = node.arguments + if (!hasValidExceptionPaths) { + reportInvalidExceptionPath(node); + return; + } + + const pathIsExcepted = absoluteExceptionPaths + .some((absoluteExceptionPath) => containsPath(absoluteImportPath, absoluteExceptionPath)); - checkForRestrictedImportPath(firstArgument.value, firstArgument) + if (pathIsExcepted) { + return; } - }, + + context.report({ + node, + message: `Unexpected path "{{importPath}}" imported in restricted zone.${zone.message ? ` ${zone.message}` : ''}`, + data: { importPath }, + }); + }); } + + return moduleVisitor((source) => { + checkForRestrictedImportPath(source.value, source); + }, { commonjs: true }); }, -} +}; diff --git a/src/rules/no-self-import.js b/src/rules/no-self-import.js index b869d46e06..a10be56786 100644 --- a/src/rules/no-self-import.js +++ b/src/rules/no-self-import.js @@ -3,19 +3,19 @@ * @author Gio d'Amelio */ -import resolve from 'eslint-module-utils/resolve' -import isStaticRequire from '../core/staticRequire' -import docsUrl from '../docsUrl' +import resolve from 'eslint-module-utils/resolve'; +import moduleVisitor from 'eslint-module-utils/moduleVisitor'; +import docsUrl from '../docsUrl'; function isImportingSelf(context, node, requireName) { - const filePath = context.getFilename() + const filePath = context.getFilename(); // If the input is from stdin, this test can't fail if (filePath !== '' && filePath === resolve(requireName, context)) { context.report({ - node, - message: 'Module imports itself.', - }) + node, + message: 'Module imports itself.', + }); } } @@ -31,15 +31,8 @@ module.exports = { schema: [], }, create: function (context) { - return { - ImportDeclaration(node) { - isImportingSelf(context, node, node.source.value) - }, - CallExpression(node) { - if (isStaticRequire(node)) { - isImportingSelf(context, node, node.arguments[0].value) - } - }, - } + return moduleVisitor((source, node) => { + isImportingSelf(context, node, source.value); + }, { commonjs: true }); }, -} +}; diff --git a/src/rules/no-unassigned-import.js b/src/rules/no-unassigned-import.js index 5ea637e67b..ed292e9128 100644 --- a/src/rules/no-unassigned-import.js +++ b/src/rules/no-unassigned-import.js @@ -1,54 +1,54 @@ -import path from 'path' -import minimatch from 'minimatch' +import path from 'path'; +import minimatch from 'minimatch'; -import isStaticRequire from '../core/staticRequire' -import docsUrl from '../docsUrl' +import isStaticRequire from '../core/staticRequire'; +import docsUrl from '../docsUrl'; function report(context, node) { context.report({ node, message: 'Imported module should be assigned', - }) + }); } function testIsAllow(globs, filename, source) { if (!Array.isArray(globs)) { - return false // default doesn't allow any patterns + return false; // default doesn't allow any patterns } - let filePath + let filePath; if (source[0] !== '.' && source[0] !== '/') { // a node module - filePath = source + filePath = source; } else { - filePath = path.resolve(path.dirname(filename), source) // get source absolute path + filePath = path.resolve(path.dirname(filename), source); // get source absolute path } return globs.find(glob => ( minimatch(filePath, glob) || minimatch(filePath, path.join(process.cwd(), glob)) - )) !== undefined + )) !== undefined; } function create(context) { - const options = context.options[0] || {} - const filename = context.getFilename() - const isAllow = source => testIsAllow(options.allow, filename, source) + const options = context.options[0] || {}; + const filename = context.getFilename(); + const isAllow = source => testIsAllow(options.allow, filename, source); return { ImportDeclaration(node) { if (node.specifiers.length === 0 && !isAllow(node.source.value)) { - report(context, node) + report(context, node); } }, ExpressionStatement(node) { if (node.expression.type === 'CallExpression' && isStaticRequire(node.expression) && !isAllow(node.expression.arguments[0].value)) { - report(context, node.expression) + report(context, node.expression); } }, - } + }; } module.exports = { @@ -76,4 +76,4 @@ module.exports = { }, ], }, -} +}; diff --git a/src/rules/no-unresolved.js b/src/rules/no-unresolved.js index 8436e4c92b..61dc0b6c70 100644 --- a/src/rules/no-unresolved.js +++ b/src/rules/no-unresolved.js @@ -3,10 +3,10 @@ * @author Ben Mosher */ -import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve' -import ModuleCache from 'eslint-module-utils/ModuleCache' -import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor' -import docsUrl from '../docsUrl' +import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve'; +import ModuleCache from 'eslint-module-utils/ModuleCache'; +import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'; +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -24,26 +24,26 @@ module.exports = { function checkSourceValue(source) { const shouldCheckCase = !CASE_SENSITIVE_FS && - (!context.options[0] || context.options[0].caseSensitive !== false) + (!context.options[0] || context.options[0].caseSensitive !== false); - const resolvedPath = resolve(source.value, context) + const resolvedPath = resolve(source.value, context); if (resolvedPath === undefined) { context.report(source, - `Unable to resolve path to module '${source.value}'.`) + `Unable to resolve path to module '${source.value}'.`); } else if (shouldCheckCase) { - const cacheSettings = ModuleCache.getSettings(context.settings) + const cacheSettings = ModuleCache.getSettings(context.settings); if (!fileExistsWithCaseSync(resolvedPath, cacheSettings)) { context.report(source, - `Casing of ${source.value} does not match the underlying filesystem.`) + `Casing of ${source.value} does not match the underlying filesystem.`); } } } - return moduleVisitor(checkSourceValue, context.options[0]) + return moduleVisitor(checkSourceValue, context.options[0]); }, -} +}; diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 25139b681f..99b564edab 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -4,66 +4,97 @@ * @author René Fermann */ -import Exports from '../ExportMap' -import { getFileExtensions } from 'eslint-module-utils/ignore' -import resolve from 'eslint-module-utils/resolve' -import docsUrl from '../docsUrl' -import { dirname, join } from 'path' -import readPkgUp from 'read-pkg-up' -import values from 'object.values' -import includes from 'array-includes' +import Exports, { recursivePatternCapture } from '../ExportMap'; +import { getFileExtensions } from 'eslint-module-utils/ignore'; +import resolve from 'eslint-module-utils/resolve'; +import docsUrl from '../docsUrl'; +import { dirname, join } from 'path'; +import readPkgUp from 'read-pkg-up'; +import values from 'object.values'; +import includes from 'array-includes'; // eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3 // and has been moved to eslint/lib/cli-engine/file-enumerator in version 6 -let listFilesToProcess +let listFilesToProcess; try { - const FileEnumerator = require('eslint/lib/cli-engine/file-enumerator').FileEnumerator + const FileEnumerator = require('eslint/lib/cli-engine/file-enumerator').FileEnumerator; listFilesToProcess = function (src, extensions) { const e = new FileEnumerator({ extensions: extensions, - }) + }); return Array.from(e.iterateFiles(src), ({ filePath, ignored }) => ({ ignored, filename: filePath, - })) - } + })); + }; } catch (e1) { // Prevent passing invalid options (extensions array) to old versions of the function. // https://github.com/eslint/eslint/blob/v5.16.0/lib/util/glob-utils.js#L178-L280 // https://github.com/eslint/eslint/blob/v5.2.0/lib/util/glob-util.js#L174-L269 - let originalListFilesToProcess + let originalListFilesToProcess; try { - originalListFilesToProcess = require('eslint/lib/util/glob-utils').listFilesToProcess + originalListFilesToProcess = require('eslint/lib/util/glob-utils').listFilesToProcess; listFilesToProcess = function (src, extensions) { return originalListFilesToProcess(src, { extensions: extensions, - }) - } + }); + }; } catch (e2) { - originalListFilesToProcess = require('eslint/lib/util/glob-util').listFilesToProcess + originalListFilesToProcess = require('eslint/lib/util/glob-util').listFilesToProcess; listFilesToProcess = function (src, extensions) { const patterns = src.reduce((carry, pattern) => { return carry.concat(extensions.map((extension) => { - return /\*\*|\*\./.test(pattern) ? pattern : `${pattern}/**/*${extension}` - })) - }, src.slice()) + return /\*\*|\*\./.test(pattern) ? pattern : `${pattern}/**/*${extension}`; + })); + }, src.slice()); - return originalListFilesToProcess(patterns) - } + return originalListFilesToProcess(patterns); + }; } } -const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration' -const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration' -const EXPORT_ALL_DECLARATION = 'ExportAllDeclaration' -const IMPORT_DECLARATION = 'ImportDeclaration' -const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier' -const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier' -const VARIABLE_DECLARATION = 'VariableDeclaration' -const FUNCTION_DECLARATION = 'FunctionDeclaration' -const CLASS_DECLARATION = 'ClassDeclaration' -const DEFAULT = 'default' +const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration'; +const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration'; +const EXPORT_ALL_DECLARATION = 'ExportAllDeclaration'; +const IMPORT_DECLARATION = 'ImportDeclaration'; +const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier'; +const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier'; +const VARIABLE_DECLARATION = 'VariableDeclaration'; +const FUNCTION_DECLARATION = 'FunctionDeclaration'; +const CLASS_DECLARATION = 'ClassDeclaration'; +const IDENTIFIER = 'Identifier'; +const OBJECT_PATTERN = 'ObjectPattern'; +const TS_INTERFACE_DECLARATION = 'TSInterfaceDeclaration'; +const TS_TYPE_ALIAS_DECLARATION = 'TSTypeAliasDeclaration'; +const TS_ENUM_DECLARATION = 'TSEnumDeclaration'; +const DEFAULT = 'default'; + +function forEachDeclarationIdentifier(declaration, cb) { + if (declaration) { + if ( + declaration.type === FUNCTION_DECLARATION || + declaration.type === CLASS_DECLARATION || + declaration.type === TS_INTERFACE_DECLARATION || + declaration.type === TS_TYPE_ALIAS_DECLARATION || + declaration.type === TS_ENUM_DECLARATION + ) { + cb(declaration.id.name); + } else if (declaration.type === VARIABLE_DECLARATION) { + declaration.declarations.forEach(({ id }) => { + if (id.type === OBJECT_PATTERN) { + recursivePatternCapture(id, (pattern) => { + if (pattern.type === IDENTIFIER) { + cb(pattern.name); + } + }); + } else { + cb(id.name); + } + }); + } + } +} /** * List of imports per file. @@ -84,7 +115,7 @@ const DEFAULT = 'default' * * @type {Map>>} */ -const importList = new Map() +const importList = new Map(); /** * List of exports per file. @@ -93,7 +124,7 @@ const importList = new Map() * keys are the paths to the modules containing the exports, while the * lower-level Map keys are the specific identifiers or special AST node names * being exported. The leaf-level metadata object at the moment only contains a - * `whereUsed` propoerty, which contains a Set of paths to modules that import + * `whereUsed` property, which contains a Set of paths to modules that import * the name. * * For example, if we have a file named bar.js containing the following exports: @@ -111,14 +142,14 @@ const importList = new Map() * * @type {Map>} */ -const exportList = new Map() +const exportList = new Map(); -const ignoredFiles = new Set() -const filesOutsideSrc = new Set() +const ignoredFiles = new Set(); +const filesOutsideSrc = new Set(); const isNodeModule = path => { - return /\/(node_modules)\//.test(path) -} + return /\/(node_modules)\//.test(path); +}; /** * read all files matching the patterns in src and ignoreExports @@ -126,109 +157,107 @@ const isNodeModule = path => { * return all files matching src pattern, which are not matching the ignoreExports pattern */ const resolveFiles = (src, ignoreExports, context) => { - const extensions = Array.from(getFileExtensions(context.settings)) + const extensions = Array.from(getFileExtensions(context.settings)); - const srcFiles = new Set() - const srcFileList = listFilesToProcess(src, extensions) + const srcFiles = new Set(); + const srcFileList = listFilesToProcess(src, extensions); // prepare list of ignored files - const ignoredFilesList = listFilesToProcess(ignoreExports, extensions) - ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename)) + const ignoredFilesList = listFilesToProcess(ignoreExports, extensions); + ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename)); // prepare list of source files, don't consider files from node_modules srcFileList.filter(({ filename }) => !isNodeModule(filename)).forEach(({ filename }) => { - srcFiles.add(filename) - }) - return srcFiles -} + srcFiles.add(filename); + }); + return srcFiles; +}; /** * parse all source files and build up 2 maps containing the existing imports and exports */ const prepareImportsAndExports = (srcFiles, context) => { - const exportAll = new Map() + const exportAll = new Map(); srcFiles.forEach(file => { - const exports = new Map() - const imports = new Map() - const currentExports = Exports.get(file, context) + const exports = new Map(); + const imports = new Map(); + const currentExports = Exports.get(file, context); if (currentExports) { - const { dependencies, reexports, imports: localImportList, namespace } = currentExports + const { dependencies, reexports, imports: localImportList, namespace } = currentExports; // dependencies === export * from - const currentExportAll = new Set() + const currentExportAll = new Set(); dependencies.forEach(getDependency => { - const dependency = getDependency() + const dependency = getDependency(); if (dependency === null) { - return + return; } - currentExportAll.add(dependency.path) - }) - exportAll.set(file, currentExportAll) + currentExportAll.add(dependency.path); + }); + exportAll.set(file, currentExportAll); reexports.forEach((value, key) => { if (key === DEFAULT) { - exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() }) + exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() }); } else { - exports.set(key, { whereUsed: new Set() }) + exports.set(key, { whereUsed: new Set() }); } - const reexport = value.getImport() + const reexport = value.getImport(); if (!reexport) { - return + return; } - let localImport = imports.get(reexport.path) - let currentValue + let localImport = imports.get(reexport.path); + let currentValue; if (value.local === DEFAULT) { - currentValue = IMPORT_DEFAULT_SPECIFIER + currentValue = IMPORT_DEFAULT_SPECIFIER; } else { - currentValue = value.local + currentValue = value.local; } if (typeof localImport !== 'undefined') { - localImport = new Set([...localImport, currentValue]) + localImport = new Set([...localImport, currentValue]); } else { - localImport = new Set([currentValue]) + localImport = new Set([currentValue]); } - imports.set(reexport.path, localImport) - }) + imports.set(reexport.path, localImport); + }); localImportList.forEach((value, key) => { if (isNodeModule(key)) { - return + return; } - 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) + const localImport = imports.get(key) || new Set(); + value.declarations.forEach(({ importedSpecifiers }) => + importedSpecifiers.forEach(specifier => localImport.add(specifier)) + ); + imports.set(key, localImport); + }); + importList.set(file, imports); // build up export list only, if file is not ignored if (ignoredFiles.has(file)) { - return + return; } namespace.forEach((value, key) => { if (key === DEFAULT) { - exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() }) + exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() }); } else { - exports.set(key, { whereUsed: new Set() }) + exports.set(key, { whereUsed: new Set() }); } - }) + }); } - exports.set(EXPORT_ALL_DECLARATION, { whereUsed: new Set() }) - exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed: new Set() }) - exportList.set(file, exports) - }) + exports.set(EXPORT_ALL_DECLARATION, { whereUsed: new Set() }); + exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed: new Set() }); + exportList.set(file, exports); + }); exportAll.forEach((value, key) => { value.forEach(val => { - const currentExports = exportList.get(val) - const currentExport = currentExports.get(EXPORT_ALL_DECLARATION) - currentExport.whereUsed.add(key) - }) - }) -} + const currentExports = exportList.get(val); + const currentExport = currentExports.get(EXPORT_ALL_DECLARATION); + currentExport.whereUsed.add(key); + }); + }); +}; /** * traverse through all imports and add the respective path to the whereUsed-list @@ -237,122 +266,122 @@ const prepareImportsAndExports = (srcFiles, context) => { const determineUsage = () => { importList.forEach((listValue, listKey) => { listValue.forEach((value, key) => { - const exports = exportList.get(key) + const exports = exportList.get(key); if (typeof exports !== 'undefined') { value.forEach(currentImport => { - let specifier + let specifier; if (currentImport === IMPORT_NAMESPACE_SPECIFIER) { - specifier = IMPORT_NAMESPACE_SPECIFIER + specifier = IMPORT_NAMESPACE_SPECIFIER; } else if (currentImport === IMPORT_DEFAULT_SPECIFIER) { - specifier = IMPORT_DEFAULT_SPECIFIER + specifier = IMPORT_DEFAULT_SPECIFIER; } else { - specifier = currentImport + specifier = currentImport; } if (typeof specifier !== 'undefined') { - const exportStatement = exports.get(specifier) + const exportStatement = exports.get(specifier); if (typeof exportStatement !== 'undefined') { - const { whereUsed } = exportStatement - whereUsed.add(listKey) - exports.set(specifier, { whereUsed }) + const { whereUsed } = exportStatement; + whereUsed.add(listKey); + exports.set(specifier, { whereUsed }); } } - }) + }); } - }) - }) -} + }); + }); +}; const getSrc = src => { if (src) { - return src + return src; } - return [process.cwd()] -} + return [process.cwd()]; +}; /** * prepare the lists of existing imports and exports - should only be executed once at * the start of a new eslint run */ -let srcFiles -let lastPrepareKey +let srcFiles; +let lastPrepareKey; const doPreparation = (src, ignoreExports, context) => { const prepareKey = JSON.stringify({ src: (src || []).sort(), ignoreExports: (ignoreExports || []).sort(), extensions: Array.from(getFileExtensions(context.settings)).sort(), - }) + }); if (prepareKey === lastPrepareKey) { - return + return; } - importList.clear() - exportList.clear() - ignoredFiles.clear() - filesOutsideSrc.clear() + importList.clear(); + exportList.clear(); + ignoredFiles.clear(); + filesOutsideSrc.clear(); - srcFiles = resolveFiles(getSrc(src), ignoreExports, context) - prepareImportsAndExports(srcFiles, context) - determineUsage() - lastPrepareKey = prepareKey -} + srcFiles = resolveFiles(getSrc(src), ignoreExports, context); + prepareImportsAndExports(srcFiles, context); + determineUsage(); + lastPrepareKey = prepareKey; +}; const newNamespaceImportExists = specifiers => - specifiers.some(({ type }) => type === IMPORT_NAMESPACE_SPECIFIER) + specifiers.some(({ type }) => type === IMPORT_NAMESPACE_SPECIFIER); const newDefaultImportExists = specifiers => - specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER) + specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER); const fileIsInPkg = file => { - const { path, pkg } = readPkgUp.sync({cwd: file, normalize: false}) - const basePath = dirname(path) + const { path, pkg } = readPkgUp.sync({ cwd: file, normalize: false }); + const basePath = dirname(path); const checkPkgFieldString = pkgField => { if (join(basePath, pkgField) === file) { - return true - } - } + return true; + } + }; const checkPkgFieldObject = pkgField => { - const pkgFieldFiles = values(pkgField).map(value => join(basePath, value)) - if (includes(pkgFieldFiles, file)) { - return true - } - } + const pkgFieldFiles = values(pkgField).map(value => join(basePath, value)); + if (includes(pkgFieldFiles, file)) { + return true; + } + }; const checkPkgField = pkgField => { if (typeof pkgField === 'string') { - return checkPkgFieldString(pkgField) + return checkPkgFieldString(pkgField); } if (typeof pkgField === 'object') { - return checkPkgFieldObject(pkgField) + return checkPkgFieldObject(pkgField); } - } + }; if (pkg.private === true) { - return false + return false; } if (pkg.bin) { if (checkPkgField(pkg.bin)) { - return true + return true; } } if (pkg.browser) { if (checkPkgField(pkg.browser)) { - return true + return true; } } if (pkg.main) { if (checkPkgFieldString(pkg.main)) { - return true + return true; } } - return false -} + return false; +}; module.exports = { meta: { @@ -428,103 +457,103 @@ module.exports = { ignoreExports = [], missingExports, unusedExports, - } = context.options[0] || {} + } = context.options[0] || {}; if (unusedExports) { - doPreparation(src, ignoreExports, context) + doPreparation(src, ignoreExports, context); } - const file = context.getFilename() + const file = context.getFilename(); const checkExportPresence = node => { if (!missingExports) { - return + return; } if (ignoredFiles.has(file)) { - return + return; } - const exportCount = exportList.get(file) - const exportAll = exportCount.get(EXPORT_ALL_DECLARATION) - const namespaceImports = exportCount.get(IMPORT_NAMESPACE_SPECIFIER) + const exportCount = exportList.get(file); + const exportAll = exportCount.get(EXPORT_ALL_DECLARATION); + const namespaceImports = exportCount.get(IMPORT_NAMESPACE_SPECIFIER); - exportCount.delete(EXPORT_ALL_DECLARATION) - exportCount.delete(IMPORT_NAMESPACE_SPECIFIER) + exportCount.delete(EXPORT_ALL_DECLARATION); + exportCount.delete(IMPORT_NAMESPACE_SPECIFIER); if (exportCount.size < 1) { // node.body[0] === 'undefined' only happens, if everything is commented out in the file // being linted - context.report(node.body[0] ? node.body[0] : node, 'No exports found') + context.report(node.body[0] ? node.body[0] : node, 'No exports found'); } - exportCount.set(EXPORT_ALL_DECLARATION, exportAll) - exportCount.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports) - } + exportCount.set(EXPORT_ALL_DECLARATION, exportAll); + exportCount.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports); + }; const checkUsage = (node, exportedValue) => { if (!unusedExports) { - return + return; } if (ignoredFiles.has(file)) { - return + return; } if (fileIsInPkg(file)) { - return + return; } if (filesOutsideSrc.has(file)) { - return + return; } // make sure file to be linted is included in source files if (!srcFiles.has(file)) { - srcFiles = resolveFiles(getSrc(src), ignoreExports, context) + srcFiles = resolveFiles(getSrc(src), ignoreExports, context); if (!srcFiles.has(file)) { - filesOutsideSrc.add(file) - return + filesOutsideSrc.add(file); + return; } } - exports = exportList.get(file) + exports = exportList.get(file); // special case: export * from - const exportAll = exports.get(EXPORT_ALL_DECLARATION) + const exportAll = exports.get(EXPORT_ALL_DECLARATION); if (typeof exportAll !== 'undefined' && exportedValue !== IMPORT_DEFAULT_SPECIFIER) { if (exportAll.whereUsed.size > 0) { - return + return; } } // special case: namespace import - const namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER) + const namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER); if (typeof namespaceImports !== 'undefined') { if (namespaceImports.whereUsed.size > 0) { - return + return; } } // exportsList will always map any imported value of 'default' to 'ImportDefaultSpecifier' - const exportsKey = exportedValue === DEFAULT ? IMPORT_DEFAULT_SPECIFIER : exportedValue + const exportsKey = exportedValue === DEFAULT ? IMPORT_DEFAULT_SPECIFIER : exportedValue; - const exportStatement = exports.get(exportsKey) + const exportStatement = exports.get(exportsKey); - const value = exportsKey === IMPORT_DEFAULT_SPECIFIER ? DEFAULT : exportsKey + const value = exportsKey === IMPORT_DEFAULT_SPECIFIER ? DEFAULT : exportsKey; if (typeof exportStatement !== 'undefined'){ if (exportStatement.whereUsed.size < 1) { context.report( node, `exported declaration '${value}' not used within other modules` - ) + ); } } else { context.report( node, `exported declaration '${value}' not used within other modules` - ) + ); } - } + }; /** * only useful for tools like vscode-eslint @@ -533,74 +562,64 @@ module.exports = { */ const updateExportUsage = node => { if (ignoredFiles.has(file)) { - return + return; } - let exports = exportList.get(file) + let exports = exportList.get(file); // new module has been created during runtime // include it in further processing if (typeof exports === 'undefined') { - exports = new Map() + exports = new Map(); } - const newExports = new Map() - const newExportIdentifiers = new Set() + const newExports = new Map(); + const newExportIdentifiers = new Set(); node.body.forEach(({ type, declaration, specifiers }) => { if (type === EXPORT_DEFAULT_DECLARATION) { - newExportIdentifiers.add(IMPORT_DEFAULT_SPECIFIER) + newExportIdentifiers.add(IMPORT_DEFAULT_SPECIFIER); } if (type === EXPORT_NAMED_DECLARATION) { if (specifiers.length > 0) { specifiers.forEach(specifier => { if (specifier.exported) { - newExportIdentifiers.add(specifier.exported.name) + newExportIdentifiers.add(specifier.exported.name); } - }) - } - if (declaration) { - if ( - declaration.type === FUNCTION_DECLARATION || - declaration.type === CLASS_DECLARATION - ) { - newExportIdentifiers.add(declaration.id.name) - } - if (declaration.type === VARIABLE_DECLARATION) { - declaration.declarations.forEach(({ id }) => { - newExportIdentifiers.add(id.name) - }) - } + }); } + forEachDeclarationIdentifier(declaration, (name) => { + newExportIdentifiers.add(name); + }); } - }) + }); // old exports exist within list of new exports identifiers: add to map of new exports exports.forEach((value, key) => { if (newExportIdentifiers.has(key)) { - newExports.set(key, value) + newExports.set(key, value); } - }) + }); // new export identifiers added: add to map of new exports newExportIdentifiers.forEach(key => { if (!exports.has(key)) { - newExports.set(key, { whereUsed: new Set() }) + newExports.set(key, { whereUsed: new Set() }); } - }) + }); // preserve information about namespace imports - let exportAll = exports.get(EXPORT_ALL_DECLARATION) - let namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER) + const exportAll = exports.get(EXPORT_ALL_DECLARATION); + let namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER); if (typeof namespaceImports === 'undefined') { - namespaceImports = { whereUsed: new Set() } + namespaceImports = { whereUsed: new Set() }; } - newExports.set(EXPORT_ALL_DECLARATION, exportAll) - newExports.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports) - exportList.set(file, newExports) - } + newExports.set(EXPORT_ALL_DECLARATION, exportAll); + newExports.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports); + exportList.set(file, newExports); + }; /** * only useful for tools like vscode-eslint @@ -609,294 +628,284 @@ module.exports = { */ const updateImportUsage = node => { if (!unusedExports) { - return + return; } - let oldImportPaths = importList.get(file) + let oldImportPaths = importList.get(file); if (typeof oldImportPaths === 'undefined') { - oldImportPaths = new Map() + oldImportPaths = new Map(); } - const oldNamespaceImports = new Set() - const newNamespaceImports = new Set() + const oldNamespaceImports = new Set(); + const newNamespaceImports = new Set(); - const oldExportAll = new Set() - const newExportAll = new Set() + const oldExportAll = new Set(); + const newExportAll = new Set(); - const oldDefaultImports = new Set() - const newDefaultImports = new Set() + const oldDefaultImports = new Set(); + const newDefaultImports = new Set(); - const oldImports = new Map() - const newImports = new Map() + const oldImports = new Map(); + const newImports = new Map(); oldImportPaths.forEach((value, key) => { if (value.has(EXPORT_ALL_DECLARATION)) { - oldExportAll.add(key) + oldExportAll.add(key); } if (value.has(IMPORT_NAMESPACE_SPECIFIER)) { - oldNamespaceImports.add(key) + oldNamespaceImports.add(key); } if (value.has(IMPORT_DEFAULT_SPECIFIER)) { - oldDefaultImports.add(key) + oldDefaultImports.add(key); } value.forEach(val => { if (val !== IMPORT_NAMESPACE_SPECIFIER && val !== IMPORT_DEFAULT_SPECIFIER) { - oldImports.set(val, key) - } - }) - }) + oldImports.set(val, key); + } + }); + }); node.body.forEach(astNode => { - let resolvedPath + let resolvedPath; // support for export { value } from 'module' if (astNode.type === EXPORT_NAMED_DECLARATION) { if (astNode.source) { - resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context) + resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context); astNode.specifiers.forEach(specifier => { - const name = specifier.local.name + const name = specifier.local.name; if (specifier.local.name === DEFAULT) { - newDefaultImports.add(resolvedPath) + newDefaultImports.add(resolvedPath); } else { - newImports.set(name, resolvedPath) + newImports.set(name, resolvedPath); } - }) + }); } } if (astNode.type === EXPORT_ALL_DECLARATION) { - resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context) - newExportAll.add(resolvedPath) + resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context); + newExportAll.add(resolvedPath); } if (astNode.type === IMPORT_DECLARATION) { - resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context) + resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context); if (!resolvedPath) { - return + return; } if (isNodeModule(resolvedPath)) { - return + return; } if (newNamespaceImportExists(astNode.specifiers)) { - newNamespaceImports.add(resolvedPath) + newNamespaceImports.add(resolvedPath); } if (newDefaultImportExists(astNode.specifiers)) { - newDefaultImports.add(resolvedPath) + newDefaultImports.add(resolvedPath); } astNode.specifiers.forEach(specifier => { if (specifier.type === IMPORT_DEFAULT_SPECIFIER || specifier.type === IMPORT_NAMESPACE_SPECIFIER) { - return + return; } - newImports.set(specifier.imported.name, resolvedPath) - }) + newImports.set(specifier.imported.name, resolvedPath); + }); } - }) + }); newExportAll.forEach(value => { if (!oldExportAll.has(value)) { - let imports = oldImportPaths.get(value) + let imports = oldImportPaths.get(value); if (typeof imports === 'undefined') { - imports = new Set() + imports = new Set(); } - imports.add(EXPORT_ALL_DECLARATION) - oldImportPaths.set(value, imports) + imports.add(EXPORT_ALL_DECLARATION); + oldImportPaths.set(value, imports); - let exports = exportList.get(value) - let currentExport + let exports = exportList.get(value); + let currentExport; if (typeof exports !== 'undefined') { - currentExport = exports.get(EXPORT_ALL_DECLARATION) + currentExport = exports.get(EXPORT_ALL_DECLARATION); } else { - exports = new Map() - exportList.set(value, exports) + exports = new Map(); + exportList.set(value, exports); } if (typeof currentExport !== 'undefined') { - currentExport.whereUsed.add(file) + currentExport.whereUsed.add(file); } else { - const whereUsed = new Set() - whereUsed.add(file) - exports.set(EXPORT_ALL_DECLARATION, { whereUsed }) + const whereUsed = new Set(); + whereUsed.add(file); + exports.set(EXPORT_ALL_DECLARATION, { whereUsed }); } } - }) + }); oldExportAll.forEach(value => { if (!newExportAll.has(value)) { - const imports = oldImportPaths.get(value) - imports.delete(EXPORT_ALL_DECLARATION) + const imports = oldImportPaths.get(value); + imports.delete(EXPORT_ALL_DECLARATION); - const exports = exportList.get(value) + const exports = exportList.get(value); if (typeof exports !== 'undefined') { - const currentExport = exports.get(EXPORT_ALL_DECLARATION) + const currentExport = exports.get(EXPORT_ALL_DECLARATION); if (typeof currentExport !== 'undefined') { - currentExport.whereUsed.delete(file) + currentExport.whereUsed.delete(file); } } } - }) + }); newDefaultImports.forEach(value => { if (!oldDefaultImports.has(value)) { - let imports = oldImportPaths.get(value) + let imports = oldImportPaths.get(value); if (typeof imports === 'undefined') { - imports = new Set() + imports = new Set(); } - imports.add(IMPORT_DEFAULT_SPECIFIER) - oldImportPaths.set(value, imports) + imports.add(IMPORT_DEFAULT_SPECIFIER); + oldImportPaths.set(value, imports); - let exports = exportList.get(value) - let currentExport + let exports = exportList.get(value); + let currentExport; if (typeof exports !== 'undefined') { - currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER) + currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER); } else { - exports = new Map() - exportList.set(value, exports) + exports = new Map(); + exportList.set(value, exports); } if (typeof currentExport !== 'undefined') { - currentExport.whereUsed.add(file) + currentExport.whereUsed.add(file); } else { - const whereUsed = new Set() - whereUsed.add(file) - exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed }) + const whereUsed = new Set(); + whereUsed.add(file); + exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed }); } } - }) + }); oldDefaultImports.forEach(value => { if (!newDefaultImports.has(value)) { - const imports = oldImportPaths.get(value) - imports.delete(IMPORT_DEFAULT_SPECIFIER) + const imports = oldImportPaths.get(value); + imports.delete(IMPORT_DEFAULT_SPECIFIER); - const exports = exportList.get(value) + const exports = exportList.get(value); if (typeof exports !== 'undefined') { - const currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER) + const currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER); if (typeof currentExport !== 'undefined') { - currentExport.whereUsed.delete(file) + currentExport.whereUsed.delete(file); } } } - }) + }); newNamespaceImports.forEach(value => { if (!oldNamespaceImports.has(value)) { - let imports = oldImportPaths.get(value) + let imports = oldImportPaths.get(value); if (typeof imports === 'undefined') { - imports = new Set() + imports = new Set(); } - imports.add(IMPORT_NAMESPACE_SPECIFIER) - oldImportPaths.set(value, imports) + imports.add(IMPORT_NAMESPACE_SPECIFIER); + oldImportPaths.set(value, imports); - let exports = exportList.get(value) - let currentExport + let exports = exportList.get(value); + let currentExport; if (typeof exports !== 'undefined') { - currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER) + currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER); } else { - exports = new Map() - exportList.set(value, exports) + exports = new Map(); + exportList.set(value, exports); } if (typeof currentExport !== 'undefined') { - currentExport.whereUsed.add(file) + currentExport.whereUsed.add(file); } else { - const whereUsed = new Set() - whereUsed.add(file) - exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed }) + const whereUsed = new Set(); + whereUsed.add(file); + exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed }); } } - }) + }); oldNamespaceImports.forEach(value => { if (!newNamespaceImports.has(value)) { - const imports = oldImportPaths.get(value) - imports.delete(IMPORT_NAMESPACE_SPECIFIER) + const imports = oldImportPaths.get(value); + imports.delete(IMPORT_NAMESPACE_SPECIFIER); - const exports = exportList.get(value) + const exports = exportList.get(value); if (typeof exports !== 'undefined') { - const currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER) + const currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER); if (typeof currentExport !== 'undefined') { - currentExport.whereUsed.delete(file) + currentExport.whereUsed.delete(file); } } } - }) + }); newImports.forEach((value, key) => { if (!oldImports.has(key)) { - let imports = oldImportPaths.get(value) + let imports = oldImportPaths.get(value); if (typeof imports === 'undefined') { - imports = new Set() + imports = new Set(); } - imports.add(key) - oldImportPaths.set(value, imports) + imports.add(key); + oldImportPaths.set(value, imports); - let exports = exportList.get(value) - let currentExport + let exports = exportList.get(value); + let currentExport; if (typeof exports !== 'undefined') { - currentExport = exports.get(key) + currentExport = exports.get(key); } else { - exports = new Map() - exportList.set(value, exports) + exports = new Map(); + exportList.set(value, exports); } if (typeof currentExport !== 'undefined') { - currentExport.whereUsed.add(file) + currentExport.whereUsed.add(file); } else { - const whereUsed = new Set() - whereUsed.add(file) - exports.set(key, { whereUsed }) + const whereUsed = new Set(); + whereUsed.add(file); + exports.set(key, { whereUsed }); } } - }) + }); oldImports.forEach((value, key) => { if (!newImports.has(key)) { - const imports = oldImportPaths.get(value) - imports.delete(key) + const imports = oldImportPaths.get(value); + imports.delete(key); - const exports = exportList.get(value) + const exports = exportList.get(value); if (typeof exports !== 'undefined') { - const currentExport = exports.get(key) + const currentExport = exports.get(key); if (typeof currentExport !== 'undefined') { - currentExport.whereUsed.delete(file) + currentExport.whereUsed.delete(file); } } } - }) - } + }); + }; return { 'Program:exit': node => { - updateExportUsage(node) - updateImportUsage(node) - checkExportPresence(node) + updateExportUsage(node); + updateImportUsage(node); + checkExportPresence(node); }, 'ExportDefaultDeclaration': node => { - checkUsage(node, IMPORT_DEFAULT_SPECIFIER) + checkUsage(node, IMPORT_DEFAULT_SPECIFIER); }, 'ExportNamedDeclaration': node => { node.specifiers.forEach(specifier => { - checkUsage(node, specifier.exported.name) - }) - if (node.declaration) { - if ( - node.declaration.type === FUNCTION_DECLARATION || - node.declaration.type === CLASS_DECLARATION - ) { - checkUsage(node, node.declaration.id.name) - } - if (node.declaration.type === VARIABLE_DECLARATION) { - node.declaration.declarations.forEach(declaration => { - checkUsage(node, declaration.id.name) - }) - } - } + checkUsage(node, specifier.exported.name); + }); + forEachDeclarationIdentifier(node.declaration, (name) => { + checkUsage(node, name); + }); }, - } + }; }, -} +}; diff --git a/src/rules/no-useless-path-segments.js b/src/rules/no-useless-path-segments.js index 785b98f0d6..b22aa94788 100644 --- a/src/rules/no-useless-path-segments.js +++ b/src/rules/no-useless-path-segments.js @@ -3,11 +3,11 @@ * @author Thomas Grainger */ -import { getFileExtensions } from 'eslint-module-utils/ignore' -import moduleVisitor from 'eslint-module-utils/moduleVisitor' -import resolve from 'eslint-module-utils/resolve' -import path from 'path' -import docsUrl from '../docsUrl' +import { getFileExtensions } from 'eslint-module-utils/ignore'; +import moduleVisitor from 'eslint-module-utils/moduleVisitor'; +import resolve from 'eslint-module-utils/resolve'; +import path from 'path'; +import docsUrl from '../docsUrl'; /** * convert a potentially relative path from node utils into a true @@ -23,17 +23,17 @@ import docsUrl from '../docsUrl' * @returns {string} relative posix path that always starts with a ./ **/ function toRelativePath(relativePath) { - const stripped = relativePath.replace(/\/$/g, '') // Remove trailing / + const stripped = relativePath.replace(/\/$/g, ''); // Remove trailing / - return /^((\.\.)|(\.))($|\/)/.test(stripped) ? stripped : `./${stripped}` + return /^((\.\.)|(\.))($|\/)/.test(stripped) ? stripped : `./${stripped}`; } function normalize(fn) { - return toRelativePath(path.posix.normalize(fn)) + return toRelativePath(path.posix.normalize(fn)); } function countRelativeParents(pathSegments) { - return pathSegments.reduce((sum, pathSegment) => pathSegment === '..' ? sum + 1 : sum, 0) + return pathSegments.reduce((sum, pathSegment) => pathSegment === '..' ? sum + 1 : sum, 0); } module.exports = { @@ -58,11 +58,11 @@ module.exports = { }, create(context) { - const currentDir = path.dirname(context.getFilename()) - const options = context.options[0] + const currentDir = path.dirname(context.getFilename()); + const options = context.options[0]; function checkSourceValue(source) { - const { value: importPath } = source + const { value: importPath } = source; function reportWithProposedPath(proposedPath) { context.report({ @@ -70,63 +70,63 @@ module.exports = { // Note: Using messageIds is not possible due to the support for ESLint 2 and 3 message: `Useless path segments for "${importPath}", should be "${proposedPath}"`, fix: fixer => proposedPath && fixer.replaceText(source, JSON.stringify(proposedPath)), - }) + }); } // Only relative imports are relevant for this rule --> Skip checking if (!importPath.startsWith('.')) { - return + return; } // Report rule violation if path is not the shortest possible - const resolvedPath = resolve(importPath, context) - const normedPath = normalize(importPath) - const resolvedNormedPath = resolve(normedPath, context) + const resolvedPath = resolve(importPath, context); + const normedPath = normalize(importPath); + const resolvedNormedPath = resolve(normedPath, context); if (normedPath !== importPath && resolvedPath === resolvedNormedPath) { - return reportWithProposedPath(normedPath) + return reportWithProposedPath(normedPath); } - const fileExtensions = getFileExtensions(context.settings) + const fileExtensions = getFileExtensions(context.settings); const regexUnnecessaryIndex = new RegExp( `.*\\/index(\\${Array.from(fileExtensions).join('|\\')})?$` - ) + ); // Check if path contains unnecessary index (including a configured extension) if (options && options.noUselessIndex && regexUnnecessaryIndex.test(importPath)) { - const parentDirectory = path.dirname(importPath) + const parentDirectory = path.dirname(importPath); // Try to find ambiguous imports if (parentDirectory !== '.' && parentDirectory !== '..') { - for (let fileExtension of fileExtensions) { + for (const fileExtension of fileExtensions) { if (resolve(`${parentDirectory}${fileExtension}`, context)) { - return reportWithProposedPath(`${parentDirectory}/`) + return reportWithProposedPath(`${parentDirectory}/`); } } } - return reportWithProposedPath(parentDirectory) + return reportWithProposedPath(parentDirectory); } // Path is shortest possible + starts from the current directory --> Return directly if (importPath.startsWith('./')) { - return + return; } // Path is not existing --> Return directly (following code requires path to be defined) if (resolvedPath === undefined) { - return + return; } - const expected = path.relative(currentDir, resolvedPath) // Expected import path - const expectedSplit = expected.split(path.sep) // Split by / or \ (depending on OS) - const importPathSplit = importPath.replace(/^\.\//, '').split('/') - const countImportPathRelativeParents = countRelativeParents(importPathSplit) - const countExpectedRelativeParents = countRelativeParents(expectedSplit) - const diff = countImportPathRelativeParents - countExpectedRelativeParents + const expected = path.relative(currentDir, resolvedPath); // Expected import path + const expectedSplit = expected.split(path.sep); // Split by / or \ (depending on OS) + const importPathSplit = importPath.replace(/^\.\//, '').split('/'); + const countImportPathRelativeParents = countRelativeParents(importPathSplit); + const countExpectedRelativeParents = countRelativeParents(expectedSplit); + const diff = countImportPathRelativeParents - countExpectedRelativeParents; // Same number of relative parents --> Paths are the same --> Return directly if (diff <= 0) { - return + return; } // Report and propose minimal number of required relative parents @@ -137,9 +137,9 @@ module.exports = { .concat(importPathSplit.slice(countImportPathRelativeParents + diff)) .join('/') ) - ) + ); } - return moduleVisitor(checkSourceValue, options) + return moduleVisitor(checkSourceValue, options); }, -} +}; diff --git a/src/rules/no-webpack-loader-syntax.js b/src/rules/no-webpack-loader-syntax.js index 8075a6f9eb..b3228e0d28 100644 --- a/src/rules/no-webpack-loader-syntax.js +++ b/src/rules/no-webpack-loader-syntax.js @@ -1,11 +1,11 @@ -import isStaticRequire from '../core/staticRequire' -import docsUrl from '../docsUrl' +import moduleVisitor from 'eslint-module-utils/moduleVisitor'; +import docsUrl from '../docsUrl'; function reportIfNonStandard(context, node, name) { - if (name.indexOf('!') !== -1) { + if (name && name.indexOf('!') !== -1) { context.report(node, `Unexpected '!' in '${name}'. ` + 'Do not use import syntax to configure webpack loaders.' - ) + ); } } @@ -19,15 +19,8 @@ module.exports = { }, create: function (context) { - return { - ImportDeclaration: function handleImports(node) { - reportIfNonStandard(context, node, node.source.value) - }, - CallExpression: function handleRequires(node) { - if (isStaticRequire(node)) { - reportIfNonStandard(context, node, node.arguments[0].value) - } - }, - } + return moduleVisitor((source, node) => { + reportIfNonStandard(context, node, source.value); + }, { commonjs: true }); }, -} +}; diff --git a/src/rules/order.js b/src/rules/order.js index b407145405..ce34604c64 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -1,146 +1,142 @@ -'use strict' +'use strict'; -import minimatch from 'minimatch' -import importType from '../core/importType' -import isStaticRequire from '../core/staticRequire' -import docsUrl from '../docsUrl' +import minimatch from 'minimatch'; +import importType from '../core/importType'; +import isStaticRequire from '../core/staticRequire'; +import docsUrl from '../docsUrl'; -const defaultGroups = ['builtin', 'external', 'parent', 'sibling', 'index'] +const defaultGroups = ['builtin', 'external', 'parent', 'sibling', 'index']; // REPORTING AND FIXING function reverse(array) { return array.map(function (v) { - return { - name: v.name, - rank: -v.rank, - node: v.node, - } - }).reverse() + return Object.assign({}, v, { rank: -v.rank }); + }).reverse(); } function getTokensOrCommentsAfter(sourceCode, node, count) { - let currentNodeOrToken = node - const result = [] + let currentNodeOrToken = node; + const result = []; for (let i = 0; i < count; i++) { - currentNodeOrToken = sourceCode.getTokenOrCommentAfter(currentNodeOrToken) + currentNodeOrToken = sourceCode.getTokenOrCommentAfter(currentNodeOrToken); if (currentNodeOrToken == null) { - break + break; } - result.push(currentNodeOrToken) + result.push(currentNodeOrToken); } - return result + return result; } function getTokensOrCommentsBefore(sourceCode, node, count) { - let currentNodeOrToken = node - const result = [] + let currentNodeOrToken = node; + const result = []; for (let i = 0; i < count; i++) { - currentNodeOrToken = sourceCode.getTokenOrCommentBefore(currentNodeOrToken) + currentNodeOrToken = sourceCode.getTokenOrCommentBefore(currentNodeOrToken); if (currentNodeOrToken == null) { - break + break; } - result.push(currentNodeOrToken) + result.push(currentNodeOrToken); } - return result.reverse() + return result.reverse(); } function takeTokensAfterWhile(sourceCode, node, condition) { - const tokens = getTokensOrCommentsAfter(sourceCode, node, 100) - const result = [] + const tokens = getTokensOrCommentsAfter(sourceCode, node, 100); + const result = []; for (let i = 0; i < tokens.length; i++) { if (condition(tokens[i])) { - result.push(tokens[i]) + result.push(tokens[i]); } else { - break + break; } } - return result + return result; } function takeTokensBeforeWhile(sourceCode, node, condition) { - const tokens = getTokensOrCommentsBefore(sourceCode, node, 100) - const result = [] + const tokens = getTokensOrCommentsBefore(sourceCode, node, 100); + const result = []; for (let i = tokens.length - 1; i >= 0; i--) { if (condition(tokens[i])) { - result.push(tokens[i]) + result.push(tokens[i]); } else { - break + break; } } - return result.reverse() + return result.reverse(); } function findOutOfOrder(imported) { if (imported.length === 0) { - return [] + return []; } - let maxSeenRankNode = imported[0] + let maxSeenRankNode = imported[0]; return imported.filter(function (importedModule) { - const res = importedModule.rank < maxSeenRankNode.rank + const res = importedModule.rank < maxSeenRankNode.rank; if (maxSeenRankNode.rank < importedModule.rank) { - maxSeenRankNode = importedModule + maxSeenRankNode = importedModule; } - return res - }) + return res; + }); } function findRootNode(node) { - let parent = node + let parent = node; while (parent.parent != null && parent.parent.body == null) { - parent = parent.parent + parent = parent.parent; } - return parent + return parent; } function findEndOfLineWithComments(sourceCode, node) { - const tokensToEndOfLine = takeTokensAfterWhile(sourceCode, node, commentOnSameLineAs(node)) - let endOfTokens = tokensToEndOfLine.length > 0 + const tokensToEndOfLine = takeTokensAfterWhile(sourceCode, node, commentOnSameLineAs(node)); + const endOfTokens = tokensToEndOfLine.length > 0 ? tokensToEndOfLine[tokensToEndOfLine.length - 1].range[1] - : node.range[1] - let result = endOfTokens + : node.range[1]; + let result = endOfTokens; for (let i = endOfTokens; i < sourceCode.text.length; i++) { if (sourceCode.text[i] === '\n') { - result = i + 1 - break + result = i + 1; + break; } if (sourceCode.text[i] !== ' ' && sourceCode.text[i] !== '\t' && sourceCode.text[i] !== '\r') { - break + break; } - result = i + 1 + result = i + 1; } - return result + return result; } function commentOnSameLineAs(node) { return token => (token.type === 'Block' || token.type === 'Line') && token.loc.start.line === token.loc.end.line && - token.loc.end.line === node.loc.end.line + token.loc.end.line === node.loc.end.line; } function findStartOfLineWithComments(sourceCode, node) { - const tokensToEndOfLine = takeTokensBeforeWhile(sourceCode, node, commentOnSameLineAs(node)) - let startOfTokens = tokensToEndOfLine.length > 0 ? tokensToEndOfLine[0].range[0] : node.range[0] - let result = startOfTokens + const tokensToEndOfLine = takeTokensBeforeWhile(sourceCode, node, commentOnSameLineAs(node)); + const startOfTokens = tokensToEndOfLine.length > 0 ? tokensToEndOfLine[0].range[0] : node.range[0]; + let result = startOfTokens; for (let i = startOfTokens - 1; i > 0; i--) { if (sourceCode.text[i] !== ' ' && sourceCode.text[i] !== '\t') { - break + break; } - result = i + result = i; } - return result + return result; } function isPlainRequireModule(node) { if (node.type !== 'VariableDeclaration') { - return false + return false; } if (node.declarations.length !== 1) { - return false + return false; } - const decl = node.declarations[0] + const decl = node.declarations[0]; const result = decl.id && (decl.id.type === 'Identifier' || decl.id.type === 'ObjectPattern') && decl.init != null && @@ -149,56 +145,55 @@ function isPlainRequireModule(node) { decl.init.callee.name === 'require' && decl.init.arguments != null && decl.init.arguments.length === 1 && - decl.init.arguments[0].type === 'Literal' - return result + decl.init.arguments[0].type === 'Literal'; + return result; } function isPlainImportModule(node) { - return node.type === 'ImportDeclaration' && node.specifiers != null && node.specifiers.length > 0 + return node.type === 'ImportDeclaration' && node.specifiers != null && node.specifiers.length > 0; } function isPlainImportEquals(node) { - return node.type === 'TSImportEqualsDeclaration' && node.moduleReference.expression + return node.type === 'TSImportEqualsDeclaration' && node.moduleReference.expression; } function canCrossNodeWhileReorder(node) { - return isPlainRequireModule(node) || isPlainImportModule(node) || isPlainImportEquals(node) + return isPlainRequireModule(node) || isPlainImportModule(node) || isPlainImportEquals(node); } function canReorderItems(firstNode, secondNode) { - const parent = firstNode.parent + const parent = firstNode.parent; const [firstIndex, secondIndex] = [ parent.body.indexOf(firstNode), parent.body.indexOf(secondNode), - ].sort() - const nodesBetween = parent.body.slice(firstIndex, secondIndex + 1) - for (var nodeBetween of nodesBetween) { + ].sort(); + const nodesBetween = parent.body.slice(firstIndex, secondIndex + 1); + for (const nodeBetween of nodesBetween) { if (!canCrossNodeWhileReorder(nodeBetween)) { - return false + return false; } } - return true + return true; } function fixOutOfOrder(context, firstNode, secondNode, order) { - const sourceCode = context.getSourceCode() + const sourceCode = context.getSourceCode(); - const firstRoot = findRootNode(firstNode.node) - const firstRootStart = findStartOfLineWithComments(sourceCode, firstRoot) - const firstRootEnd = findEndOfLineWithComments(sourceCode, firstRoot) + const firstRoot = findRootNode(firstNode.node); + const firstRootStart = findStartOfLineWithComments(sourceCode, firstRoot); + const firstRootEnd = findEndOfLineWithComments(sourceCode, firstRoot); - const secondRoot = findRootNode(secondNode.node) - const secondRootStart = findStartOfLineWithComments(sourceCode, secondRoot) - const secondRootEnd = findEndOfLineWithComments(sourceCode, secondRoot) - const canFix = canReorderItems(firstRoot, secondRoot) + const secondRoot = findRootNode(secondNode.node); + const secondRootStart = findStartOfLineWithComments(sourceCode, secondRoot); + const secondRootEnd = findEndOfLineWithComments(sourceCode, secondRoot); + const canFix = canReorderItems(firstRoot, secondRoot); - let newCode = sourceCode.text.substring(secondRootStart, secondRootEnd) + let newCode = sourceCode.text.substring(secondRootStart, secondRootEnd); if (newCode[newCode.length - 1] !== '\n') { - newCode = newCode + '\n' + newCode = newCode + '\n'; } - const message = '`' + secondNode.name + '` import should occur ' + order + - ' import of `' + firstNode.name + '`' + const message = `\`${secondNode.displayName}\` import should occur ${order} import of \`${firstNode.displayName}\``; if (order === 'before') { context.report({ @@ -209,7 +204,7 @@ function fixOutOfOrder(context, firstNode, secondNode, order) { [firstRootStart, secondRootEnd], newCode + sourceCode.text.substring(firstRootStart, secondRootStart) )), - }) + }); } else if (order === 'after') { context.report({ node: secondNode.node, @@ -219,126 +214,148 @@ function fixOutOfOrder(context, firstNode, secondNode, order) { [secondRootStart, firstRootEnd], sourceCode.text.substring(secondRootEnd, firstRootEnd) + newCode )), - }) + }); } } function reportOutOfOrder(context, imported, outOfOrder, order) { outOfOrder.forEach(function (imp) { const found = imported.find(function hasHigherRank(importedItem) { - return importedItem.rank > imp.rank - }) - fixOutOfOrder(context, found, imp, order) - }) + return importedItem.rank > imp.rank; + }); + fixOutOfOrder(context, found, imp, order); + }); } function makeOutOfOrderReport(context, imported) { - const outOfOrder = findOutOfOrder(imported) + const outOfOrder = findOutOfOrder(imported); if (!outOfOrder.length) { - return + return; } // There are things to report. Try to minimize the number of reported errors. - const reversedImported = reverse(imported) - const reversedOrder = findOutOfOrder(reversedImported) + const reversedImported = reverse(imported); + const reversedOrder = findOutOfOrder(reversedImported); if (reversedOrder.length < outOfOrder.length) { - reportOutOfOrder(context, reversedImported, reversedOrder, 'after') - return + reportOutOfOrder(context, reversedImported, reversedOrder, 'after'); + return; } - reportOutOfOrder(context, imported, outOfOrder, 'before') + reportOutOfOrder(context, imported, outOfOrder, 'before'); } function getSorter(ascending) { - let multiplier = (ascending ? 1 : -1) + const multiplier = ascending ? 1 : -1; return function importsSorter(importA, importB) { - let result + let result; - if ((importA < importB) || importB === null) { - result = -1 - } else if ((importA > importB) || importA === null) { - result = 1 + if (importA < importB) { + result = -1; + } else if (importA > importB) { + result = 1; } else { - result = 0 + result = 0; } - return result * multiplier - } + return result * multiplier; + }; } function mutateRanksToAlphabetize(imported, alphabetizeOptions) { const groupedByRanks = imported.reduce(function(acc, importedItem) { if (!Array.isArray(acc[importedItem.rank])) { - acc[importedItem.rank] = [] + acc[importedItem.rank] = []; } - acc[importedItem.rank].push(importedItem.name) - return acc - }, {}) + acc[importedItem.rank].push(importedItem); + return acc; + }, {}); - const groupRanks = Object.keys(groupedByRanks) + const groupRanks = Object.keys(groupedByRanks); + + const sorterFn = getSorter(alphabetizeOptions.order === 'asc'); + const comparator = alphabetizeOptions.caseInsensitive + ? (a, b) => sorterFn(String(a.value).toLowerCase(), String(b.value).toLowerCase()) + : (a, b) => sorterFn(a.value, b.value); - 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) { - groupedByRanks[groupRank].sort(comparator) - }) + groupedByRanks[groupRank].sort(comparator); + }); // assign globally unique rank to each import - let newRank = 0 + let newRank = 0; const alphabetizedRanks = groupRanks.sort().reduce(function(acc, groupRank) { - groupedByRanks[groupRank].forEach(function(importedItemName) { - acc[importedItemName] = parseInt(groupRank, 10) + newRank - newRank += 1 - }) - return acc - }, {}) + groupedByRanks[groupRank].forEach(function(importedItem) { + acc[`${importedItem.value}|${importedItem.node.importKind}`] = parseInt(groupRank, 10) + newRank; + newRank += 1; + }); + return acc; + }, {}); // mutate the original group-rank with alphabetized-rank imported.forEach(function(importedItem) { - importedItem.rank = alphabetizedRanks[importedItem.name] - }) + importedItem.rank = alphabetizedRanks[`${importedItem.value}|${importedItem.node.importKind}`]; + }); } // DETECTING function computePathRank(ranks, pathGroups, path, maxPosition) { for (let i = 0, l = pathGroups.length; i < l; i++) { - const { pattern, patternOptions, group, position = 1 } = pathGroups[i] + const { pattern, patternOptions, group, position = 1 } = pathGroups[i]; if (minimatch(path, pattern, patternOptions || { nocomment: true })) { - return ranks[group] + (position / maxPosition) + return ranks[group] + (position / maxPosition); } } } -function computeRank(context, ranks, name, type, excludedImportTypes) { - const impType = importType(name, context) - let rank +function computeRank(context, ranks, importEntry, excludedImportTypes) { + let impType; + let rank; + if (importEntry.type === 'import:object') { + impType = 'object'; + } else if (importEntry.node.importKind === 'type' && ranks.omittedTypes.indexOf('type') === -1) { + impType = 'type'; + } else { + impType = importType(importEntry.value, context); + } if (!excludedImportTypes.has(impType)) { - rank = computePathRank(ranks.groups, ranks.pathGroups, name, ranks.maxPosition) + rank = computePathRank(ranks.groups, ranks.pathGroups, importEntry.value, ranks.maxPosition); } if (typeof rank === 'undefined') { - rank = ranks.groups[impType] + rank = ranks.groups[impType]; } - if (type !== 'import') { - rank += 100 + if (importEntry.type !== 'import' && !importEntry.type.startsWith('import:')) { + rank += 100; } - return rank + return rank; } -function registerNode(context, node, name, type, ranks, imported, excludedImportTypes) { - const rank = computeRank(context, ranks, name, type, excludedImportTypes) +function registerNode(context, importEntry, ranks, imported, excludedImportTypes) { + const rank = computeRank(context, ranks, importEntry, excludedImportTypes); if (rank !== -1) { - imported.push({name, rank, node}) + imported.push(Object.assign({}, importEntry, { rank })); } } -function isInVariableDeclarator(node) { - return node && - (node.type === 'VariableDeclarator' || isInVariableDeclarator(node.parent)) +function isModuleLevelRequire(node) { + let n = node; + // Handle cases like `const baz = require('foo').bar.baz` + // and `const foo = require('foo')()` + while ( + (n.parent.type === 'MemberExpression' && n.parent.object === n) || + (n.parent.type === 'CallExpression' && n.parent.callee === n) + ) { + n = n.parent; + } + return ( + n.parent.type === 'VariableDeclarator' && + n.parent.parent.type === 'VariableDeclaration' && + n.parent.parent.parent.type === 'Program' + ); } -const types = ['builtin', 'external', 'internal', 'unknown', 'parent', 'sibling', 'index'] +const types = ['builtin', 'external', 'internal', 'unknown', 'parent', 'sibling', 'index', 'object', 'type']; // Creates an object with type-rank pairs. // Example: { index: 0, sibling: 1, parent: 1, external: 1, builtin: 2, internal: 2 } @@ -346,98 +363,100 @@ const types = ['builtin', 'external', 'internal', 'unknown', 'parent', 'sibling' function convertGroupsToRanks(groups) { const rankObject = groups.reduce(function(res, group, index) { if (typeof group === 'string') { - group = [group] + group = [group]; } group.forEach(function(groupItem) { if (types.indexOf(groupItem) === -1) { throw new Error('Incorrect configuration of the rule: Unknown type `' + - JSON.stringify(groupItem) + '`') + JSON.stringify(groupItem) + '`'); } if (res[groupItem] !== undefined) { - throw new Error('Incorrect configuration of the rule: `' + groupItem + '` is duplicated') + throw new Error('Incorrect configuration of the rule: `' + groupItem + '` is duplicated'); } - res[groupItem] = index - }) - return res - }, {}) + res[groupItem] = index; + }); + return res; + }, {}); const omittedTypes = types.filter(function(type) { - return rankObject[type] === undefined - }) + return rankObject[type] === undefined; + }); - return omittedTypes.reduce(function(res, type) { - res[type] = groups.length - return res - }, rankObject) + const ranks = omittedTypes.reduce(function(res, type) { + res[type] = groups.length; + return res; + }, rankObject); + + return { groups: ranks, omittedTypes }; } function convertPathGroupsForRanks(pathGroups) { - const after = {} - const before = {} + const after = {}; + const before = {}; const transformed = pathGroups.map((pathGroup, index) => { - const { group, position: positionString } = pathGroup - let position = 0 + const { group, position: positionString } = pathGroup; + let position = 0; if (positionString === 'after') { if (!after[group]) { - after[group] = 1 + after[group] = 1; } - position = after[group]++ + position = after[group]++; } else if (positionString === 'before') { if (!before[group]) { - before[group] = [] + before[group] = []; } - before[group].push(index) + before[group].push(index); } - return Object.assign({}, pathGroup, { position }) - }) + return Object.assign({}, pathGroup, { position }); + }); - let maxPosition = 1 + let maxPosition = 1; Object.keys(before).forEach((group) => { - const groupLength = before[group].length + const groupLength = before[group].length; before[group].forEach((groupIndex, index) => { - transformed[groupIndex].position = -1 * (groupLength - index) - }) - maxPosition = Math.max(maxPosition, groupLength) - }) + transformed[groupIndex].position = -1 * (groupLength - index); + }); + maxPosition = Math.max(maxPosition, groupLength); + }); Object.keys(after).forEach((key) => { - const groupNextPosition = after[key] - maxPosition = Math.max(maxPosition, groupNextPosition - 1) - }) + const groupNextPosition = after[key]; + maxPosition = Math.max(maxPosition, groupNextPosition - 1); + }); return { pathGroups: transformed, maxPosition: maxPosition > 10 ? Math.pow(10, Math.ceil(Math.log10(maxPosition))) : 10, - } + }; } function fixNewLineAfterImport(context, previousImport) { - const prevRoot = findRootNode(previousImport.node) + const prevRoot = findRootNode(previousImport.node); const tokensToEndOfLine = takeTokensAfterWhile( - context.getSourceCode(), prevRoot, commentOnSameLineAs(prevRoot)) + context.getSourceCode(), prevRoot, commentOnSameLineAs(prevRoot)); - let endOfLine = prevRoot.range[1] + let endOfLine = prevRoot.range[1]; if (tokensToEndOfLine.length > 0) { - endOfLine = tokensToEndOfLine[tokensToEndOfLine.length - 1].range[1] + endOfLine = tokensToEndOfLine[tokensToEndOfLine.length - 1].range[1]; } - return (fixer) => fixer.insertTextAfterRange([prevRoot.range[0], endOfLine], '\n') + return (fixer) => fixer.insertTextAfterRange([prevRoot.range[0], endOfLine], '\n'); } function removeNewLineAfterImport(context, currentImport, previousImport) { - const sourceCode = context.getSourceCode() - const prevRoot = findRootNode(previousImport.node) - const currRoot = findRootNode(currentImport.node) + const sourceCode = context.getSourceCode(); + const prevRoot = findRootNode(previousImport.node); + const currRoot = findRootNode(currentImport.node); const rangeToRemove = [ findEndOfLineWithComments(sourceCode, prevRoot), findStartOfLineWithComments(sourceCode, currRoot), - ] + ]; if (/^\s*$/.test(sourceCode.text.substring(rangeToRemove[0], rangeToRemove[1]))) { - return (fixer) => fixer.removeRange(rangeToRemove) + return (fixer) => fixer.removeRange(rangeToRemove); } - return undefined + return undefined; } function makeNewlinesBetweenReport (context, imported, newlinesBetweenImports) { @@ -445,14 +464,14 @@ function makeNewlinesBetweenReport (context, imported, newlinesBetweenImports) { const linesBetweenImports = context.getSourceCode().lines.slice( previousImport.node.loc.end.line, currentImport.node.loc.start.line - 1 - ) + ); - return linesBetweenImports.filter((line) => !line.trim().length).length - } - let previousImport = imported[0] + return linesBetweenImports.filter((line) => !line.trim().length).length; + }; + let previousImport = imported[0]; imported.slice(1).forEach(function(currentImport) { - const emptyLinesBetween = getNumberOfEmptyLinesBetween(currentImport, previousImport) + const emptyLinesBetween = getNumberOfEmptyLinesBetween(currentImport, previousImport); if (newlinesBetweenImports === 'always' || newlinesBetweenImports === 'always-and-inside-groups') { @@ -461,7 +480,7 @@ function makeNewlinesBetweenReport (context, imported, newlinesBetweenImports) { node: previousImport.node, message: 'There should be at least one empty line between import groups', fix: fixNewLineAfterImport(context, previousImport), - }) + }); } else if (currentImport.rank === previousImport.rank && emptyLinesBetween > 0 && newlinesBetweenImports !== 'always-and-inside-groups') { @@ -469,26 +488,26 @@ function makeNewlinesBetweenReport (context, imported, newlinesBetweenImports) { node: previousImport.node, message: 'There should be no empty line within import group', fix: removeNewLineAfterImport(context, currentImport, previousImport), - }) + }); } } else if (emptyLinesBetween > 0) { context.report({ node: previousImport.node, message: 'There should be no empty line between import groups', fix: removeNewLineAfterImport(context, currentImport, previousImport), - }) + }); } - previousImport = currentImport - }) + previousImport = currentImport; + }); } function getAlphabetizeConfig(options) { - const alphabetize = options.alphabetize || {} - const order = alphabetize.order || 'ignore' - const caseInsensitive = alphabetize.caseInsensitive || false + const alphabetize = options.alphabetize || {}; + const order = alphabetize.order || 'ignore'; + const caseInsensitive = alphabetize.caseInsensitive || false; - return {order, caseInsensitive} + return { order, caseInsensitive }; } module.exports = { @@ -554,6 +573,10 @@ module.exports = { }, additionalProperties: false, }, + warnOnUnassignedImports: { + type: 'boolean', + default: false, + }, }, additionalProperties: false, }, @@ -561,109 +584,111 @@ module.exports = { }, create: function importOrderRule (context) { - const options = context.options[0] || {} - const newlinesBetweenImports = options['newlines-between'] || 'ignore' - const pathGroupsExcludedImportTypes = new Set(options['pathGroupsExcludedImportTypes'] || ['builtin', 'external']) - const alphabetize = getAlphabetizeConfig(options) - let ranks + const options = context.options[0] || {}; + const newlinesBetweenImports = options['newlines-between'] || 'ignore'; + const pathGroupsExcludedImportTypes = new Set(options['pathGroupsExcludedImportTypes'] || ['builtin', 'external', 'object']); + const alphabetize = getAlphabetizeConfig(options); + let ranks; try { - const { pathGroups, maxPosition } = convertPathGroupsForRanks(options.pathGroups || []) + const { pathGroups, maxPosition } = convertPathGroupsForRanks(options.pathGroups || []); + const { groups, omittedTypes } = convertGroupsToRanks(options.groups || defaultGroups); ranks = { - groups: convertGroupsToRanks(options.groups || defaultGroups), + groups, + omittedTypes, pathGroups, maxPosition, - } + }; } catch (error) { // Malformed configuration return { Program: function(node) { - context.report(node, error.message) + context.report(node, error.message); }, - } - } - let imported = [] - let level = 0 - - function incrementLevel() { - level++ - } - function decrementLevel() { - level-- + }; } + let imported = []; return { ImportDeclaration: function handleImports(node) { - if (node.specifiers.length) { // Ignoring unassigned imports - const name = node.source.value + // Ignoring unassigned imports unless warnOnUnassignedImports is set + if (node.specifiers.length || options.warnOnUnassignedImports) { + const name = node.source.value; registerNode( context, - node, - name, - 'import', + { + node, + value: name, + displayName: name, + type: 'import', + }, ranks, imported, pathGroupsExcludedImportTypes - ) + ); } }, TSImportEqualsDeclaration: function handleImports(node) { - let name + let displayName; + let value; + let type; + // skip "export import"s + if (node.isExport) { + return; + } if (node.moduleReference.type === 'TSExternalModuleReference') { - name = node.moduleReference.expression.value - } else if (node.isExport) { - name = node.moduleReference.name + value = node.moduleReference.expression.value; + displayName = value; + type = 'import'; } else { - name = null + value = ''; + displayName = context.getSourceCode().getText(node.moduleReference); + type = 'import:object'; } registerNode( context, - node, - name, - 'import', + { + node, + value, + displayName, + type, + }, ranks, imported, pathGroupsExcludedImportTypes - ) + ); }, CallExpression: function handleRequires(node) { - if (level !== 0 || !isStaticRequire(node) || !isInVariableDeclarator(node.parent)) { - return + if (!isStaticRequire(node) || !isModuleLevelRequire(node)) { + return; } - const name = node.arguments[0].value + const name = node.arguments[0].value; registerNode( context, - node, - name, - 'require', + { + node, + value: name, + displayName: name, + type: 'require', + }, ranks, imported, pathGroupsExcludedImportTypes - ) + ); }, 'Program:exit': function reportAndReset() { if (newlinesBetweenImports !== 'ignore') { - makeNewlinesBetweenReport(context, imported, newlinesBetweenImports) + makeNewlinesBetweenReport(context, imported, newlinesBetweenImports); } if (alphabetize.order !== 'ignore') { - mutateRanksToAlphabetize(imported, alphabetize) + mutateRanksToAlphabetize(imported, alphabetize); } - makeOutOfOrderReport(context, imported) + makeOutOfOrderReport(context, imported); - imported = [] + imported = []; }, - FunctionDeclaration: incrementLevel, - FunctionExpression: incrementLevel, - ArrowFunctionExpression: incrementLevel, - BlockStatement: incrementLevel, - ObjectExpression: incrementLevel, - 'FunctionDeclaration:exit': decrementLevel, - 'FunctionExpression:exit': decrementLevel, - 'ArrowFunctionExpression:exit': decrementLevel, - 'BlockStatement:exit': decrementLevel, - 'ObjectExpression:exit': decrementLevel, - } + }; }, -} +}; diff --git a/src/rules/prefer-default-export.js b/src/rules/prefer-default-export.js index f1db4eaaa6..5e77126dc0 100644 --- a/src/rules/prefer-default-export.js +++ b/src/rules/prefer-default-export.js @@ -1,6 +1,6 @@ -'use strict' +'use strict'; -import docsUrl from '../docsUrl' +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -12,47 +12,47 @@ module.exports = { }, create: function(context) { - let specifierExportCount = 0 - let hasDefaultExport = false - let hasStarExport = false - let hasTypeExport = false - let namedExportNode = null + let specifierExportCount = 0; + let hasDefaultExport = false; + let hasStarExport = false; + let hasTypeExport = false; + let namedExportNode = null; function captureDeclaration(identifierOrPattern) { - if (identifierOrPattern.type === 'ObjectPattern') { + if (identifierOrPattern && identifierOrPattern.type === 'ObjectPattern') { // recursively capture identifierOrPattern.properties .forEach(function(property) { - captureDeclaration(property.value) - }) - } else if (identifierOrPattern.type === 'ArrayPattern') { + captureDeclaration(property.value); + }); + } else if (identifierOrPattern && identifierOrPattern.type === 'ArrayPattern') { identifierOrPattern.elements - .forEach(captureDeclaration) + .forEach(captureDeclaration); } else { // assume it's a single standard identifier - specifierExportCount++ + specifierExportCount++; } } return { 'ExportDefaultSpecifier': function() { - hasDefaultExport = true + hasDefaultExport = true; }, 'ExportSpecifier': function(node) { if (node.exported.name === 'default') { - hasDefaultExport = true + hasDefaultExport = true; } else { - specifierExportCount++ - namedExportNode = node + specifierExportCount++; + namedExportNode = node; } }, 'ExportNamedDeclaration': function(node) { // if there are specifiers, node.declaration should be null - if (!node.declaration) return + if (!node.declaration) return; - const { type } = node.declaration + const { type } = node.declaration; if ( type === 'TSTypeAliasDeclaration' || @@ -60,37 +60,37 @@ module.exports = { type === 'TSInterfaceDeclaration' || type === 'InterfaceDeclaration' ) { - specifierExportCount++ - hasTypeExport = true - return + specifierExportCount++; + hasTypeExport = true; + return; } if (node.declaration.declarations) { node.declaration.declarations.forEach(function(declaration) { - captureDeclaration(declaration.id) - }) + captureDeclaration(declaration.id); + }); } else { // captures 'export function foo() {}' syntax - specifierExportCount++ + specifierExportCount++; } - namedExportNode = node + namedExportNode = node; }, 'ExportDefaultDeclaration': function() { - hasDefaultExport = true + hasDefaultExport = true; }, 'ExportAllDeclaration': function() { - hasStarExport = true + hasStarExport = true; }, 'Program:exit': function() { if (specifierExportCount === 1 && !hasDefaultExport && !hasStarExport && !hasTypeExport) { - context.report(namedExportNode, 'Prefer default export.') + context.report(namedExportNode, 'Prefer default export.'); } }, - } + }; }, -} +}; diff --git a/src/rules/unambiguous.js b/src/rules/unambiguous.js index 52c2f5ac19..c0570b066e 100644 --- a/src/rules/unambiguous.js +++ b/src/rules/unambiguous.js @@ -3,8 +3,8 @@ * @author Ben Mosher */ -import { isModule } from 'eslint-module-utils/unambiguous' -import docsUrl from '../docsUrl' +import { isModule } from 'eslint-module-utils/unambiguous'; +import docsUrl from '../docsUrl'; module.exports = { meta: { @@ -18,7 +18,7 @@ module.exports = { create: function (context) { // ignore non-modules if (context.parserOptions.sourceType !== 'module') { - return {} + return {}; } return { @@ -27,10 +27,10 @@ module.exports = { context.report({ node: ast, message: 'This module could be parsed as a valid script.', - }) + }); } }, - } + }; }, -} +}; diff --git a/tests/dep-time-travel.sh b/tests/dep-time-travel.sh index 078d9059b8..ad00568e4c 100755 --- a/tests/dep-time-travel.sh +++ b/tests/dep-time-travel.sh @@ -2,7 +2,11 @@ # expected: ESLINT_VERSION numeric env var -npm install --no-save eslint@$ESLINT_VERSION --ignore-scripts || true +echo "installing ${ESLINT_VERSION}..." + +export NPM_CONFIG_LEGACY_PEER_DEPS=true + +npm install --no-save "eslint@${ESLINT_VERSION}" --ignore-scripts # completely remove the new TypeScript parser for ESLint < v5 if [[ "$ESLINT_VERSION" -lt "5" ]]; then @@ -24,3 +28,8 @@ if [[ "$TRAVIS_NODE_VERSION" -lt "8" ]]; then echo "Downgrading eslint-import-resolver-typescript..." npm i --no-save eslint-import-resolver-typescript@1.0.2 fi + +if [[ -n "$TS_PARSER" ]]; then + echo "Downgrading @typescript-eslint/parser..." + npm i --no-save @typescript-eslint/parser@2 +fi diff --git a/tests/files/color.js b/tests/files/color.js new file mode 100644 index 0000000000..dcdbf84ac3 --- /dev/null +++ b/tests/files/color.js @@ -0,0 +1 @@ +export const example = 'example'; diff --git a/tests/files/cycles/flow-types-only-importing-multiple-types.js b/tests/files/cycles/flow-types-only-importing-multiple-types.js new file mode 100644 index 0000000000..ab61606fd3 --- /dev/null +++ b/tests/files/cycles/flow-types-only-importing-multiple-types.js @@ -0,0 +1,3 @@ +// @flow + +import { type FooType, type BarType } from './depth-zero'; diff --git a/tests/files/cycles/flow-types-only-importing-type.js b/tests/files/cycles/flow-types-only-importing-type.js new file mode 100644 index 0000000000..b407da9870 --- /dev/null +++ b/tests/files/cycles/flow-types-only-importing-type.js @@ -0,0 +1,3 @@ +// @flow + +import type { FooType } from './depth-zero'; diff --git a/tests/files/cycles/flow-types-some-type-imports.js b/tests/files/cycles/flow-types-some-type-imports.js new file mode 100644 index 0000000000..9008ba1af8 --- /dev/null +++ b/tests/files/cycles/flow-types-some-type-imports.js @@ -0,0 +1,3 @@ +// @flow + +import { foo, type BarType } from './depth-zero' diff --git a/tests/files/missing-entrypoint/package.json b/tests/files/missing-entrypoint/package.json new file mode 100644 index 0000000000..4138a88f4b --- /dev/null +++ b/tests/files/missing-entrypoint/package.json @@ -0,0 +1,3 @@ +{ + "bin": "./cli.js" +} diff --git a/tests/files/named-export-collision/a.js b/tests/files/named-export-collision/a.js new file mode 100644 index 0000000000..cb04b2cb26 --- /dev/null +++ b/tests/files/named-export-collision/a.js @@ -0,0 +1 @@ +export const FOO = 'a-foobar'; diff --git a/tests/files/named-export-collision/b.js b/tests/files/named-export-collision/b.js new file mode 100644 index 0000000000..ebf954ee0c --- /dev/null +++ b/tests/files/named-export-collision/b.js @@ -0,0 +1 @@ +export const FOO = 'b-foobar'; diff --git a/tests/files/named-exports.js b/tests/files/named-exports.js index f2881c10c5..d8b17bb908 100644 --- a/tests/files/named-exports.js +++ b/tests/files/named-exports.js @@ -13,9 +13,9 @@ export class ExportedClass { // destructuring exports -export var { destructuredProp } = {} +export var { destructuredProp, ...restProps } = {} , { destructingAssign = null } = {} , { destructingAssign: destructingRenamedAssign = null } = {} - , [ arrayKeyProp ] = [] + , [ arrayKeyProp, ...arrayRestKeyProps ] = [] , [ { deepProp } ] = [] , { arr: [ ,, deepSparseElement ] } = {} diff --git a/tests/files/no-unused-modules/destructuring-a.js b/tests/files/no-unused-modules/destructuring-a.js new file mode 100644 index 0000000000..1da867deff --- /dev/null +++ b/tests/files/no-unused-modules/destructuring-a.js @@ -0,0 +1 @@ +import {a, b} from "./destructuring-b"; diff --git a/tests/files/no-unused-modules/destructuring-b.js b/tests/files/no-unused-modules/destructuring-b.js new file mode 100644 index 0000000000..b477a5b6f2 --- /dev/null +++ b/tests/files/no-unused-modules/destructuring-b.js @@ -0,0 +1,2 @@ +const obj = {a: 1, dummy: {b: 2}}; +export const {a, dummy: {b}} = obj; diff --git a/tests/files/no-unused-modules/file-destructured-1.js b/tests/files/no-unused-modules/file-destructured-1.js new file mode 100644 index 0000000000..f2223ac3b0 --- /dev/null +++ b/tests/files/no-unused-modules/file-destructured-1.js @@ -0,0 +1,2 @@ +export const { destructured } = {}; +export const { destructured2 } = {}; diff --git a/tests/files/no-unused-modules/file-destructured-2.js b/tests/files/no-unused-modules/file-destructured-2.js new file mode 100644 index 0000000000..06dc48a9dc --- /dev/null +++ b/tests/files/no-unused-modules/file-destructured-2.js @@ -0,0 +1 @@ +import { destructured } from './file-destructured-1'; \ No newline at end of file diff --git a/tests/files/no-unused-modules/flow/flow-0.js b/tests/files/no-unused-modules/flow/flow-0.js new file mode 100644 index 0000000000..b5e5d8b015 --- /dev/null +++ b/tests/files/no-unused-modules/flow/flow-0.js @@ -0,0 +1 @@ +import { type FooType, type FooInterface } from './flow-2'; diff --git a/tests/files/no-unused-modules/flow/flow-1.js b/tests/files/no-unused-modules/flow/flow-1.js new file mode 100644 index 0000000000..4828eb575c --- /dev/null +++ b/tests/files/no-unused-modules/flow/flow-1.js @@ -0,0 +1,3 @@ +// @flow strict +export type Bar = number; +export interface BarInterface {}; diff --git a/tests/files/no-unused-modules/flow/flow-2.js b/tests/files/no-unused-modules/flow/flow-2.js new file mode 100644 index 0000000000..0c632c2476 --- /dev/null +++ b/tests/files/no-unused-modules/flow/flow-2.js @@ -0,0 +1,3 @@ +// @flow strict +export type FooType = string; +export interface FooInterface {}; diff --git a/tests/files/no-unused-modules/flow/flow-3.js b/tests/files/no-unused-modules/flow/flow-3.js new file mode 100644 index 0000000000..ade5393a7d --- /dev/null +++ b/tests/files/no-unused-modules/flow/flow-3.js @@ -0,0 +1 @@ +import type { FooType, FooInterface } from './flow-4'; diff --git a/tests/files/no-unused-modules/flow/flow-4.js b/tests/files/no-unused-modules/flow/flow-4.js new file mode 100644 index 0000000000..0c632c2476 --- /dev/null +++ b/tests/files/no-unused-modules/flow/flow-4.js @@ -0,0 +1,3 @@ +// @flow strict +export type FooType = string; +export interface FooInterface {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-a-import-type.ts b/tests/files/no-unused-modules/typescript/file-ts-a-import-type.ts new file mode 100644 index 0000000000..357d890b9d --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-a-import-type.ts @@ -0,0 +1,9 @@ +import type {b} from './file-ts-b-used-as-type'; +import type {c} from './file-ts-c-used-as-type'; +import type {d} from './file-ts-d-used-as-type'; +import type {e} from './file-ts-e-used-as-type'; + +const a: typeof b = 2; +const a2: c = {}; +const a3: d = {}; +const a4: typeof e = undefined; diff --git a/tests/files/no-unused-modules/typescript/file-ts-a.ts b/tests/files/no-unused-modules/typescript/file-ts-a.ts index a4272256e6..2e7984cb95 100644 --- a/tests/files/no-unused-modules/typescript/file-ts-a.ts +++ b/tests/files/no-unused-modules/typescript/file-ts-a.ts @@ -1,3 +1,8 @@ import {b} from './file-ts-b'; +import {c} from './file-ts-c'; +import {d} from './file-ts-d'; +import {e} from './file-ts-e'; -export const a = b + 1; +const a = b + 1 + e.f; +const a2: c = {}; +const a3: d = {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-b-unused.ts b/tests/files/no-unused-modules/typescript/file-ts-b-unused.ts new file mode 100644 index 0000000000..202103085c --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-b-unused.ts @@ -0,0 +1 @@ +export const b = 2; diff --git a/tests/files/no-unused-modules/typescript/file-ts-b-used-as-type.ts b/tests/files/no-unused-modules/typescript/file-ts-b-used-as-type.ts new file mode 100644 index 0000000000..202103085c --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-b-used-as-type.ts @@ -0,0 +1 @@ +export const b = 2; diff --git a/tests/files/no-unused-modules/typescript/file-ts-c-unused.ts b/tests/files/no-unused-modules/typescript/file-ts-c-unused.ts new file mode 100644 index 0000000000..aedf4062be --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-c-unused.ts @@ -0,0 +1 @@ +export interface c {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-c-used-as-type.ts b/tests/files/no-unused-modules/typescript/file-ts-c-used-as-type.ts new file mode 100644 index 0000000000..aedf4062be --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-c-used-as-type.ts @@ -0,0 +1 @@ +export interface c {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-c.ts b/tests/files/no-unused-modules/typescript/file-ts-c.ts new file mode 100644 index 0000000000..aedf4062be --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-c.ts @@ -0,0 +1 @@ +export interface c {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-d-unused.ts b/tests/files/no-unused-modules/typescript/file-ts-d-unused.ts new file mode 100644 index 0000000000..7679b3de03 --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-d-unused.ts @@ -0,0 +1 @@ +export type d = {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-d-used-as-type.ts b/tests/files/no-unused-modules/typescript/file-ts-d-used-as-type.ts new file mode 100644 index 0000000000..7679b3de03 --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-d-used-as-type.ts @@ -0,0 +1 @@ +export type d = {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-d.ts b/tests/files/no-unused-modules/typescript/file-ts-d.ts new file mode 100644 index 0000000000..7679b3de03 --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-d.ts @@ -0,0 +1 @@ +export type d = {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-e-unused.ts b/tests/files/no-unused-modules/typescript/file-ts-e-unused.ts new file mode 100644 index 0000000000..d1787a11af --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-e-unused.ts @@ -0,0 +1 @@ +export enum e { f }; diff --git a/tests/files/no-unused-modules/typescript/file-ts-e-used-as-type.ts b/tests/files/no-unused-modules/typescript/file-ts-e-used-as-type.ts new file mode 100644 index 0000000000..d1787a11af --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-e-used-as-type.ts @@ -0,0 +1 @@ +export enum e { f }; diff --git a/tests/files/no-unused-modules/typescript/file-ts-e.ts b/tests/files/no-unused-modules/typescript/file-ts-e.ts new file mode 100644 index 0000000000..d1787a11af --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-e.ts @@ -0,0 +1 @@ +export enum e { f }; diff --git a/tests/files/no-unused-modules/typescript/file-ts-f-import-type.ts b/tests/files/no-unused-modules/typescript/file-ts-f-import-type.ts new file mode 100644 index 0000000000..dd82043774 --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-f-import-type.ts @@ -0,0 +1 @@ +import type {g} from './file-ts-g-used-as-type' diff --git a/tests/files/no-unused-modules/typescript/file-ts-f.ts b/tests/files/no-unused-modules/typescript/file-ts-f.ts new file mode 100644 index 0000000000..f3a1ca7ab4 --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-f.ts @@ -0,0 +1 @@ +import {g} from './file-ts-g'; diff --git a/tests/files/no-unused-modules/typescript/file-ts-g-used-as-type.ts b/tests/files/no-unused-modules/typescript/file-ts-g-used-as-type.ts new file mode 100644 index 0000000000..fe5318fbe7 --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-g-used-as-type.ts @@ -0,0 +1 @@ +export interface g {} diff --git a/tests/files/no-unused-modules/typescript/file-ts-g.ts b/tests/files/no-unused-modules/typescript/file-ts-g.ts new file mode 100644 index 0000000000..fe5318fbe7 --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-g.ts @@ -0,0 +1 @@ +export interface g {} diff --git a/tests/files/node_modules/@generated/bar/package.json b/tests/files/node_modules/@generated/bar/package.json new file mode 100644 index 0000000000..b70db688d6 --- /dev/null +++ b/tests/files/node_modules/@generated/bar/package.json @@ -0,0 +1,3 @@ +{ + "name": "@generated/bar" +} diff --git a/tests/files/node_modules/@generated/foo/package.json b/tests/files/node_modules/@generated/foo/package.json new file mode 100644 index 0000000000..c5d0d6b332 --- /dev/null +++ b/tests/files/node_modules/@generated/foo/package.json @@ -0,0 +1,3 @@ +{ + "name": "@generated/foo" +} diff --git a/tests/files/node_modules/@org/not-a-dependency/package.json b/tests/files/node_modules/@org/not-a-dependency/package.json new file mode 100644 index 0000000000..a81c5f2919 --- /dev/null +++ b/tests/files/node_modules/@org/not-a-dependency/package.json @@ -0,0 +1,3 @@ +{ + "name": "@org/not-a-dependency" +} diff --git a/tests/files/node_modules/@org/package/package.json b/tests/files/node_modules/@org/package/package.json new file mode 100644 index 0000000000..7cb5d73daf --- /dev/null +++ b/tests/files/node_modules/@org/package/package.json @@ -0,0 +1,3 @@ +{ + "name": "@org/package" +} diff --git a/tests/files/node_modules/a/package.json b/tests/files/node_modules/a/package.json new file mode 100644 index 0000000000..44d21f1fa7 --- /dev/null +++ b/tests/files/node_modules/a/package.json @@ -0,0 +1,3 @@ +{ + "name": "a" +} diff --git a/tests/files/node_modules/chai/package.json b/tests/files/node_modules/chai/package.json new file mode 100644 index 0000000000..00acdd2ca7 --- /dev/null +++ b/tests/files/node_modules/chai/package.json @@ -0,0 +1,3 @@ +{ + "name": "chai" +} diff --git a/tests/files/node_modules/es6-module/package.json b/tests/files/node_modules/es6-module/package.json new file mode 100644 index 0000000000..0bff4dda08 --- /dev/null +++ b/tests/files/node_modules/es6-module/package.json @@ -0,0 +1,3 @@ +{ + "name": "es6-module" +} diff --git a/tests/files/node_modules/eslint-import-resolver-foo/package.json b/tests/files/node_modules/eslint-import-resolver-foo/package.json new file mode 100644 index 0000000000..190e8e6e4c --- /dev/null +++ b/tests/files/node_modules/eslint-import-resolver-foo/package.json @@ -0,0 +1,3 @@ +{ + "name": "eslint-import-resolver-foo" +} diff --git a/tests/files/node_modules/exceljs/package.json b/tests/files/node_modules/exceljs/package.json index 70d59eaaa7..f2412292d2 100644 --- a/tests/files/node_modules/exceljs/package.json +++ b/tests/files/node_modules/exceljs/package.json @@ -1,3 +1,4 @@ { + "name": "exceljs", "main": "./excel.js" } diff --git a/tests/files/node_modules/jquery/package.json b/tests/files/node_modules/jquery/package.json new file mode 100644 index 0000000000..e0563fbf49 --- /dev/null +++ b/tests/files/node_modules/jquery/package.json @@ -0,0 +1,3 @@ +{ + "name": "jquery" +} diff --git a/tests/files/node_modules/jsx-module/package.json b/tests/files/node_modules/jsx-module/package.json new file mode 100644 index 0000000000..6edbe5fc98 --- /dev/null +++ b/tests/files/node_modules/jsx-module/package.json @@ -0,0 +1,3 @@ +{ + "name": "jsx-module" +} diff --git a/tests/files/node_modules/left-pad b/tests/files/node_modules/left-pad deleted file mode 120000 index dbbbe75d2d..0000000000 --- a/tests/files/node_modules/left-pad +++ /dev/null @@ -1 +0,0 @@ -not-a-dependency \ No newline at end of file diff --git a/tests/files/node_modules/left-pad/index.js b/tests/files/node_modules/left-pad/index.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/files/node_modules/left-pad/not-a-dependency b/tests/files/node_modules/left-pad/not-a-dependency new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/files/node_modules/left-pad/package.json b/tests/files/node_modules/left-pad/package.json new file mode 100644 index 0000000000..a95a5e067f --- /dev/null +++ b/tests/files/node_modules/left-pad/package.json @@ -0,0 +1,3 @@ +{ + "name": "left-pad" +} diff --git a/tests/files/node_modules/not-a-dependency/package.json b/tests/files/node_modules/not-a-dependency/package.json new file mode 100644 index 0000000000..8572331218 --- /dev/null +++ b/tests/files/node_modules/not-a-dependency/package.json @@ -0,0 +1,3 @@ +{ + "name": "not-a-dependency" +} diff --git a/tests/files/node_modules/react b/tests/files/node_modules/react deleted file mode 120000 index dbbbe75d2d..0000000000 --- a/tests/files/node_modules/react +++ /dev/null @@ -1 +0,0 @@ -not-a-dependency \ No newline at end of file diff --git a/tests/files/node_modules/react/index.js b/tests/files/node_modules/react/index.js new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/files/node_modules/react/index.js @@ -0,0 +1 @@ + diff --git a/tests/files/node_modules/react/not-a-dependency b/tests/files/node_modules/react/not-a-dependency new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/files/node_modules/react/package.json b/tests/files/node_modules/react/package.json new file mode 100644 index 0000000000..bcbea4166f --- /dev/null +++ b/tests/files/node_modules/react/package.json @@ -0,0 +1,3 @@ +{ + "name": "react" +} diff --git a/tests/files/node_modules/rxjs/index.js b/tests/files/node_modules/rxjs/index.js new file mode 100644 index 0000000000..ea9b101e1c --- /dev/null +++ b/tests/files/node_modules/rxjs/index.js @@ -0,0 +1 @@ +export default function () {} diff --git a/tests/files/node_modules/rxjs/operators/index.js b/tests/files/node_modules/rxjs/operators/index.js new file mode 100644 index 0000000000..ea9b101e1c --- /dev/null +++ b/tests/files/node_modules/rxjs/operators/index.js @@ -0,0 +1 @@ +export default function () {} diff --git a/tests/files/node_modules/rxjs/operators/package.json b/tests/files/node_modules/rxjs/operators/package.json new file mode 100644 index 0000000000..c857f8e31d --- /dev/null +++ b/tests/files/node_modules/rxjs/operators/package.json @@ -0,0 +1,5 @@ +{ + "name": "rxjs/operators", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/files/node_modules/rxjs/package.json b/tests/files/node_modules/rxjs/package.json new file mode 100644 index 0000000000..4fb9c6fa6d --- /dev/null +++ b/tests/files/node_modules/rxjs/package.json @@ -0,0 +1,5 @@ +{ + "name": "rxjs", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/files/package-named/index.js b/tests/files/package-named/index.js new file mode 100644 index 0000000000..ea9b101e1c --- /dev/null +++ b/tests/files/package-named/index.js @@ -0,0 +1 @@ +export default function () {} diff --git a/tests/files/package-named/package.json b/tests/files/package-named/package.json new file mode 100644 index 0000000000..dbda7111f0 --- /dev/null +++ b/tests/files/package-named/package.json @@ -0,0 +1,5 @@ +{ + "name": "package-named", + "description": "Standard, named package", + "main": "index.js" +} \ No newline at end of file diff --git a/tests/files/package-scoped/index.js b/tests/files/package-scoped/index.js new file mode 100644 index 0000000000..ea9b101e1c --- /dev/null +++ b/tests/files/package-scoped/index.js @@ -0,0 +1 @@ +export default function () {} diff --git a/tests/files/package-scoped/package.json b/tests/files/package-scoped/package.json new file mode 100644 index 0000000000..a2d81cbae3 --- /dev/null +++ b/tests/files/package-scoped/package.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/package-named", + "description": "Scoped, named package", + "main": "index.js" +} diff --git a/tests/files/package.json b/tests/files/package.json index 0ca8e77737..62bd3764a3 100644 --- a/tests/files/package.json +++ b/tests/files/package.json @@ -11,7 +11,8 @@ "@org/package": "^1.0.0", "jquery": "^3.1.0", "lodash.cond": "^4.3.0", - "pkg-up": "^1.0.0" + "pkg-up": "^1.0.0", + "rxjs": "^1.0.0" }, "optionalDependencies": { "lodash.isarray": "^4.0.0" diff --git a/tests/files/package/index.js b/tests/files/package/index.js new file mode 100644 index 0000000000..ea9b101e1c --- /dev/null +++ b/tests/files/package/index.js @@ -0,0 +1 @@ +export default function () {} diff --git a/tests/files/package/package.json b/tests/files/package/package.json new file mode 100644 index 0000000000..ad83f1ea7f --- /dev/null +++ b/tests/files/package/package.json @@ -0,0 +1,4 @@ +{ + "description": "Unnamed package for reaching through main field - rxjs style", + "main": "index.js" +} \ No newline at end of file diff --git a/tests/files/restricted-paths/server/two-new/a.js b/tests/files/restricted-paths/server/two-new/a.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/files/typescript-export-assign-function.ts b/tests/files/typescript-export-assign-function.ts new file mode 100644 index 0000000000..930d6dacee --- /dev/null +++ b/tests/files/typescript-export-assign-function.ts @@ -0,0 +1 @@ +export = function foo() {}; diff --git a/tests/files/typescript-export-assign-property.ts b/tests/files/typescript-export-assign-property.ts new file mode 100644 index 0000000000..8dc2b9981e --- /dev/null +++ b/tests/files/typescript-export-assign-property.ts @@ -0,0 +1,3 @@ +const AnalyticsNode = { Analytics: {} }; + +export = AnalyticsNode.Analytics; diff --git a/tests/files/typescript-no-compiler-options/index.d.ts b/tests/files/typescript-no-compiler-options/index.d.ts new file mode 100644 index 0000000000..953c3410b1 --- /dev/null +++ b/tests/files/typescript-no-compiler-options/index.d.ts @@ -0,0 +1,3 @@ +export as namespace Foo + +export function bar(): void diff --git a/tests/files/typescript-no-compiler-options/tsconfig.json b/tests/files/typescript-no-compiler-options/tsconfig.json new file mode 100644 index 0000000000..2c63c08510 --- /dev/null +++ b/tests/files/typescript-no-compiler-options/tsconfig.json @@ -0,0 +1,2 @@ +{ +} diff --git a/tests/files/webpack.config.js b/tests/files/webpack.config.js index 980c32425e..6a5dc0b88c 100644 --- a/tests/files/webpack.config.js +++ b/tests/files/webpack.config.js @@ -2,5 +2,8 @@ module.exports = { resolve: { extensions: ['', '.js', '.jsx'], root: __dirname, + alias: { + 'alias/chai$': 'chai' // alias for no-extraneous-dependencies tests + } }, } diff --git a/tests/files/with-typescript-dev-dependencies/package.json b/tests/files/with-typescript-dev-dependencies/package.json new file mode 100644 index 0000000000..e17fbd9777 --- /dev/null +++ b/tests/files/with-typescript-dev-dependencies/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@types/json-schema": "*" + } +} diff --git a/tests/src/cli.js b/tests/src/cli.js index 5e0a74e36c..033513ad62 100644 --- a/tests/src/cli.js +++ b/tests/src/cli.js @@ -1,16 +1,16 @@ /** * tests that require fully booting up ESLint */ -import path from 'path' +import path from 'path'; -import { expect } from 'chai' -import { CLIEngine } from 'eslint' -import eslintPkg from 'eslint/package.json' -import semver from 'semver' +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 () { - let cli + let cli; before(function () { cli = new CLIEngine({ useEslintrc: false, @@ -19,31 +19,31 @@ describe('CLI regression tests', function () { rules: { 'named': 2, }, - }) - }) + }); + }); it("doesn't throw an error on gratuitous, erroneous self-reference", function () { - expect(() => cli.executeOnFiles(['./tests/files/issue210.js'])).not.to.throw() - }) - }) + expect(() => cli.executeOnFiles(['./tests/files/issue210.js'])).not.to.throw(); + }); + }); describe('issue #1645', function () { - let cli + let cli; beforeEach(function () { if (semver.satisfies(eslintPkg.version, '< 6')) { - this.skip() + 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]) + const invalidJSON = './tests/files/just-json-files/invalid.json'; + const results = cli.executeOnFiles([invalidJSON]); expect(results).to.eql({ results: [ { @@ -58,14 +58,14 @@ describe('CLI regression tests', function () { nodeType: results.results[0].messages[0].nodeType, // we don't care about this one ruleId: 'json/*', severity: 2, - source: '\n', + source: results.results[0].messages[0].source, // NewLine-characters might differ depending on git-settings }, ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, - source: ',\n', + source: results.results[0].source, // NewLine-characters might differ depending on git-settings }, ], errorCount: 1, @@ -73,7 +73,7 @@ describe('CLI regression tests', function () { fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: results.usedDeprecatedRules, // we don't care about this one - }) - }) - }) -}) + }); + }); + }); +}); diff --git a/tests/src/config/typescript.js b/tests/src/config/typescript.js index d5e3ec8507..34df49b38a 100644 --- a/tests/src/config/typescript.js +++ b/tests/src/config/typescript.js @@ -1,14 +1,14 @@ -import path from 'path' -import { expect } from 'chai' +import path from 'path'; +import { expect } from 'chai'; -const config = require(path.join(__dirname, '..', '..', '..', 'config', 'typescript')) +const config = require(path.join(__dirname, '..', '..', '..', 'config', 'typescript')); describe('config typescript', () => { // https://github.com/benmosher/eslint-plugin-import/issues/1525 it('should mark @types paths as external', () => { - const externalModuleFolders = config.settings['import/external-module-folders'] - expect(externalModuleFolders).to.exist - expect(externalModuleFolders).to.contain('node_modules') - expect(externalModuleFolders).to.contain('node_modules/@types') - }) -}) + const externalModuleFolders = config.settings['import/external-module-folders']; + expect(externalModuleFolders).to.exist; + expect(externalModuleFolders).to.contain('node_modules'); + expect(externalModuleFolders).to.contain('node_modules/@types'); + }); +}); diff --git a/tests/src/core/docsUrl.js b/tests/src/core/docsUrl.js index 2ba778a4a5..57b186b2f7 100644 --- a/tests/src/core/docsUrl.js +++ b/tests/src/core/docsUrl.js @@ -1,14 +1,14 @@ -import { expect } from 'chai' +import { expect } from 'chai'; -import pkg from '../../../package.json' -import docsUrl from '../../../src/docsUrl' +import pkg from '../../../package.json'; +import docsUrl from '../../../src/docsUrl'; describe('docsUrl', function () { it('returns the rule documentation URL when given a rule name', function () { - expect(docsUrl('foo')).to.equal(`https://github.com/benmosher/eslint-plugin-import/blob/v${pkg.version}/docs/rules/foo.md`) - }) + expect(docsUrl('foo')).to.equal(`https://github.com/benmosher/eslint-plugin-import/blob/v${pkg.version}/docs/rules/foo.md`); + }); it('supports an optional commit-ish parameter', function () { - expect(docsUrl('foo', 'bar')).to.equal('https://github.com/benmosher/eslint-plugin-import/blob/bar/docs/rules/foo.md') - }) -}) + expect(docsUrl('foo', 'bar')).to.equal('https://github.com/benmosher/eslint-plugin-import/blob/bar/docs/rules/foo.md'); + }); +}); diff --git a/tests/src/core/eslintParser.js b/tests/src/core/eslintParser.js index 3870ccc6e4..492b83ea4d 100644 --- a/tests/src/core/eslintParser.js +++ b/tests/src/core/eslintParser.js @@ -2,6 +2,6 @@ module.exports = { parseForESLint: function() { return { ast: {}, - } + }; }, -} +}; diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index d61544e7a9..5a9bdadb15 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -1,186 +1,189 @@ -import { expect } from 'chai' -import semver from 'semver' -import eslintPkg from 'eslint/package.json' -import ExportMap from '../../../src/ExportMap' +import { expect } from 'chai'; +import semver from 'semver'; +import sinon from 'sinon'; +import eslintPkg from 'eslint/package.json'; +import * as tsConfigLoader from 'tsconfig-paths/lib/tsconfig-loader'; +import ExportMap from '../../../src/ExportMap'; -import * as fs from 'fs' +import * as fs from 'fs'; -import { getFilename } from '../utils' -import * as unambiguous from 'eslint-module-utils/unambiguous' +import { getFilename } from '../utils'; +import * as unambiguous from 'eslint-module-utils/unambiguous'; describe('ExportMap', function () { const fakeContext = { getFilename: getFilename, settings: {}, parserPath: 'babel-eslint', - } + }; it('handles ExportAllDeclaration', function () { - var imports + let imports; expect(function () { - imports = ExportMap.get('./export-all', fakeContext) - }).not.to.throw(Error) + imports = ExportMap.get('./export-all', fakeContext); + }).not.to.throw(Error); - expect(imports).to.exist - expect(imports.has('foo')).to.be.true + expect(imports).to.exist; + expect(imports.has('foo')).to.be.true; - }) + }); it('returns a cached copy on subsequent requests', function () { expect(ExportMap.get('./named-exports', fakeContext)) - .to.exist.and.equal(ExportMap.get('./named-exports', fakeContext)) - }) + .to.exist.and.equal(ExportMap.get('./named-exports', fakeContext)); + }); it('does not return a cached copy after modification', (done) => { - const firstAccess = ExportMap.get('./mutator', fakeContext) - expect(firstAccess).to.exist + const firstAccess = ExportMap.get('./mutator', fakeContext); + expect(firstAccess).to.exist; // mutate (update modified time) - const newDate = new Date() + const newDate = new Date(); fs.utimes(getFilename('mutator.js'), newDate, newDate, (error) => { - expect(error).not.to.exist - expect(ExportMap.get('./mutator', fakeContext)).not.to.equal(firstAccess) - done() - }) - }) + expect(error).not.to.exist; + expect(ExportMap.get('./mutator', fakeContext)).not.to.equal(firstAccess); + done(); + }); + }); it('does not return a cached copy with different settings', () => { - const firstAccess = ExportMap.get('./named-exports', fakeContext) - expect(firstAccess).to.exist + const firstAccess = ExportMap.get('./named-exports', fakeContext); + expect(firstAccess).to.exist; const differentSettings = Object.assign( {}, fakeContext, - { parserPath: 'espree' }) + { parserPath: 'espree' }, + ); expect(ExportMap.get('./named-exports', differentSettings)) .to.exist.and - .not.to.equal(firstAccess) - }) + .not.to.equal(firstAccess); + }); it('does not throw for a missing file', function () { - var imports + let imports; expect(function () { - imports = ExportMap.get('./does-not-exist', fakeContext) - }).not.to.throw(Error) + imports = ExportMap.get('./does-not-exist', fakeContext); + }).not.to.throw(Error); - expect(imports).not.to.exist + expect(imports).not.to.exist; - }) + }); it('exports explicit names for a missing file in exports', function () { - var imports + let imports; expect(function () { - imports = ExportMap.get('./exports-missing', fakeContext) - }).not.to.throw(Error) + imports = ExportMap.get('./exports-missing', fakeContext); + }).not.to.throw(Error); - expect(imports).to.exist - expect(imports.has('bar')).to.be.true + expect(imports).to.exist; + expect(imports.has('bar')).to.be.true; - }) + }); it('finds exports for an ES7 module with babel-eslint', function () { - const path = getFilename('jsx/FooES7.js') - , contents = fs.readFileSync(path, { encoding: 'utf8' }) - var imports = ExportMap.parse( + const path = getFilename('jsx/FooES7.js'); + const contents = fs.readFileSync(path, { encoding: 'utf8' }); + const imports = ExportMap.parse( path, contents, { parserPath: 'babel-eslint', settings: {} }, - ) + ); - expect(imports, 'imports').to.exist - expect(imports.errors).to.be.empty - expect(imports.get('default'), 'default export').to.exist - expect(imports.has('Bar')).to.be.true - }) + expect(imports, 'imports').to.exist; + expect(imports.errors).to.be.empty; + expect(imports.get('default'), 'default export').to.exist; + expect(imports.has('Bar')).to.be.true; + }); context('deprecation metadata', function () { function jsdocTests(parseContext, lineEnding) { context('deprecated imports', function () { - let imports + let imports; before('parse file', function () { - const path = getFilename('deprecated.js') - , contents = fs.readFileSync(path, { encoding: 'utf8' }).replace(/[\r]\n/g, lineEnding) - imports = ExportMap.parse(path, contents, parseContext) + const path = getFilename('deprecated.js'); + const contents = fs.readFileSync(path, { encoding: 'utf8' }).replace(/[\r]\n/g, lineEnding); + imports = ExportMap.parse(path, contents, parseContext); // sanity checks - expect(imports.errors).to.be.empty - }) + expect(imports.errors).to.be.empty; + }); it('works with named imports.', function () { - expect(imports.has('fn')).to.be.true + expect(imports.has('fn')).to.be.true; expect(imports.get('fn')) - .to.have.nested.property('doc.tags[0].title', 'deprecated') + .to.have.nested.property('doc.tags[0].title', 'deprecated'); expect(imports.get('fn')) - .to.have.nested.property('doc.tags[0].description', 'please use \'x\' instead.') - }) + .to.have.nested.property('doc.tags[0].description', 'please use \'x\' instead.'); + }); it('works with default imports.', function () { - expect(imports.has('default')).to.be.true - const importMeta = imports.get('default') + expect(imports.has('default')).to.be.true; + const importMeta = imports.get('default'); - expect(importMeta).to.have.nested.property('doc.tags[0].title', 'deprecated') - expect(importMeta).to.have.nested.property('doc.tags[0].description', 'this is awful, use NotAsBadClass.') - }) + expect(importMeta).to.have.nested.property('doc.tags[0].title', 'deprecated'); + expect(importMeta).to.have.nested.property('doc.tags[0].description', 'this is awful, use NotAsBadClass.'); + }); it('works with variables.', function () { - expect(imports.has('MY_TERRIBLE_ACTION')).to.be.true - const importMeta = imports.get('MY_TERRIBLE_ACTION') + expect(imports.has('MY_TERRIBLE_ACTION')).to.be.true; + const importMeta = imports.get('MY_TERRIBLE_ACTION'); expect(importMeta).to.have.nested.property( - 'doc.tags[0].title', 'deprecated') + 'doc.tags[0].title', 'deprecated'); expect(importMeta).to.have.nested.property( - 'doc.tags[0].description', 'please stop sending/handling this action type.') - }) + 'doc.tags[0].description', 'please stop sending/handling this action type.'); + }); context('multi-line variables', function () { it('works for the first one', function () { - expect(imports.has('CHAIN_A')).to.be.true - const importMeta = imports.get('CHAIN_A') + expect(imports.has('CHAIN_A')).to.be.true; + const importMeta = imports.get('CHAIN_A'); expect(importMeta).to.have.nested.property( - 'doc.tags[0].title', 'deprecated') + 'doc.tags[0].title', 'deprecated'); expect(importMeta).to.have.nested.property( - 'doc.tags[0].description', 'this chain is awful') - }) + 'doc.tags[0].description', 'this chain is awful'); + }); it('works for the second one', function () { - expect(imports.has('CHAIN_B')).to.be.true - const importMeta = imports.get('CHAIN_B') + expect(imports.has('CHAIN_B')).to.be.true; + const importMeta = imports.get('CHAIN_B'); expect(importMeta).to.have.nested.property( - 'doc.tags[0].title', 'deprecated') + 'doc.tags[0].title', 'deprecated'); expect(importMeta).to.have.nested.property( - 'doc.tags[0].description', 'so awful') - }) + 'doc.tags[0].description', 'so awful'); + }); it('works for the third one, etc.', function () { - expect(imports.has('CHAIN_C')).to.be.true - const importMeta = imports.get('CHAIN_C') + expect(imports.has('CHAIN_C')).to.be.true; + const importMeta = imports.get('CHAIN_C'); expect(importMeta).to.have.nested.property( - 'doc.tags[0].title', 'deprecated') + 'doc.tags[0].title', 'deprecated'); expect(importMeta).to.have.nested.property( - 'doc.tags[0].description', 'still terrible') - }) - }) - }) + 'doc.tags[0].description', 'still terrible'); + }); + }); + }); context('full module', function () { - let imports + let imports; before('parse file', function () { - const path = getFilename('deprecated-file.js') - , contents = fs.readFileSync(path, { encoding: 'utf8' }) - imports = ExportMap.parse(path, contents, parseContext) + const path = getFilename('deprecated-file.js'); + const contents = fs.readFileSync(path, { encoding: 'utf8' }); + imports = ExportMap.parse(path, contents, parseContext); // sanity checks - expect(imports.errors).to.be.empty - }) + expect(imports.errors).to.be.empty; + }); it('has JSDoc metadata', function () { - expect(imports.doc).to.exist - }) - }) + expect(imports.doc).to.exist; + }); + }); } context('default parser', function () { @@ -192,7 +195,7 @@ describe('ExportMap', function () { attachComment: true, }, settings: {}, - }, '\n') + }, '\n'); jsdocTests({ parserPath: 'espree', parserOptions: { @@ -201,8 +204,8 @@ describe('ExportMap', function () { attachComment: true, }, settings: {}, - }, '\r\n') - }) + }, '\r\n'); + }); context('babel-eslint', function () { jsdocTests({ @@ -213,7 +216,7 @@ describe('ExportMap', function () { attachComment: true, }, settings: {}, - }, '\n') + }, '\n'); jsdocTests({ parserPath: 'babel-eslint', parserOptions: { @@ -222,128 +225,128 @@ describe('ExportMap', function () { attachComment: true, }, settings: {}, - }, '\r\n') - }) - }) + }, '\r\n'); + }); + }); context('exported static namespaces', function () { - const espreeContext = { parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} } - const babelContext = { parserPath: 'babel-eslint', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} } + const espreeContext = { parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} }; + const babelContext = { parserPath: 'babel-eslint', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} }; it('works with espree & traditional namespace exports', function () { - const path = getFilename('deep/a.js') - , contents = fs.readFileSync(path, { encoding: 'utf8' }) - const a = ExportMap.parse(path, contents, espreeContext) - expect(a.errors).to.be.empty - expect(a.get('b').namespace).to.exist - expect(a.get('b').namespace.has('c')).to.be.true - }) + const path = getFilename('deep/a.js'); + const contents = fs.readFileSync(path, { encoding: 'utf8' }); + const a = ExportMap.parse(path, contents, espreeContext); + expect(a.errors).to.be.empty; + expect(a.get('b').namespace).to.exist; + expect(a.get('b').namespace.has('c')).to.be.true; + }); it('captures namespace exported as default', function () { - const path = getFilename('deep/default.js') - , contents = fs.readFileSync(path, { encoding: 'utf8' }) - const def = ExportMap.parse(path, contents, espreeContext) - expect(def.errors).to.be.empty - expect(def.get('default').namespace).to.exist - expect(def.get('default').namespace.has('c')).to.be.true - }) + const path = getFilename('deep/default.js'); + const contents = fs.readFileSync(path, { encoding: 'utf8' }); + const def = ExportMap.parse(path, contents, espreeContext); + expect(def.errors).to.be.empty; + expect(def.get('default').namespace).to.exist; + expect(def.get('default').namespace.has('c')).to.be.true; + }); it('works with babel-eslint & ES7 namespace exports', function () { - const path = getFilename('deep-es7/a.js') - , contents = fs.readFileSync(path, { encoding: 'utf8' }) - const a = ExportMap.parse(path, contents, babelContext) - expect(a.errors).to.be.empty - expect(a.get('b').namespace).to.exist - expect(a.get('b').namespace.has('c')).to.be.true - }) - }) + const path = getFilename('deep-es7/a.js'); + const contents = fs.readFileSync(path, { encoding: 'utf8' }); + const a = ExportMap.parse(path, contents, babelContext); + expect(a.errors).to.be.empty; + expect(a.get('b').namespace).to.exist; + expect(a.get('b').namespace.has('c')).to.be.true; + }); + }); context('deep namespace caching', function () { - const espreeContext = { parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} } - let a + const espreeContext = { parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, settings: {} }; + let a; before('sanity check and prime cache', function (done) { // first version fs.writeFileSync(getFilename('deep/cache-2.js'), - fs.readFileSync(getFilename('deep/cache-2a.js'))) + fs.readFileSync(getFilename('deep/cache-2a.js'))); - const path = getFilename('deep/cache-1.js') - , contents = fs.readFileSync(path, { encoding: 'utf8' }) - a = ExportMap.parse(path, contents, espreeContext) - expect(a.errors).to.be.empty + const path = getFilename('deep/cache-1.js'); + const contents = fs.readFileSync(path, { encoding: 'utf8' }); + a = ExportMap.parse(path, contents, espreeContext); + expect(a.errors).to.be.empty; - expect(a.get('b').namespace).to.exist - expect(a.get('b').namespace.has('c')).to.be.true + expect(a.get('b').namespace).to.exist; + expect(a.get('b').namespace.has('c')).to.be.true; // wait ~1s, cache check is 1s resolution setTimeout(function reup() { - fs.unlinkSync(getFilename('deep/cache-2.js')) + fs.unlinkSync(getFilename('deep/cache-2.js')); // swap in a new file and touch it fs.writeFileSync(getFilename('deep/cache-2.js'), - fs.readFileSync(getFilename('deep/cache-2b.js'))) - done() - }, 1100) - }) + fs.readFileSync(getFilename('deep/cache-2b.js'))); + done(); + }, 1100); + }); it('works', function () { - expect(a.get('b').namespace.has('c')).to.be.false - }) + expect(a.get('b').namespace.has('c')).to.be.false; + }); - after('remove test file', (done) => fs.unlink(getFilename('deep/cache-2.js'), done)) - }) + after('remove test file', (done) => fs.unlink(getFilename('deep/cache-2.js'), done)); + }); context('Map API', function () { context('#size', function () { it('counts the names', () => expect(ExportMap.get('./named-exports', fakeContext)) - .to.have.property('size', 10)) + .to.have.property('size', 12)); it('includes exported namespace size', () => expect(ExportMap.get('./export-all', fakeContext)) - .to.have.property('size', 1)) + .to.have.property('size', 1)); - }) - }) + }); + }); context('issue #210: self-reference', function () { it(`doesn't crash`, function () { - expect(() => ExportMap.get('./narcissist', fakeContext)).not.to.throw(Error) - }) + expect(() => ExportMap.get('./narcissist', fakeContext)).not.to.throw(Error); + }); it(`'has' circular reference`, function () { expect(ExportMap.get('./narcissist', fakeContext)) - .to.exist.and.satisfy(m => m.has('soGreat')) - }) + .to.exist.and.satisfy(m => m.has('soGreat')); + }); it(`can 'get' circular reference`, function () { expect(ExportMap.get('./narcissist', fakeContext)) - .to.exist.and.satisfy(m => m.get('soGreat') != null) - }) - }) + .to.exist.and.satisfy(m => m.get('soGreat') != null); + }); + }); context('issue #478: never parse non-whitelist extensions', function () { const context = Object.assign({}, fakeContext, - { settings: { 'import/extensions': ['.js'] } }) + { settings: { 'import/extensions': ['.js'] } }); - let imports + let imports; before('load imports', function () { - imports = ExportMap.get('./typescript.ts', context) - }) + imports = ExportMap.get('./typescript.ts', context); + }); it('returns nothing for a TypeScript file', function () { - expect(imports).not.to.exist - }) + expect(imports).not.to.exist; + }); - }) + }); context('alternate parsers', function () { const configs = [ // ['string form', { 'typescript-eslint-parser': '.ts' }], - ] + ]; - if (semver.satisfies(eslintPkg.version, '>5.0.0')) { - configs.push(['array form', { '@typescript-eslint/parser': ['.ts', '.tsx'] }]) + if (semver.satisfies(eslintPkg.version, '>5')) { + configs.push(['array form', { '@typescript-eslint/parser': ['.ts', '.tsx'] }]); } - if (semver.satisfies(eslintPkg.version, '<6.0.0')) { - configs.push(['array form', { 'typescript-eslint-parser': ['.ts', '.tsx'] }]) + if (semver.satisfies(eslintPkg.version, '<6')) { + configs.push(['array form', { 'typescript-eslint-parser': ['.ts', '.tsx'] }]); } configs.forEach(([description, parserConfig]) => { @@ -353,45 +356,78 @@ describe('ExportMap', function () { { settings: { 'import/extensions': ['.js'], 'import/parsers': parserConfig, - } }) + } }); - let imports + let imports; before('load imports', function () { - this.timeout(20000) // takes a long time :shrug: - imports = ExportMap.get('./typescript.ts', context) - }) + this.timeout(20000); // takes a long time :shrug: + sinon.spy(tsConfigLoader, 'tsConfigLoader'); + imports = ExportMap.get('./typescript.ts', context); + }); + after('clear spies', function () { + tsConfigLoader.tsConfigLoader.restore(); + }); it('returns something for a TypeScript file', function () { - expect(imports).to.exist - }) + expect(imports).to.exist; + }); it('has no parse errors', function () { - expect(imports).property('errors').to.be.empty - }) + expect(imports).property('errors').to.be.empty; + }); it('has exported function', function () { - expect(imports.has('getFoo')).to.be.true - }) + expect(imports.has('getFoo')).to.be.true; + }); it('has exported typedef', function () { - expect(imports.has('MyType')).to.be.true - }) + expect(imports.has('MyType')).to.be.true; + }); it('has exported enum', function () { - expect(imports.has('MyEnum')).to.be.true - }) + expect(imports.has('MyEnum')).to.be.true; + }); it('has exported interface', function () { - expect(imports.has('Foo')).to.be.true - }) + expect(imports.has('Foo')).to.be.true; + }); it('has exported abstract class', function () { - expect(imports.has('Bar')).to.be.true - }) - }) - }) - - }) + expect(imports.has('Bar')).to.be.true; + }); + + it('should cache tsconfig until tsconfigRootDir parser option changes', function () { + const customContext = Object.assign( + {}, + context, + { + parserOptions: { + tsconfigRootDir: null, + }, + }, + ); + expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(0); + ExportMap.parse('./baz.ts', 'export const baz = 5', customContext); + expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(1); + ExportMap.parse('./baz.ts', 'export const baz = 5', customContext); + expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(1); + + const differentContext = Object.assign( + {}, + context, + { + parserOptions: { + tsconfigRootDir: process.cwd(), + }, + }, + ); + + ExportMap.parse('./baz.ts', 'export const baz = 5', differentContext); + expect(tsConfigLoader.tsConfigLoader.callCount).to.equal(2); + }); + }); + }); + }); // todo: move to utils describe('unambiguous regex', function () { @@ -401,15 +437,15 @@ describe('ExportMap', function () { ['bar.js', true], ['deep-es7/b.js', true], ['common.js', false], - ] + ]; - for (let [testFile, expectedRegexResult] of testFiles) { + for (const [testFile, expectedRegexResult] of testFiles) { it(`works for ${testFile} (${expectedRegexResult})`, function () { - const content = fs.readFileSync('./tests/files/' + testFile, 'utf8') - expect(unambiguous.test(content)).to.equal(expectedRegexResult) - }) + const content = fs.readFileSync('./tests/files/' + testFile, 'utf8'); + expect(unambiguous.test(content)).to.equal(expectedRegexResult); + }); } - }) + }); -}) +}); diff --git a/tests/src/core/hash.js b/tests/src/core/hash.js index f8dd4b49ac..e75783fb06 100644 --- a/tests/src/core/hash.js +++ b/tests/src/core/hash.js @@ -1,76 +1,76 @@ -import { expect } from 'chai' +import { expect } from 'chai'; -import hashify, { hashArray, hashObject } from 'eslint-module-utils/hash' +import hashify, { hashArray, hashObject } from 'eslint-module-utils/hash'; -const createHash = require('crypto').createHash +const createHash = require('crypto').createHash; function expectHash(actualHash, expectedString) { - const expectedHash = createHash('sha256') - expectedHash.update(expectedString) - expect(actualHash.digest('hex'), 'to be a hex digest of sha256 hash of string <' + expectedString + '>').to.equal(expectedHash.digest('hex')) + const expectedHash = createHash('sha256'); + expectedHash.update(expectedString); + expect(actualHash.digest('hex'), 'to be a hex digest of sha256 hash of string <' + expectedString + '>').to.equal(expectedHash.digest('hex')); } describe('hash', function () { describe('hashify', function () { it('handles null', function () { - expectHash(hashify(null), 'null') - }) + expectHash(hashify(null), 'null'); + }); it('handles undefined', function () { - expectHash(hashify(undefined), 'undefined') - }) + expectHash(hashify(undefined), 'undefined'); + }); it('handles numbers', function () { - expectHash(hashify(123.456), '123.456') - }) + expectHash(hashify(123.456), '123.456'); + }); it('handles strings', function () { - expectHash(hashify('a string'), '"a string"') - }) + expectHash(hashify('a string'), '"a string"'); + }); it('handles Array instances', function () { - expectHash(hashify([ 'a string' ]), '["a string",]') - }) + expectHash(hashify([ 'a string' ]), '["a string",]'); + }); it('handles empty Array instances', function () { - expectHash(hashify([]), '[]') - }) + expectHash(hashify([]), '[]'); + }); it('handles Object instances', function () { - expectHash(hashify({ foo: 123.456, 'a key': 'a value' }), '{"a key":"a value","foo":123.456,}') - }) + expectHash(hashify({ foo: 123.456, 'a key': 'a value' }), '{"a key":"a value","foo":123.456,}'); + }); it('handles nested Object instances', function () { - expectHash(hashify({ foo: 123.456, 'a key': 'a value', obj: { abc: { def: 'ghi' } } }), '{"a key":"a value","foo":123.456,"obj":{"abc":{"def":"ghi",},},}') - }) + expectHash(hashify({ foo: 123.456, 'a key': 'a value', obj: { abc: { def: 'ghi' } } }), '{"a key":"a value","foo":123.456,"obj":{"abc":{"def":"ghi",},},}'); + }); it('handles nested Object and Array instances', function () { - expectHash(hashify({ foo: 123.456, 'a key': 'a value', obj: { arr: [ { def: 'ghi' } ] } }), '{"a key":"a value","foo":123.456,"obj":{"arr":[{"def":"ghi",},],},}') - }) - }) + expectHash(hashify({ foo: 123.456, 'a key': 'a value', obj: { arr: [ { def: 'ghi' } ] } }), '{"a key":"a value","foo":123.456,"obj":{"arr":[{"def":"ghi",},],},}'); + }); + }); describe('hashArray', function () { it('handles Array instances', function () { - expectHash(hashArray([ 'a string' ]), '["a string",]') - }) + expectHash(hashArray([ 'a string' ]), '["a string",]'); + }); it('handles empty Array instances', function () { - expectHash(hashArray([]), '[]') - }) - }) + expectHash(hashArray([]), '[]'); + }); + }); describe('hashObject', function () { it('handles Object instances', function () { - expectHash(hashObject({ foo: 123.456, 'a key': 'a value' }), '{"a key":"a value","foo":123.456,}') - }) + expectHash(hashObject({ foo: 123.456, 'a key': 'a value' }), '{"a key":"a value","foo":123.456,}'); + }); it('handles nested Object instances', function () { - expectHash(hashObject({ foo: 123.456, 'a key': 'a value', obj: { abc: { def: 'ghi' } } }), '{"a key":"a value","foo":123.456,"obj":{"abc":{"def":"ghi",},},}') - }) + expectHash(hashObject({ foo: 123.456, 'a key': 'a value', obj: { abc: { def: 'ghi' } } }), '{"a key":"a value","foo":123.456,"obj":{"abc":{"def":"ghi",},},}'); + }); it('handles nested Object and Array instances', function () { - expectHash(hashObject({ foo: 123.456, 'a key': 'a value', obj: { arr: [ { def: 'ghi' } ] } }), '{"a key":"a value","foo":123.456,"obj":{"arr":[{"def":"ghi",},],},}') - }) - }) + expectHash(hashObject({ foo: 123.456, 'a key': 'a value', obj: { arr: [ { def: 'ghi' } ] } }), '{"a key":"a value","foo":123.456,"obj":{"arr":[{"def":"ghi",},],},}'); + }); + }); -}) +}); diff --git a/tests/src/core/ignore.js b/tests/src/core/ignore.js index 8870158a51..2b2126c8b5 100644 --- a/tests/src/core/ignore.js +++ b/tests/src/core/ignore.js @@ -1,90 +1,90 @@ -import { expect } from 'chai' +import { expect } from 'chai'; -import isIgnored, { getFileExtensions, hasValidExtension } from 'eslint-module-utils/ignore' +import isIgnored, { getFileExtensions, hasValidExtension } from 'eslint-module-utils/ignore'; -import * as utils from '../utils' +import * as utils from '../utils'; describe('ignore', function () { describe('isIgnored', function () { it('ignores paths with extensions other than .js', function () { - const testContext = utils.testContext({}) + const testContext = utils.testContext({}); - expect(isIgnored('../files/foo.js', testContext)).to.equal(false) + expect(isIgnored('../files/foo.js', testContext)).to.equal(false); - expect(isIgnored('../files/bar.jsx', testContext)).to.equal(true) + expect(isIgnored('../files/bar.jsx', testContext)).to.equal(true); - expect(isIgnored('../files/typescript.ts', testContext)).to.equal(true) + expect(isIgnored('../files/typescript.ts', testContext)).to.equal(true); - expect(isIgnored('../files/ignore.invalid.extension', testContext)).to.equal(true) - }) + expect(isIgnored('../files/ignore.invalid.extension', testContext)).to.equal(true); + }); it('ignores paths with invalid extensions when configured with import/extensions', function () { - const testContext = utils.testContext({ 'import/extensions': [ '.js', '.jsx', '.ts' ] }) + const testContext = utils.testContext({ 'import/extensions': [ '.js', '.jsx', '.ts' ] }); - expect(isIgnored('../files/foo.js', testContext)).to.equal(false) + expect(isIgnored('../files/foo.js', testContext)).to.equal(false); - expect(isIgnored('../files/bar.jsx', testContext)).to.equal(false) + expect(isIgnored('../files/bar.jsx', testContext)).to.equal(false); - expect(isIgnored('../files/typescript.ts', testContext)).to.equal(false) + expect(isIgnored('../files/typescript.ts', testContext)).to.equal(false); - expect(isIgnored('../files/ignore.invalid.extension', testContext)).to.equal(true) - }) - }) + expect(isIgnored('../files/ignore.invalid.extension', testContext)).to.equal(true); + }); + }); describe('hasValidExtension', function () { it('assumes only .js as valid by default', function () { - const testContext = utils.testContext({}) + const testContext = utils.testContext({}); - expect(hasValidExtension('../files/foo.js', testContext)).to.equal(true) + expect(hasValidExtension('../files/foo.js', testContext)).to.equal(true); - expect(hasValidExtension('../files/foo.jsx', testContext)).to.equal(false) + expect(hasValidExtension('../files/foo.jsx', testContext)).to.equal(false); - expect(hasValidExtension('../files/foo.css', testContext)).to.equal(false) + expect(hasValidExtension('../files/foo.css', testContext)).to.equal(false); - expect(hasValidExtension('../files/foo.invalid.extension', testContext)).to.equal(false) - }) + expect(hasValidExtension('../files/foo.invalid.extension', testContext)).to.equal(false); + }); it('can be configured with import/extensions', function () { - const testContext = utils.testContext({ 'import/extensions': [ '.foo', '.bar' ] }) + const testContext = utils.testContext({ 'import/extensions': [ '.foo', '.bar' ] }); - expect(hasValidExtension('../files/foo.foo', testContext)).to.equal(true) + expect(hasValidExtension('../files/foo.foo', testContext)).to.equal(true); - expect(hasValidExtension('../files/foo.bar', testContext)).to.equal(true) + expect(hasValidExtension('../files/foo.bar', testContext)).to.equal(true); - expect(hasValidExtension('../files/foo.js', testContext)).to.equal(false) - }) - }) + expect(hasValidExtension('../files/foo.js', testContext)).to.equal(false); + }); + }); describe('getFileExtensions', function () { it('returns a set with the file extension ".js" if "import/extensions" is not configured', function () { - const fileExtensions = getFileExtensions({}) + const fileExtensions = getFileExtensions({}); - expect(fileExtensions).to.include('.js') - }) + expect(fileExtensions).to.include('.js'); + }); it('returns a set with the file extensions configured in "import/extension"', function () { const settings = { 'import/extensions': ['.js', '.jsx'], - } + }; - const fileExtensions = getFileExtensions(settings) + const fileExtensions = getFileExtensions(settings); - expect(fileExtensions).to.include('.js') - expect(fileExtensions).to.include('.jsx') - }) + expect(fileExtensions).to.include('.js'); + expect(fileExtensions).to.include('.jsx'); + }); it('returns a set with the file extensions configured in "import/extension" and "import/parsers"', function () { const settings = { 'import/parsers': { 'typescript-eslint-parser': ['.ts', '.tsx'], }, - } + }; - const fileExtensions = getFileExtensions(settings) + const fileExtensions = getFileExtensions(settings); - expect(fileExtensions).to.include('.js') // If "import/extensions" is not configured, this is the default - expect(fileExtensions).to.include('.ts') - expect(fileExtensions).to.include('.tsx') - }) - }) -}) + expect(fileExtensions).to.include('.js'); // If "import/extensions" is not configured, this is the default + expect(fileExtensions).to.include('.ts'); + expect(fileExtensions).to.include('.tsx'); + }); + }); +}); diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js index b3bfb6bb62..371a3d7397 100644 --- a/tests/src/core/importType.js +++ b/tests/src/core/importType.js @@ -1,158 +1,158 @@ -import { expect } from 'chai' -import * as path from 'path' +import { expect } from 'chai'; +import * as path from 'path'; -import importType, {isExternalModule} from 'core/importType' +import importType, { isExternalModule, isScopedModule } from 'core/importType'; -import { testContext, testFilePath } from '../utils' +import { testContext, testFilePath } from '../utils'; describe('importType(name)', function () { - const context = testContext() - const pathToTestFiles = path.join(__dirname, '..', '..', 'files') + const context = testContext(); + const pathToTestFiles = path.join(__dirname, '..', '..', 'files'); it("should return 'absolute' for paths starting with a /", function() { - expect(importType('/', context)).to.equal('absolute') - expect(importType('/path', context)).to.equal('absolute') - expect(importType('/some/path', context)).to.equal('absolute') - }) + expect(importType('/', context)).to.equal('absolute'); + expect(importType('/path', context)).to.equal('absolute'); + expect(importType('/some/path', context)).to.equal('absolute'); + }); it("should return 'builtin' for node.js modules", function() { - expect(importType('fs', context)).to.equal('builtin') - expect(importType('path', context)).to.equal('builtin') - }) + expect(importType('fs', context)).to.equal('builtin'); + expect(importType('path', context)).to.equal('builtin'); + }); it("should return 'external' for non-builtin modules without a relative path", function() { - expect(importType('lodash', context)).to.equal('external') - expect(importType('async', context)).to.equal('external') - expect(importType('chalk', context)).to.equal('external') - expect(importType('foo', context)).to.equal('external') - expect(importType('lodash.find', context)).to.equal('external') - expect(importType('lodash/fp', context)).to.equal('external') - }) + expect(importType('lodash', context)).to.equal('external'); + expect(importType('async', context)).to.equal('external'); + expect(importType('chalk', context)).to.equal('external'); + expect(importType('foo', context)).to.equal('external'); + expect(importType('lodash.find', context)).to.equal('external'); + expect(importType('lodash/fp', context)).to.equal('external'); + }); it("should return 'external' for scopes packages", function() { - expect(importType('@cycle/', context)).to.equal('external') - expect(importType('@cycle/core', context)).to.equal('external') - expect(importType('@cycle/dom', context)).to.equal('external') - expect(importType('@some-thing/something', context)).to.equal('external') - expect(importType('@some-thing/something/some-module', context)).to.equal('external') - expect(importType('@some-thing/something/some-directory/someModule.js', context)).to.equal('external') - }) + expect(importType('@cycle/', context)).to.equal('external'); + expect(importType('@cycle/core', context)).to.equal('external'); + expect(importType('@cycle/dom', context)).to.equal('external'); + expect(importType('@some-thing/something', context)).to.equal('external'); + expect(importType('@some-thing/something/some-module', context)).to.equal('external'); + expect(importType('@some-thing/something/some-directory/someModule.js', context)).to.equal('external'); + }); it("should return 'external' for external modules that redirect to its parent module using package.json", function() { - expect(importType('eslint-import-test-order-redirect/module', context)).to.equal('external') - expect(importType('@eslint/import-test-order-redirect-scoped/module', context)).to.equal('external') - }) + expect(importType('eslint-import-test-order-redirect/module', context)).to.equal('external'); + expect(importType('@eslint/import-test-order-redirect-scoped/module', context)).to.equal('external'); + }); it("should return 'internal' for non-builtins resolved outside of node_modules", function () { - const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }) - expect(importType('importType', pathContext)).to.equal('internal') - }) + const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }); + expect(importType('importType', pathContext)).to.equal('internal'); + }); it("should return 'internal' for scoped packages resolved outside of node_modules", function () { - const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }) - expect(importType('@importType/index', pathContext)).to.equal('internal') - }) + const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }); + expect(importType('@importType/index', pathContext)).to.equal('internal'); + }); it("should return 'internal' for internal modules that are referenced by aliases", function () { - const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }) - expect(importType('@my-alias/fn', pathContext)).to.equal('internal') - expect(importType('@importType', pathContext)).to.equal('internal') - }) + const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }); + expect(importType('@my-alias/fn', pathContext)).to.equal('internal'); + expect(importType('@importType', pathContext)).to.equal('internal'); + }); it("should return 'internal' for aliased internal modules that look like core modules (node resolver)", function () { - const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }) - expect(importType('constants/index', pathContext)).to.equal('internal') - expect(importType('constants/', pathContext)).to.equal('internal') + const pathContext = testContext({ 'import/resolver': { node: { paths: [pathToTestFiles] } } }); + expect(importType('constants/index', pathContext)).to.equal('internal'); + expect(importType('constants/', pathContext)).to.equal('internal'); // resolves exact core modules over internal modules - expect(importType('constants', pathContext)).to.equal('builtin') - }) + expect(importType('constants', pathContext)).to.equal('builtin'); + }); it("should return 'internal' for aliased internal modules that look like core modules (webpack resolver)", function () { - const webpackConfig = { resolve: { modules: [pathToTestFiles, 'node_modules'] } } - const pathContext = testContext({ 'import/resolver': { webpack: { config: webpackConfig } } }) - expect(importType('constants/index', pathContext)).to.equal('internal') - expect(importType('constants/', pathContext)).to.equal('internal') - expect(importType('constants', pathContext)).to.equal('internal') - }) + const webpackConfig = { resolve: { modules: [pathToTestFiles, 'node_modules'] } }; + const pathContext = testContext({ 'import/resolver': { webpack: { config: webpackConfig } } }); + expect(importType('constants/index', pathContext)).to.equal('internal'); + expect(importType('constants/', pathContext)).to.equal('internal'); + expect(importType('constants', pathContext)).to.equal('internal'); + }); it("should return 'parent' for internal modules that go through the parent", function() { - expect(importType('../foo', context)).to.equal('parent') - expect(importType('../../foo', context)).to.equal('parent') - expect(importType('../bar/foo', context)).to.equal('parent') - }) + expect(importType('../foo', context)).to.equal('parent'); + expect(importType('../../foo', context)).to.equal('parent'); + expect(importType('../bar/foo', context)).to.equal('parent'); + }); it("should return 'sibling' for internal modules that are connected to one of the siblings", function() { - expect(importType('./foo', context)).to.equal('sibling') - expect(importType('./foo/bar', context)).to.equal('sibling') - expect(importType('./importType', context)).to.equal('sibling') - expect(importType('./importType/', context)).to.equal('sibling') - expect(importType('./importType/index', context)).to.equal('sibling') - expect(importType('./importType/index.js', context)).to.equal('sibling') - }) + expect(importType('./foo', context)).to.equal('sibling'); + expect(importType('./foo/bar', context)).to.equal('sibling'); + expect(importType('./importType', context)).to.equal('sibling'); + expect(importType('./importType/', context)).to.equal('sibling'); + expect(importType('./importType/index', context)).to.equal('sibling'); + expect(importType('./importType/index.js', context)).to.equal('sibling'); + }); it("should return 'index' for sibling index file", function() { - expect(importType('.', context)).to.equal('index') - expect(importType('./', context)).to.equal('index') - expect(importType('./index', context)).to.equal('index') - expect(importType('./index.js', context)).to.equal('index') - }) + expect(importType('.', context)).to.equal('index'); + expect(importType('./', context)).to.equal('index'); + expect(importType('./index', context)).to.equal('index'); + expect(importType('./index.js', context)).to.equal('index'); + }); it("should return 'unknown' for any unhandled cases", function() { - expect(importType(' /malformed', context)).to.equal('unknown') - expect(importType(' foo', context)).to.equal('unknown') - }) + expect(importType(' /malformed', context)).to.equal('unknown'); + expect(importType(' foo', context)).to.equal('unknown'); + }); it("should return 'builtin' for additional core modules", function() { // without extra config, should be marked external - expect(importType('electron', context)).to.equal('external') - expect(importType('@org/foobar', context)).to.equal('external') + expect(importType('electron', context)).to.equal('external'); + expect(importType('@org/foobar', context)).to.equal('external'); - const electronContext = testContext({ 'import/core-modules': ['electron'] }) - expect(importType('electron', electronContext)).to.equal('builtin') + const electronContext = testContext({ 'import/core-modules': ['electron'] }); + expect(importType('electron', electronContext)).to.equal('builtin'); - const scopedContext = testContext({ 'import/core-modules': ['@org/foobar'] }) - expect(importType('@org/foobar', scopedContext)).to.equal('builtin') - }) + const scopedContext = testContext({ 'import/core-modules': ['@org/foobar'] }); + expect(importType('@org/foobar', scopedContext)).to.equal('builtin'); + }); it("should return 'builtin' for resources inside additional core modules", function() { - const electronContext = testContext({ 'import/core-modules': ['electron'] }) - expect(importType('electron/some/path/to/resource.json', electronContext)).to.equal('builtin') + const electronContext = testContext({ 'import/core-modules': ['electron'] }); + expect(importType('electron/some/path/to/resource.json', electronContext)).to.equal('builtin'); - const scopedContext = testContext({ 'import/core-modules': ['@org/foobar'] }) - expect(importType('@org/foobar/some/path/to/resource.json', scopedContext)).to.equal('builtin') - }) + const scopedContext = testContext({ 'import/core-modules': ['@org/foobar'] }); + expect(importType('@org/foobar/some/path/to/resource.json', scopedContext)).to.equal('builtin'); + }); it("should return 'external' for module from 'node_modules' with default config", function() { - expect(importType('resolve', context)).to.equal('external') - }) + expect(importType('resolve', context)).to.equal('external'); + }); it("should return 'internal' for module from 'node_modules' if 'node_modules' missed in 'external-module-folders'", function() { - const foldersContext = testContext({ 'import/external-module-folders': [] }) - expect(importType('resolve', foldersContext)).to.equal('internal') - }) + const foldersContext = testContext({ 'import/external-module-folders': [] }); + expect(importType('chai', foldersContext)).to.equal('internal'); + }); it("should return 'internal' for module from 'node_modules' if its name matched 'internal-regex'", function() { - const foldersContext = testContext({ 'import/internal-regex': '^@org' }) - expect(importType('@org/foobar', foldersContext)).to.equal('internal') - }) + const foldersContext = testContext({ 'import/internal-regex': '^@org' }); + expect(importType('@org/foobar', foldersContext)).to.equal('internal'); + }); it("should return 'external' for module from 'node_modules' if its name did not match 'internal-regex'", function() { - const foldersContext = testContext({ 'import/internal-regex': '^@bar' }) - expect(importType('@org/foobar', foldersContext)).to.equal('external') - }) + const foldersContext = testContext({ 'import/internal-regex': '^@bar' }); + expect(importType('@org/foobar', foldersContext)).to.equal('external'); + }); it("should return 'external' for module from 'node_modules' if 'node_modules' contained in 'external-module-folders'", function() { - const foldersContext = testContext({ 'import/external-module-folders': ['node_modules'] }) - expect(importType('resolve', foldersContext)).to.equal('external') - }) + const foldersContext = testContext({ 'import/external-module-folders': ['node_modules'] }); + expect(importType('resolve', foldersContext)).to.equal('external'); + }); it('returns "external" for a scoped symlinked module', function() { const foldersContext = testContext({ 'import/resolver': 'node', 'import/external-module-folders': ['node_modules'], - }) - expect(importType('@test-scope/some-module', foldersContext)).to.equal('external') - }) + }); + expect(importType('@test-scope/some-module', foldersContext)).to.equal('external'); + }); // We're using Webpack resolver here since it resolves all symlinks, which means that // directory path will not contain node_modules/ but will point to the @@ -161,80 +161,86 @@ describe('importType(name)', function () { const foldersContext = testContext({ 'import/resolver': 'webpack', 'import/external-module-folders': ['symlinked-module'], - }) - expect(importType('@test-scope/some-module', foldersContext)).to.equal('external') - }) + }); + expect(importType('@test-scope/some-module', foldersContext)).to.equal('external'); + }); it('returns "internal" for a scoped module from a symlinked directory which incomplete name is contained in "external-module-folders" (webpack resolver)', function() { const foldersContext_1 = testContext({ 'import/resolver': 'webpack', 'import/external-module-folders': ['symlinked-mod'], - }) - expect(importType('@test-scope/some-module', foldersContext_1)).to.equal('internal') + }); + expect(importType('@test-scope/some-module', foldersContext_1)).to.equal('internal'); const foldersContext_2 = testContext({ 'import/resolver': 'webpack', 'import/external-module-folders': ['linked-module'], - }) - expect(importType('@test-scope/some-module', foldersContext_2)).to.equal('internal') - }) + }); + expect(importType('@test-scope/some-module', foldersContext_2)).to.equal('internal'); + }); it('returns "external" for a scoped module from a symlinked directory which partial path is contained in "external-module-folders" (webpack resolver)', function() { const originalFoldersContext = testContext({ 'import/resolver': 'webpack', 'import/external-module-folders': [], - }) - expect(importType('@test-scope/some-module', originalFoldersContext)).to.equal('internal') + }); + expect(importType('@test-scope/some-module', originalFoldersContext)).to.equal('internal'); const foldersContext = testContext({ 'import/resolver': 'webpack', - 'import/external-module-folders': ['files/symlinked-module'], - }) - expect(importType('@test-scope/some-module', foldersContext)).to.equal('external') - }) + 'import/external-module-folders': ['symlinked-module'], + }); + expect(importType('@test-scope/some-module', foldersContext)).to.equal('external'); + }); it('returns "internal" for a scoped module from a symlinked directory which partial path w/ incomplete segment is contained in "external-module-folders" (webpack resolver)', function() { const foldersContext_1 = testContext({ 'import/resolver': 'webpack', 'import/external-module-folders': ['files/symlinked-mod'], - }) - expect(importType('@test-scope/some-module', foldersContext_1)).to.equal('internal') + }); + expect(importType('@test-scope/some-module', foldersContext_1)).to.equal('internal'); const foldersContext_2 = testContext({ 'import/resolver': 'webpack', - 'import/external-module-folders': ['les/symlinked-module'], - }) - expect(importType('@test-scope/some-module', foldersContext_2)).to.equal('internal') - }) + 'import/external-module-folders': ['ymlinked-module'], + }); + expect(importType('@test-scope/some-module', foldersContext_2)).to.equal('internal'); + }); it('returns "external" for a scoped module from a symlinked directory which partial path ending w/ slash is contained in "external-module-folders" (webpack resolver)', function() { const foldersContext = testContext({ 'import/resolver': 'webpack', - 'import/external-module-folders': ['files/symlinked-module/'], - }) - expect(importType('@test-scope/some-module', foldersContext)).to.equal('external') - }) + 'import/external-module-folders': ['symlinked-module/'], + }); + expect(importType('@test-scope/some-module', foldersContext)).to.equal('external'); + }); it('returns "internal" for a scoped module from a symlinked directory when "external-module-folders" contains an absolute path resembling directory‘s relative path (webpack resolver)', function() { const foldersContext = testContext({ 'import/resolver': 'webpack', - 'import/external-module-folders': ['/files/symlinked-module'], - }) - expect(importType('@test-scope/some-module', foldersContext)).to.equal('internal') - }) + 'import/external-module-folders': ['/symlinked-module'], + }); + expect(importType('@test-scope/some-module', foldersContext)).to.equal('internal'); + }); it('returns "external" for a scoped module from a symlinked directory which absolute path is contained in "external-module-folders" (webpack resolver)', function() { const foldersContext = testContext({ 'import/resolver': 'webpack', 'import/external-module-folders': [testFilePath('symlinked-module')], - }) - expect(importType('@test-scope/some-module', foldersContext)).to.equal('external') - }) + }); + expect(importType('@test-scope/some-module', foldersContext)).to.equal('external'); + }); it('`isExternalModule` works with windows directory separator', function() { - expect(isExternalModule('foo', {}, 'E:\\path\\to\\node_modules\\foo')).to.equal(true) + const context = testContext(); + expect(isExternalModule('foo', {}, 'E:\\path\\to\\node_modules\\foo', context)).to.equal(true); expect(isExternalModule('foo', { 'import/external-module-folders': ['E:\\path\\to\\node_modules'], - }, 'E:\\path\\to\\node_modules\\foo')).to.equal(true) - }) -}) + }, 'E:\\path\\to\\node_modules\\foo', context)).to.equal(true); + }); + + it('correctly identifies scoped modules with `isScopedModule`', () => { + expect(isScopedModule('@/abc')).to.equal(false); + expect(isScopedModule('@a/abc')).to.equal(true); + }); +}); diff --git a/tests/src/core/parse.js b/tests/src/core/parse.js index 72d232730d..a9c9ba5849 100644 --- a/tests/src/core/parse.js +++ b/tests/src/core/parse.js @@ -1,73 +1,73 @@ -import * as fs from 'fs' -import { expect } from 'chai' -import sinon from 'sinon' -import parse from 'eslint-module-utils/parse' +import * as fs from 'fs'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import parse from 'eslint-module-utils/parse'; -import { getFilename } from '../utils' +import { getFilename } from '../utils'; describe('parse(content, { settings, ecmaFeatures })', function () { - const path = getFilename('jsx.js') - const parseStubParser = require('./parseStubParser') - const parseStubParserPath = require.resolve('./parseStubParser') - const eslintParser = require('./eslintParser') - const eslintParserPath = require.resolve('./eslintParser') - let content + const path = getFilename('jsx.js'); + const parseStubParser = require('./parseStubParser'); + const parseStubParserPath = require.resolve('./parseStubParser'); + const eslintParser = require('./eslintParser'); + const eslintParserPath = require.resolve('./eslintParser'); + let content; before((done) => fs.readFile(path, { encoding: 'utf8' }, - (err, f) => { if (err) { done(err) } else { content = f; done() }})) + (err, f) => { if (err) { done(err); } else { content = f; done(); }})); it('doesn\'t support JSX by default', function () { - expect(() => parse(path, content, { parserPath: 'espree' })).to.throw(Error) - }) + expect(() => parse(path, content, { parserPath: 'espree' })).to.throw(Error); + }); it('infers jsx from ecmaFeatures when using stock parser', function () { expect(() => parse(path, content, { settings: {}, parserPath: 'espree', parserOptions: { ecmaVersion: 2015, sourceType: 'module', ecmaFeatures: { jsx: true } } })) - .not.to.throw(Error) - }) + .not.to.throw(Error); + }); it('passes expected parserOptions to custom parser', function () { - const parseSpy = sinon.spy() - const parserOptions = { ecmaFeatures: { jsx: true } } - parseStubParser.parse = parseSpy - parse(path, content, { settings: {}, parserPath: parseStubParserPath, parserOptions: parserOptions }) - expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1) - expect(parseSpy.args[0][0], 'custom parser to get content as its first argument').to.equal(content) - expect(parseSpy.args[0][1], 'custom parser to get an object as its second argument').to.be.an('object') - expect(parseSpy.args[0][1], 'custom parser to clone the parserOptions object').to.not.equal(parserOptions) + const parseSpy = sinon.spy(); + const parserOptions = { ecmaFeatures: { jsx: true } }; + parseStubParser.parse = parseSpy; + parse(path, content, { settings: {}, parserPath: parseStubParserPath, parserOptions: parserOptions }); + expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1); + expect(parseSpy.args[0][0], 'custom parser to get content as its first argument').to.equal(content); + expect(parseSpy.args[0][1], 'custom parser to get an object as its second argument').to.be.an('object'); + expect(parseSpy.args[0][1], 'custom parser to clone the parserOptions object').to.not.equal(parserOptions); expect(parseSpy.args[0][1], 'custom parser to get ecmaFeatures in parserOptions which is a clone of ecmaFeatures passed in') .to.have.property('ecmaFeatures') - .that.is.eql(parserOptions.ecmaFeatures) - .and.is.not.equal(parserOptions.ecmaFeatures) - expect(parseSpy.args[0][1], 'custom parser to get parserOptions.attachComment equal to true').to.have.property('attachComment', true) - expect(parseSpy.args[0][1], 'custom parser to get parserOptions.tokens equal to true').to.have.property('tokens', true) - expect(parseSpy.args[0][1], 'custom parser to get parserOptions.range equal to true').to.have.property('range', true) - expect(parseSpy.args[0][1], 'custom parser to get parserOptions.filePath equal to the full path of the source file').to.have.property('filePath', path) - }) + .that.is.eql(parserOptions.ecmaFeatures) + .and.is.not.equal(parserOptions.ecmaFeatures); + expect(parseSpy.args[0][1], 'custom parser to get parserOptions.attachComment equal to true').to.have.property('attachComment', true); + expect(parseSpy.args[0][1], 'custom parser to get parserOptions.tokens equal to true').to.have.property('tokens', true); + expect(parseSpy.args[0][1], 'custom parser to get parserOptions.range equal to true').to.have.property('range', true); + expect(parseSpy.args[0][1], 'custom parser to get parserOptions.filePath equal to the full path of the source file').to.have.property('filePath', path); + }); it('passes with custom `parseForESLint` parser', function () { - const parseForESLintSpy = sinon.spy(eslintParser, 'parseForESLint') - const parseSpy = sinon.spy() - eslintParser.parse = parseSpy - parse(path, content, { settings: {}, parserPath: eslintParserPath }) - expect(parseForESLintSpy.callCount, 'custom `parseForESLint` parser to be called once').to.equal(1) - expect(parseSpy.callCount, '`parseForESLint` takes higher priority than `parse`').to.equal(0) - }) + const parseForESLintSpy = sinon.spy(eslintParser, 'parseForESLint'); + const parseSpy = sinon.spy(); + eslintParser.parse = parseSpy; + parse(path, content, { settings: {}, parserPath: eslintParserPath }); + expect(parseForESLintSpy.callCount, 'custom `parseForESLint` parser to be called once').to.equal(1); + expect(parseSpy.callCount, '`parseForESLint` takes higher priority than `parse`').to.equal(0); + }); it('throws on context == null', function () { - expect(parse.bind(null, path, content, null)).to.throw(Error) - }) + expect(parse.bind(null, path, content, null)).to.throw(Error); + }); it('throws on unable to resolve parserPath', function () { - expect(parse.bind(null, path, content, { settings: {}, parserPath: null })).to.throw(Error) - }) + expect(parse.bind(null, path, content, { settings: {}, parserPath: null })).to.throw(Error); + }); it('takes the alternate parser specified in settings', function () { - const parseSpy = sinon.spy() - const parserOptions = { ecmaFeatures: { jsx: true } } - parseStubParser.parse = parseSpy - expect(parse.bind(null, path, content, { settings: { 'import/parsers': { [parseStubParserPath]: [ '.js' ] } }, parserPath: null, parserOptions: parserOptions })).not.to.throw(Error) - expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1) - }) + const parseSpy = sinon.spy(); + const parserOptions = { ecmaFeatures: { jsx: true } }; + parseStubParser.parse = parseSpy; + expect(parse.bind(null, path, content, { settings: { 'import/parsers': { [parseStubParserPath]: [ '.js' ] } }, parserPath: null, parserOptions: parserOptions })).not.to.throw(Error); + expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1); + }); -}) +}); diff --git a/tests/src/core/parseStubParser.js b/tests/src/core/parseStubParser.js index 81daace434..9d17f0b041 100644 --- a/tests/src/core/parseStubParser.js +++ b/tests/src/core/parseStubParser.js @@ -1,4 +1,4 @@ // this stub must be in a separate file to require from parse via moduleRequire module.exports = { parse: function () {}, -} +}; diff --git a/tests/src/core/resolve.js b/tests/src/core/resolve.js index 1aa2071db6..b8deaa6d25 100644 --- a/tests/src/core/resolve.js +++ b/tests/src/core/resolve.js @@ -1,273 +1,273 @@ -import { expect } from 'chai' +import { expect } from 'chai'; -import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve' -import ModuleCache from 'eslint-module-utils/ModuleCache' +import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve'; +import ModuleCache from 'eslint-module-utils/ModuleCache'; -import * as path from 'path' -import * as fs from 'fs' -import * as utils from '../utils' +import * as path from 'path'; +import * as fs from 'fs'; +import * as utils from '../utils'; describe('resolve', function () { // We don't want to test for a specific stack, just that it was there in the error message. function replaceErrorStackForTest(str) { - return typeof str === 'string' ? str.replace(/(\n\s+at .+:\d+\)?)+$/, '\n') : str + return typeof str === 'string' ? str.replace(/(\n\s+at .+:\d+\)?)+$/, '\n') : str; } it('throws on bad parameters', function () { - expect(resolve.bind(null, null, null)).to.throw(Error) - }) + expect(resolve.bind(null, null, null)).to.throw(Error); + }); it('resolves via a custom resolver with interface version 1', function () { - const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v1' }) + const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v1' }); expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), - )).to.equal(utils.testFilePath('./bar.jsx')) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); expect(resolve( '../files/exception' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }), - )).to.equal(undefined) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js'); } }), + )).to.equal(undefined); expect(resolve( '../files/not-found' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }), - )).to.equal(undefined) - }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js'); } }), + )).to.equal(undefined); + }); it('resolves via a custom resolver with interface version 1 assumed if not specified', function () { - const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-no-version' }) + const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-no-version' }); expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), - )).to.equal(utils.testFilePath('./bar.jsx')) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); expect(resolve( '../files/exception' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }), - )).to.equal(undefined) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js'); } }), + )).to.equal(undefined); expect(resolve( '../files/not-found' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }), - )).to.equal(undefined) - }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js'); } }), + )).to.equal(undefined); + }); it('resolves via a custom resolver with interface version 2', function () { - const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v2' }) - const testContextReports = [] + const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v2' }); + const testContextReports = []; testContext.report = function (reportInfo) { - testContextReports.push(reportInfo) - } + testContextReports.push(reportInfo); + }; expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), - )).to.equal(utils.testFilePath('./bar.jsx')) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); - testContextReports.length = 0 + testContextReports.length = 0; expect(resolve( '../files/exception' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }), - )).to.equal(undefined) - expect(testContextReports[0]).to.be.an('object') - expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: foo-bar-resolver-v2 resolve test exception\n') - expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js'); } }), + )).to.equal(undefined); + expect(testContextReports[0]).to.be.an('object'); + expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: foo-bar-resolver-v2 resolve test exception\n'); + expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }); - testContextReports.length = 0 + testContextReports.length = 0; expect(resolve( '../files/not-found' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }), - )).to.equal(undefined) - expect(testContextReports.length).to.equal(0) - }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js'); } }), + )).to.equal(undefined); + expect(testContextReports.length).to.equal(0); + }); it('respects import/resolver as array of strings', function () { - const testContext = utils.testContext({ 'import/resolver': [ './foo-bar-resolver-v2', './foo-bar-resolver-v1' ] }) + const testContext = utils.testContext({ 'import/resolver': [ './foo-bar-resolver-v2', './foo-bar-resolver-v1' ] }); expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), - )).to.equal(utils.testFilePath('./bar.jsx')) - }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); + }); it('respects import/resolver as object', function () { - const testContext = utils.testContext({ 'import/resolver': { './foo-bar-resolver-v2': {} } }) + const testContext = utils.testContext({ 'import/resolver': { './foo-bar-resolver-v2': {} } }); expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), - )).to.equal(utils.testFilePath('./bar.jsx')) - }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); + }); it('respects import/resolver as array of objects', function () { - const testContext = utils.testContext({ 'import/resolver': [ { './foo-bar-resolver-v2': {} }, { './foo-bar-resolver-v1': {} } ] }) + const testContext = utils.testContext({ 'import/resolver': [ { './foo-bar-resolver-v2': {} }, { './foo-bar-resolver-v1': {} } ] }); expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), - )).to.equal(utils.testFilePath('./bar.jsx')) - }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); + }); it('finds resolvers from the source files rather than eslint-module-utils', function () { - const testContext = utils.testContext({ 'import/resolver': { 'foo': {} } }) + const testContext = utils.testContext({ 'import/resolver': { 'foo': {} } }); expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), - )).to.equal(utils.testFilePath('./bar.jsx')) - }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(utils.testFilePath('./bar.jsx')); + }); it('reports invalid import/resolver config', function () { - const testContext = utils.testContext({ 'import/resolver': 123.456 }) - const testContextReports = [] + const testContext = utils.testContext({ 'import/resolver': 123.456 }); + const testContextReports = []; testContext.report = function (reportInfo) { - testContextReports.push(reportInfo) - } + testContextReports.push(reportInfo); + }; - testContextReports.length = 0 + testContextReports.length = 0; expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), - )).to.equal(undefined) - expect(testContextReports[0]).to.be.an('object') - expect(testContextReports[0].message).to.equal('Resolve error: invalid resolver config') - expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }) - }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(undefined); + expect(testContextReports[0]).to.be.an('object'); + expect(testContextReports[0].message).to.equal('Resolve error: invalid resolver config'); + expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }); + }); it('reports loaded resolver with invalid interface', function () { - const resolverName = './foo-bar-resolver-invalid' - const testContext = utils.testContext({ 'import/resolver': resolverName }) - const testContextReports = [] + const resolverName = './foo-bar-resolver-invalid'; + const testContext = utils.testContext({ 'import/resolver': resolverName }); + const testContextReports = []; testContext.report = function (reportInfo) { - testContextReports.push(reportInfo) - } - testContextReports.length = 0 + testContextReports.push(reportInfo); + }; + testContextReports.length = 0; expect(resolve( '../files/foo' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }), - )).to.equal(undefined) - expect(testContextReports[0]).to.be.an('object') - expect(testContextReports[0].message).to.equal(`Resolve error: ${resolverName} with invalid interface loaded as resolver`) - expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }) - }) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js'); } }), + )).to.equal(undefined); + expect(testContextReports[0]).to.be.an('object'); + expect(testContextReports[0].message).to.equal(`Resolve error: ${resolverName} with invalid interface loaded as resolver`); + expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }); + }); it('respects import/resolve extensions', function () { - const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] }}) + const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] } }); expect(resolve( './jsx/MyCoolComponent' - , testContext, - )).to.equal(utils.testFilePath('./jsx/MyCoolComponent.jsx')) - }) + , testContext, + )).to.equal(utils.testFilePath('./jsx/MyCoolComponent.jsx')); + }); it('reports load exception in a user resolver', function () { - const testContext = utils.testContext({ 'import/resolver': './load-error-resolver' }) - const testContextReports = [] + const testContext = utils.testContext({ 'import/resolver': './load-error-resolver' }); + const testContextReports = []; testContext.report = function (reportInfo) { - testContextReports.push(reportInfo) - } + testContextReports.push(reportInfo); + }; expect(resolve( '../files/exception' - , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }), - )).to.equal(undefined) - expect(testContextReports[0]).to.be.an('object') - expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: SyntaxError: TEST SYNTAX ERROR\n') - expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }) - }) - - const caseDescribe = (!CASE_SENSITIVE_FS ? describe : describe.skip) + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js'); } }), + )).to.equal(undefined); + expect(testContextReports[0]).to.be.an('object'); + expect(replaceErrorStackForTest(testContextReports[0].message)).to.equal('Resolve error: SyntaxError: TEST SYNTAX ERROR\n'); + expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }); + }); + + const caseDescribe = (!CASE_SENSITIVE_FS ? describe : describe.skip); caseDescribe('case sensitivity', function () { - let file - const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] }}) + let file; + const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] } }); before('resolve', function () { file = resolve( // Note the case difference 'MyUncoolComponent' vs 'MyUnCoolComponent' - './jsx/MyUncoolComponent', testContext) - }) + './jsx/MyUncoolComponent', testContext); + }); it('resolves regardless of case', function () { - expect(file, 'path to ./jsx/MyUncoolComponent').to.exist - }) + expect(file, 'path to ./jsx/MyUncoolComponent').to.exist; + }); it('detects case does not match FS', function () { expect(fileExistsWithCaseSync(file, ModuleCache.getSettings(testContext))) - .to.be.false - }) + .to.be.false; + }); it('detecting case does not include parent folder path (issue #720)', function () { - const f = path.join(process.cwd().toUpperCase(), './tests/files/jsx/MyUnCoolComponent.jsx') + const f = path.join(process.cwd().toUpperCase(), './tests/files/jsx/MyUnCoolComponent.jsx'); expect(fileExistsWithCaseSync(f, ModuleCache.getSettings(testContext), true)) - .to.be.true - }) - }) + .to.be.true; + }); + }); describe('rename cache correctness', function () { const context = utils.testContext({ 'import/cache': { 'lifetime': 1 }, - }) + }); const infiniteContexts = [ '∞', 'Infinity' ].map(inf => [inf, utils.testContext({ 'import/cache': { 'lifetime': inf }, - })]) + })]); const pairs = [ ['./CaseyKasem.js', './CASEYKASEM2.js'], - ] + ]; pairs.forEach(([original, changed]) => { describe(`${original} => ${changed}`, function () { before('sanity check', function () { - expect(resolve(original, context)).to.exist - expect(resolve(changed, context)).not.to.exist - }) + expect(resolve(original, context)).to.exist; + expect(resolve(changed, context)).not.to.exist; + }); // settings are part of cache key before('warm up infinite entries', function () { infiniteContexts.forEach(([,c]) => { - expect(resolve(original, c)).to.exist - }) - }) + expect(resolve(original, c)).to.exist; + }); + }); before('rename', function (done) { fs.rename( utils.testFilePath(original), utils.testFilePath(changed), - done) - }) + done); + }); before('verify rename', (done) => fs.exists( utils.testFilePath(changed), - exists => done(exists ? null : new Error('new file does not exist')))) + exists => done(exists ? null : new Error('new file does not exist')))); it('gets cached values within cache lifetime', function () { // get cached values initially - expect(resolve(original, context)).to.exist - }) + expect(resolve(original, context)).to.exist; + }); it('gets updated values immediately', function () { // get cached values initially - expect(resolve(changed, context)).to.exist - }) + expect(resolve(changed, context)).to.exist; + }); // special behavior for infinity describe('infinite cache', function () { - this.timeout(1500) + this.timeout(1500); - before((done) => setTimeout(done, 1100)) + before((done) => setTimeout(done, 1100)); infiniteContexts.forEach(([inf, infiniteContext]) => { it(`lifetime: ${inf} still gets cached values after ~1s`, function () { - expect(resolve(original, infiniteContext), original).to.exist - }) - }) + expect(resolve(original, infiniteContext), original).to.exist; + }); + }); - }) + }); describe('finite cache', function () { - this.timeout(1200) - before((done) => setTimeout(done, 1000)) + this.timeout(1200); + before((done) => setTimeout(done, 1000)); it('gets correct values after cache lifetime', function () { - expect(resolve(original, context)).not.to.exist - expect(resolve(changed, context)).to.exist - }) - }) + expect(resolve(original, context)).not.to.exist; + expect(resolve(changed, context)).to.exist; + }); + }); after('restore original case', function (done) { fs.rename( utils.testFilePath(changed), utils.testFilePath(original), - done) - }) - }) - }) - }) + done); + }); + }); + }); + }); -}) +}); diff --git a/tests/src/package.js b/tests/src/package.js index 700d4121b4..f759819758 100644 --- a/tests/src/package.js +++ b/tests/src/package.js @@ -1,58 +1,58 @@ -var expect = require('chai').expect +const expect = require('chai').expect; -var path = require('path') - , fs = require('fs') +const path = require('path'); +const fs = require('fs'); function isJSFile(f) { - return path.extname(f) === '.js' + return path.extname(f) === '.js'; } describe('package', function () { - let pkg = path.join(process.cwd(), 'src') - , module + const pkg = path.join(process.cwd(), 'src'); + let module; before('is importable', function () { - module = require(pkg) - }) + module = require(pkg); + }); it('exists', function () { - expect(module).to.exist - }) + expect(module).to.exist; + }); it('has every rule', function (done) { fs.readdir( path.join(pkg, 'rules') - , function (err, files) { - expect(err).not.to.exist + , function (err, files) { + expect(err).not.to.exist; files.filter(isJSFile).forEach(function (f) { expect(module.rules).to.have - .property(path.basename(f, '.js')) - }) + .property(path.basename(f, '.js')); + }); - done() - }) - }) + done(); + }); + }); it('exports all configs', function (done) { fs.readdir(path.join(process.cwd(), 'config'), function (err, files) { - if (err) { done(err); return } + if (err) { done(err); return; } files.filter(isJSFile).forEach(file => { - if (file[0] === '.') return - expect(module.configs).to.have.property(path.basename(file, '.js')) - }) - done() - }) - }) + if (file[0] === '.') return; + expect(module.configs).to.have.property(path.basename(file, '.js')); + }); + done(); + }); + }); it('has configs only for rules that exist', function () { - for (let configFile in module.configs) { - let preamble = 'import/' + for (const configFile in module.configs) { + const preamble = 'import/'; - for (let rule in module.configs[configFile].rules) { + for (const rule in module.configs[configFile].rules) { expect(() => require(getRulePath(rule.slice(preamble.length)))) - .not.to.throw(Error) + .not.to.throw(Error); } } @@ -60,13 +60,13 @@ describe('package', function () { // 'require' does not work with dynamic paths because of the compilation step by babel // (which resolves paths according to the root folder configuration) // the usage of require.resolve on a static path gets around this - return path.resolve(require.resolve('rules/no-unresolved'), '..', ruleName) + return path.resolve(require.resolve('rules/no-unresolved'), '..', ruleName); } - }) + }); it('marks deprecated rules in their metadata', function () { - expect(module.rules['imports-first'].meta.deprecated).to.be.true - expect(module.rules['first'].meta.deprecated).not.to.be.true - }) + expect(module.rules['imports-first'].meta.deprecated).to.be.true; + expect(module.rules['first'].meta.deprecated).not.to.be.true; + }); -}) +}); diff --git a/tests/src/rules/default.js b/tests/src/rules/default.js index d3d4aae4a7..c7eb780d00 100644 --- a/tests/src/rules/default.js +++ b/tests/src/rules/default.js @@ -1,26 +1,26 @@ -import path from 'path' -import { test, SYNTAX_CASES, getTSParsers } from '../utils' -import { RuleTester } from 'eslint' +import path from 'path'; +import { test, SYNTAX_CASES, getTSParsers } from '../utils'; +import { RuleTester } from 'eslint'; -import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve' +import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'; -var ruleTester = new RuleTester() - , rule = require('rules/default') +const ruleTester = new RuleTester(); +const rule = require('rules/default'); ruleTester.run('default', rule, { valid: [ test({ code: 'import "./malformed.js"' }), - test({code: 'import foo from "./empty-folder";'}), - test({code: 'import { foo } from "./default-export";'}), - test({code: 'import foo from "./default-export";'}), - test({code: 'import foo from "./mixed-exports";'}), + test({ code: 'import foo from "./empty-folder";' }), + test({ code: 'import { foo } from "./default-export";' }), + test({ code: 'import foo from "./default-export";' }), + test({ code: 'import foo from "./mixed-exports";' }), test({ - code: 'import bar from "./default-export";'}), + code: 'import bar from "./default-export";' }), test({ - code: 'import CoolClass from "./default-class";'}), + code: 'import CoolClass from "./default-class";' }), test({ - code: 'import bar, { baz } from "./default-export";'}), + code: 'import bar, { baz } from "./default-export";' }), // core modules always have a default test({ code: 'import crypto from "crypto";' }), @@ -28,14 +28,14 @@ ruleTester.run('default', rule, { test({ code: 'import common from "./common";' }), // es7 export syntax - test({ code: 'export bar from "./bar"' - , parser: require.resolve('babel-eslint') }), + test({ code: 'export bar from "./bar"', + parser: require.resolve('babel-eslint') }), test({ code: 'export { default as bar } from "./bar"' }), - test({ code: 'export bar, { foo } from "./bar"' - , parser: require.resolve('babel-eslint') }), + test({ code: 'export bar, { foo } from "./bar"', + parser: require.resolve('babel-eslint') }), test({ code: 'export { default as bar, foo } from "./bar"' }), - test({ code: 'export bar, * as names from "./bar"' - , parser: require.resolve('babel-eslint') }), + test({ code: 'export bar, * as names from "./bar"', + parser: require.resolve('babel-eslint') }), // sanity check test({ code: 'export {a} from "./named-exports"' }), @@ -103,8 +103,8 @@ ruleTester.run('default', rule, { test({ code: 'import baz from "./named-exports";', - errors: [{ message: 'No default export found in imported module "./named-exports".' - , type: 'ImportDefaultSpecifier'}]}), + errors: [{ message: 'No default export found in imported module "./named-exports".', + type: 'ImportDefaultSpecifier' }] }), // es7 export syntax test({ @@ -135,7 +135,7 @@ ruleTester.run('default', rule, { errors: ['No default export found in imported module "./re-export".'], }), ], -}) +}); // #311: import of mismatched case if (!CASE_SENSITIVE_FS) { @@ -151,98 +151,134 @@ if (!CASE_SENSITIVE_FS) { errors: ['No default export found in imported module "./Named-Exports".'], }), ], - }) + }); } 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 }, + 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-function"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import foobar from "./typescript-export-assign-mixed"`, + parser: parser, + 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/'), + }, + }), + test({ + code: `import foobar from "./typescript-export-assign-property"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + ], + + 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".'], + }), + 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-no-compiler-options/'), + }, + errors: [ + { + message: 'No default export found in imported module "./typescript-export-as-default-namespace".', + line: 1, + column: 8, + endLine: 1, + endColumn: 11, }, - errors: ['No default export found in imported module "./typescript-export-as-default-namespace".'], - }), - ], - }) - }) -}) + ], + }), + ], + }); + }); +}); diff --git a/tests/src/rules/dynamic-import-chunkname.js b/tests/src/rules/dynamic-import-chunkname.js index e8cbb9c6f9..a2ee55f13a 100644 --- a/tests/src/rules/dynamic-import-chunkname.js +++ b/tests/src/rules/dynamic-import-chunkname.js @@ -1,27 +1,28 @@ -import { SYNTAX_CASES } from '../utils' -import { RuleTester } from 'eslint' +import { SYNTAX_CASES, getTSParsers } from '../utils'; +import { RuleTester } from 'eslint'; +import semver from 'semver'; -const rule = require('rules/dynamic-import-chunkname') -const ruleTester = new RuleTester() +const rule = require('rules/dynamic-import-chunkname'); +const ruleTester = new RuleTester(); -const commentFormat = '[0-9a-zA-Z-_/.]+' -const pickyCommentFormat = '[a-zA-Z-_/.]+' -const options = [{ importFunctions: ['dynamicImport'] }] +const commentFormat = '[0-9a-zA-Z-_/.]+'; +const pickyCommentFormat = '[a-zA-Z-_/.]+'; +const options = [{ importFunctions: ['dynamicImport'] }]; const pickyCommentOptions = [{ importFunctions: ['dynamicImport'], webpackChunknameFormat: pickyCommentFormat, -}] +}]; const multipleImportFunctionOptions = [{ importFunctions: ['dynamicImport', 'definitelyNotStaticImport'], -}] -const parser = require.resolve('babel-eslint') +}]; +const parser = require.resolve('babel-eslint'); -const noLeadingCommentError = 'dynamic imports require a leading comment with the webpack chunkname' -const nonBlockCommentError = 'dynamic imports require a /* foo */ style comment, not a // foo comment' -const noPaddingCommentError = 'dynamic imports require a block comment padded with spaces - /* foo */' -const invalidSyntaxCommentError = 'dynamic imports require a "webpack" comment with valid syntax' -const commentFormatError = `dynamic imports require a leading comment in the form /* webpackChunkName: "${commentFormat}",? */` -const pickyCommentFormatError = `dynamic imports require a leading comment in the form /* webpackChunkName: "${pickyCommentFormat}",? */` +const noLeadingCommentError = 'dynamic imports require a leading comment with the webpack chunkname'; +const nonBlockCommentError = 'dynamic imports require a /* foo */ style comment, not a // foo comment'; +const noPaddingCommentError = 'dynamic imports require a block comment padded with spaces - /* foo */'; +const invalidSyntaxCommentError = 'dynamic imports require a "webpack" comment with valid syntax'; +const commentFormatError = `dynamic imports require a leading comment in the form /* webpackChunkName: ["']${commentFormat}["'],? */`; +const pickyCommentFormatError = `dynamic imports require a leading comment in the form /* webpackChunkName: ["']${pickyCommentFormat}["'],? */`; ruleTester.run('dynamic-import-chunkname', rule, { valid: [ @@ -131,6 +132,14 @@ ruleTester.run('dynamic-import-chunkname', rule, { options, parser, }, + { + code: `import( + /* webpackChunkName: 'someModule' */ + 'someModule' + )`, + options, + parser, + }, { code: `import( /* webpackChunkName: "someModule" */ @@ -191,17 +200,33 @@ ruleTester.run('dynamic-import-chunkname', rule, { }, { code: `import( - /* webpackChunkName: 'someModule' */ + /* webpackChunkName: "someModule' */ 'someModule' )`, options, parser, output: `import( - /* webpackChunkName: 'someModule' */ + /* webpackChunkName: "someModule' */ 'someModule' )`, errors: [{ - message: commentFormatError, + message: invalidSyntaxCommentError, + type: 'CallExpression', + }], + }, + { + code: `import( + /* webpackChunkName: 'someModule" */ + 'someModule' + )`, + options, + parser, + output: `import( + /* webpackChunkName: 'someModule" */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, type: 'CallExpression', }], }, @@ -420,21 +445,6 @@ ruleTester.run('dynamic-import-chunkname', rule, { type: 'CallExpression', }], }, - { - code: `dynamicImport( - /* webpackChunkName: 'someModule' */ - 'someModule' - )`, - options, - output: `dynamicImport( - /* webpackChunkName: 'someModule' */ - 'someModule' - )`, - errors: [{ - message: commentFormatError, - type: 'CallExpression', - }], - }, { code: `dynamicImport( /* webpackChunkName "someModule" */ @@ -481,4 +491,333 @@ ruleTester.run('dynamic-import-chunkname', rule, { }], }, ], -}) +}); + +context('TypeScript', () => { + getTSParsers().forEach((typescriptParser) => { + const nodeType = typescriptParser.includes('typescript-eslint-parser') || (typescriptParser.includes('@typescript-eslint/parser') && semver.satisfies(require('@typescript-eslint/parser/package.json').version, '^2')) + ? 'CallExpression' + : 'ImportExpression'; + + ruleTester.run('dynamic-import-chunkname', rule, { + valid: [ + { + code: `import( + /* webpackChunkName: "someModule" */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "Some_Other_Module" */ + "test" + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "SomeModule123" */ + "test" + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule", webpackPrefetch: true */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule", webpackPrefetch: true, */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunkName: "someModule" */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunkName: "someModule", */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackPrefetch: true */ + /* webpackChunkName: "someModule" */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule" */ + /* webpackPrefetch: true */ + 'test' + )`, + options, + parser: typescriptParser, + }, + { + code: `import( + /* webpackChunkName: "someModule" */ + 'someModule' + )`, + options: pickyCommentOptions, + parser: typescriptParser, + errors: [{ + message: pickyCommentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: 'someModule' */ + 'test' + )`, + options, + parser: typescriptParser, + }, + ], + invalid: [ + { + code: `import( + // webpackChunkName: "someModule" + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + // webpackChunkName: "someModule" + 'someModule' + )`, + errors: [{ + message: nonBlockCommentError, + type: nodeType, + }], + }, + { + code: 'import(\'test\')', + options, + parser: typescriptParser, + output: 'import(\'test\')', + errors: [{ + message: noLeadingCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: someModule */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName: someModule */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName "someModule' */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName "someModule' */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName 'someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName 'someModule" */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName "someModule" */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName:"someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName:"someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /*webpackChunkName: "someModule"*/ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /*webpackChunkName: "someModule"*/ + 'someModule' + )`, + errors: [{ + message: noPaddingCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName : "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName : "someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: "someModule" ; */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackChunkName: "someModule" ; */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* totally not webpackChunkName: "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* totally not webpackChunkName: "someModule" */ + 'someModule' + )`, + errors: [{ + message: invalidSyntaxCommentError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackPrefetch: true */ + /* webpackChunk: "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackPrefetch: true */ + /* webpackChunk: "someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackPrefetch: true, webpackChunk: "someModule" */ + 'someModule' + )`, + options, + parser: typescriptParser, + output: `import( + /* webpackPrefetch: true, webpackChunk: "someModule" */ + 'someModule' + )`, + errors: [{ + message: commentFormatError, + type: nodeType, + }], + }, + { + code: `import( + /* webpackChunkName: "someModule123" */ + 'someModule' + )`, + options: pickyCommentOptions, + parser: typescriptParser, + output: `import( + /* webpackChunkName: "someModule123" */ + 'someModule' + )`, + errors: [{ + message: pickyCommentFormatError, + type: nodeType, + }], + }, + ], + }); + }); +}); diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index fb301495e7..d997b949b6 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -1,17 +1,19 @@ -import { test, testFilePath, SYNTAX_CASES, getTSParsers } from '../utils' +import { test, testFilePath, SYNTAX_CASES, getTSParsers, testVersion } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; +import eslintPkg from 'eslint/package.json'; +import semver from 'semver'; -var ruleTester = new RuleTester() - , rule = require('rules/export') +const ruleTester = new RuleTester(); +const rule = require('rules/export'); ruleTester.run('export', rule, { - valid: [ + valid: [].concat( test({ code: 'import "./malformed.js"' }), // default test({ code: 'var foo = "foo"; export default foo;' }), - test({ code: 'export var foo = "foo"; export var bar = "bar";'}), + test({ code: 'export var foo = "foo"; export var bar = "bar";' }), test({ code: 'export var foo = "foo", bar = "bar";' }), test({ code: 'export var { foo, bar } = object;' }), test({ code: 'export var [ foo, bar ] = array;' }), @@ -24,7 +26,25 @@ ruleTester.run('export', rule, { test({ code: 'export default foo; export * from "./bar"' }), ...SYNTAX_CASES, - ], + + test({ + code: ` + import * as A from './named-export-collision/a'; + import * as B from './named-export-collision/b'; + + export { A, B }; + `, + }), + testVersion('>= 6', () => ({ + code: ` + export * as A from './named-export-collision/a'; + export * as B from './named-export-collision/b'; + `, + parserOptions: { + ecmaVersion: 2020, + }, + })) || [], + ), invalid: [ // multiple defaults @@ -62,7 +82,7 @@ ruleTester.run('export', rule, { test({ code: 'let foo; export { foo }; export * from "./export-all"', errors: ['Multiple exports of name \'foo\'.', - 'Multiple exports of name \'foo\'.'], + 'Multiple exports of name \'foo\'.'], }), // test({ code: 'export * from "./default-export"' // , errors: [{ message: 'No named exports found in module ' + @@ -103,7 +123,7 @@ ruleTester.run('export', rule, { errors: [`No named exports found in module './default-export'.`], }), ], -}) +}); context('TypeScript', function () { @@ -114,7 +134,7 @@ context('TypeScript', function () { 'import/parsers': { [parser]: ['.ts'] }, 'import/resolver': { 'eslint-import-resolver-typescript': true }, }, - } + }; ruleTester.run('export', rule, { valid: [ @@ -191,6 +211,40 @@ context('TypeScript', function () { code: 'export * from "./file1.ts"', filename: testFilePath('typescript-d-ts/file-2.ts'), }, parserConfig)), + + ...(semver.satisfies(eslintPkg.version, '< 6') ? [] : [ + test({ + code: ` + export * as A from './named-export-collision/a'; + export * as B from './named-export-collision/b'; + `, + parser: parser, + }), + ]), + + // Exports in ambient modules + test(Object.assign({ + code: ` + declare module "a" { + const Foo = 1; + export {Foo as default}; + } + declare module "b" { + const Bar = 2; + export {Bar as default}; + } + `, + }, parserConfig)), + test(Object.assign({ + code: ` + declare module "a" { + const Foo = 1; + export {Foo as default}; + } + const Bar = 2; + export {Bar as default}; + `, + }, parserConfig)), ], invalid: [ // type/value name clash @@ -282,7 +336,31 @@ context('TypeScript', function () { }, ], }, parserConfig)), + + // Exports in ambient modules + test(Object.assign({ + code: ` + declare module "a" { + const Foo = 1; + export {Foo as default}; + } + const Bar = 2; + export {Bar as default}; + const Baz = 3; + export {Baz as default}; + `, + errors: [ + { + message: 'Multiple default exports.', + line: 7, + }, + { + message: 'Multiple default exports.', + line: 9, + }, + ], + }, parserConfig)), ], - }) - }) -}) + }); + }); +}); diff --git a/tests/src/rules/exports-last.js b/tests/src/rules/exports-last.js index cc853ba76c..9f01f27f42 100644 --- a/tests/src/rules/exports-last.js +++ b/tests/src/rules/exports-last.js @@ -1,14 +1,14 @@ -import { test } from '../utils' +import { test } from '../utils'; -import { RuleTester } from 'eslint' -import rule from 'rules/exports-last' +import { RuleTester } from 'eslint'; +import rule from 'rules/exports-last'; -const ruleTester = new RuleTester() +const ruleTester = new RuleTester(); const error = type => ({ message: 'Export statements should appear at the end of the file', type, -}) +}); ruleTester.run('exports-last', rule, { valid: [ @@ -120,4 +120,4 @@ ruleTester.run('exports-last', rule, { ], }), ], -}) +}); diff --git a/tests/src/rules/extensions.js b/tests/src/rules/extensions.js index 8cdb3399d8..4070c6a6bc 100644 --- a/tests/src/rules/extensions.js +++ b/tests/src/rules/extensions.js @@ -1,8 +1,8 @@ -import { RuleTester } from 'eslint' -import rule from 'rules/extensions' -import { test, testFilePath } from '../utils' +import { RuleTester } from 'eslint'; +import rule from 'rules/extensions'; +import { test, testFilePath } from '../utils'; -const ruleTester = new RuleTester() +const ruleTester = new RuleTester(); ruleTester.run('extensions', rule, { valid: [ @@ -77,7 +77,7 @@ ruleTester.run('extensions', rule, { import Component from './Component.jsx' import express from 'express' `, - options: [ 'always', {ignorePackages: true} ], + options: [ 'always', { ignorePackages: true } ], }), test({ @@ -87,7 +87,7 @@ ruleTester.run('extensions', rule, { import Component from './Component' import express from 'express' `, - options: [ 'never', {ignorePackages: true} ], + options: [ 'never', { ignorePackages: true } ], }), test({ @@ -198,9 +198,9 @@ ruleTester.run('extensions', rule, { settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } }, errors: [ { - message: 'Unexpected use of file extension "js" for "./bar.js"', - line: 1, - column: 17, + message: 'Unexpected use of file extension "js" for "./bar.js"', + line: 1, + column: 17, }, ], }), @@ -214,9 +214,9 @@ ruleTester.run('extensions', rule, { settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } }, errors: [ { - message: 'Unexpected use of file extension "js" for "./bar.js"', - line: 1, - column: 17, + message: 'Unexpected use of file extension "js" for "./bar.js"', + line: 1, + column: 17, }, ], }), @@ -230,9 +230,9 @@ ruleTester.run('extensions', rule, { settings: { 'import/resolve': { 'extensions': [ '.jsx', '.json', '.js' ] } }, errors: [ { - message: 'Unexpected use of file extension "jsx" for "./bar.jsx"', - line: 1, - column: 23, + message: 'Unexpected use of file extension "jsx" for "./bar.jsx"', + line: 1, + column: 23, }, ], }), @@ -259,9 +259,9 @@ ruleTester.run('extensions', rule, { settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } }, errors: [ { - message: 'Unexpected use of file extension "js" for "./bar.js"', - line: 1, - column: 19, + message: 'Unexpected use of file extension "js" for "./bar.js"', + line: 1, + column: 19, }, ], }), @@ -276,9 +276,9 @@ ruleTester.run('extensions', rule, { settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } }, errors: [ { - message: 'Unexpected use of file extension "js" for "./bar.js"', - line: 1, - column: 19, + message: 'Unexpected use of file extension "js" for "./bar.js"', + line: 1, + column: 19, }, ], }), @@ -289,9 +289,9 @@ ruleTester.run('extensions', rule, { options: [ 'never' ], errors: [ { - message: 'Unexpected use of file extension "js" for "./fake-file.js"', - line: 1, - column: 19, + message: 'Unexpected use of file extension "js" for "./fake-file.js"', + line: 1, + column: 19, }, ], }), @@ -300,9 +300,9 @@ ruleTester.run('extensions', rule, { options: [ 'always' ], errors: [ { - message: 'Missing file extension for "non-package/test"', - line: 1, - column: 19, + message: 'Missing file extension for "non-package/test"', + line: 1, + column: 19, }, ], }), @@ -312,9 +312,9 @@ ruleTester.run('extensions', rule, { options: [ 'always' ], errors: [ { - message: 'Missing file extension for "@name/pkg/test"', - line: 1, - column: 19, + message: 'Missing file extension for "@name/pkg/test"', + line: 1, + column: 19, }, ], }), @@ -324,9 +324,9 @@ ruleTester.run('extensions', rule, { options: [ 'never' ], errors: [ { - message: 'Unexpected use of file extension "js" for "@name/pkg/test.js"', - line: 1, - column: 19, + message: 'Unexpected use of file extension "js" for "@name/pkg/test.js"', + line: 1, + column: 19, }, ], }), @@ -339,9 +339,10 @@ ruleTester.run('extensions', rule, { import Component from './Component' import baz from 'foo/baz' import baw from '@scoped/baw/import' + import chart from '@/configs/chart' import express from 'express' `, - options: [ 'always', {ignorePackages: true} ], + options: [ 'always', { ignorePackages: true } ], errors: [ { message: 'Missing file extension for "./Component"', @@ -358,6 +359,7 @@ ruleTester.run('extensions', rule, { import Component from './Component' import baz from 'foo/baz' import baw from '@scoped/baw/import' + import chart from '@/configs/chart' import express from 'express' `, options: [ 'ignorePackages' ], @@ -388,7 +390,23 @@ ruleTester.run('extensions', rule, { column: 31, }, ], - options: [ 'never', {ignorePackages: true} ], + options: [ 'never', { ignorePackages: true } ], + }), + + test({ + code: ` + import foo from './foo.js' + import bar from './bar.json' + import Component from './Component.jsx' + `, + errors: [ + { + message: 'Unexpected use of file extension "jsx" for "./Component.jsx"', + line: 4, + column: 31, + }, + ], + options: [ 'always', { pattern: { jsx: 'never' } } ], }), // export (#964) @@ -444,5 +462,128 @@ ruleTester.run('extensions', rule, { }, ], }), + // require (#1230) + test({ + code: [ + 'const { foo } = require("./foo")', + 'export { foo }', + ].join('\n'), + options: [ 'always' ], + errors: [ + { + message: 'Missing file extension for "./foo"', + line: 1, + column: 25, + }, + ], + }), + test({ + code: [ + 'const { foo } = require("./foo.js")', + 'export { foo }', + ].join('\n'), + options: [ 'never' ], + errors: [ + { + message: 'Unexpected use of file extension "js" for "./foo.js"', + line: 1, + column: 25, + }, + ], + }), + + // export { } from + test({ + code: 'export { foo } from "./foo"', + options: [ 'always' ], + errors: [ + { + message: 'Missing file extension for "./foo"', + line: 1, + column: 21, + }, + ], + }), + test({ + code: ` + import foo from "@/ImNotAScopedModule"; + import chart from '@/configs/chart'; + `, + options: ['always'], + errors: [ + { + message: 'Missing file extension for "@/ImNotAScopedModule"', + line: 2, + }, + { + message: 'Missing file extension for "@/configs/chart"', + line: 3, + }, + ], + }), + test({ + code: 'export { foo } from "./foo.js"', + options: [ 'never' ], + errors: [ + { + message: 'Unexpected use of file extension "js" for "./foo.js"', + line: 1, + column: 21, + }, + ], + }), + + // export * from + test({ + code: 'export * from "./foo"', + options: [ 'always' ], + errors: [ + { + message: 'Missing file extension for "./foo"', + line: 1, + column: 15, + }, + ], + }), + test({ + code: 'export * from "./foo.js"', + options: [ 'never' ], + errors: [ + { + message: 'Unexpected use of file extension "js" for "./foo.js"', + line: 1, + column: 15, + }, + ], + }), + test({ + code: 'import foo from "@/ImNotAScopedModule.js"', + options: ['never'], + errors: [ + { + message: 'Unexpected use of file extension "js" for "@/ImNotAScopedModule.js"', + line: 1, + }, + ], + }), + test({ + code: ` + import _ from 'lodash'; + import m from '@test-scope/some-module/index.js'; + + import bar from './bar'; + `, + options: ['never'], + settings: { + 'import/resolver': 'webpack', + 'import/external-module-folders': ['node_modules', 'symlinked-module'], + }, + errors: [ + { + message: 'Unexpected use of file extension "js" for "@test-scope/some-module/index.js"', + line: 3, + }, + ], + }), ], -}) +}); diff --git a/tests/src/rules/first.js b/tests/src/rules/first.js index 8c5d72a34c..3f7301319d 100644 --- a/tests/src/rules/first.js +++ b/tests/src/rules/first.js @@ -1,70 +1,116 @@ -import { test } from '../utils' +import { test, getTSParsers } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/first') +const ruleTester = new RuleTester(); +const rule = require('rules/first'); ruleTester.run('first', rule, { valid: [ test({ code: "import { x } from './foo'; import { y } from './bar';\ - export { x, y }" }) - , test({ code: "import { x } from 'foo'; import { y } from './bar'" }) - , test({ code: "import { x } from './foo'; import { y } from 'bar'" }) - , test({ code: "import { x } from './foo'; import { y } from 'bar'" - , options: ['disable-absolute-first'], - }) - , test({ code: "'use directive';\ - import { x } from 'foo';" }) - , + export { x, y }" }), + test({ code: "import { x } from 'foo'; import { y } from './bar'" }), + test({ code: "import { x } from './foo'; import { y } from 'bar'" }), + test({ code: "import { x } from './foo'; import { y } from 'bar'", + options: ['disable-absolute-first'], + }), + test({ code: "'use directive';\ + import { x } from 'foo';" }), ], invalid: [ test({ code: "import { x } from './foo';\ export { x };\ - import { y } from './bar';" - , errors: 1 - , output: "import { x } from './foo';\ + import { y } from './bar';", + errors: 1, + output: "import { x } from './foo';\ import { y } from './bar';\ export { x };", - }) - , test({ code: "import { x } from './foo';\ + }), + test({ code: "import { x } from './foo';\ export { x };\ import { y } from './bar';\ - import { z } from './baz';" - , errors: 2 - , output: "import { x } from './foo';\ + import { z } from './baz';", + errors: 2, + output: "import { x } from './foo';\ import { y } from './bar';\ import { z } from './baz';\ export { x };", - }) - , test({ code: "import { x } from './foo'; import { y } from 'bar'" - , options: ['absolute-first'] - , errors: 1, - }) - , test({ code: "import { x } from 'foo';\ + }), + test({ code: "import { x } from './foo'; import { y } from 'bar'", + options: ['absolute-first'], + errors: 1, + }), + test({ code: "import { x } from 'foo';\ 'use directive';\ - import { y } from 'bar';" - , errors: 1 - , output: "import { x } from 'foo';\ + import { y } from 'bar';", + errors: 1, + output: "import { x } from 'foo';\ import { y } from 'bar';\ 'use directive';", - }) - , test({ code: "var a = 1;\ + }), + test({ code: "var a = 1;\ import { y } from './bar';\ if (true) { x() };\ import { x } from './foo';\ - import { z } from './baz';" - , errors: 3 - , output: "import { y } from './bar';\ + import { z } from './baz';", + errors: 3, + output: "import { y } from './bar';\ var a = 1;\ if (true) { x() };\ import { x } from './foo';\ import { z } from './baz';", - }) - , test({ code: "if (true) { console.log(1) }import a from 'b'" - , errors: 1 - , output: "import a from 'b'\nif (true) { console.log(1) }", - }) - , + }), + test({ code: "if (true) { console.log(1) }import a from 'b'", + errors: 1, + output: "import a from 'b'\nif (true) { console.log(1) }", + }), ], -}) +}); + +context('TypeScript', function () { + getTSParsers() + .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: [ + test( + { + code: ` + import y = require('bar'); + import { x } from 'foo'; + import z = require('baz'); + `, + parser, + }, + parserConfig, + ), + ], + invalid: [ + test( + { + code: ` + import { x } from './foo'; + import y = require('bar'); + `, + options: ['absolute-first'], + parser, + errors: [ + { + message: 'Absolute imports should come before relative imports.', + }, + ], + }, + parserConfig, + ), + ], + }); + }); +}); diff --git a/tests/src/rules/group-exports.js b/tests/src/rules/group-exports.js index 0766a325e6..c3d07046f0 100644 --- a/tests/src/rules/group-exports.js +++ b/tests/src/rules/group-exports.js @@ -1,14 +1,14 @@ -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' +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 = { named: 'Multiple named export declarations; consolidate all named exports into a single export declaration', commonjs: 'Multiple CommonJS exports; consolidate all exports into a single assignment to `module.exports`', -} +}; /* eslint-enable max-len */ const ruleTester = new RuleTester({ parser: resolve(__dirname, '../../../node_modules/babel-eslint'), @@ -19,7 +19,7 @@ const ruleTester = new RuleTester({ presets: [babelPresetFlow], }, }, -}) +}); ruleTester.run('group-exports', rule, { valid: [ @@ -60,7 +60,7 @@ ruleTester.run('group-exports', rule, { export { default as module1 } from './module-1' export { default as module2 } from './module-2' ` }), - test({ code: 'module.exports = {} '}), + test({ code: 'module.exports = {} ' }), test({ code: ` module.exports = { test: true, another: false } @@ -292,4 +292,4 @@ ruleTester.run('group-exports', rule, { ], }), ], -}) +}); diff --git a/tests/src/rules/max-dependencies.js b/tests/src/rules/max-dependencies.js index ee35b648fb..f4e5f9a976 100644 --- a/tests/src/rules/max-dependencies.js +++ b/tests/src/rules/max-dependencies.js @@ -1,9 +1,9 @@ -import { test } from '../utils' +import { test } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/max-dependencies') +const ruleTester = new RuleTester(); +const rule = require('rules/max-dependencies'); ruleTester.run('max-dependencies', rule, { valid: [ @@ -21,7 +21,7 @@ ruleTester.run('max-dependencies', rule, { }], }), - test({ code: 'import {x, y, z} from "./foo"'}), + test({ code: 'import {x, y, z} from "./foo"' }), ], invalid: [ test({ @@ -75,4 +75,4 @@ ruleTester.run('max-dependencies', rule, { ], }), ], -}) +}); diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index eba7bec1ad..f09ee20595 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -1,45 +1,45 @@ -import { test, SYNTAX_CASES, getTSParsers } from '../utils' -import { RuleTester } from 'eslint' +import { test, SYNTAX_CASES, getTSParsers } from '../utils'; +import { RuleTester } from 'eslint'; -import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve' +import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'; -var ruleTester = new RuleTester() - , rule = require('rules/named') +const ruleTester = new RuleTester(); +const rule = require('rules/named'); function error(name, module) { - return { message: name + ' not found in \'' + module + '\'' - , type: 'Identifier' } + return { message: name + ' not found in \'' + module + '\'', + type: 'Identifier' }; } ruleTester.run('named', rule, { valid: [ test({ code: 'import "./malformed.js"' }), - test({code: 'import { foo } from "./bar"'}), - test({code: 'import { foo } from "./empty-module"'}), - test({code: 'import bar from "./bar.js"'}), - test({code: 'import bar, { foo } from "./bar.js"'}), - test({code: 'import {a, b, d} from "./named-exports"'}), - test({code: 'import {ExportedClass} from "./named-exports"'}), - test({code: 'import { destructingAssign } from "./named-exports"'}), - test({code: 'import { destructingRenamedAssign } from "./named-exports"'}), - test({code: 'import { ActionTypes } from "./qc"'}), - test({code: 'import {a, b, c, d} from "./re-export"'}), - test({code: 'import {a, b, c} from "./re-export-common-star"'}), - test({code: 'import {RuleTester} from "./re-export-node_modules"'}), - - test({ code: 'import { jsxFoo } from "./jsx/AnotherComponent"' - , settings: { 'import/resolve': { 'extensions': ['.js', '.jsx'] } } }), + test({ code: 'import { foo } from "./bar"' }), + test({ code: 'import { foo } from "./empty-module"' }), + test({ code: 'import bar from "./bar.js"' }), + test({ code: 'import bar, { foo } from "./bar.js"' }), + test({ code: 'import {a, b, d} from "./named-exports"' }), + test({ code: 'import {ExportedClass} from "./named-exports"' }), + test({ code: 'import { destructingAssign } from "./named-exports"' }), + test({ code: 'import { destructingRenamedAssign } from "./named-exports"' }), + test({ code: 'import { ActionTypes } from "./qc"' }), + test({ code: 'import {a, b, c, d} from "./re-export"' }), + test({ code: 'import {a, b, c} from "./re-export-common-star"' }), + test({ code: 'import {RuleTester} from "./re-export-node_modules"' }), + + test({ code: 'import { jsxFoo } from "./jsx/AnotherComponent"', + settings: { 'import/resolve': { 'extensions': ['.js', '.jsx'] } } }), // validate that eslint-disable-line silences this properly - test({code: 'import {a, b, d} from "./common"; ' + + test({ code: 'import {a, b, d} from "./common"; ' + '// eslint-disable-line named' }), test({ code: 'import { foo, bar } from "./re-export-names"' }), - test({ code: 'import { foo, bar } from "./common"' - , settings: { 'import/ignore': ['common'] } }), + test({ code: 'import { foo, bar } from "./common"', + settings: { 'import/ignore': ['common'] } }), // ignore core modules by default test({ code: 'import { foo } from "crypto"' }), @@ -63,7 +63,7 @@ ruleTester.run('named', rule, { }), // regression tests - test({ code: 'let foo; export { foo as bar }'}), + test({ code: 'let foo; export { foo as bar }' }), // destructured exports test({ code: 'import { destructuredProp } from "./named-exports"' }), @@ -151,27 +151,27 @@ ruleTester.run('named', rule, { invalid: [ - test({ code: 'import { somethingElse } from "./test-module"' - , errors: [ error('somethingElse', './test-module') ] }), + test({ code: 'import { somethingElse } from "./test-module"', + errors: [ error('somethingElse', './test-module') ] }), - test({code: 'import { baz } from "./bar"', - errors: [error('baz', './bar')]}), + test({ code: 'import { baz } from "./bar"', + errors: [error('baz', './bar')] }), // test multiple - test({code: 'import { baz, bop } from "./bar"', - errors: [error('baz', './bar'), error('bop', './bar')]}), + test({ code: 'import { baz, bop } from "./bar"', + errors: [error('baz', './bar'), error('bop', './bar')] }), - test({code: 'import {a, b, c} from "./named-exports"', - errors: [error('c', './named-exports')]}), + test({ code: 'import {a, b, c} from "./named-exports"', + errors: [error('c', './named-exports')] }), - test({code: 'import { a } from "./default-export"', - errors: [error('a', './default-export')]}), + test({ code: 'import { a } from "./default-export"', + errors: [error('a', './default-export')] }), - test({code: 'import { ActionTypess } from "./qc"', - errors: [error('ActionTypess', './qc')]}), + test({ code: 'import { ActionTypess } from "./qc"', + errors: [error('ActionTypess', './qc')] }), - test({code: 'import {a, b, c, d, e} from "./re-export"', - errors: [error('e', './re-export')]}), + test({ code: 'import {a, b, c, d, e} from "./re-export"', + errors: [error('e', './re-export')] }), test({ code: 'import { a } from "./re-export-names"', @@ -248,7 +248,7 @@ ruleTester.run('named', rule, { errors: [`default not found in './re-export'`], }), ], -}) +}); // #311: import of mismatched case if (!CASE_SENSITIVE_FS) { @@ -264,7 +264,7 @@ if (!CASE_SENSITIVE_FS) { errors: [`foo not found in './Named-Exports'`], }), ], - }) + }); } // export-all @@ -280,15 +280,15 @@ ruleTester.run('named (export *)', rule, { errors: [`bar not found in './export-all'`], }), ], -}) +}); context('TypeScript', function () { getTSParsers().forEach((parser) => { [ - 'typescript', - 'typescript-declare', - 'typescript-export-assign-namespace', + 'typescript', + 'typescript-declare', + 'typescript-export-assign-namespace', 'typescript-export-assign-namespace-merged', ].forEach((source) => { ruleTester.run(`named`, rule, { @@ -383,7 +383,7 @@ context('TypeScript', function () { }], }), ], - }) - }) - }) -}) + }); + }); + }); +}); diff --git a/tests/src/rules/namespace.js b/tests/src/rules/namespace.js index 5627c7132a..ce55bccc35 100644 --- a/tests/src/rules/namespace.js +++ b/tests/src/rules/namespace.js @@ -1,19 +1,19 @@ -import { test, SYNTAX_CASES, getTSParsers } from '../utils' -import { RuleTester } from 'eslint' -import flatMap from 'array.prototype.flatmap' +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') +const ruleTester = new RuleTester({ env: { es6: true } }); +const rule = require('rules/namespace'); function error(name, namespace) { - return { message: `'${name}' not found in imported namespace '${namespace}'.` } + return { message: `'${name}' not found in imported namespace '${namespace}'.` }; } const valid = [ test({ code: 'import "./malformed.js"' }), - test({ code: "import * as foo from './empty-folder';"}), + test({ code: "import * as foo from './empty-folder';" }), test({ code: 'import * as names from "./named-exports"; ' + 'console.log((names.b).c); ' }), @@ -56,13 +56,13 @@ const valid = [ ///////// // es7 // ///////// - test({ code: 'export * as names from "./named-exports"' - , parser: require.resolve('babel-eslint') }), - test({ code: 'export defport, * as names from "./named-exports"' - , parser: require.resolve('babel-eslint') }), + test({ code: 'export * as names from "./named-exports"', + parser: require.resolve('babel-eslint') }), + test({ code: 'export defport, * as names from "./named-exports"', + parser: require.resolve('babel-eslint') }), // non-existent is handled by no-unresolved - test({ code: 'export * as names from "./does-not-exist"' - , parser: require.resolve('babel-eslint') }), + test({ code: 'export * as names from "./does-not-exist"', + parser: require.resolve('babel-eslint') }), test({ code: 'import * as Endpoints from "./issue-195/Endpoints"; console.log(Endpoints.Users)', @@ -80,8 +80,8 @@ const valid = [ test({ code: "import * as names from './default-export';" }), test({ code: "import * as names from './default-export'; console.log(names.default)" }), test({ - code: 'export * as names from "./default-export"', - parser: require.resolve('babel-eslint'), + code: 'export * as names from "./default-export"', + parser: require.resolve('babel-eslint'), }), test({ code: 'export defport, * as names from "./default-export"', @@ -152,25 +152,42 @@ const valid = [ 'import/resolver': { 'eslint-import-resolver-typescript': true }, }, }), + + test({ + code: 'export = function name() {}', + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), ]), ...SYNTAX_CASES, -] + + test({ + code: ` + import * as color from './color'; + export const getBackgroundFromColor = (color) => color.bg; + export const getExampleColor = () => color.example + `, + }), +]; const invalid = [ test({ code: "import * as names from './named-exports'; " + - ' console.log(names.c);' - , errors: [error('c', 'names')] }), + ' console.log(names.c);', + errors: [error('c', 'names')] }), test({ code: "import * as names from './named-exports';" + - " console.log(names['a']);" - , errors: ["Unable to validate computed reference to imported namespace 'names'."] }), + " console.log(names['a']);", + errors: ["Unable to validate computed reference to imported namespace 'names'."] }), // assignment warning (from no-reassign) - test({ code: 'import * as foo from \'./bar\'; foo.foo = \'y\';' - , errors: [{ message: 'Assignment to member of namespace \'foo\'.'}] }), - test({ code: 'import * as foo from \'./bar\'; foo.x = \'y\';' - , errors: ['Assignment to member of namespace \'foo\'.', "'x' not found in imported namespace 'foo'."] }), + test({ code: 'import * as foo from \'./bar\'; foo.foo = \'y\';', + errors: [{ message: 'Assignment to member of namespace \'foo\'.' }] }), + test({ code: 'import * as foo from \'./bar\'; foo.x = \'y\';', + errors: ['Assignment to member of namespace \'foo\'.', "'x' not found in imported namespace 'foo'."] }), // invalid destructuring test({ @@ -257,10 +274,10 @@ const invalid = [ test({ parser, code: `import { b } from "./${folder}/a"; console.log(b.c.d.e)` }), test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.c.d.e.f)` }), test({ parser, code: `import * as a from "./${folder}/a"; var {b:{c:{d:{e}}}} = a` }), - test({ parser, code: `import { b } from "./${folder}/a"; var {c:{d:{e}}} = b` })) + test({ parser, code: `import { b } from "./${folder}/a"; var {c:{d:{e}}} = b` })); - // deep namespaces should include explicitly exported defaults - test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.default)` }), + // deep namespaces should include explicitly exported defaults + test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.default)` }), invalid.push( test({ @@ -292,7 +309,7 @@ const invalid = [ parser, code: `import * as a from "./${folder}/a"; var {b:{c:{ e }}} = a`, errors: [ "'e' not found in deeply imported namespace 'a.b.c'." ], - })) -}) + })); +}); -ruleTester.run('namespace', rule, { valid, invalid }) +ruleTester.run('namespace', rule, { valid, invalid }); diff --git a/tests/src/rules/newline-after-import.js b/tests/src/rules/newline-after-import.js index 626e6e0261..867a648575 100644 --- a/tests/src/rules/newline-after-import.js +++ b/tests/src/rules/newline-after-import.js @@ -1,15 +1,15 @@ -import { RuleTester } from 'eslint' -import flatMap from 'array.prototype.flatmap' +import { RuleTester } from 'eslint'; +import flatMap from 'array.prototype.flatmap'; -import { getTSParsers } from '../utils' +import { getTSParsers, testVersion } from '../utils'; -const IMPORT_ERROR_MESSAGE = 'Expected 1 empty line after import statement not followed by another import.' +const IMPORT_ERROR_MESSAGE = 'Expected 1 empty line after import statement not followed by another import.'; const IMPORT_ERROR_MESSAGE_MULTIPLE = (count) => { - return `Expected ${count} empty lines after import statement not followed by another import.` -} -const REQUIRE_ERROR_MESSAGE = 'Expected 1 empty line after require statement not followed by another require.' + return `Expected ${count} empty lines after import statement not followed by another import.`; +}; +const REQUIRE_ERROR_MESSAGE = 'Expected 1 empty line after require statement not followed by another require.'; -const ruleTester = new RuleTester() +const ruleTester = new RuleTester(); ruleTester.run('newline-after-import', require('rules/newline-after-import'), { valid: [ @@ -68,7 +68,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { return somethingElse(); } }`, - parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { code: `import path from 'path';\nimport foo from 'foo';\n`, @@ -213,10 +213,49 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { parser: parser, parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, + { + code: ` + export import a = obj;\nf(a); + `, + parser: parser, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + }, + { + code: ` + import { a } from "./a"; + + export namespace SomeNamespace { + export import a2 = a; + f(a); + }`, + parser: parser, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + }, + { + code: ` + import stub from './stub'; + + export { + stub + } + `, + parser: parser, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + }, ]), + { + code: ` + import stub from './stub'; + + export { + stub + } + `, + parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, + }, ], - invalid: [ + invalid: [].concat( { code: `import foo from 'foo';\nexport default function() {};`, output: `import foo from 'foo';\n\nexport default function() {};`, @@ -263,16 +302,16 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { code: `import foo from 'foo';\nvar a = 123;\n\nimport { bar } from './bar-lib';\nvar b=456;`, output: `import foo from 'foo';\n\nvar a = 123;\n\nimport { bar } from './bar-lib';\n\nvar b=456;`, errors: [ - { - line: 1, - column: 1, - message: IMPORT_ERROR_MESSAGE, - }, - { - line: 4, - column: 1, - message: IMPORT_ERROR_MESSAGE, - }], + { + line: 1, + column: 1, + message: IMPORT_ERROR_MESSAGE, + }, + { + line: 4, + column: 1, + message: IMPORT_ERROR_MESSAGE, + }], parserOptions: { ecmaVersion: 2015, sourceType: 'module' }, }, { @@ -411,5 +450,29 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { parserOptions: { sourceType: 'module' }, parser: require.resolve('babel-eslint'), }, - ], -}) + testVersion('>= 6', () => ({ + code: ` + // issue 1784 + import { map } from 'rxjs/operators'; + @Component({}) + export class Test {} + `, + output: ` + // issue 1784 + import { map } from 'rxjs/operators'; + + @Component({}) + export class Test {} + `, + errors: [ + { + line: 3, + column: 9, + message: IMPORT_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 2a95829b00..63fb8c0b6b 100644 --- a/tests/src/rules/no-absolute-path.js +++ b/tests/src/rules/no-absolute-path.js @@ -1,30 +1,30 @@ -import { test } from '../utils' +import { test } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/no-absolute-path') +const ruleTester = new RuleTester(); +const rule = require('rules/no-absolute-path'); const error = { message: 'Do not import modules using an absolute path', -} +}; ruleTester.run('no-absolute-path', rule, { valid: [ - test({ code: 'import _ from "lodash"'}), - test({ code: 'import find from "lodash.find"'}), - test({ code: 'import foo from "./foo"'}), - test({ code: 'import foo from "../foo"'}), - test({ code: 'import foo from "foo"'}), - test({ code: 'import foo from "./"'}), - test({ code: 'import foo from "@scope/foo"'}), - test({ code: 'var _ = require("lodash")'}), - test({ code: 'var find = require("lodash.find")'}), - test({ code: 'var foo = require("./foo")'}), - test({ code: 'var foo = require("../foo")'}), - test({ code: 'var foo = require("foo")'}), - test({ code: 'var foo = require("./")'}), - test({ code: 'var foo = require("@scope/foo")'}), + test({ code: 'import _ from "lodash"' }), + test({ code: 'import find from "lodash.find"' }), + test({ code: 'import foo from "./foo"' }), + test({ code: 'import foo from "../foo"' }), + test({ code: 'import foo from "foo"' }), + test({ code: 'import foo from "./"' }), + test({ code: 'import foo from "@scope/foo"' }), + test({ code: 'var _ = require("lodash")' }), + test({ code: 'var find = require("lodash.find")' }), + test({ code: 'var foo = require("./foo")' }), + test({ code: 'var foo = require("../foo")' }), + test({ code: 'var foo = require("foo")' }), + test({ code: 'var foo = require("./")' }), + test({ code: 'var foo = require("@scope/foo")' }), test({ code: 'import events from "events"' }), test({ code: 'import path from "path"' }), @@ -97,4 +97,4 @@ ruleTester.run('no-absolute-path', rule, { errors: [error], }), ], -}) +}); diff --git a/tests/src/rules/no-amd.js b/tests/src/rules/no-amd.js index 62de5ac26a..74c89c4116 100644 --- a/tests/src/rules/no-amd.js +++ b/tests/src/rules/no-amd.js @@ -1,8 +1,8 @@ -import { RuleTester } from 'eslint' -import eslintPkg from 'eslint/package.json' -import semver from 'semver' +import { RuleTester } from 'eslint'; +import eslintPkg from 'eslint/package.json'; +import semver from 'semver'; -var ruleTester = new RuleTester() +const ruleTester = new RuleTester(); ruleTester.run('no-amd', require('rules/no-amd'), { valid: [ @@ -12,7 +12,7 @@ ruleTester.run('no-amd', require('rules/no-amd'), { 'require("x")', // 2-args, not an array - 'require("x", "y")', + 'require("x", "y")', // random other function 'setTimeout(foo, 100)', // non-identifier callee @@ -25,13 +25,13 @@ ruleTester.run('no-amd', require('rules/no-amd'), { // unmatched arg types/number 'define(0, 1, 2)', 'define("a")', - ], + ], - invalid: semver.satisfies(eslintPkg.version, '< 4.0.0') ? [] : [ - { code: 'define([], function() {})', errors: [ { message: 'Expected imports instead of AMD define().' }] }, - { code: 'define(["a"], function(a) { console.log(a); })', errors: [ { message: 'Expected imports instead of AMD define().' }] }, + invalid: semver.satisfies(eslintPkg.version, '< 4.0.0') ? [] : [ + { code: 'define([], function() {})', errors: [ { message: 'Expected imports instead of AMD define().' }] }, + { code: 'define(["a"], function(a) { console.log(a); })', errors: [ { message: 'Expected imports instead of AMD define().' }] }, - { code: 'require([], function() {})', errors: [ { message: 'Expected imports instead of AMD require().' }] }, - { code: 'require(["a"], function(a) { console.log(a); })', errors: [ { message: 'Expected imports instead of AMD require().' }] }, - ], -}) + { code: 'require([], function() {})', errors: [ { message: 'Expected imports instead of AMD require().' }] }, + { code: 'require(["a"], function(a) { console.log(a); })', errors: [ { message: 'Expected imports instead of AMD require().' }] }, + ], +}); diff --git a/tests/src/rules/no-anonymous-default-export.js b/tests/src/rules/no-anonymous-default-export.js index c872cf4d09..231f1b667d 100644 --- a/tests/src/rules/no-anonymous-default-export.js +++ b/tests/src/rules/no-anonymous-default-export.js @@ -1,55 +1,55 @@ -import { test, SYNTAX_CASES } from '../utils' +import { test, SYNTAX_CASES } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() -const rule = require('rules/no-anonymous-default-export') +const ruleTester = new RuleTester(); +const rule = require('rules/no-anonymous-default-export'); ruleTester.run('no-anonymous-default-export', rule, { - valid: [ - // Exports with identifiers are valid - test({ code: 'const foo = 123\nexport default foo' }), - test({ code: 'export default function foo() {}'}), - test({ code: 'export default class MyClass {}'}), - - // Allow each forbidden type with appropriate option - test({ code: 'export default []', options: [{ allowArray: true }] }), - test({ code: 'export default () => {}', options: [{ allowArrowFunction: true }] }), - test({ code: 'export default class {}', options: [{ allowAnonymousClass: true }] }), - test({ code: 'export default function() {}', options: [{ allowAnonymousFunction: true }] }), - test({ code: 'export default 123', options: [{ allowLiteral: true }] }), - test({ code: 'export default \'foo\'', options: [{ allowLiteral: true }] }), - test({ code: 'export default `foo`', options: [{ allowLiteral: true }] }), - test({ code: 'export default {}', options: [{ allowObject: true }] }), - test({ code: 'export default foo(bar)', options: [{ allowCallExpression: true }] }), - - // Allow forbidden types with multiple options - test({ code: 'export default 123', options: [{ allowLiteral: true, allowObject: true }] }), - test({ code: 'export default {}', options: [{ allowLiteral: true, allowObject: true }] }), - - // Sanity check unrelated export syntaxes - test({ code: 'export * from \'foo\'' }), - test({ code: 'const foo = 123\nexport { foo }' }), - test({ code: 'const foo = 123\nexport { foo as default }' }), - - // Allow call expressions by default for backwards compatibility - test({ code: 'export default foo(bar)' }), - - ...SYNTAX_CASES, - ], - - invalid: [ - test({ code: 'export default []', errors: [{ message: 'Assign array to a variable before exporting as module default' }] }), - test({ code: 'export default () => {}', errors: [{ message: 'Assign arrow function to a variable before exporting as module default' }] }), - test({ code: 'export default class {}', errors: [{ message: 'Unexpected default export of anonymous class' }] }), - test({ code: 'export default function() {}', errors: [{ message: 'Unexpected default export of anonymous function' }] }), - test({ code: 'export default 123', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), - test({ code: 'export default \'foo\'', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), - test({ code: 'export default `foo`', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), - test({ code: 'export default {}', errors: [{ message: 'Assign object to a variable before exporting as module default' }] }), - test({ code: 'export default foo(bar)', options: [{ allowCallExpression: false }], errors: [{ message: 'Assign call result to a variable before exporting as module default' }] }), - - // Test failure with non-covering exception - test({ code: 'export default 123', options: [{ allowObject: true }], errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), - ], -}) + valid: [ + // Exports with identifiers are valid + test({ code: 'const foo = 123\nexport default foo' }), + test({ code: 'export default function foo() {}' }), + test({ code: 'export default class MyClass {}' }), + + // Allow each forbidden type with appropriate option + test({ code: 'export default []', options: [{ allowArray: true }] }), + test({ code: 'export default () => {}', options: [{ allowArrowFunction: true }] }), + test({ code: 'export default class {}', options: [{ allowAnonymousClass: true }] }), + test({ code: 'export default function() {}', options: [{ allowAnonymousFunction: true }] }), + test({ code: 'export default 123', options: [{ allowLiteral: true }] }), + test({ code: 'export default \'foo\'', options: [{ allowLiteral: true }] }), + test({ code: 'export default `foo`', options: [{ allowLiteral: true }] }), + test({ code: 'export default {}', options: [{ allowObject: true }] }), + test({ code: 'export default foo(bar)', options: [{ allowCallExpression: true }] }), + + // Allow forbidden types with multiple options + test({ code: 'export default 123', options: [{ allowLiteral: true, allowObject: true }] }), + test({ code: 'export default {}', options: [{ allowLiteral: true, allowObject: true }] }), + + // Sanity check unrelated export syntaxes + test({ code: 'export * from \'foo\'' }), + test({ code: 'const foo = 123\nexport { foo }' }), + test({ code: 'const foo = 123\nexport { foo as default }' }), + + // Allow call expressions by default for backwards compatibility + test({ code: 'export default foo(bar)' }), + + ...SYNTAX_CASES, + ], + + invalid: [ + test({ code: 'export default []', errors: [{ message: 'Assign array to a variable before exporting as module default' }] }), + test({ code: 'export default () => {}', errors: [{ message: 'Assign arrow function to a variable before exporting as module default' }] }), + test({ code: 'export default class {}', errors: [{ message: 'Unexpected default export of anonymous class' }] }), + test({ code: 'export default function() {}', errors: [{ message: 'Unexpected default export of anonymous function' }] }), + test({ code: 'export default 123', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), + test({ code: 'export default \'foo\'', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), + test({ code: 'export default `foo`', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), + test({ code: 'export default {}', errors: [{ message: 'Assign object to a variable before exporting as module default' }] }), + test({ code: 'export default foo(bar)', options: [{ allowCallExpression: false }], errors: [{ message: 'Assign call result to a variable before exporting as module default' }] }), + + // Test failure with non-covering exception + test({ code: 'export default 123', options: [{ allowObject: true }], errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), + ], +}); diff --git a/tests/src/rules/no-commonjs.js b/tests/src/rules/no-commonjs.js index d2c4ed3100..b1d8c03c1d 100644 --- a/tests/src/rules/no-commonjs.js +++ b/tests/src/rules/no-commonjs.js @@ -1,11 +1,11 @@ -import { RuleTester } from 'eslint' -import eslintPkg from 'eslint/package.json' -import semver from 'semver' +import { RuleTester } from 'eslint'; +import eslintPkg from 'eslint/package.json'; +import semver from 'semver'; -const EXPORT_MESSAGE = 'Expected "export" or "export default"' - , IMPORT_MESSAGE = 'Expected "import" instead of "require()"' +const EXPORT_MESSAGE = 'Expected "export" or "export default"'; +const IMPORT_MESSAGE = 'Expected "import" instead of "require()"'; -const ruleTester = new RuleTester() +const ruleTester = new RuleTester(); ruleTester.run('no-commonjs', require('rules/no-commonjs'), { valid: [ @@ -37,6 +37,7 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { { code: "var bar = require('./bar', true);" }, { code: "var bar = proxyquire('./bar');" }, { code: "var bar = require('./ba' + 'r');" }, + { code: 'var bar = require(`x${1}`);', parserOptions: { ecmaVersion: 2015 } }, { code: 'var zero = require(0);' }, { code: 'require("x")', options: [{ allowRequire: true }] }, @@ -49,7 +50,7 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { // commonJS rules should be scoped to commonJS spec. `rootRequire` is not // recognized by this commonJS plugin. { code: 'rootRequire("x")', options: [{ allowRequire: true }] }, - { code: 'rootRequire("x")', options: [{ allowRequire: false}] }, + { code: 'rootRequire("x")', options: [{ allowRequire: false }] }, { code: 'module.exports = function () {}', options: ['allow-primitive-modules'] }, { code: 'module.exports = function () {}', options: [{ allowPrimitiveModules: true }] }, @@ -71,6 +72,11 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { { code: 'var x = require("x")', output: 'var x = require("x")', errors: [ { message: IMPORT_MESSAGE }] }, { code: 'x = require("x")', output: 'x = require("x")', errors: [ { message: IMPORT_MESSAGE }] }, { code: 'require("x")', output: 'require("x")', errors: [ { message: IMPORT_MESSAGE }] }, + { code: 'require(`x`)', + parserOptions: { ecmaVersion: 2015 }, + output: 'require(`x`)', + errors: [ { message: IMPORT_MESSAGE }], + }, { code: 'if (typeof window !== "undefined") require("x")', options: [{ allowConditionalRequire: false }], @@ -106,4 +112,4 @@ ruleTester.run('no-commonjs', require('rules/no-commonjs'), { errors: [ { message: EXPORT_MESSAGE }], }, ], -}) +}); diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js index b0f4153e8d..302db8351b 100644 --- a/tests/src/rules/no-cycle.js +++ b/tests/src/rules/no-cycle.js @@ -1,21 +1,21 @@ -import { test as _test, testFilePath } from '../utils' +import { test as _test, testFilePath } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/no-cycle') +const ruleTester = new RuleTester(); +const rule = require('rules/no-cycle'); -const error = message => ({ message }) +const error = message => ({ message }); const test = def => _test(Object.assign(def, { filename: testFilePath('./cycles/depth-zero.js'), -})) +})); // describe.only("no-cycle", () => { ruleTester.run('no-cycle', rule, { valid: [ // this rule doesn't care if the cycle length is 0 - test({ code: 'import foo from "./foo.js"'}), + test({ code: 'import foo from "./foo.js"' }), test({ code: 'import _ from "lodash"' }), test({ code: 'import foo from "@scope/foo"' }), @@ -45,7 +45,7 @@ ruleTester.run('no-cycle', rule, { options: [{ ignoreExternal: true }], settings: { 'import/resolver': 'webpack', - 'import/external-module-folders': ['external'], + 'import/external-module-folders': ['cycles/external'], }, }), test({ @@ -53,7 +53,7 @@ ruleTester.run('no-cycle', rule, { options: [{ ignoreExternal: true }], settings: { 'import/resolver': 'webpack', - 'import/external-module-folders': ['external'], + 'import/external-module-folders': ['cycles/external'], }, }), test({ @@ -73,18 +73,31 @@ ruleTester.run('no-cycle', rule, { code: 'import { bar } from "./flow-types"', parser: require.resolve('babel-eslint'), }), + test({ + code: 'import { bar } from "./flow-types-only-importing-type"', + parser: require.resolve('babel-eslint'), + }), + test({ + code: 'import { bar } from "./flow-types-only-importing-multiple-types"', + parser: require.resolve('babel-eslint'), + }), ], invalid: [ test({ code: 'import { foo } from "./depth-one"', errors: [error(`Dependency cycle detected.`)], }), + test({ + code: 'import { bar } from "./flow-types-some-type-imports"', + parser: require.resolve('babel-eslint'), + 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'], + 'import/external-module-folders': ['cycles/external'], }, }), test({ @@ -92,7 +105,7 @@ ruleTester.run('no-cycle', rule, { errors: [error(`Dependency cycle via cycles/external/depth-one:1`)], settings: { 'import/resolver': 'webpack', - 'import/external-module-folders': ['external'], + 'import/external-module-folders': ['cycles/external'], }, }), test({ @@ -161,6 +174,16 @@ ruleTester.run('no-cycle', rule, { parser: require.resolve('babel-eslint'), errors: [error(`Dependency cycle via ./flow-types-depth-two:4=>./depth-one:1`)], }), + test({ + code: 'import { foo } from "./depth-two"', + options: [{ maxDepth: Infinity }], + errors: [error(`Dependency cycle via ./depth-one:1`)], + }), + test({ + code: 'import { foo } from "./depth-two"', + options: [{ maxDepth: '∞' }], + errors: [error(`Dependency cycle via ./depth-one:1`)], + }), ], -}) +}); // }) diff --git a/tests/src/rules/no-default-export.js b/tests/src/rules/no-default-export.js index d11b7d3b11..bc0119a019 100644 --- a/tests/src/rules/no-default-export.js +++ b/tests/src/rules/no-default-export.js @@ -1,9 +1,9 @@ -import { test } from '../utils' +import { test } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/no-default-export') +const ruleTester = new RuleTester(); +const rule = require('rules/no-default-export'); ruleTester.run('no-default-export', rule, { valid: [ @@ -119,4 +119,4 @@ ruleTester.run('no-default-export', rule, { }], }), ], -}) +}); diff --git a/tests/src/rules/no-deprecated.js b/tests/src/rules/no-deprecated.js index 36a137f7ad..aa2aebedc6 100644 --- a/tests/src/rules/no-deprecated.js +++ b/tests/src/rules/no-deprecated.js @@ -1,9 +1,9 @@ -import { test, SYNTAX_CASES, getTSParsers } from '../utils' +import { test, SYNTAX_CASES, getTSParsers } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/no-deprecated') +const ruleTester = new RuleTester(); +const rule = require('rules/no-deprecated'); ruleTester.run('no-deprecated', rule, { valid: [ @@ -174,7 +174,7 @@ ruleTester.run('no-deprecated', rule, { ], }), ], -}) +}); ruleTester.run('no-deprecated: hoisting', rule, { valid: [ @@ -196,7 +196,7 @@ ruleTester.run('no-deprecated: hoisting', rule, { }), ], -}) +}); describe('TypeScript', function () { getTSParsers().forEach((parser) => { @@ -206,7 +206,7 @@ describe('TypeScript', function () { 'import/parsers': { [parser]: ['.ts'] }, 'import/resolver': { 'eslint-import-resolver-typescript': true }, }, - } + }; ruleTester.run(parser, rule, { valid: [ @@ -220,9 +220,9 @@ describe('TypeScript', function () { errors: [ { type: 'ImportSpecifier', message: 'Deprecated: don\'t use this!' }, { type: 'Identifier', message: 'Deprecated: don\'t use this!' }, - ]}, - parserConfig)), + ] }, + parserConfig)), ], - }) - }) -}) + }); + }); +}); diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index 0137221b03..6b5bc739d5 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -1,14 +1,17 @@ -import * as path from 'path' -import { test as testUtil, getNonDefaultParsers } from '../utils' +import * as path from 'path'; +import { test as testUtil, getNonDefaultParsers } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; +import eslintPkg from 'eslint/package.json'; +import semver from 'semver'; -const ruleTester = new RuleTester() - , rule = require('rules/no-duplicates') +const ruleTester = new RuleTester(); +const rule = require('rules/no-duplicates'); -const test = process.env.ESLINT_VERSION === '3' || process.env.ESLINT_VERSION === '2' - ? t => testUtil(Object.assign({}, t, {output: t.code})) - : testUtil +// autofix only possible with eslint 4+ +const test = semver.satisfies(eslintPkg.version, '< 4') + ? t => testUtil(Object.assign({}, t, { output: t.code })) + : testUtil; ruleTester.run('no-duplicates', rule, { valid: [ @@ -29,12 +32,12 @@ ruleTester.run('no-duplicates', rule, { // #1107: Using different query strings that trigger different webpack loaders. test({ code: "import x from './bar?optionX'; import y from './bar?optionY';", - options: [{'considerQueryString': true}], + options: [{ 'considerQueryString': true }], settings: { 'import/resolver': 'webpack' }, }), test({ code: "import x from './foo'; import y from './bar';", - options: [{'considerQueryString': true}], + options: [{ 'considerQueryString': true }], settings: { 'import/resolver': 'webpack' }, }), @@ -65,30 +68,30 @@ ruleTester.run('no-duplicates', rule, { output: "import { x , y } from './bar'; ", settings: { 'import/resolve': { paths: [path.join( process.cwd() - , 'tests', 'files', - )] }}, + , 'tests', 'files', + )] } }, errors: 2, // path ends up hardcoded - }), + }), // #1107: Using different query strings that trigger different webpack loaders. test({ code: "import x from './bar.js?optionX'; import y from './bar?optionX';", settings: { 'import/resolver': 'webpack' }, errors: 2, // path ends up hardcoded - }), + }), test({ code: "import x from './bar?optionX'; import y from './bar?optionY';", settings: { 'import/resolver': 'webpack' }, errors: 2, // path ends up hardcoded - }), + }), // #1107: Using same query strings that trigger the same loader. test({ code: "import x from './bar?optionX'; import y from './bar.js?optionX';", - options: [{'considerQueryString': true}], + options: [{ 'considerQueryString': true }], settings: { 'import/resolver': 'webpack' }, errors: 2, // path ends up hardcoded - }), + }), // #86: duplicate unresolved modules should be flagged test({ @@ -398,33 +401,33 @@ ruleTester.run('no-duplicates', rule, { errors: ['\'./foo\' imported multiple times.', '\'./foo\' imported multiple times.'], }), ], -}) +}); 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: [ + .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: [], - }) - }) -}) + 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 5846032004..7dba242313 100644 --- a/tests/src/rules/no-dynamic-require.js +++ b/tests/src/rules/no-dynamic-require.js @@ -1,27 +1,27 @@ -import { test } from '../utils' +import { test } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/no-dynamic-require') +const ruleTester = new RuleTester(); +const rule = require('rules/no-dynamic-require'); const error = { message: 'Calls to require() should use string literals', -} +}; ruleTester.run('no-dynamic-require', rule, { valid: [ - test({ code: 'import _ from "lodash"'}), - test({ code: 'require("foo")'}), - test({ code: 'require(`foo`)'}), - test({ code: 'require("./foo")'}), - test({ code: 'require("@scope/foo")'}), - test({ code: 'require()'}), - test({ code: 'require("./foo", "bar" + "okay")'}), - test({ code: 'var foo = require("foo")'}), - test({ code: 'var foo = require(`foo`)'}), - test({ code: 'var foo = require("./foo")'}), - test({ code: 'var foo = require("@scope/foo")'}), + test({ code: 'import _ from "lodash"' }), + test({ code: 'require("foo")' }), + test({ code: 'require(`foo`)' }), + test({ code: 'require("./foo")' }), + test({ code: 'require("@scope/foo")' }), + test({ code: 'require()' }), + test({ code: 'require("./foo", "bar" + "okay")' }), + test({ code: 'var foo = require("foo")' }), + test({ code: 'var foo = require(`foo`)' }), + test({ code: 'var foo = require("./foo")' }), + test({ code: 'var foo = require("@scope/foo")' }), ], invalid: [ test({ @@ -44,5 +44,13 @@ ruleTester.run('no-dynamic-require', rule, { code: 'require(name + "foo", "bar")', errors: [error], }), + test({ + code: 'require(`foo${x}`)', + errors: [error], + }), + test({ + code: 'var foo = require(`foo${x}`)', + errors: [error], + }), ], -}) +}); diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index 97279d8538..6bb84358ae 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -1,33 +1,38 @@ -import { test } from '../utils' -import * as path from 'path' -import * as fs from 'fs' +import { getTSParsers, test, testFilePath } from '../utils'; +import typescriptConfig from '../../../config/typescript'; +import path from 'path'; +import fs from 'fs'; +import semver from 'semver'; +import eslintPkg from 'eslint/package.json'; -import { RuleTester } from 'eslint' -import flatMap from 'array.prototype.flatmap' +import { RuleTester } from 'eslint'; +import flatMap from 'array.prototype.flatmap'; -const ruleTester = new RuleTester() -const rule = require('rules/no-extraneous-dependencies') +const ruleTester = new RuleTester(); +const typescriptRuleTester = new RuleTester(typescriptConfig); +const rule = require('rules/no-extraneous-dependencies'); -const packageDirWithSyntaxError = path.join(__dirname, '../../files/with-syntax-error') +const packageDirWithSyntaxError = path.join(__dirname, '../../files/with-syntax-error'); const packageFileWithSyntaxErrorMessage = (() => { try { - JSON.parse(fs.readFileSync(path.join(packageDirWithSyntaxError, 'package.json'))) + JSON.parse(fs.readFileSync(path.join(packageDirWithSyntaxError, 'package.json'))); } catch (error) { - return error.message + return error.message; } -})() -const packageDirWithFlowTyped = path.join(__dirname, '../../files/with-flow-typed') -const packageDirMonoRepoRoot = path.join(__dirname, '../../files/monorepo') -const packageDirMonoRepoWithNested = path.join(__dirname, '../../files/monorepo/packages/nested-package') -const packageDirWithEmpty = path.join(__dirname, '../../files/empty') -const packageDirBundleDeps = path.join(__dirname, '../../files/bundled-dependencies/as-array-bundle-deps') -const packageDirBundledDepsAsObject = path.join(__dirname, '../../files/bundled-dependencies/as-object') -const packageDirBundledDepsRaceCondition = path.join(__dirname, '../../files/bundled-dependencies/race-condition') +})(); +const packageDirWithFlowTyped = path.join(__dirname, '../../files/with-flow-typed'); +const packageDirWithTypescriptDevDependencies = path.join(__dirname, '../../files/with-typescript-dev-dependencies'); +const packageDirMonoRepoRoot = path.join(__dirname, '../../files/monorepo'); +const packageDirMonoRepoWithNested = path.join(__dirname, '../../files/monorepo/packages/nested-package'); +const packageDirWithEmpty = path.join(__dirname, '../../files/empty'); +const packageDirBundleDeps = path.join(__dirname, '../../files/bundled-dependencies/as-array-bundle-deps'); +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') +} = require('../../files/package.json'); ruleTester.run('no-extraneous-dependencies', rule, { valid: [ @@ -39,17 +44,16 @@ ruleTester.run('no-extraneous-dependencies', rule, { test({ code: `export { foo } from "${pkg}"` }), test({ code: `export * from "${pkg}"` }), ]), - test({ code: 'import "eslint"'}), - test({ code: 'import "eslint/lib/api"'}), - test({ code: 'import "fs"'}), - test({ code: 'import "./foo"'}), - test({ code: 'import "@org/package"'}), + test({ code: 'import "eslint"' }), + test({ code: 'import "eslint/lib/api"' }), + test({ code: 'import "fs"' }), + test({ code: 'import "./foo"' }), + test({ code: 'import "@org/package"' }), test({ code: 'import "electron"', settings: { 'import/core-modules': ['electron'] } }), - test({ code: 'import "eslint"' }), test({ code: 'import "eslint"', - options: [{peerDependencies: true}], + options: [{ peerDependencies: true }], }), // 'project' type @@ -59,76 +63,103 @@ ruleTester.run('no-extraneous-dependencies', rule, { }), test({ code: 'import chai from "chai"', - options: [{devDependencies: ['*.spec.js']}], + options: [{ devDependencies: ['*.spec.js'] }], filename: 'foo.spec.js', }), test({ code: 'import chai from "chai"', - options: [{devDependencies: ['*.spec.js']}], + options: [{ devDependencies: ['*.spec.js'] }], filename: path.join(process.cwd(), 'foo.spec.js'), }), test({ code: 'import chai from "chai"', - options: [{devDependencies: ['*.test.js', '*.spec.js']}], + options: [{ devDependencies: ['*.test.js', '*.spec.js'] }], filename: path.join(process.cwd(), 'foo.spec.js'), }), test({ code: 'require(6)' }), test({ code: 'import "doctrine"', - options: [{packageDir: path.join(__dirname, '../../../')}], + options: [{ packageDir: path.join(__dirname, '../../../') }], }), test({ code: 'import type MyType from "myflowtyped";', - options: [{packageDir: packageDirWithFlowTyped}], + options: [{ packageDir: packageDirWithFlowTyped }], + parser: require.resolve('babel-eslint'), + }), + test({ + code: ` + // @flow + import typeof TypeScriptModule from 'typescript'; + `, + options: [{ packageDir: packageDirWithFlowTyped }], parser: require.resolve('babel-eslint'), }), test({ code: 'import react from "react";', - options: [{packageDir: packageDirMonoRepoWithNested}], + options: [{ packageDir: packageDirMonoRepoWithNested }], }), test({ code: 'import leftpad from "left-pad";', - options: [{packageDir: [packageDirMonoRepoWithNested, packageDirMonoRepoRoot]}], + options: [{ packageDir: [packageDirMonoRepoWithNested, packageDirMonoRepoRoot] }], }), test({ code: 'import leftpad from "left-pad";', - options: [{packageDir: packageDirMonoRepoRoot}], + options: [{ packageDir: packageDirMonoRepoRoot }], }), test({ code: 'import react from "react";', - options: [{packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested]}], + options: [{ packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested] }], }), test({ code: 'import leftpad from "left-pad";', - options: [{packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested]}], + options: [{ packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested] }], }), test({ code: 'import rightpad from "right-pad";', - options: [{packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested]}], + options: [{ packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested] }], }), - test({ code: 'import foo from "@generated/foo"'}), + test({ code: 'import foo from "@generated/foo"' }), test({ code: 'import foo from "@generated/foo"', - options: [{packageDir: packageDirBundleDeps}], + options: [{ packageDir: packageDirBundleDeps }], }), test({ code: 'import foo from "@generated/foo"', - options: [{packageDir: packageDirBundledDepsAsObject}], + options: [{ packageDir: packageDirBundledDepsAsObject }], }), test({ code: 'import foo from "@generated/foo"', - options: [{packageDir: packageDirBundledDepsRaceCondition}], + options: [{ packageDir: packageDirBundledDepsRaceCondition }], }), test({ code: 'export function getToken() {}' }), test({ code: 'export class Component extends React.Component {}' }), test({ code: 'export function Component() {}' }), test({ code: 'export const Component = () => {}' }), + + test({ + code: 'import "not-a-dependency"', + filename: path.join(packageDirMonoRepoRoot, 'foo.js'), + options: [{ packageDir: packageDirMonoRepoRoot }], + settings: { 'import/core-modules': ['not-a-dependency'] }, + }), + test({ + code: 'import "@generated/bar/module"', + settings: { 'import/core-modules': ['@generated/bar'] }, + }), + test({ + code: 'import "@generated/bar/and/sub/path"', + settings: { 'import/core-modules': ['@generated/bar'] }, + }), + // check if "rxjs" dependency declaration fix the "rxjs/operators subpackage + test({ + code: 'import "rxjs/operators"', + }), ], invalid: [ test({ code: 'import "not-a-dependency"', filename: path.join(packageDirMonoRepoRoot, 'foo.js'), - options: [{packageDir: packageDirMonoRepoRoot }], + options: [{ packageDir: packageDirMonoRepoRoot }], errors: [{ message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', }], @@ -136,14 +167,14 @@ ruleTester.run('no-extraneous-dependencies', rule, { test({ code: 'import "not-a-dependency"', filename: path.join(packageDirMonoRepoWithNested, 'foo.js'), - options: [{packageDir: packageDirMonoRepoRoot}], + options: [{ packageDir: packageDirMonoRepoRoot }], errors: [{ 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"', - options: [{packageDir: packageDirMonoRepoRoot}], + options: [{ packageDir: packageDirMonoRepoRoot }], errors: [{ message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', }], @@ -168,14 +199,14 @@ ruleTester.run('no-extraneous-dependencies', rule, { }), test({ code: 'import "eslint"', - options: [{devDependencies: false, peerDependencies: false}], + options: [{ devDependencies: false, peerDependencies: false }], errors: [{ message: '\'eslint\' should be listed in the project\'s dependencies, not devDependencies.', }], }), test({ code: 'import "lodash.isarray"', - options: [{optionalDependencies: false}], + options: [{ optionalDependencies: false }], errors: [{ message: '\'lodash.isarray\' should be listed in the project\'s dependencies, not optionalDependencies.', }], @@ -188,14 +219,14 @@ ruleTester.run('no-extraneous-dependencies', rule, { }), test({ code: 'var glob = require("glob")', - options: [{devDependencies: false}], + options: [{ devDependencies: false }], errors: [{ message: '\'glob\' should be listed in the project\'s dependencies, not devDependencies.', }], }), test({ code: 'import chai from "chai"', - options: [{devDependencies: ['*.test.js']}], + options: [{ devDependencies: ['*.test.js'] }], filename: 'foo.tes.js', errors: [{ message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.', @@ -203,7 +234,7 @@ ruleTester.run('no-extraneous-dependencies', rule, { }), test({ code: 'import chai from "chai"', - options: [{devDependencies: ['*.test.js']}], + options: [{ devDependencies: ['*.test.js'] }], filename: path.join(process.cwd(), 'foo.tes.js'), errors: [{ message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.', @@ -211,7 +242,7 @@ ruleTester.run('no-extraneous-dependencies', rule, { }), test({ code: 'import chai from "chai"', - options: [{devDependencies: ['*.test.js', '*.spec.js']}], + options: [{ devDependencies: ['*.test.js', '*.spec.js'] }], filename: 'foo.tes.js', errors: [{ message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.', @@ -219,7 +250,7 @@ ruleTester.run('no-extraneous-dependencies', rule, { }), test({ code: 'import chai from "chai"', - options: [{devDependencies: ['*.test.js', '*.spec.js']}], + options: [{ devDependencies: ['*.test.js', '*.spec.js'] }], filename: path.join(process.cwd(), 'foo.tes.js'), errors: [{ message: '\'chai\' should be listed in the project\'s dependencies, not devDependencies.', @@ -227,28 +258,28 @@ ruleTester.run('no-extraneous-dependencies', rule, { }), test({ code: 'var eslint = require("lodash.isarray")', - options: [{optionalDependencies: false}], + options: [{ optionalDependencies: false }], errors: [{ message: '\'lodash.isarray\' should be listed in the project\'s dependencies, not optionalDependencies.', }], }), test({ code: 'import "not-a-dependency"', - options: [{packageDir: path.join(__dirname, '../../../')}], + options: [{ packageDir: path.join(__dirname, '../../../') }], errors: [{ 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 "bar"', - options: [{packageDir: path.join(__dirname, './doesn-exist/')}], + options: [{ packageDir: path.join(__dirname, './doesn-exist/') }], errors: [{ message: 'The package.json file could not be found.', }], }), test({ code: 'import foo from "foo"', - options: [{packageDir: packageDirWithSyntaxError}], + options: [{ packageDir: packageDirWithSyntaxError }], errors: [{ message: 'The package.json file could not be parsed: ' + packageFileWithSyntaxErrorMessage, }], @@ -256,7 +287,7 @@ ruleTester.run('no-extraneous-dependencies', rule, { test({ code: 'import leftpad from "left-pad";', filename: path.join(packageDirMonoRepoWithNested, 'foo.js'), - options: [{packageDir: packageDirMonoRepoWithNested}], + options: [{ packageDir: packageDirMonoRepoWithNested }], errors: [{ message: "'left-pad' should be listed in the project's dependencies. Run 'npm i -S left-pad' to add it", }], @@ -271,7 +302,7 @@ ruleTester.run('no-extraneous-dependencies', rule, { test({ code: 'import react from "react";', filename: path.join(packageDirMonoRepoWithNested, 'foo.js'), - options: [{packageDir: packageDirMonoRepoRoot}], + options: [{ packageDir: packageDirMonoRepoRoot }], errors: [{ message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it", }], @@ -279,7 +310,7 @@ ruleTester.run('no-extraneous-dependencies', rule, { test({ code: 'import "react";', filename: path.join(packageDirWithEmpty, 'index.js'), - options: [{packageDir: packageDirWithEmpty}], + options: [{ packageDir: packageDirWithEmpty }], errors: [{ message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it", }], @@ -290,12 +321,12 @@ ruleTester.run('no-extraneous-dependencies', rule, { }), test({ code: 'import foo from "@generated/foo"', - options: [{bundledDependencies: false}], + options: [{ bundledDependencies: false }], errors: ["'@generated/foo' should be listed in the project's dependencies. Run 'npm i -S @generated/foo' to add it"], }), test({ code: 'import bar from "@generated/bar"', - options: [{packageDir: packageDirBundledDepsRaceCondition}], + options: [{ packageDir: packageDirBundledDepsRaceCondition }], errors: ["'@generated/bar' should be listed in the project's dependencies. Run 'npm i -S @generated/bar' to add it"], }), test({ @@ -310,5 +341,94 @@ ruleTester.run('no-extraneous-dependencies', rule, { 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 chai from "alias/chai";', + settings: { 'import/resolver': 'webpack' }, + errors: [{ + // missing dependency is chai not alias + message: "'chai' should be listed in the project's dependencies. Run 'npm i -S chai' to add it", + }], + }), + + test({ + code: 'import "not-a-dependency"', + filename: path.join(packageDirMonoRepoRoot, 'foo.js'), + options: [{ packageDir: packageDirMonoRepoRoot }], + errors: [{ + message: `'not-a-dependency' should be listed in the project's dependencies. Run 'npm i -S not-a-dependency' to add it`, + }], + }), ], -}) +}); + +// TODO: figure out why these tests fail in eslint 4 +describe('TypeScript', { skip: semver.satisfies(eslintPkg.version, '^4') }, function () { + getTSParsers().forEach((parser) => { + const parserConfig = { + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }; + + if (parser !== require.resolve('typescript-eslint-parser')) { + ruleTester.run('no-extraneous-dependencies', rule, { + valid: [ + test(Object.assign({ + code: 'import type { JSONSchema7Type } from "@types/json-schema";', + options: [{ packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }], + }, parserConfig)), + ], + invalid: [ + test(Object.assign({ + code: 'import { JSONSchema7Type } from "@types/json-schema";', + options: [{ packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }], + errors: [{ + message: "'@types/json-schema' should be listed in the project's dependencies, not devDependencies.", + }], + }, parserConfig)), + ], + }); + } else { + ruleTester.run('no-extraneous-dependencies', rule, { + valid: [], + invalid: [ + test(Object.assign({ + code: 'import { JSONSchema7Type } from "@types/json-schema"; /* typescript-eslint-parser */', + options: [{ packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }], + errors: [{ + message: "'@types/json-schema' should be listed in the project's dependencies, not devDependencies.", + }], + }, parserConfig)), + test(Object.assign({ + code: 'import type { JSONSchema7Type } from "@types/json-schema"; /* typescript-eslint-parser */', + options: [{ packageDir: packageDirWithTypescriptDevDependencies, devDependencies: false }], + errors: [{ + message: "'@types/json-schema' should be listed in the project's dependencies, not devDependencies.", + }], + }, parserConfig)), + ], + }); + } + }); +}); + +if (semver.satisfies(eslintPkg.version, '>5.0.0')) { + typescriptRuleTester.run('no-extraneous-dependencies typescript type imports', rule, { + valid: [ + test({ + code: 'import type MyType from "not-a-dependency";', + filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'), + parser: require.resolve('babel-eslint'), + }), + test({ + code: 'import type { MyType } from "not-a-dependency";', + filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'), + parser: require.resolve('babel-eslint'), + }), + ], + invalid: [ + ], + }); +} diff --git a/tests/src/rules/no-import-module-exports.js b/tests/src/rules/no-import-module-exports.js new file mode 100644 index 0000000000..9ffbe6b56e --- /dev/null +++ b/tests/src/rules/no-import-module-exports.js @@ -0,0 +1,108 @@ +import path from 'path'; +import { RuleTester } from 'eslint'; + +import { test } from '../utils'; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, +}); +const rule = require('rules/no-import-module-exports'); + +const error = { + message: `Cannot use import declarations in modules that export using CommonJS ` + + `(module.exports = 'foo' or exports.bar = 'hi')`, + type: 'ImportDeclaration', +}; + +ruleTester.run('no-import-module-exports', rule, { + valid: [ + test({ + code: ` + const thing = require('thing') + module.exports = thing + `, + }), + test({ + code: ` + import thing from 'otherthing' + console.log(thing.module.exports) + `, + }), + test({ + code: ` + import thing from 'other-thing' + export default thing + `, + }), + test({ + code: ` + const thing = require('thing') + exports.foo = bar + `, + }), + test({ + code: ` + import foo from 'path'; + module.exports = foo; + `, + // When the file matches the entry point defined in package.json + // See tests/files/package.json + filename: path.join(process.cwd(), 'tests/files/index.js'), + }), + test({ + code: ` + import foo from 'path'; + module.exports = foo; + `, + filename: path.join(process.cwd(), 'tests/files/some/other/entry-point.js'), + options: [{ exceptions: ['**/*/other/entry-point.js'] }], + }), + test({ + code: ` + import * as process from 'process'; + console.log(process.env); + `, + filename: path.join(process.cwd(), 'tests/files/missing-entrypoint/cli.js'), + }), + ], + invalid: [ + test({ + code: ` + import { stuff } from 'starwars' + module.exports = thing + `, + errors: [error], + }), + test({ + code: ` + import thing from 'starwars' + const baz = module.exports = thing + console.log(baz) + `, + errors: [error], + }), + test({ + code: ` + import * as allThings from 'starwars' + exports.bar = thing + `, + errors: [error], + }), + test({ + code: ` + import thing from 'other-thing' + exports.foo = bar + `, + errors: [error], + }), + test({ + code: ` + import foo from 'path'; + module.exports = foo; + `, + filename: path.join(process.cwd(), 'tests/files/some/other/entry-point.js'), + options: [{ exceptions: ['**/*/other/file.js'] }], + errors: [error], + }), + ], +}); diff --git a/tests/src/rules/no-internal-modules.js b/tests/src/rules/no-internal-modules.js index da9a4ca1a0..2bad32c460 100644 --- a/tests/src/rules/no-internal-modules.js +++ b/tests/src/rules/no-internal-modules.js @@ -1,10 +1,10 @@ -import { RuleTester } from 'eslint' -import flatMap from 'array.prototype.flatmap' -import rule from 'rules/no-internal-modules' +import { RuleTester } from 'eslint'; +import flatMap from 'array.prototype.flatmap'; +import rule from 'rules/no-internal-modules'; -import { test, testFilePath, getTSParsers } from '../utils' +import { test, testFilePath, getTSParsers } from '../utils'; -const ruleTester = new RuleTester() +const ruleTester = new RuleTester(); ruleTester.run('no-internal-modules', rule, { valid: [ @@ -59,6 +59,34 @@ ruleTester.run('no-internal-modules', rule, { allow: [ '**/index{.js,}' ], } ], }), + test({ + code: 'import a from "./plugin2/thing"', + filename: testFilePath('./internal-modules/plugins/plugin.js'), + options: [ { + forbid: [ '**/api/*' ], + } ], + }), + test({ + code: 'const a = require("./plugin2/thing")', + filename: testFilePath('./internal-modules/plugins/plugin.js'), + options: [ { + forbid: [ '**/api/*' ], + } ], + }), + test({ + code: 'import b from "app/a"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + forbid: [ 'app/**/**' ], + } ], + }), + test({ + code: 'import b from "@org/package"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + forbid: [ '@org/package/*' ], + } ], + }), // exports test({ code: 'export {a} from "./internal.js"', @@ -114,6 +142,34 @@ ruleTester.run('no-internal-modules', rule, { parser: parser, }), ]), + test({ + code: 'export * from "./plugin2/thing"', + filename: testFilePath('./internal-modules/plugins/plugin.js'), + options: [ { + forbid: [ '**/api/*' ], + } ], + }), + test({ + code: 'export * from "app/a"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + forbid: [ 'app/**/**' ], + } ], + }), + test({ + code: 'export { b } from "@org/package"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + forbid: [ '@org/package/*' ], + } ], + }), + test({ + code: 'export * from "./app/index.js";\nexport * from "./app/index"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + forbid: [ '**/index.ts' ], + } ], + }), ], invalid: [ @@ -184,6 +240,70 @@ ruleTester.run('no-internal-modules', rule, { }, ], }), + test({ + code: 'import "./app/index.js"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + forbid: [ '*/app/*' ], + } ], + errors: [ { + message: 'Reaching to "./app/index.js" is not allowed.', + line: 1, + column: 8, + } ], + }), + test({ + code: 'import b from "@org/package"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + forbid: [ '@org/**' ], + } ], + errors: [ { + message: 'Reaching to "@org/package" is not allowed.', + line: 1, + column: 15, + } ], + }), + test({ + code: 'import b from "app/a/b"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + forbid: [ 'app/**/**' ], + } ], + errors: [ { + message: 'Reaching to "app/a/b" is not allowed.', + line: 1, + column: 15, + } ], + }), + test({ + code: 'import get from "lodash.get"', + filename: testFilePath('./internal-modules/plugins/plugin2/index.js'), + options: [ { + forbid: [ 'lodash.*' ], + } ], + errors: [ { + message: 'Reaching to "lodash.get" is not allowed.', + line: 1, + column: 17, + } ], + }), + test({ + code: 'import "./app/index.js";\nimport "./app/index"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + forbid: [ '**/index{.js,}' ], + } ], + errors: [ { + message: 'Reaching to "./app/index.js" is not allowed.', + line: 1, + column: 8, + }, { + message: 'Reaching to "./app/index" is not allowed.', + line: 2, + column: 8, + } ], + }), // exports test({ code: 'export * from "./plugin2/index.js";\nexport * from "./plugin2/app/index"', @@ -251,5 +371,33 @@ ruleTester.run('no-internal-modules', rule, { }, ], }), + test({ + code: 'export * from "./plugin2/thing"', + filename: testFilePath('./internal-modules/plugins/plugin.js'), + options: [ { + forbid: [ '**/plugin2/*' ], + } ], + errors: [ + { + message: 'Reaching to "./plugin2/thing" is not allowed.', + line: 1, + column: 15, + }, + ], + }), + test({ + code: 'export * from "app/a"', + filename: testFilePath('./internal-modules/plugins/plugin2/internal.js'), + options: [ { + forbid: [ '**' ], + } ], + errors: [ + { + message: 'Reaching to "app/a" is not allowed.', + line: 1, + column: 15, + }, + ], + }), ], -}) +}); diff --git a/tests/src/rules/no-mutable-exports.js b/tests/src/rules/no-mutable-exports.js index 6d83566b74..2ecae48cdb 100644 --- a/tests/src/rules/no-mutable-exports.js +++ b/tests/src/rules/no-mutable-exports.js @@ -1,29 +1,29 @@ -import {test} from '../utils' -import {RuleTester} from 'eslint' -import rule from 'rules/no-mutable-exports' +import { test } from '../utils'; +import { RuleTester } from 'eslint'; +import rule from 'rules/no-mutable-exports'; -const ruleTester = new RuleTester() +const ruleTester = new RuleTester(); ruleTester.run('no-mutable-exports', rule, { valid: [ - test({ code: 'export const count = 1'}), - test({ code: 'export function getCount() {}'}), - test({ code: 'export class Counter {}'}), - test({ code: 'export default count = 1'}), - test({ code: 'export default function getCount() {}'}), - test({ code: 'export default class Counter {}'}), - test({ code: 'const count = 1\nexport { count }'}), - test({ code: 'const count = 1\nexport { count as counter }'}), - test({ code: 'const count = 1\nexport default count'}), - test({ code: 'const count = 1\nexport { count as default }'}), - test({ code: 'function getCount() {}\nexport { getCount }'}), - test({ code: 'function getCount() {}\nexport { getCount as getCounter }'}), - test({ code: 'function getCount() {}\nexport default getCount'}), - test({ code: 'function getCount() {}\nexport { getCount as default }'}), - test({ code: 'class Counter {}\nexport { Counter }'}), - test({ code: 'class Counter {}\nexport { Counter as Count }'}), - test({ code: 'class Counter {}\nexport default Counter'}), - test({ code: 'class Counter {}\nexport { Counter as default }'}), + test({ code: 'export const count = 1' }), + test({ code: 'export function getCount() {}' }), + test({ code: 'export class Counter {}' }), + test({ code: 'export default count = 1' }), + test({ code: 'export default function getCount() {}' }), + test({ code: 'export default class Counter {}' }), + test({ code: 'const count = 1\nexport { count }' }), + test({ code: 'const count = 1\nexport { count as counter }' }), + test({ code: 'const count = 1\nexport default count' }), + test({ code: 'const count = 1\nexport { count as default }' }), + test({ code: 'function getCount() {}\nexport { getCount }' }), + test({ code: 'function getCount() {}\nexport { getCount as getCounter }' }), + test({ code: 'function getCount() {}\nexport default getCount' }), + test({ code: 'function getCount() {}\nexport { getCount as default }' }), + test({ code: 'class Counter {}\nexport { Counter }' }), + test({ code: 'class Counter {}\nexport { Counter as Count }' }), + test({ code: 'class Counter {}\nexport default Counter' }), + test({ code: 'class Counter {}\nexport { Counter as default }' }), test({ parser: require.resolve('babel-eslint'), code: 'export Something from "./something";', @@ -77,4 +77,4 @@ ruleTester.run('no-mutable-exports', rule, { // errors: ['Exporting mutable global binding, use \'const\' instead.'], // }), ], -}) +}); diff --git a/tests/src/rules/no-named-as-default-member.js b/tests/src/rules/no-named-as-default-member.js index b5e294d190..b4f3cf5896 100644 --- a/tests/src/rules/no-named-as-default-member.js +++ b/tests/src/rules/no-named-as-default-member.js @@ -1,16 +1,16 @@ -import { test, SYNTAX_CASES } from '../utils' -import {RuleTester} from 'eslint' -import rule from 'rules/no-named-as-default-member' +import { test, SYNTAX_CASES } from '../utils'; +import { RuleTester } from 'eslint'; +import rule from 'rules/no-named-as-default-member'; -const ruleTester = new RuleTester() +const ruleTester = new RuleTester(); ruleTester.run('no-named-as-default-member', rule, { valid: [ - test({code: 'import bar, {foo} from "./bar";'}), - test({code: 'import bar from "./bar"; const baz = bar.baz'}), - test({code: 'import {foo} from "./bar"; const baz = foo.baz;'}), - test({code: 'import * as named from "./named-exports"; const a = named.a'}), - test({code: 'import foo from "./default-export-default-property"; const a = foo.default'}), + test({ code: 'import bar, {foo} from "./bar";' }), + test({ code: 'import bar from "./bar"; const baz = bar.baz' }), + test({ code: 'import {foo} from "./bar"; const baz = foo.baz;' }), + test({ code: 'import * as named from "./named-exports"; const a = named.a' }), + test({ code: 'import foo from "./default-export-default-property"; const a = foo.default' }), ...SYNTAX_CASES, ], @@ -57,4 +57,4 @@ ruleTester.run('no-named-as-default-member', rule, { }], }), ], -}) +}); diff --git a/tests/src/rules/no-named-as-default.js b/tests/src/rules/no-named-as-default.js index a5e0f041f5..57b2f53bd8 100644 --- a/tests/src/rules/no-named-as-default.js +++ b/tests/src/rules/no-named-as-default.js @@ -1,25 +1,25 @@ -import { test, SYNTAX_CASES } from '../utils' -import { RuleTester } from 'eslint' +import { test, SYNTAX_CASES } from '../utils'; +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/no-named-as-default') +const ruleTester = new RuleTester(); +const rule = require('rules/no-named-as-default'); ruleTester.run('no-named-as-default', rule, { valid: [ test({ code: 'import "./malformed.js"' }), - test({code: 'import bar, { foo } from "./bar";'}), - test({code: 'import bar, { foo } from "./empty-folder";'}), + test({ code: 'import bar, { foo } from "./bar";' }), + test({ code: 'import bar, { foo } from "./empty-folder";' }), // es7 - test({ code: 'export bar, { foo } from "./bar";' - , parser: require.resolve('babel-eslint') }), - test({ code: 'export bar from "./bar";' - , parser: require.resolve('babel-eslint') }), + test({ code: 'export bar, { foo } from "./bar";', + parser: require.resolve('babel-eslint') }), + test({ code: 'export bar from "./bar";', + parser: require.resolve('babel-eslint') }), // #566: don't false-positive on `default` itself - test({ code: 'export default from "./bar";' - , parser: require.resolve('babel-eslint') }), + test({ code: 'export default from "./bar";', + parser: require.resolve('babel-eslint') }), ...SYNTAX_CASES, ], @@ -28,27 +28,27 @@ ruleTester.run('no-named-as-default', rule, { test({ code: 'import foo from "./bar";', errors: [ { - message: 'Using exported name \'foo\' as identifier for default export.' - , type: 'ImportDefaultSpecifier' } ] }), + message: 'Using exported name \'foo\' as identifier for default export.', + type: 'ImportDefaultSpecifier' } ] }), test({ code: 'import foo, { foo as bar } from "./bar";', errors: [ { - message: 'Using exported name \'foo\' as identifier for default export.' - , type: 'ImportDefaultSpecifier' } ] }), + message: 'Using exported name \'foo\' as identifier for default export.', + type: 'ImportDefaultSpecifier' } ] }), // es7 test({ code: 'export foo from "./bar";', parser: require.resolve('babel-eslint'), errors: [ { - message: 'Using exported name \'foo\' as identifier for default export.' - , type: 'ExportDefaultSpecifier' } ] }), + message: 'Using exported name \'foo\' as identifier for default export.', + type: 'ExportDefaultSpecifier' } ] }), test({ code: 'export foo, { foo as bar } from "./bar";', parser: require.resolve('babel-eslint'), errors: [ { - message: 'Using exported name \'foo\' as identifier for default export.' - , type: 'ExportDefaultSpecifier' } ] }), + message: 'Using exported name \'foo\' as identifier for default export.', + type: 'ExportDefaultSpecifier' } ] }), test({ code: 'import foo from "./malformed.js"', @@ -58,4 +58,4 @@ ruleTester.run('no-named-as-default', rule, { }], }), ], -}) +}); diff --git a/tests/src/rules/no-named-default.js b/tests/src/rules/no-named-default.js index f9109fa8fd..56470f2bac 100644 --- a/tests/src/rules/no-named-default.js +++ b/tests/src/rules/no-named-default.js @@ -1,13 +1,23 @@ -import { test, SYNTAX_CASES } from '../utils' -import { RuleTester } from 'eslint' +import { test, SYNTAX_CASES } from '../utils'; +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/no-named-default') +const ruleTester = new RuleTester(); +const rule = require('rules/no-named-default'); ruleTester.run('no-named-default', rule, { valid: [ - test({code: 'import bar from "./bar";'}), - test({code: 'import bar, { foo } from "./bar";'}), + test({ code: 'import bar from "./bar";' }), + test({ code: 'import bar, { foo } from "./bar";' }), + + // Should ignore imported flow types + test({ + code: 'import { type default as Foo } from "./bar";', + parser: require.resolve('babel-eslint'), + }), + test({ + code: 'import { typeof default as Foo } from "./bar";', + parser: require.resolve('babel-eslint'), + }), ...SYNTAX_CASES, ], @@ -36,4 +46,4 @@ ruleTester.run('no-named-default', rule, { }], }), ], -}) +}); diff --git a/tests/src/rules/no-named-export.js b/tests/src/rules/no-named-export.js index bde92b9e41..41d0fcd7cf 100644 --- a/tests/src/rules/no-named-export.js +++ b/tests/src/rules/no-named-export.js @@ -1,8 +1,8 @@ -import { RuleTester } from 'eslint' -import { test } from '../utils' +import { RuleTester } from 'eslint'; +import { test } from '../utils'; -const ruleTester = new RuleTester() - , rule = require('rules/no-named-export') +const ruleTester = new RuleTester(); +const rule = require('rules/no-named-export'); ruleTester.run('no-named-export', rule, { valid: [ @@ -177,4 +177,4 @@ ruleTester.run('no-named-export', rule, { }], }), ], -}) +}); diff --git a/tests/src/rules/no-namespace.js b/tests/src/rules/no-namespace.js index a7cb4dd21f..d7c4c9cf8f 100644 --- a/tests/src/rules/no-namespace.js +++ b/tests/src/rules/no-namespace.js @@ -1,11 +1,11 @@ -import { RuleTester } from 'eslint' -import eslintPkg from 'eslint/package.json' -import semver from 'semver' -import { test } from '../utils' +import { RuleTester } from 'eslint'; +import eslintPkg from 'eslint/package.json'; +import semver from 'semver'; +import { test } from '../utils'; -const ERROR_MESSAGE = 'Unexpected namespace import.' +const ERROR_MESSAGE = 'Unexpected namespace import.'; -const ruleTester = new RuleTester() +const ruleTester = new RuleTester(); // --fix functionality requires ESLint 5+ const FIX_TESTS = semver.satisfies(eslintPkg.version, '>5.0.0') ? [ @@ -70,7 +70,7 @@ const FIX_TESTS = semver.satisfies(eslintPkg.version, '>5.0.0') ? [ message: ERROR_MESSAGE, }], }), -] : [] +] : []; ruleTester.run('no-namespace', require('rules/no-namespace'), { valid: [ @@ -110,4 +110,4 @@ ruleTester.run('no-namespace', require('rules/no-namespace'), { }), ...FIX_TESTS, ], -}) +}); diff --git a/tests/src/rules/no-nodejs-modules.js b/tests/src/rules/no-nodejs-modules.js index 4be050c63f..3587a71dca 100644 --- a/tests/src/rules/no-nodejs-modules.js +++ b/tests/src/rules/no-nodejs-modules.js @@ -1,30 +1,30 @@ -import { test } from '../utils' +import { test } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/no-nodejs-modules') +const ruleTester = new RuleTester(); +const rule = require('rules/no-nodejs-modules'); const error = message => ({ message, -}) +}); ruleTester.run('no-nodejs-modules', rule, { valid: [ - test({ code: 'import _ from "lodash"'}), - test({ code: 'import find from "lodash.find"'}), - test({ code: 'import foo from "./foo"'}), - test({ code: 'import foo from "../foo"'}), - test({ code: 'import foo from "foo"'}), - test({ code: 'import foo from "./"'}), - test({ code: 'import foo from "@scope/foo"'}), - test({ code: 'var _ = require("lodash")'}), - test({ code: 'var find = require("lodash.find")'}), - test({ code: 'var foo = require("./foo")'}), - test({ code: 'var foo = require("../foo")'}), - test({ code: 'var foo = require("foo")'}), - test({ code: 'var foo = require("./")'}), - test({ code: 'var foo = require("@scope/foo")'}), + test({ code: 'import _ from "lodash"' }), + test({ code: 'import find from "lodash.find"' }), + test({ code: 'import foo from "./foo"' }), + test({ code: 'import foo from "../foo"' }), + test({ code: 'import foo from "foo"' }), + test({ code: 'import foo from "./"' }), + test({ code: 'import foo from "@scope/foo"' }), + test({ code: 'var _ = require("lodash")' }), + test({ code: 'var find = require("lodash.find")' }), + test({ code: 'var foo = require("./foo")' }), + test({ code: 'var foo = require("../foo")' }), + test({ code: 'var foo = require("foo")' }), + test({ code: 'var foo = require("./")' }), + test({ code: 'var foo = require("@scope/foo")' }), test({ code: 'import events from "events"', options: [{ @@ -81,4 +81,4 @@ ruleTester.run('no-nodejs-modules', rule, { errors: [error('Do not import Node.js builtin module "fs"')], }), ], -}) +}); diff --git a/tests/src/rules/no-relative-packages.js b/tests/src/rules/no-relative-packages.js new file mode 100644 index 0000000000..1a706387c0 --- /dev/null +++ b/tests/src/rules/no-relative-packages.js @@ -0,0 +1,79 @@ +import { RuleTester } from 'eslint'; +import rule from 'rules/no-relative-packages'; +import { normalize } from 'path'; + +import { test, testFilePath } from '../utils'; + +const ruleTester = new RuleTester(); + +ruleTester.run('no-relative-packages', rule, { + valid: [ + test({ + code: 'import foo from "./index.js"', + filename: testFilePath('./package/index.js'), + }), + test({ + code: 'import bar from "../bar"', + filename: testFilePath('./package/index.js'), + }), + test({ + code: 'import {foo} from "a"', + filename: testFilePath('./package-named/index.js'), + }), + test({ + code: 'const bar = require("../bar.js")', + filename: testFilePath('./package/index.js'), + }), + test({ + code: 'const bar = require("../not/a/file/path.js")', + filename: testFilePath('./package/index.js'), + }), + test({ + code: 'import "package"', + filename: testFilePath('./package/index.js'), + }), + test({ + code: 'require("../bar.js")', + filename: testFilePath('./package/index.js'), + }), + ], + + invalid: [ + test({ + code: 'import foo from "./package-named"', + filename: testFilePath('./bar.js'), + errors: [ { + message: 'Relative import from another package is not allowed. Use `package-named` instead of `./package-named`', + line: 1, + column: 17, + } ], + }), + test({ + code: 'import foo from "../package-named"', + filename: testFilePath('./package/index.js'), + errors: [ { + message: 'Relative import from another package is not allowed. Use `package-named` instead of `../package-named`', + line: 1, + column: 17, + } ], + }), + test({ + code: 'import foo from "../package-scoped"', + filename: testFilePath('./package/index.js'), + errors: [ { + message: `Relative import from another package is not allowed. Use \`${normalize('@scope/package-named')}\` instead of \`../package-scoped\``, + line: 1, + column: 17, + } ], + }), + test({ + code: 'import bar from "../bar"', + filename: testFilePath('./package-named/index.js'), + errors: [ { + message: `Relative import from another package is not allowed. Use \`${normalize('eslint-plugin-import/tests/files/bar')}\` instead of \`../bar\``, + line: 1, + column: 17, + } ], + }), + ], +}); diff --git a/tests/src/rules/no-relative-parent-imports.js b/tests/src/rules/no-relative-parent-imports.js index 05ef9d8bff..d6a47ae373 100644 --- a/tests/src/rules/no-relative-parent-imports.js +++ b/tests/src/rules/no-relative-parent-imports.js @@ -1,13 +1,13 @@ -import { RuleTester } from 'eslint' -import rule from 'rules/no-relative-parent-imports' -import { test as _test, testFilePath } from '../utils' +import { RuleTester } from 'eslint'; +import rule from 'rules/no-relative-parent-imports'; +import { test as _test, testFilePath } from '../utils'; const test = def => _test(Object.assign(def, { filename: testFilePath('./internal-modules/plugins/plugin2/index.js'), parser: require.resolve('babel-eslint'), -})) +})); -const ruleTester = new RuleTester() +const ruleTester = new RuleTester(); ruleTester.run('no-relative-parent-imports', rule, { valid: [ @@ -103,4 +103,4 @@ ruleTester.run('no-relative-parent-imports', rule, { }], }), ], -}) +}); diff --git a/tests/src/rules/no-restricted-paths.js b/tests/src/rules/no-restricted-paths.js index bd5ab29314..3ee728c5c7 100644 --- a/tests/src/rules/no-restricted-paths.js +++ b/tests/src/rules/no-restricted-paths.js @@ -1,9 +1,9 @@ -import { RuleTester } from 'eslint' -import rule from 'rules/no-restricted-paths' +import { RuleTester } from 'eslint'; +import rule from 'rules/no-restricted-paths'; -import { test, testFilePath } from '../utils' +import { test, testFilePath } from '../utils'; -const ruleTester = new RuleTester() +const ruleTester = new RuleTester(); ruleTester.run('no-restricted-paths', rule, { valid: [ @@ -50,6 +50,17 @@ ruleTester.run('no-restricted-paths', rule, { } ], } ], }), + test({ + code: 'import a from "../one/a.js"', + filename: testFilePath('./restricted-paths/server/two-new/a.js'), + options: [ { + zones: [ { + target: './tests/files/restricted-paths/server/two', + from: './tests/files/restricted-paths/server', + except: [], + } ], + } ], + }), // irrelevant function calls @@ -57,9 +68,9 @@ ruleTester.run('no-restricted-paths', rule, { test({ code: 'notrequire("../server/b.js")', filename: testFilePath('./restricted-paths/client/a.js'), - options: [ { - zones: [ { target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/server' } ], - } ] }), + options: [ { + zones: [ { target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/server' } ], + } ] }), // no config test({ code: 'require("../server/b.js")' }), @@ -180,4 +191,4 @@ ruleTester.run('no-restricted-paths', rule, { } ], }), ], -}) +}); diff --git a/tests/src/rules/no-self-import.js b/tests/src/rules/no-self-import.js index 281d67107f..ff1248b43c 100644 --- a/tests/src/rules/no-self-import.js +++ b/tests/src/rules/no-self-import.js @@ -1,13 +1,13 @@ -import { test, testFilePath } from '../utils' +import { test, testFilePath } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/no-self-import') +const ruleTester = new RuleTester(); +const rule = require('rules/no-self-import'); const error = { message: 'Module imports itself.', -} +}; ruleTester.run('no-self-import', rule, { valid: [ @@ -117,4 +117,4 @@ ruleTester.run('no-self-import', rule, { filename: testFilePath('./no-self-import-folder/index.js'), }), ], -}) +}); diff --git a/tests/src/rules/no-unassigned-import.js b/tests/src/rules/no-unassigned-import.js index d4fca8f457..8724b80d30 100644 --- a/tests/src/rules/no-unassigned-import.js +++ b/tests/src/rules/no-unassigned-import.js @@ -1,32 +1,32 @@ -import { test } from '../utils' -import * as path from 'path' +import { test } from '../utils'; +import * as path from 'path'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/no-unassigned-import') +const ruleTester = new RuleTester(); +const rule = require('rules/no-unassigned-import'); const error = { message: 'Imported module should be assigned', -} +}; ruleTester.run('no-unassigned-import', rule, { valid: [ - test({ code: 'import _ from "lodash"'}), - test({ code: 'import _, {foo} from "lodash"'}), - test({ code: 'import _, {foo as bar} from "lodash"'}), - test({ code: 'import {foo as bar} from "lodash"'}), - test({ code: 'import * as _ from "lodash"'}), - test({ code: 'import _ from "./"'}), - test({ code: 'const _ = require("lodash")'}), - test({ code: 'const {foo} = require("lodash")'}), - test({ code: 'const {foo: bar} = require("lodash")'}), - test({ code: 'const [a, b] = require("lodash")'}), - test({ code: 'const _ = require("./")'}), - test({ code: 'foo(require("lodash"))'}), - test({ code: 'require("lodash").foo'}), - test({ code: 'require("lodash").foo()'}), - test({ code: 'require("lodash")()'}), + test({ code: 'import _ from "lodash"' }), + test({ code: 'import _, {foo} from "lodash"' }), + test({ code: 'import _, {foo as bar} from "lodash"' }), + test({ code: 'import {foo as bar} from "lodash"' }), + test({ code: 'import * as _ from "lodash"' }), + test({ code: 'import _ from "./"' }), + test({ code: 'const _ = require("lodash")' }), + test({ code: 'const {foo} = require("lodash")' }), + test({ code: 'const {foo: bar} = require("lodash")' }), + test({ code: 'const [a, b] = require("lodash")' }), + test({ code: 'const _ = require("./")' }), + test({ code: 'foo(require("lodash"))' }), + test({ code: 'require("lodash").foo' }), + test({ code: 'require("lodash").foo()' }), + test({ code: 'require("lodash")()' }), test({ code: 'import "app.css"', options: [{ 'allow': ['**/*.css'] }], @@ -105,4 +105,4 @@ ruleTester.run('no-unassigned-import', rule, { errors: [error], }), ], -}) +}); diff --git a/tests/src/rules/no-unresolved.js b/tests/src/rules/no-unresolved.js index cc3aca8844..a3ac4b19b8 100644 --- a/tests/src/rules/no-unresolved.js +++ b/tests/src/rules/no-unresolved.js @@ -1,13 +1,13 @@ -import * as path from 'path' +import * as path from 'path'; -import { test, SYNTAX_CASES } from '../utils' +import { test, SYNTAX_CASES, testVersion } from '../utils'; -import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve' +import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -var ruleTester = new RuleTester() - , rule = require('rules/no-unresolved') +const ruleTester = new RuleTester(); +const rule = require('rules/no-unresolved'); function runResolverTests(resolver) { // redefine 'test' to set a resolver @@ -16,21 +16,27 @@ function runResolverTests(resolver) { specs.settings = Object.assign({}, specs.settings, { 'import/resolver': resolver }, - ) + ); - return test(specs) + return test(specs); } ruleTester.run(`no-unresolved (${resolver})`, rule, { - valid: [ + valid: [].concat( test({ code: 'import "./malformed.js"' }), rest({ code: 'import foo from "./bar";' }), rest({ code: "import bar from './bar.js';" }), rest({ code: "import {someThing} from './test-module';" }), rest({ code: "import fs from 'fs';" }), - rest({ code: "import('fs');" - , parser: require.resolve('babel-eslint') }), + rest({ code: "import('fs');", + parser: require.resolve('babel-eslint') }), + + // check with eslint parser + testVersion('>= 7', () => rest({ + code: "import('fs');", + parserOptions: { ecmaVersion: 2021 }, + })) || [], rest({ code: 'import * as foo from "a"' }), @@ -39,53 +45,53 @@ function runResolverTests(resolver) { rest({ code: 'let foo; export { foo }' }), // stage 1 proposal for export symmetry, - rest({ code: 'export * as bar from "./bar"' - , parser: require.resolve('babel-eslint') }), - rest({ code: 'export bar from "./bar"' - , parser: require.resolve('babel-eslint') }), + rest({ code: 'export * as bar from "./bar"', + parser: require.resolve('babel-eslint') }), + rest({ code: 'export bar from "./bar"', + parser: require.resolve('babel-eslint') }), rest({ code: 'import foo from "./jsx/MyUnCoolComponent.jsx"' }), // commonjs setting - rest({ code: 'var foo = require("./bar")' - , options: [{ commonjs: true }]}), - rest({ code: 'require("./bar")' - , options: [{ commonjs: true }]}), - rest({ code: 'require("./does-not-exist")' - , options: [{ commonjs: false }]}), + rest({ code: 'var foo = require("./bar")', + options: [{ commonjs: true }] }), + rest({ code: 'require("./bar")', + options: [{ commonjs: true }] }), + rest({ code: 'require("./does-not-exist")', + options: [{ commonjs: false }] }), rest({ code: 'require("./does-not-exist")' }), // amd setting - rest({ code: 'require(["./bar"], function (bar) {})' - , options: [{ amd: true }]}), - rest({ code: 'define(["./bar"], function (bar) {})' - , options: [{ amd: true }]}), - rest({ code: 'require(["./does-not-exist"], function (bar) {})' - , options: [{ amd: false }]}), + rest({ code: 'require(["./bar"], function (bar) {})', + options: [{ amd: true }] }), + rest({ code: 'define(["./bar"], function (bar) {})', + options: [{ amd: true }] }), + rest({ code: 'require(["./does-not-exist"], function (bar) {})', + options: [{ amd: false }] }), // magic modules: http://git.io/vByan - rest({ code: 'define(["require", "exports", "module"], function (r, e, m) { })' - , options: [{ amd: true }]}), + rest({ code: 'define(["require", "exports", "module"], function (r, e, m) { })', + options: [{ amd: true }] }), // don't validate without callback param - rest({ code: 'require(["./does-not-exist"])' - , options: [{ amd: true }]}), + rest({ code: 'require(["./does-not-exist"])', + options: [{ amd: true }] }), rest({ code: 'define(["./does-not-exist"], function (bar) {})' }), // stress tests - rest({ code: 'require("./does-not-exist", "another arg")' - , options: [{ commonjs: true, amd: true }]}), - rest({ code: 'proxyquire("./does-not-exist")' - , options: [{ commonjs: true, amd: true }]}), - rest({ code: '(function() {})("./does-not-exist")' - , options: [{ commonjs: true, amd: true }]}), - rest({ code: 'define([0, foo], function (bar) {})' - , options: [{ amd: true }]}), - rest({ code: 'require(0)' - , options: [{ commonjs: true }]}), - rest({ code: 'require(foo)' - , options: [{ commonjs: true }]}), - ], - - invalid: [ + rest({ code: 'require("./does-not-exist", "another arg")', + options: [{ commonjs: true, amd: true }] }), + rest({ code: 'proxyquire("./does-not-exist")', + options: [{ commonjs: true, amd: true }] }), + rest({ code: '(function() {})("./does-not-exist")', + options: [{ commonjs: true, amd: true }] }), + rest({ code: 'define([0, foo], function (bar) {})', + options: [{ amd: true }] }), + rest({ code: 'require(0)', + options: [{ commonjs: true }] }), + rest({ code: 'require(foo)', + options: [{ commonjs: true }] }), + ), + + invalid: [].concat( rest({ code: 'import reallyfake from "./reallyfake/module"', settings: { 'import/ignore': ['^\\./fake/'] }, @@ -93,53 +99,62 @@ function runResolverTests(resolver) { '\'./reallyfake/module\'.' }], }), - rest({ code: "import bar from './baz';", - errors: [{ message: "Unable to resolve path to module './baz'." - , type: 'Literal' }], + errors: [{ message: "Unable to resolve path to module './baz'.", + type: 'Literal' }], }), - rest({ code: "import bar from './baz';" - , errors: [{ message: "Unable to resolve path to module './baz'." - , type: 'Literal', - }] }), + rest({ code: "import bar from './baz';", + errors: [{ message: "Unable to resolve path to module './baz'.", + type: 'Literal', + }] }), rest({ code: "import bar from './empty-folder';", - errors: [{ message: "Unable to resolve path to module './empty-folder'." - , type: 'Literal', - }]}), + errors: [{ message: "Unable to resolve path to module './empty-folder'.", + type: 'Literal', + }] }), // sanity check that this module is _not_ found without proper settings rest({ code: "import { DEEP } from 'in-alternate-root';", errors: [{ message: 'Unable to resolve path to ' + - "module 'in-alternate-root'." - , type: 'Literal', - }]}), + "module 'in-alternate-root'.", + type: 'Literal', + }] }), rest({ - code: "import('in-alternate-root').then(function({DEEP}){});", - errors: [{ message: 'Unable to resolve path to ' + - "module 'in-alternate-root'." - , type: 'Literal', - }], - parser: require.resolve('babel-eslint')}), - - rest({ code: 'export { foo } from "./does-not-exist"' - , errors: ["Unable to resolve path to module './does-not-exist'."] }), + code: "import('in-alternate-root').then(function({DEEP}){});", + errors: [{ + message: 'Unable to resolve path to module \'in-alternate-root\'.', + type: 'Literal', + }], + parser: require.resolve('babel-eslint') }), + + rest({ code: 'export { foo } from "./does-not-exist"', + errors: ["Unable to resolve path to module './does-not-exist'."] }), rest({ code: 'export * from "./does-not-exist"', errors: ["Unable to resolve path to module './does-not-exist'."], }), + // check with eslint parser + testVersion('>= 7', () => rest({ + code: "import('in-alternate-root').then(function({DEEP}){});", + errors: [{ + message: 'Unable to resolve path to module \'in-alternate-root\'.', + type: 'Literal', + }], + parserOptions: { ecmaVersion: 2021 }, + })) || [], + // export symmetry proposal - rest({ code: 'export * as bar from "./does-not-exist"' - , parser: require.resolve('babel-eslint') - , errors: ["Unable to resolve path to module './does-not-exist'."], - }), - rest({ code: 'export bar from "./does-not-exist"' - , parser: require.resolve('babel-eslint') - , errors: ["Unable to resolve path to module './does-not-exist'."], - }), + rest({ code: 'export * as bar from "./does-not-exist"', + parser: require.resolve('babel-eslint'), + errors: ["Unable to resolve path to module './does-not-exist'."], + }), + rest({ code: 'export bar from "./does-not-exist"', + parser: require.resolve('babel-eslint'), + errors: ["Unable to resolve path to module './does-not-exist'."], + }), // commonjs setting rest({ @@ -187,8 +202,8 @@ function runResolverTests(resolver) { type: 'Literal', }], }), - ], - }) + ), + }); ruleTester.run(`issue #333 (${resolver})`, rule, { valid: [ @@ -204,12 +219,12 @@ function runResolverTests(resolver) { }), ], invalid: [ - rest({ - code: 'import bar from "./foo.json"', - errors: ["Unable to resolve path to module './foo.json'."], - }), + rest({ + code: 'import bar from "./foo.json"', + errors: ["Unable to resolve path to module './foo.json'."], + }), ], - }) + }); if (!CASE_SENSITIVE_FS) { ruleTester.run('case sensitivity', rule, { @@ -231,12 +246,12 @@ function runResolverTests(resolver) { errors: [`Casing of ./jsx/MyUncoolComponent.jsx does not match the underlying filesystem.`], }), ], - }) + }); } } -['node', 'webpack'].forEach(runResolverTests) +['node', 'webpack'].forEach(runResolverTests); ruleTester.run('no-unresolved (import/resolve legacy)', rule, { valid: [ @@ -245,7 +260,7 @@ ruleTester.run('no-unresolved (import/resolve legacy)', rule, { settings: { 'import/resolve': { 'paths': [path.join( process.cwd() - , 'tests', 'files', 'alternate-root')], + , 'tests', 'files', 'alternate-root')], }, }, }), @@ -253,10 +268,10 @@ ruleTester.run('no-unresolved (import/resolve legacy)', rule, { test({ code: "import { DEEP } from 'in-alternate-root'; " + "import { bar } from 'src-bar';", - settings: {'import/resolve': { 'paths': [ + settings: { 'import/resolve': { 'paths': [ path.join('tests', 'files', 'src-root'), path.join('tests', 'files', 'alternate-root'), - ]}}}), + ] } } }), test({ code: 'import * as foo from "jsx-module/foo"', @@ -270,7 +285,7 @@ ruleTester.run('no-unresolved (import/resolve legacy)', rule, { errors: [ "Unable to resolve path to module 'jsx-module/foo'." ], }), ], -}) +}); ruleTester.run('no-unresolved (webpack-specific)', rule, { valid: [ @@ -295,45 +310,45 @@ ruleTester.run('no-unresolved (webpack-specific)', rule, { errors: [ "Unable to resolve path to module 'jsx-module/foo'." ], }), ], -}) +}); ruleTester.run('no-unresolved ignore list', rule, { valid: [ test({ code: 'import "./malformed.js"', - options: [{ ignore: ['.png$', '.gif$']}], + options: [{ ignore: ['.png$', '.gif$'] }], }), test({ code: 'import "./test.giffy"', - options: [{ ignore: ['.png$', '.gif$']}], + options: [{ ignore: ['.png$', '.gif$'] }], }), test({ code: 'import "./test.gif"', - options: [{ ignore: ['.png$', '.gif$']}], + options: [{ ignore: ['.png$', '.gif$'] }], }), test({ code: 'import "./test.png"', - options: [{ ignore: ['.png$', '.gif$']}], + options: [{ ignore: ['.png$', '.gif$'] }], }), ], invalid:[ test({ code: 'import "./test.gif"', - options: [{ ignore: ['.png$']}], + options: [{ ignore: ['.png$'] }], errors: [ "Unable to resolve path to module './test.gif'." ], }), test({ code: 'import "./test.png"', - options: [{ ignore: ['.gif$']}], + options: [{ ignore: ['.gif$'] }], errors: [ "Unable to resolve path to module './test.png'." ], }), ], -}) +}); ruleTester.run('no-unresolved unknown resolver', rule, { valid: [], @@ -361,7 +376,7 @@ ruleTester.run('no-unresolved unknown resolver', rule, { ], }), ], -}) +}); ruleTester.run('no-unresolved electron', rule, { valid: [ @@ -376,9 +391,26 @@ ruleTester.run('no-unresolved electron', rule, { errors: [`Unable to resolve path to module 'electron'.`], }), ], -}) +}); ruleTester.run('no-unresolved syntax verification', rule, { valid: SYNTAX_CASES, invalid:[], -}) +}); + +// https://github.com/benmosher/eslint-plugin-import/issues/2024 +ruleTester.run('import() with built-in parser', rule, { + valid: [].concat( + testVersion('>=7', () => ({ + code: "import('fs');", + parserOptions: { ecmaVersion: 2021 }, + })) || [], + ), + invalid: [].concat( + testVersion('>=7', () => ({ + code: 'import("./does-not-exist-l0w9ssmcqy9").then(() => {})', + parserOptions: { ecmaVersion: 2021 }, + errors: ["Unable to resolve path to module './does-not-exist-l0w9ssmcqy9'."], + })) || [], + ), +}); diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index ef2d3e66c2..283fa3e984 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -1,61 +1,94 @@ -import { test, testFilePath } from '../utils' -import jsxConfig from '../../../config/react' -import typescriptConfig from '../../../config/typescript' +import { test, testFilePath, getTSParsers } from '../utils'; +import jsxConfig from '../../../config/react'; +import typescriptConfig from '../../../config/typescript'; -import { RuleTester } from 'eslint' -import fs from 'fs' +import { RuleTester } from 'eslint'; +import fs from 'fs'; +import semver from 'semver'; +import eslintPkg from 'eslint/package.json'; -const ruleTester = new RuleTester() - , typescriptRuleTester = new RuleTester(typescriptConfig) - , jsxRuleTester = new RuleTester(jsxConfig) - , rule = require('rules/no-unused-modules') +// TODO: figure out why these tests fail in eslint 4 +const isESLint4TODO = semver.satisfies(eslintPkg.version, '^4'); -const error = message => ({ message }) +const ruleTester = new RuleTester(); +const typescriptRuleTester = new RuleTester(typescriptConfig); +const jsxRuleTester = new RuleTester(jsxConfig); +const rule = require('rules/no-unused-modules'); + +const error = message => ({ message }); const missingExportsOptions = [{ missingExports: true, -}] +}]; const unusedExportsOptions = [{ unusedExports: true, src: [testFilePath('./no-unused-modules/**/*.js')], ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')], -}] +}]; const unusedExportsTypescriptOptions = [{ unusedExports: true, src: [testFilePath('./no-unused-modules/typescript')], ignoreExports: undefined, -}] +}]; const unusedExportsJsxOptions = [{ unusedExports: true, src: [testFilePath('./no-unused-modules/jsx')], ignoreExports: undefined, -}] +}]; // tests for missing exports ruleTester.run('no-unused-modules', rule, { valid: [ - test({ code: 'export default function noOptions() {}' }), - test({ options: missingExportsOptions, - code: 'export default () => 1'}), - test({ options: missingExportsOptions, - code: 'export const a = 1'}), - test({ options: missingExportsOptions, - code: 'const a = 1; export { a }'}), - test({ options: missingExportsOptions, - code: 'function a() { return true }; export { a }'}), - test({ options: missingExportsOptions, - code: 'const a = 1; const b = 2; export { a, b }'}), - test({ options: missingExportsOptions, - code: 'const a = 1; export default a'}), - test({ options: missingExportsOptions, - code: 'export class Foo {}'}), - test({ options: missingExportsOptions, - code: 'export const [foobar] = [];'}), - test({ options: missingExportsOptions, - code: 'export const [foobar] = foobarFactory();'}), + test({ + code: 'export default function noOptions() {}', + }), + test({ + options: missingExportsOptions, + code: 'export default () => 1', + }), + test({ + options: missingExportsOptions, + code: 'export const a = 1', + }), + test({ + options: missingExportsOptions, + code: 'const a = 1; export { a }', + }), + test({ + options: missingExportsOptions, + code: 'function a() { return true }; export { a }', + }), + test({ + options: missingExportsOptions, + code: 'const a = 1; const b = 2; export { a, b }', + }), + test({ + options: missingExportsOptions, + code: 'const a = 1; export default a', + }), + test({ + options: missingExportsOptions, + code: 'export class Foo {}', + }), + test({ + options: missingExportsOptions, + code: 'export const [foobar] = [];', + }), + test({ + options: missingExportsOptions, + code: 'export const [foobar] = foobarFactory();', + }), + test({ + options: missingExportsOptions, + code: ` + export default function NewComponent () { + return 'I am new component' + } + `, + }), ], invalid: [ test({ @@ -69,7 +102,7 @@ ruleTester.run('no-unused-modules', rule, { errors: [error(`No exports found`)], }), ], -}) +}); // tests for exports @@ -77,33 +110,33 @@ ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: 'import { o2 } from "./file-o";export default () => 12', - filename: testFilePath('./no-unused-modules/file-a.js')}), + code: 'import { o2 } from "./file-o";export default () => 12', + filename: testFilePath('./no-unused-modules/file-a.js') }), test({ options: unusedExportsOptions, - code: 'export const b = 2', - filename: testFilePath('./no-unused-modules/file-b.js')}), + code: 'export const b = 2', + filename: testFilePath('./no-unused-modules/file-b.js') }), test({ options: unusedExportsOptions, - code: 'const c1 = 3; function c2() { return 3 }; export { c1, c2 }', - filename: testFilePath('./no-unused-modules/file-c.js')}), + code: 'const c1 = 3; function c2() { return 3 }; export { c1, c2 }', + filename: testFilePath('./no-unused-modules/file-c.js') }), test({ options: unusedExportsOptions, - code: 'export function d() { return 4 }', - filename: testFilePath('./no-unused-modules/file-d.js')}), + code: 'export function d() { return 4 }', + filename: testFilePath('./no-unused-modules/file-d.js') }), test({ options: unusedExportsOptions, - code: 'export class q { q0() {} }', - filename: testFilePath('./no-unused-modules/file-q.js')}), + code: 'export class q { q0() {} }', + filename: testFilePath('./no-unused-modules/file-q.js') }), test({ options: unusedExportsOptions, - code: 'const e0 = 5; export { e0 as e }', - filename: testFilePath('./no-unused-modules/file-e.js')}), + code: 'const e0 = 5; export { e0 as e }', + filename: testFilePath('./no-unused-modules/file-e.js') }), test({ options: unusedExportsOptions, - code: 'const l0 = 5; const l = 10; export { l0 as l1, l }; export default () => {}', - filename: testFilePath('./no-unused-modules/file-l.js')}), + code: 'const l0 = 5; const l = 10; export { l0 as l1, l }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-l.js') }), test({ options: unusedExportsOptions, - code: 'const o0 = 0; const o1 = 1; export { o0, o1 as o2 }; export default () => {}', - filename: testFilePath('./no-unused-modules/file-o.js')}), - ], + code: 'const o0 = 0; const o1 = 1; export { o0, o1 as o2 }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-o.js') }), + ], invalid: [ test({ options: unusedExportsOptions, - code: `import eslint from 'eslint' + code: `import eslint from 'eslint' import fileA from './file-a' import { b } from './file-b' import { c1, c2 } from './file-c' @@ -116,252 +149,252 @@ ruleTester.run('no-unused-modules', rule, { export { default, o0, o3 } from './file-o' export { p } from './file-p' import s from './file-s'`, - filename: testFilePath('./no-unused-modules/file-0.js'), - errors: [ - error(`exported declaration 'default' not used within other modules`), - error(`exported declaration 'o0' not used within other modules`), - error(`exported declaration 'o3' not used within other modules`), - error(`exported declaration 'p' not used within other modules`), - ]}), - test({ options: unusedExportsOptions, - code: `const n0 = 'n0'; const n1 = 42; export { n0, n1 }; export default () => {}`, - filename: testFilePath('./no-unused-modules/file-n.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), + filename: testFilePath('./no-unused-modules/file-0.js'), + errors: [ + error(`exported declaration 'default' not used within other modules`), + error(`exported declaration 'o0' not used within other modules`), + error(`exported declaration 'o3' not used within other modules`), + error(`exported declaration 'p' not used within other modules`), + ] }), + test({ options: unusedExportsOptions, + code: `const n0 = 'n0'; const n1 = 42; export { n0, n1 }; export default () => {}`, + filename: testFilePath('./no-unused-modules/file-n.js'), + errors: [error(`exported declaration 'default' not used within other modules`)] }), ], -}) +}); // test for unused exports ruleTester.run('no-unused-modules', rule, { valid: [], invalid: [ test({ options: unusedExportsOptions, - code: 'export default () => 13', - filename: testFilePath('./no-unused-modules/file-f.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), + code: 'export default () => 13', + filename: testFilePath('./no-unused-modules/file-f.js'), + errors: [error(`exported declaration 'default' not used within other modules`)] }), test({ options: unusedExportsOptions, - code: 'export const g = 2', - filename: testFilePath('./no-unused-modules/file-g.js'), - errors: [error(`exported declaration 'g' not used within other modules`)]}), + code: 'export const g = 2', + filename: testFilePath('./no-unused-modules/file-g.js'), + errors: [error(`exported declaration 'g' not used within other modules`)] }), test({ options: unusedExportsOptions, - code: 'const h1 = 3; function h2() { return 3 }; const h3 = true; export { h1, h2, h3 }', - filename: testFilePath('./no-unused-modules/file-h.js'), - errors: [error(`exported declaration 'h1' not used within other modules`)]}), + code: 'const h1 = 3; function h2() { return 3 }; const h3 = true; export { h1, h2, h3 }', + filename: testFilePath('./no-unused-modules/file-h.js'), + errors: [error(`exported declaration 'h1' not used within other modules`)] }), test({ options: unusedExportsOptions, - code: 'const i1 = 3; function i2() { return 3 }; export { i1, i2 }', - filename: testFilePath('./no-unused-modules/file-i.js'), - errors: [ - error(`exported declaration 'i1' not used within other modules`), - error(`exported declaration 'i2' not used within other modules`), - ]}), + code: 'const i1 = 3; function i2() { return 3 }; export { i1, i2 }', + filename: testFilePath('./no-unused-modules/file-i.js'), + errors: [ + error(`exported declaration 'i1' not used within other modules`), + error(`exported declaration 'i2' not used within other modules`), + ] }), test({ options: unusedExportsOptions, - code: 'export function j() { return 4 }', - filename: testFilePath('./no-unused-modules/file-j.js'), - errors: [error(`exported declaration 'j' not used within other modules`)]}), + code: 'export function j() { return 4 }', + filename: testFilePath('./no-unused-modules/file-j.js'), + errors: [error(`exported declaration 'j' not used within other modules`)] }), test({ options: unusedExportsOptions, - code: 'export class q { q0() {} }', - filename: testFilePath('./no-unused-modules/file-q.js'), - errors: [error(`exported declaration 'q' not used within other modules`)]}), + code: 'export class q { q0() {} }', + filename: testFilePath('./no-unused-modules/file-q.js'), + errors: [error(`exported declaration 'q' not used within other modules`)] }), test({ options: unusedExportsOptions, - code: 'const k0 = 5; export { k0 as k }', - filename: testFilePath('./no-unused-modules/file-k.js'), - errors: [error(`exported declaration 'k' not used within other modules`)]}), + code: 'const k0 = 5; export { k0 as k }', + filename: testFilePath('./no-unused-modules/file-k.js'), + errors: [error(`exported declaration 'k' not used within other modules`)] }), ], -}) +}); // // test for export from ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `export { default } from './file-o'`, - filename: testFilePath('./no-unused-modules/file-s.js')}), + code: `export { default } from './file-o'`, + filename: testFilePath('./no-unused-modules/file-s.js') }), ], invalid: [ test({ options: unusedExportsOptions, - code: `export { k } from '${testFilePath('./no-unused-modules/file-k.js')}'`, - filename: testFilePath('./no-unused-modules/file-j.js'), - errors: [error(`exported declaration 'k' not used within other modules`)]}), + code: `export { k } from '${testFilePath('./no-unused-modules/file-k.js')}'`, + filename: testFilePath('./no-unused-modules/file-j.js'), + errors: [error(`exported declaration 'k' not used within other modules`)] }), ], -}) +}); ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: 'const k0 = 5; export { k0 as k }', - filename: testFilePath('./no-unused-modules/file-k.js')}), + code: 'const k0 = 5; export { k0 as k }', + filename: testFilePath('./no-unused-modules/file-k.js') }), ], invalid: [], -}) +}); // test for ignored files ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, code: 'export default () => 14', - filename: testFilePath('./no-unused-modules/file-ignored-a.js')}), + filename: testFilePath('./no-unused-modules/file-ignored-a.js') }), test({ options: unusedExportsOptions, code: 'export const b = 2', - filename: testFilePath('./no-unused-modules/file-ignored-b.js')}), + filename: testFilePath('./no-unused-modules/file-ignored-b.js') }), test({ options: unusedExportsOptions, code: 'const c1 = 3; function c2() { return 3 }; export { c1, c2 }', - filename: testFilePath('./no-unused-modules/file-ignored-c.js')}), + filename: testFilePath('./no-unused-modules/file-ignored-c.js') }), test({ options: unusedExportsOptions, code: 'export function d() { return 4 }', - filename: testFilePath('./no-unused-modules/file-ignored-d.js')}), + filename: testFilePath('./no-unused-modules/file-ignored-d.js') }), test({ options: unusedExportsOptions, code: 'const f = 5; export { f as e }', - filename: testFilePath('./no-unused-modules/file-ignored-e.js')}), + filename: testFilePath('./no-unused-modules/file-ignored-e.js') }), test({ options: unusedExportsOptions, code: 'const l0 = 5; const l = 10; export { l0 as l1, l }; export default () => {}', - filename: testFilePath('./no-unused-modules/file-ignored-l.js')}), - ], + filename: testFilePath('./no-unused-modules/file-ignored-l.js') }), + ], invalid: [], -}) +}); // add named import for file with default export ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, code: `import { f } from '${testFilePath('./no-unused-modules/file-f.js')}'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), - ], + filename: testFilePath('./no-unused-modules/file-0.js') }), + ], invalid: [ test({ options: unusedExportsOptions, - code: 'export default () => 15', - filename: testFilePath('./no-unused-modules/file-f.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), - ], -}) + code: 'export default () => 15', + filename: testFilePath('./no-unused-modules/file-f.js'), + errors: [error(`exported declaration 'default' not used within other modules`)] }), + ], +}); // add default import for file with default export ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import f from '${testFilePath('./no-unused-modules/file-f.js')}'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `import f from '${testFilePath('./no-unused-modules/file-f.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js') }), test({ options: unusedExportsOptions, - code: 'export default () => 16', - filename: testFilePath('./no-unused-modules/file-f.js')}), - ], + code: 'export default () => 16', + filename: testFilePath('./no-unused-modules/file-f.js') }), + ], invalid: [], -}) +}); // add default import for file with named export ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import g from '${testFilePath('./no-unused-modules/file-g.js')}';import {h} from '${testFilePath('./no-unused-modules/file-gg.js')}'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), - ], + code: `import g from '${testFilePath('./no-unused-modules/file-g.js')}';import {h} from '${testFilePath('./no-unused-modules/file-gg.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js') }), + ], invalid: [ test({ options: unusedExportsOptions, - code: 'export const g = 2', - filename: testFilePath('./no-unused-modules/file-g.js'), - errors: [error(`exported declaration 'g' not used within other modules`)]})], -}) + code: 'export const g = 2', + filename: testFilePath('./no-unused-modules/file-g.js'), + errors: [error(`exported declaration 'g' not used within other modules`)] })], +}); // add named import for file with named export ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import { g } from '${testFilePath('./no-unused-modules/file-g.js')}'; import eslint from 'eslint'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `import { g } from '${testFilePath('./no-unused-modules/file-g.js')}'; import eslint from 'eslint'`, + filename: testFilePath('./no-unused-modules/file-0.js') }), test({ options: unusedExportsOptions, - code: 'export const g = 2', - filename: testFilePath('./no-unused-modules/file-g.js')}), - ], + code: 'export const g = 2', + filename: testFilePath('./no-unused-modules/file-g.js') }), + ], invalid: [], -}) +}); // add different named import for file with named export ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import { c } from '${testFilePath('./no-unused-modules/file-b.js')}'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `import { c } from '${testFilePath('./no-unused-modules/file-b.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js') }), ], invalid: [ test({ options: unusedExportsOptions, - code: 'export const b = 2', - filename: testFilePath('./no-unused-modules/file-b.js'), - errors: [error(`exported declaration 'b' not used within other modules`)]}), + code: 'export const b = 2', + filename: testFilePath('./no-unused-modules/file-b.js'), + errors: [error(`exported declaration 'b' not used within other modules`)] }), ], -}) +}); // add renamed named import for file with named export ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import { g as g1 } from '${testFilePath('./no-unused-modules/file-g.js')}'; import eslint from 'eslint'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `import { g as g1 } from '${testFilePath('./no-unused-modules/file-g.js')}'; import eslint from 'eslint'`, + filename: testFilePath('./no-unused-modules/file-0.js') }), test({ options: unusedExportsOptions, - code: 'export const g = 2', - filename: testFilePath('./no-unused-modules/file-g.js')}), - ], + code: 'export const g = 2', + filename: testFilePath('./no-unused-modules/file-g.js') }), + ], invalid: [], -}) +}); // add different renamed named import for file with named export ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import { g1 as g } from '${testFilePath('./no-unused-modules/file-g.js')}'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `import { g1 as g } from '${testFilePath('./no-unused-modules/file-g.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js') }), ], invalid: [ test({ options: unusedExportsOptions, - code: 'export const g = 2', - filename: testFilePath('./no-unused-modules/file-g.js'), - errors: [error(`exported declaration 'g' not used within other modules`)]}), + code: 'export const g = 2', + filename: testFilePath('./no-unused-modules/file-g.js'), + errors: [error(`exported declaration 'g' not used within other modules`)] }), ], -}) +}); // remove default import for file with default export ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import { a1, a2 } from '${testFilePath('./no-unused-modules/file-a.js')}'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `import { a1, a2 } from '${testFilePath('./no-unused-modules/file-a.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js') }), ], invalid: [ test({ options: unusedExportsOptions, - code: 'export default () => 17', - filename: testFilePath('./no-unused-modules/file-a.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), + code: 'export default () => 17', + filename: testFilePath('./no-unused-modules/file-a.js'), + errors: [error(`exported declaration 'default' not used within other modules`)] }), ], -}) +}); // add namespace import for file with unused exports ruleTester.run('no-unused-modules', rule, { valid: [], invalid: [ test({ options: unusedExportsOptions, - code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', - filename: testFilePath('./no-unused-modules/file-m.js'), - errors: [ - error(`exported declaration 'm1' not used within other modules`), - error(`exported declaration 'm' not used within other modules`), - error(`exported declaration 'default' not used within other modules`), - ]}), + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js'), + errors: [ + error(`exported declaration 'm1' not used within other modules`), + error(`exported declaration 'm' not used within other modules`), + error(`exported declaration 'default' not used within other modules`), + ] }), ], -}) +}); ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'; import unknown from 'unknown-module'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'; import unknown from 'unknown-module'`, + filename: testFilePath('./no-unused-modules/file-0.js') }), test({ options: unusedExportsOptions, - code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', - filename: testFilePath('./no-unused-modules/file-m.js')}), + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js') }), ], invalid: [], -}) +}); // remove all exports ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `/* import * as m from '${testFilePath('./no-unused-modules/file-m.js')}' */`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `/* import * as m from '${testFilePath('./no-unused-modules/file-m.js')}' */`, + filename: testFilePath('./no-unused-modules/file-0.js') }), ], invalid: [ test({ options: unusedExportsOptions, @@ -371,44 +404,44 @@ ruleTester.run('no-unused-modules', rule, { error(`exported declaration 'm1' not used within other modules`), error(`exported declaration 'm' not used within other modules`), error(`exported declaration 'default' not used within other modules`), - ]}), + ] }), ], -}) +}); ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `export * from '${testFilePath('./no-unused-modules/file-m.js')}';`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `export * from '${testFilePath('./no-unused-modules/file-m.js')}';`, + filename: testFilePath('./no-unused-modules/file-0.js') }), ], invalid: [], -}) +}); ruleTester.run('no-unused-modules', rule, { valid: [], invalid: [ test({ options: unusedExportsOptions, - code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', - filename: testFilePath('./no-unused-modules/file-m.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js'), + errors: [error(`exported declaration 'default' not used within other modules`)] }), ], -}) +}); ruleTester.run('no-unused-modules', rule, { valid: [], invalid: [ test({ options: unusedExportsOptions, - code: `export { m1, m} from '${testFilePath('./no-unused-modules/file-m.js')}';`, - filename: testFilePath('./no-unused-modules/file-0.js'), - errors: [ - error(`exported declaration 'm1' not used within other modules`), - error(`exported declaration 'm' not used within other modules`), - ]}), + code: `export { m1, m} from '${testFilePath('./no-unused-modules/file-m.js')}';`, + filename: testFilePath('./no-unused-modules/file-0.js'), + errors: [ + error(`exported declaration 'm1' not used within other modules`), + error(`exported declaration 'm' not used within other modules`), + ] }), test({ options: unusedExportsOptions, - code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', - filename: testFilePath('./no-unused-modules/file-m.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js'), + errors: [error(`exported declaration 'default' not used within other modules`)] }), ], -}) +}); ruleTester.run('no-unused-modules', rule, { valid: [ @@ -418,102 +451,102 @@ ruleTester.run('no-unused-modules', rule, { ], invalid: [ test({ options: unusedExportsOptions, - code: `export { default, m1 } from '${testFilePath('./no-unused-modules/file-m.js')}';`, - filename: testFilePath('./no-unused-modules/file-0.js'), - errors: [ - error(`exported declaration 'default' not used within other modules`), - error(`exported declaration 'm1' not used within other modules`), - ]}), + code: `export { default, m1 } from '${testFilePath('./no-unused-modules/file-m.js')}';`, + filename: testFilePath('./no-unused-modules/file-0.js'), + errors: [ + error(`exported declaration 'default' not used within other modules`), + error(`exported declaration 'm1' not used within other modules`), + ] }), test({ options: unusedExportsOptions, - code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', - filename: testFilePath('./no-unused-modules/file-m.js'), - errors: [error(`exported declaration 'm' not used within other modules`)]}), + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js'), + errors: [error(`exported declaration 'm' not used within other modules`)] }), ], -}) +}); // 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'), - }), + 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')}), + filename: testFilePath('./no-unused-modules/renameDefault/components.js') }), test({ options: unusedExportsOptions, code: 'export default function Component() {}', - filename: testFilePath('./no-unused-modules/renameDefault/Component.js')}), + 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')}), + 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')}), + filename: testFilePath('./no-unused-modules/renameDefault-2/ComponentA.js') }), ], invalid: [], - }) -}) + }); +}); -describe('test behaviour for new file', () => { +describe('test behavior for new file', () => { before(() => { - fs.writeFileSync(testFilePath('./no-unused-modules/file-added-0.js'), '', {encoding: 'utf8'}) - }) + fs.writeFileSync(testFilePath('./no-unused-modules/file-added-0.js'), '', { encoding: 'utf8' }); + }); // add import in newly created file ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'`, - filename: testFilePath('./no-unused-modules/file-added-0.js')}), + code: `import * as m from '${testFilePath('./no-unused-modules/file-m.js')}'`, + filename: testFilePath('./no-unused-modules/file-added-0.js') }), test({ options: unusedExportsOptions, - code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', - filename: testFilePath('./no-unused-modules/file-m.js')}), + code: 'const m0 = 5; const m = 10; export { m0 as m1, m }; export default () => {}', + filename: testFilePath('./no-unused-modules/file-m.js') }), ], invalid: [], - }) + }); // add export for newly created file ruleTester.run('no-unused-modules', rule, { valid: [], invalid: [ test({ options: unusedExportsOptions, - code: `export default () => {2}`, - filename: testFilePath('./no-unused-modules/file-added-0.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), - ], - }) + code: `export default () => {2}`, + filename: testFilePath('./no-unused-modules/file-added-0.js'), + errors: [error(`exported declaration 'default' not used within other modules`)] }), + ], + }); ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import def from '${testFilePath('./no-unused-modules/file-added-0.js')}'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `import def from '${testFilePath('./no-unused-modules/file-added-0.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js') }), test({ options: unusedExportsOptions, - code: `export default () => {}`, - filename: testFilePath('./no-unused-modules/file-added-0.js')}), + code: `export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-0.js') }), ], invalid: [], - }) + }); // export * only considers named imports. default imports still need to be reported ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `export * from '${testFilePath('./no-unused-modules/file-added-0.js')}'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `export * from '${testFilePath('./no-unused-modules/file-added-0.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js') }), // Test export * from 'external-compiled-library' test({ options: unusedExportsOptions, code: `export * from 'external-compiled-library'`, @@ -522,244 +555,457 @@ describe('test behaviour for new file', () => { ], invalid: [ test({ options: unusedExportsOptions, - code: `export const z = 'z';export default () => {}`, - filename: testFilePath('./no-unused-modules/file-added-0.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), + code: `export const z = 'z';export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-0.js'), + errors: [error(`exported declaration 'default' not used within other modules`)] }), ], - }) + }); ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `export const a = 2`, - filename: testFilePath('./no-unused-modules/file-added-0.js')}), + code: `export const a = 2`, + filename: testFilePath('./no-unused-modules/file-added-0.js') }), ], invalid: [], - }) + }); // remove export *. all exports need to be reported ruleTester.run('no-unused-modules', rule, { valid: [], invalid: [ test({ options: unusedExportsOptions, - code: `export { a } from '${testFilePath('./no-unused-modules/file-added-0.js')}'`, - filename: testFilePath('./no-unused-modules/file-0.js'), - errors: [error(`exported declaration 'a' not used within other modules`)]}), + code: `export { a } from '${testFilePath('./no-unused-modules/file-added-0.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js'), + errors: [error(`exported declaration 'a' not used within other modules`)] }), test({ options: unusedExportsOptions, - code: `export const z = 'z';export default () => {}`, - filename: testFilePath('./no-unused-modules/file-added-0.js'), - errors: [ - error(`exported declaration 'z' not used within other modules`), - error(`exported declaration 'default' not used within other modules`), - ]}), + code: `export const z = 'z';export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-0.js'), + errors: [ + error(`exported declaration 'z' not used within other modules`), + error(`exported declaration 'default' not used within other modules`), + ] }), ], - }) + }); - describe('test behaviour for new file', () => { + describe('test behavior for new file', () => { before(() => { - fs.writeFileSync(testFilePath('./no-unused-modules/file-added-1.js'), '', {encoding: 'utf8'}) - }) + fs.writeFileSync(testFilePath('./no-unused-modules/file-added-1.js'), '', { encoding: 'utf8' }); + }); ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `export * from '${testFilePath('./no-unused-modules/file-added-1.js')}'`, - filename: testFilePath('./no-unused-modules/file-0.js')}), + code: `export * from '${testFilePath('./no-unused-modules/file-added-1.js')}'`, + filename: testFilePath('./no-unused-modules/file-0.js') }), ], invalid: [ test({ options: unusedExportsOptions, - code: `export const z = 'z';export default () => {}`, - filename: testFilePath('./no-unused-modules/file-added-1.js'), - errors: [error(`exported declaration 'default' not used within other modules`)]}), + code: `export const z = 'z';export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-1.js'), + errors: [error(`exported declaration 'default' not used within other modules`)] }), ], - }) + }); after(() => { if (fs.existsSync(testFilePath('./no-unused-modules/file-added-1.js'))) { - fs.unlinkSync(testFilePath('./no-unused-modules/file-added-1.js')) + fs.unlinkSync(testFilePath('./no-unused-modules/file-added-1.js')); } - }) - }) + }); + }); after(() => { if (fs.existsSync(testFilePath('./no-unused-modules/file-added-0.js'))) { - fs.unlinkSync(testFilePath('./no-unused-modules/file-added-0.js')) + fs.unlinkSync(testFilePath('./no-unused-modules/file-added-0.js')); } - }) -}) + }); +}); -describe('test behaviour for new file', () => { +describe('test behavior for new file', () => { before(() => { - fs.writeFileSync(testFilePath('./no-unused-modules/file-added-2.js'), '', {encoding: 'utf8'}) - }) + fs.writeFileSync(testFilePath('./no-unused-modules/file-added-2.js'), '', { encoding: 'utf8' }); + }); ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import added from '${testFilePath('./no-unused-modules/file-added-2.js')}'`, - filename: testFilePath('./no-unused-modules/file-added-1.js')}), + code: `import added from '${testFilePath('./no-unused-modules/file-added-2.js')}'`, + filename: testFilePath('./no-unused-modules/file-added-1.js') }), test({ options: unusedExportsOptions, - code: `export default () => {}`, - filename: testFilePath('./no-unused-modules/file-added-2.js')}), + code: `export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-2.js') }), ], invalid: [], - }) + }); after(() => { if (fs.existsSync(testFilePath('./no-unused-modules/file-added-2.js'))) { - fs.unlinkSync(testFilePath('./no-unused-modules/file-added-2.js')) + fs.unlinkSync(testFilePath('./no-unused-modules/file-added-2.js')); } - }) -}) + }); +}); -describe('test behaviour for new file', () => { +describe('test behavior for new file', () => { before(() => { - fs.writeFileSync(testFilePath('./no-unused-modules/file-added-3.js'), '', {encoding: 'utf8'}) - }) + fs.writeFileSync(testFilePath('./no-unused-modules/file-added-3.js'), '', { encoding: 'utf8' }); + }); ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import { added } from '${testFilePath('./no-unused-modules/file-added-3.js')}'`, - filename: testFilePath('./no-unused-modules/file-added-1.js')}), + code: `import { added } from '${testFilePath('./no-unused-modules/file-added-3.js')}'`, + filename: testFilePath('./no-unused-modules/file-added-1.js') }), test({ options: unusedExportsOptions, - code: `export const added = () => {}`, - filename: testFilePath('./no-unused-modules/file-added-3.js')}), + code: `export const added = () => {}`, + filename: testFilePath('./no-unused-modules/file-added-3.js') }), ], invalid: [], - }) + }); after(() => { if (fs.existsSync(testFilePath('./no-unused-modules/file-added-3.js'))) { - fs.unlinkSync(testFilePath('./no-unused-modules/file-added-3.js')) + fs.unlinkSync(testFilePath('./no-unused-modules/file-added-3.js')); } - }) -}) + }); +}); -describe('test behaviour for new file', () => { +describe('test behavior for destructured exports', () => { + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: `import { destructured } from '${testFilePath('./no-unused-modules/file-destructured-1.js')}'`, + filename: testFilePath('./no-unused-modules/file-destructured-2.js') }), + test({ options: unusedExportsOptions, + code: `export const { destructured } = {};`, + filename: testFilePath('./no-unused-modules/file-destructured-1.js') }), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: `export const { destructured2 } = {};`, + filename: testFilePath('./no-unused-modules/file-destructured-1.js'), + errors: [`exported declaration 'destructured2' not used within other modules`] }), + ], + }); +}); + +describe('test behavior for new file', () => { before(() => { - fs.writeFileSync(testFilePath('./no-unused-modules/file-added-4.js.js'), '', {encoding: 'utf8'}) - }) + fs.writeFileSync(testFilePath('./no-unused-modules/file-added-4.js.js'), '', { encoding: 'utf8' }); + }); ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `import * as added from '${testFilePath('./no-unused-modules/file-added-4.js.js')}'`, - filename: testFilePath('./no-unused-modules/file-added-1.js')}), + code: `import * as added from '${testFilePath('./no-unused-modules/file-added-4.js.js')}'`, + filename: testFilePath('./no-unused-modules/file-added-1.js') }), test({ options: unusedExportsOptions, - code: `export const added = () => {}; export default () => {}`, - filename: testFilePath('./no-unused-modules/file-added-4.js.js')}), + code: `export const added = () => {}; export default () => {}`, + filename: testFilePath('./no-unused-modules/file-added-4.js.js') }), ], invalid: [], - }) + }); after(() => { if (fs.existsSync(testFilePath('./no-unused-modules/file-added-4.js.js'))) { - fs.unlinkSync(testFilePath('./no-unused-modules/file-added-4.js.js')) + fs.unlinkSync(testFilePath('./no-unused-modules/file-added-4.js.js')); } - }) -}) + }); +}); describe('do not report missing export for ignored file', () => { ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: [{ - src: [testFilePath('./no-unused-modules/**/*.js')], - ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')], - missingExports: true, - }], - code: 'export const test = true', - filename: testFilePath('./no-unused-modules/file-ignored-a.js')}), + src: [testFilePath('./no-unused-modules/**/*.js')], + ignoreExports: [testFilePath('./no-unused-modules/*ignored*.js')], + missingExports: true, + }], + code: 'export const test = true', + filename: testFilePath('./no-unused-modules/file-ignored-a.js') }), ], invalid: [], - }) -}) + }); +}); // lint file not available in `src` ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: `export const jsxFoo = 'foo'; export const jsxBar = 'bar'`, - filename: testFilePath('../jsx/named.jsx')}), + code: `export const jsxFoo = 'foo'; export const jsxBar = 'bar'`, + filename: testFilePath('../jsx/named.jsx') }), ], invalid: [], -}) +}); describe('do not report unused export for files mentioned in package.json', () => { ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, - code: 'export const bin = "bin"', - filename: testFilePath('./no-unused-modules/bin.js')}), + code: 'export const bin = "bin"', + filename: testFilePath('./no-unused-modules/bin.js') }), test({ options: unusedExportsOptions, - code: 'export const binObject = "binObject"', - filename: testFilePath('./no-unused-modules/binObject/index.js')}), + code: 'export const binObject = "binObject"', + filename: testFilePath('./no-unused-modules/binObject/index.js') }), test({ options: unusedExportsOptions, - code: 'export const browser = "browser"', - filename: testFilePath('./no-unused-modules/browser.js')}), + code: 'export const browser = "browser"', + filename: testFilePath('./no-unused-modules/browser.js') }), test({ options: unusedExportsOptions, - code: 'export const browserObject = "browserObject"', - filename: testFilePath('./no-unused-modules/browserObject/index.js')}), + code: 'export const browserObject = "browserObject"', + filename: testFilePath('./no-unused-modules/browserObject/index.js') }), test({ options: unusedExportsOptions, - code: 'export const main = "main"', - filename: testFilePath('./no-unused-modules/main/index.js')}), + code: 'export const main = "main"', + filename: testFilePath('./no-unused-modules/main/index.js') }), ], invalid: [ test({ options: unusedExportsOptions, - code: 'export const privatePkg = "privatePkg"', - filename: testFilePath('./no-unused-modules/privatePkg/index.js'), - errors: [error(`exported declaration 'privatePkg' not used within other modules`)]}), + code: 'export const privatePkg = "privatePkg"', + filename: testFilePath('./no-unused-modules/privatePkg/index.js'), + errors: [error(`exported declaration 'privatePkg' not used within other modules`)] }), ], - }) -}) + }); +}); describe('Avoid errors if re-export all from umd compiled library', () => { ruleTester.run('no-unused-modules', rule, { valid: [ test({ options: unusedExportsOptions, code: `export * from '${testFilePath('./no-unused-modules/bin.js')}'`, - filename: testFilePath('./no-unused-modules/main/index.js')}), + filename: testFilePath('./no-unused-modules/main/index.js') }), ], invalid: [], - }) -}) + }); +}); + +context('TypeScript', function () { + getTSParsers().forEach((parser) => { + typescriptRuleTester.run('no-unused-modules', rule, { + valid: [].concat( + test({ + options: unusedExportsTypescriptOptions, + code: ` + import {b} from './file-ts-b'; + import {c} from './file-ts-c'; + import {d} from './file-ts-d'; + import {e} from './file-ts-e'; + + const a = b + 1 + e.f; + const a2: c = {}; + const a3: d = {}; + `, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'), + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export const b = 2;`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-b.ts'), + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export interface c {};`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-c.ts'), + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export type d = {};`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-d.ts'), + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export enum e { f };`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-e.ts'), + }), + test({ + options: unusedExportsTypescriptOptions, + code: ` + import type {b} from './file-ts-b-used-as-type'; + import type {c} from './file-ts-c-used-as-type'; + import type {d} from './file-ts-d-used-as-type'; + import type {e} from './file-ts-e-used-as-type'; + + const a: typeof b = 2; + const a2: c = {}; + const a3: d = {}; + const a4: typeof e = undefined; + `, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-a-import-type.ts'), + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export const b = 2;`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-b-used-as-type.ts'), + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export interface c {};`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-c-used-as-type.ts'), + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export type d = {};`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-d-used-as-type.ts'), + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export enum e { f };`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-e-used-as-type.ts'), + }), + // Should also be valid when the exporting files are linted before the importing ones + isESLint4TODO ? [] : test({ + options: unusedExportsTypescriptOptions, + code: `export interface g {}`, + parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-g.ts'), + }), + test({ + options: unusedExportsTypescriptOptions, + code: `import {g} from './file-ts-g';`, + parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-f.ts'), + }), + isESLint4TODO ? [] : test({ + options: unusedExportsTypescriptOptions, + code: `export interface g {}; /* used-as-type */`, + parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-g-used-as-type.ts'), + }), + test({ + options: unusedExportsTypescriptOptions, + code: `import type {g} from './file-ts-g';`, + parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-f-import-type.ts'), + }), + ), + invalid: [].concat( + test({ + options: unusedExportsTypescriptOptions, + code: `export const b = 2;`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-b-unused.ts'), + errors: [ + error(`exported declaration 'b' not used within other modules`), + ], + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export interface c {};`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-c-unused.ts'), + errors: [ + error(`exported declaration 'c' not used within other modules`), + ], + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export type d = {};`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-d-unused.ts'), + errors: [ + error(`exported declaration 'd' not used within other modules`), + ], + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export enum e { f };`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-e-unused.ts'), + errors: [ + error(`exported declaration 'e' not used within other modules`), + ], + }), + ), + }); + }); +}); -describe('correctly work with Typescript only files', () => { - typescriptRuleTester.run('no-unused-modules', rule, { +describe('correctly work with JSX only files', () => { + jsxRuleTester.run('no-unused-modules', rule, { valid: [ test({ - options: unusedExportsTypescriptOptions, - code: 'import a from "file-ts-a";', + options: unusedExportsJsxOptions, + code: 'import a from "file-jsx-a";', parser: require.resolve('babel-eslint'), - filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'), + filename: testFilePath('./no-unused-modules/jsx/file-jsx-a.jsx'), }), ], invalid: [ test({ - options: unusedExportsTypescriptOptions, + options: unusedExportsJsxOptions, code: `export const b = 2;`, parser: require.resolve('babel-eslint'), - filename: testFilePath('./no-unused-modules/typescript/file-ts-b.ts'), + filename: testFilePath('./no-unused-modules/jsx/file-jsx-b.jsx'), errors: [ error(`exported declaration 'b' not used within other modules`), ], }), ], - }) -}) + }); +}); -describe('correctly work with JSX only files', () => { - jsxRuleTester.run('no-unused-modules', rule, { +describe('ignore flow types', () => { + ruleTester.run('no-unused-modules', rule, { valid: [ test({ - options: unusedExportsJsxOptions, - code: 'import a from "file-jsx-a";', + options: unusedExportsOptions, + code: 'import { type FooType, type FooInterface } from "./flow-2";', parser: require.resolve('babel-eslint'), - filename: testFilePath('./no-unused-modules/jsx/file-jsx-a.jsx'), + filename: testFilePath('./no-unused-modules/flow/flow-0.js'), + }), + test({ + options: unusedExportsOptions, + code: `// @flow strict + export type FooType = string; + export interface FooInterface {}; + `, + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/flow/flow-2.js'), + }), + test({ + options: unusedExportsOptions, + code: 'import type { FooType, FooInterface } from "./flow-4";', + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/flow/flow-3.js'), + }), + test({ + options: unusedExportsOptions, + code: `// @flow strict + export type FooType = string; + export interface FooInterface {}; + `, + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/flow/flow-4.js'), + }), + test({ + options: unusedExportsOptions, + code: `// @flow strict + export type Bar = number; + export interface BarInterface {}; + `, + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/flow/flow-1.js'), }), ], - invalid: [ + invalid: [], + }); +}); + +describe('support (nested) destructuring assignment', () => { + ruleTester.run('no-unused-modules', rule, { + valid: [ test({ - options: unusedExportsJsxOptions, - code: `export const b = 2;`, + options: unusedExportsOptions, + code: 'import {a, b} from "./destructuring-b";', parser: require.resolve('babel-eslint'), - filename: testFilePath('./no-unused-modules/jsx/file-jsx-b.jsx'), - errors: [ - error(`exported declaration 'b' not used within other modules`), - ], + filename: testFilePath('./no-unused-modules/destructuring-a.js'), + }), + test({ + options: unusedExportsOptions, + code: 'const obj = {a: 1, dummy: {b: 2}}; export const {a, dummy: {b}} = obj;', + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/destructuring-b.js'), }), ], - }) -}) + invalid: [], + }); +}); diff --git a/tests/src/rules/no-useless-path-segments.js b/tests/src/rules/no-useless-path-segments.js index 2a864d0b6c..313424d349 100644 --- a/tests/src/rules/no-useless-path-segments.js +++ b/tests/src/rules/no-useless-path-segments.js @@ -1,8 +1,8 @@ -import { test } from '../utils' -import { RuleTester } from 'eslint' +import { test } from '../utils'; +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() -const rule = require('rules/no-useless-path-segments') +const ruleTester = new RuleTester(); +const rule = require('rules/no-useless-path-segments'); function runResolverTests(resolver) { ruleTester.run(`no-useless-path-segments (${resolver})`, rule, { @@ -28,12 +28,12 @@ function runResolverTests(resolver) { test({ code: 'import "./malformed"', options: [{ noUselessIndex: true }] }), // ./malformed directory does not exist test({ code: 'import "./importType"', options: [{ noUselessIndex: true }] }), // ./importType.js does not exist - test({ code: 'import(".")' - , parser: require.resolve('babel-eslint') }), - test({ code: 'import("..")' - , parser: require.resolve('babel-eslint') }), - test({ code: 'import("fs").then(function(fs){})' - , parser: require.resolve('babel-eslint') }), + test({ code: 'import(".")', + parser: require.resolve('babel-eslint') }), + test({ code: 'import("..")', + parser: require.resolve('babel-eslint') }), + test({ code: 'import("fs").then(function(fs){})', + parser: require.resolve('babel-eslint') }), ], invalid: [ @@ -247,7 +247,7 @@ function runResolverTests(resolver) { parser: require.resolve('babel-eslint'), }), ], - }) + }); } -['node', 'webpack'].forEach(runResolverTests) +['node', 'webpack'].forEach(runResolverTests); diff --git a/tests/src/rules/no-webpack-loader-syntax.js b/tests/src/rules/no-webpack-loader-syntax.js index 23a1190fb5..5ec848bc65 100644 --- a/tests/src/rules/no-webpack-loader-syntax.js +++ b/tests/src/rules/no-webpack-loader-syntax.js @@ -1,25 +1,25 @@ -import { test } from '../utils' +import { test, getTSParsers } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/no-webpack-loader-syntax') +const ruleTester = new RuleTester(); +const rule = require('rules/no-webpack-loader-syntax'); -const message = 'Do not use import syntax to configure webpack loaders.' +const message = 'Do not use import syntax to configure webpack loaders.'; ruleTester.run('no-webpack-loader-syntax', rule, { valid: [ - test({ code: 'import _ from "lodash"'}), - test({ code: 'import find from "lodash.find"'}), - test({ code: 'import foo from "./foo.css"'}), - test({ code: 'import data from "@scope/my-package/data.json"'}), - test({ code: 'var _ = require("lodash")'}), - test({ code: 'var find = require("lodash.find")'}), - test({ code: 'var foo = require("./foo")'}), - test({ code: 'var foo = require("../foo")'}), - test({ code: 'var foo = require("foo")'}), - test({ code: 'var foo = require("./")'}), - test({ code: 'var foo = require("@scope/foo")'}), + test({ code: 'import _ from "lodash"' }), + test({ code: 'import find from "lodash.find"' }), + test({ code: 'import foo from "./foo.css"' }), + test({ code: 'import data from "@scope/my-package/data.json"' }), + test({ code: 'var _ = require("lodash")' }), + test({ code: 'var find = require("lodash.find")' }), + test({ code: 'var foo = require("./foo")' }), + test({ code: 'var foo = require("../foo")' }), + test({ code: 'var foo = require("foo")' }), + test({ code: 'var foo = require("./")' }), + test({ code: 'var foo = require("@scope/foo")' }), ], invalid: [ test({ @@ -71,4 +71,27 @@ ruleTester.run('no-webpack-loader-syntax', rule, { ], }), ], -}) +}); + +context('TypeScript', function () { + getTSParsers().forEach((parser) => { + const parserConfig = { + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }; + ruleTester.run('no-webpack-loader-syntax', rule, { + valid: [ + test(Object.assign({ + code: 'import { foo } from\nalert()', + }, parserConfig)), + test(Object.assign({ + code: 'import foo from\nalert()', + }, parserConfig)), + ], + invalid: [], + }); + }); +}); diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index e8ee82ec6c..6475e4bcea 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -1,15 +1,15 @@ -import { test, getTSParsers, getNonDefaultParsers } from '../utils' +import { test, getTSParsers, getNonDefaultParsers } from '../utils'; -import { RuleTester } from 'eslint' -import eslintPkg from 'eslint/package.json' -import semver from 'semver' -import flatMap from 'array.prototype.flatmap' +import { RuleTester } from 'eslint'; +import eslintPkg from 'eslint/package.json'; +import semver from 'semver'; +import flatMap from 'array.prototype.flatmap'; -const ruleTester = new RuleTester() - , rule = require('rules/order') +const ruleTester = new RuleTester(); +const rule = require('rules/order'); function withoutAutofixOutput(test) { - return Object.assign({}, test, { output: test.code }) + return Object.assign({}, test, { output: test.code }); } ruleTester.run('order', rule, { @@ -25,7 +25,7 @@ ruleTester.run('order', rule, { var relParent4 = require('..'); var sibling = require('./foo'); var index = require('./');`, - }), + }), // Default order using import test({ code: ` @@ -36,7 +36,7 @@ ruleTester.run('order', rule, { import relParent3 from '../'; import sibling, {foo3} from './foo'; import index from './';`, - }), + }), // Multiple module of the same rank next to each other test({ code: ` @@ -45,7 +45,7 @@ ruleTester.run('order', rule, { var path = require('path'); var _ = require('lodash'); var async = require('async');`, - }), + }), // Overriding order to be the reverse of the default order test({ code: ` @@ -57,7 +57,7 @@ ruleTester.run('order', rule, { var async = require('async'); var fs = require('fs'); `, - options: [{groups: ['index', 'sibling', 'parent', 'external', 'builtin']}], + options: [{ groups: ['index', 'sibling', 'parent', 'external', 'builtin'] }], }), // Ignore dynamic requires test({ @@ -74,7 +74,7 @@ ruleTester.run('order', rule, { var result = add(1, 2); var _ = require('lodash');`, }), - // Ignore requires that are not at the top-level + // Ignore requires that are not at the top-level #1 test({ code: ` var index = require('./'); @@ -86,6 +86,18 @@ ruleTester.run('order', rule, { require('fs'); }`, }), + // Ignore requires that are not at the top-level #2 + test({ + code: ` + const foo = [ + require('./foo'), + require('fs'), + ]`, + }), + // Ignore requires in template literal (#1936) + test({ + code: "const foo = `${require('./a')} ${require('fs')}`", + }), // Ignore unknown/invalid cases test({ code: ` @@ -104,21 +116,21 @@ ruleTester.run('order', rule, { var unknown7 = require('/unknown7'); var index = require('./'); var unknown8 = require('/unknown8'); - `}), + ` }), // Ignoring unassigned values by default (require) test({ code: ` require('./foo'); require('fs'); var path = require('path'); - `}), + ` }), // Ignoring unassigned values by default (import) test({ code: ` import './foo'; import 'fs'; import path from 'path'; - `}), + ` }), // No imports test({ code: ` @@ -126,7 +138,7 @@ ruleTester.run('order', rule, { return a + b; } var foo; - `}), + ` }), // Grouping import types test({ code: ` @@ -139,10 +151,10 @@ ruleTester.run('order', rule, { var async = require('async'); var relParent1 = require('../foo'); `, - options: [{groups: [ + options: [{ groups: [ ['builtin', 'index'], ['sibling', 'parent', 'external'], - ]}], + ] }], }), // Omitted types should implicitly be considered as the last type test({ @@ -150,11 +162,11 @@ ruleTester.run('order', rule, { var index = require('./'); var path = require('path'); `, - options: [{groups: [ + options: [{ groups: [ 'index', ['sibling', 'parent', 'external'], // missing 'builtin' - ]}], + ] }], }), // Mixing require and import should have import up top test({ @@ -364,23 +376,6 @@ ruleTester.run('order', rule, { 'import/external-module-folders': ['node_modules', 'symlinked-module'], }, }), - // Monorepo setup, using Webpack resolver, partial workspace folder path - // in external-module-folders - test({ - code: ` - import _ from 'lodash'; - import m from '@test-scope/some-module'; - - import bar from './bar'; - `, - options: [{ - 'newlines-between': 'always', - }], - settings: { - 'import/resolver': 'webpack', - 'import/external-module-folders': ['node_modules', 'files/symlinked-module'], - }, - }), // Monorepo setup, using Node resolver (doesn't resolve symlinks) test({ code: ` @@ -394,7 +389,7 @@ ruleTester.run('order', rule, { }], settings: { 'import/resolver': 'node', - 'import/external-module-folders': ['node_modules', 'files/symlinked-module'], + 'import/external-module-folders': ['node_modules', 'symlinked-module'], }, }), // Option: newlines-between: 'always' @@ -649,7 +644,7 @@ ruleTester.run('order', rule, { `, options: [{ groups: ['external', 'index'], - alphabetize: {order: 'ignore'}, + alphabetize: { order: 'ignore' }, }], }), // Option alphabetize: {order: 'asc'} @@ -663,7 +658,7 @@ ruleTester.run('order', rule, { `, options: [{ groups: ['external', 'index'], - alphabetize: {order: 'asc'}, + alphabetize: { order: 'asc' }, }], }), // Option alphabetize: {order: 'desc'} @@ -677,7 +672,7 @@ ruleTester.run('order', rule, { `, options: [{ groups: ['external', 'index'], - alphabetize: {order: 'desc'}, + alphabetize: { order: 'desc' }, }], }), // Option alphabetize with newlines-between: {order: 'asc', newlines-between: 'always'} @@ -691,7 +686,7 @@ ruleTester.run('order', rule, { `, options: [{ groups: ['external', 'index'], - alphabetize: {order: 'asc'}, + alphabetize: { order: 'asc' }, 'newlines-between': 'always', }], }), @@ -711,6 +706,117 @@ ruleTester.run('order', rule, { }, ], }), + // Order of imports with similar names + test({ + code: ` + import React from 'react'; + import { BrowserRouter } from 'react-router-dom'; + `, + options: [ + { + alphabetize: { + order: 'asc', + }, + }, + ], + }), + test({ + code: ` + import { UserInputError } from 'apollo-server-express'; + + import { new as assertNewEmail } from '~/Assertions/Email'; + `, + options: [{ + alphabetize: { + caseInsensitive: true, + order: 'asc', + }, + pathGroups: [ + { pattern: '~/*', group: 'internal' }, + ], + groups: [ + 'builtin', + 'external', + 'internal', + 'parent', + 'sibling', + 'index', + ], + 'newlines-between': 'always', + }], + }), + ...flatMap(getTSParsers, parser => [ + // Order of the `import ... = require(...)` syntax + test({ + code: ` + import blah = require('./blah'); + import { hello } from './hello';`, + parser, + options: [ + { + alphabetize: { + order: 'asc', + }, + }, + ], + }), + // Order of object-imports + test({ + code: ` + import blah = require('./blah'); + import log = console.log;`, + parser, + options: [ + { + alphabetize: { + order: 'asc', + }, + }, + ], + }), + // Object-imports should not be forced to be alphabetized + test({ + code: ` + import debug = console.debug; + import log = console.log;`, + parser, + options: [ + { + alphabetize: { + order: 'asc', + }, + }, + ], + }), + test({ + code: ` + import log = console.log; + import debug = console.debug;`, + parser, + options: [ + { + alphabetize: { + order: 'asc', + }, + }, + ], + }), + test({ + code: ` + import { a } from "./a"; + export namespace SomeNamespace { + export import a2 = a; + } + `, + parser, + options: [ + { + groups: ['external', 'index'], + alphabetize: { order: 'asc' }, + }, + ], + }), + ]), ], invalid: [ // builtin before external module (require) @@ -787,12 +893,10 @@ ruleTester.run('order', rule, { test({ code: `/* comment0 */ /* comment1 */ var async = require('async'); /* comment2 */` + `\r\n` + - `/* comment3 */ var fs = require('fs'); /* comment4 */` + `\r\n` - , + `/* comment3 */ var fs = require('fs'); /* comment4 */` + `\r\n`, output: `/* comment3 */ var fs = require('fs'); /* comment4 */` + `\r\n` + - `/* comment0 */ /* comment1 */ var async = require('async'); /* comment2 */` + `\r\n` - , + `/* comment0 */ /* comment1 */ var async = require('async'); /* comment2 */` + `\r\n`, errors: [{ message: '`fs` import should occur before import of `async`', }], @@ -936,7 +1040,7 @@ ruleTester.run('order', rule, { var async = require('async'); var fs = require('fs'); `, - output: ` + output: ` var async = require('async'); var sibling = require('./sibling'); var fs = require('fs'); @@ -980,7 +1084,7 @@ ruleTester.run('order', rule, { var index = require('./'); var fs = require('fs'); `, - options: [{groups: ['index', 'sibling', 'parent', 'external', 'builtin']}], + options: [{ groups: ['index', 'sibling', 'parent', 'external', 'builtin'] }], errors: [{ message: '`./` import should occur before import of `fs`', }], @@ -1043,10 +1147,10 @@ ruleTester.run('order', rule, { var path = require('path'); var sibling = require('./foo'); `, - options: [{groups: [ + options: [{ groups: [ ['builtin', 'index'], ['sibling', 'parent', 'external'], - ]}], + ] }], errors: [{ message: '`path` import should occur before import of `./foo`', }], @@ -1061,11 +1165,11 @@ ruleTester.run('order', rule, { var async = require('async'); var path = require('path'); `, - options: [{groups: [ + options: [{ groups: [ 'index', ['sibling', 'parent', 'external', 'internal'], // missing 'builtin' - ]}], + ] }], errors: [{ message: '`async` import should occur before import of `path`', }], @@ -1077,10 +1181,10 @@ ruleTester.run('order', rule, { var async = require('async'); var index = require('./'); `, - options: [{groups: [ + options: [{ groups: [ 'index', ['sibling', 'parent', 'UNKNOWN', 'internal'], - ]}], + ] }], errors: [{ message: 'Incorrect configuration of the rule: Unknown type `"UNKNOWN"`', }], @@ -1091,10 +1195,10 @@ ruleTester.run('order', rule, { var async = require('async'); var index = require('./'); `, - options: [{groups: [ + options: [{ groups: [ 'index', ['sibling', 'parent', ['builtin'], 'internal'], - ]}], + ] }], errors: [{ message: 'Incorrect configuration of the rule: Unknown type `["builtin"]`', }], @@ -1105,10 +1209,10 @@ ruleTester.run('order', rule, { var async = require('async'); var index = require('./'); `, - options: [{groups: [ + options: [{ groups: [ 'index', ['sibling', 'parent', 2, 'internal'], - ]}], + ] }], errors: [{ message: 'Incorrect configuration of the rule: Unknown type `2`', }], @@ -1119,10 +1223,10 @@ ruleTester.run('order', rule, { var async = require('async'); var index = require('./'); `, - options: [{groups: [ + options: [{ groups: [ 'index', ['sibling', 'parent', 'parent', 'internal'], - ]}], + ] }], errors: [{ message: 'Incorrect configuration of the rule: `parent` is duplicated', }], @@ -1167,6 +1271,7 @@ ruleTester.run('order', rule, { }], }), ...flatMap(getTSParsers(), parser => [ + // Order of the `import ... = require(...)` syntax test({ code: ` var fs = require('fs'); @@ -1183,7 +1288,7 @@ ruleTester.run('order', rule, { message: '`fs` import should occur after import of `../foo/bar`', }], }), - { + test({ code: ` var async = require('async'); var fs = require('fs'); @@ -1196,7 +1301,7 @@ ruleTester.run('order', rule, { errors: [{ message: '`fs` import should occur before import of `async`', }], - }, + }), test({ code: ` import sync = require('sync'); @@ -1212,13 +1317,23 @@ ruleTester.run('order', rule, { `, options: [{ groups: ['external', 'index'], - alphabetize: {order: 'asc'}, + alphabetize: { order: 'asc' }, }], parser, errors: [{ message: '`async` import should occur before import of `sync`', }], }), + // Order of object-imports + test({ + code: ` + import log = console.log; + import blah = require('./blah');`, + parser, + errors: [{ + message: '`./blah` import should occur before import of `console.log`', + }], + }), ]), // Default order using import with custom import alias test({ @@ -1454,7 +1569,8 @@ ruleTester.run('order', rule, { }, ], }), - // Option newlines-between: 'never' cannot fix if there are other statements between imports + // Option newlines-between: 'never' with unassigned imports and warnOnUnassignedImports disabled + // newline is preserved to match existing behavior test({ code: ` import path from 'path'; @@ -1470,6 +1586,53 @@ ruleTester.run('order', rule, { import 'something-else'; import _ from 'lodash'; `, + options: [{ 'newlines-between': 'never', warnOnUnassignedImports: false }], + errors: [ + { + line: 2, + message: 'There should be no empty line between import groups', + }, + ], + }), + // Option newlines-between: 'never' with unassigned imports and warnOnUnassignedImports enabled + test({ + code: ` + import path from 'path'; + import 'loud-rejection'; + + import 'something-else'; + import _ from 'lodash'; + `, + output: ` + import path from 'path'; + import 'loud-rejection'; + import 'something-else'; + import _ from 'lodash'; + `, + options: [{ 'newlines-between': 'never', warnOnUnassignedImports: true }], + errors: [ + { + line: 3, + message: 'There should be no empty line between import groups', + }, + ], + }), + // Option newlines-between: 'never' cannot fix if there are other statements between imports + test({ + code: ` + import path from 'path'; + export const abc = 123; + + import 'something-else'; + import _ from 'lodash'; + `, + output: ` + import path from 'path'; + export const abc = 123; + + import 'something-else'; + import _ from 'lodash'; + `, options: [{ 'newlines-between': 'never' }], errors: [ { @@ -1688,7 +1851,6 @@ ruleTester.run('order', rule, { '`./local2` import should occur after import of `global4`', ], }), - // pathGroup with position 'after' test({ code: ` @@ -1971,7 +2133,7 @@ ruleTester.run('order', rule, { `, options: [{ groups: ['external', 'index'], - alphabetize: {order: 'asc'}, + alphabetize: { order: 'asc' }, }], errors: [{ message: '`Bar` import should occur before import of `bar`', @@ -1995,7 +2157,7 @@ ruleTester.run('order', rule, { `, options: [{ groups: ['external', 'index'], - alphabetize: {order: 'desc'}, + alphabetize: { order: 'desc' }, }], errors: [{ message: '`bar` import should occur before import of `Bar`', @@ -2017,7 +2179,7 @@ ruleTester.run('order', rule, { `, options: [{ groups: ['external', 'index'], - alphabetize: {order: 'asc', caseInsensitive: true}, + alphabetize: { order: 'asc', caseInsensitive: true }, }], errors: [{ message: '`Bar` import should occur before import of `foo`', @@ -2039,7 +2201,7 @@ ruleTester.run('order', rule, { `, options: [{ groups: ['external', 'index'], - alphabetize: {order: 'desc', caseInsensitive: true}, + alphabetize: { order: 'desc', caseInsensitive: true }, }], errors: [{ message: '`foo` import should occur before import of `Bar`', @@ -2057,7 +2219,7 @@ ruleTester.run('order', rule, { `, options: [{ groups: ['external', 'index'], - alphabetize: {order: 'asc'}, + alphabetize: { order: 'asc' }, }], errors: [{ message: '`..` import should occur before import of `../a`', @@ -2086,7 +2248,7 @@ ruleTester.run('order', rule, { }), ], ].filter((t) => !!t), -}) +}); context('TypeScript', function () { @@ -2099,7 +2261,7 @@ context('TypeScript', function () { 'import/parsers': { [parser]: ['.ts'] }, 'import/resolver': { 'eslint-import-resolver-typescript': true }, }, - } + }; ruleTester.run('order', rule, { valid: [ @@ -2149,29 +2311,109 @@ context('TypeScript', function () { }, parserConfig, ), + // Option alphabetize: {order: 'asc'} with type group + test( + { + code: ` + import c from 'Bar'; + import b from 'bar'; + import a from 'foo'; + + import index from './'; + + import type { C } from 'Bar'; + import type { A } from 'foo'; + `, + parser, + options: [ + { + groups: ['external', 'index', 'type'], + alphabetize: { order: 'asc' }, + }, + ], + }, + parserConfig, + ), + // Option alphabetize: {order: 'desc'} with type group + test( + { + code: ` + import a from 'foo'; + import b from 'bar'; + import c from 'Bar'; + + import index from './'; + + import type { A } from 'foo'; + import type { C } from 'Bar'; + `, + parser, + options: [ + { + groups: ['external', 'index', 'type'], + alphabetize: { order: 'desc' }, + }, + ], + }, + parserConfig, + ), + test( + { + code: ` + import { Partner } from '@models/partner/partner'; + import { PartnerId } from '@models/partner/partner-id'; + `, + parser, + options: [ + { + alphabetize: { order: 'asc' }, + }, + ], + }, + parserConfig, + ), + test( + { + code: ` + import { serialize, parse, mapFieldErrors } from '@vtaits/form-schema'; + import type { GetFieldSchema } from '@vtaits/form-schema'; + import { useMemo, useCallback } from 'react'; + import type { ReactElement, ReactNode } from 'react'; + import { Form } from 'react-final-form'; + import type { FormProps as FinalFormProps } from 'react-final-form'; + `, + parser, + options: [ + { + alphabetize: { order: 'asc' }, + }, + ], + }, + 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 b from 'bar'; + import c from 'Bar'; + import type { C } from 'Bar'; + import a from 'foo'; + import type { A } from 'foo'; - import index from './'; - `, + 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 c from 'Bar'; + import type { C } from 'Bar'; + import b from 'bar'; + import a from 'foo'; + import type { A } from 'foo'; - import index from './'; - `, + import index from './'; + `, parser, options: [ { @@ -2181,7 +2423,9 @@ context('TypeScript', function () { ], 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`)/, + message: semver.satisfies(eslintPkg.version, '< 3') + ? '`bar` import should occur after import of `Bar`' + : /(`bar` import should occur after import of `Bar`)|(`Bar` import should occur before import of `bar`)/, }, ], }, @@ -2191,23 +2435,23 @@ context('TypeScript', function () { 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 a from 'foo'; + import type { A } from 'foo'; + import c from 'Bar'; + import type { C } from 'Bar'; + import b from 'bar'; - import index from './'; - `, + 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 a from 'foo'; + import type { A } from 'foo'; + import b from 'bar'; + import c from 'Bar'; + import type { C } from 'Bar'; - import index from './'; - `, + import index from './'; + `, parser, options: [ { @@ -2217,13 +2461,142 @@ context('TypeScript', function () { ], 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`)/, + message: semver.satisfies(eslintPkg.version, '< 3') + ? '`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, ), + // Option alphabetize: {order: 'asc'} with type group + test( + { + code: ` + import b from 'bar'; + import c from 'Bar'; + import a from 'foo'; + + import index from './'; + + import type { A } from 'foo'; + import type { C } from 'Bar'; + `, + output: ` + import c from 'Bar'; + import b from 'bar'; + import a from 'foo'; + + import index from './'; + + import type { C } from 'Bar'; + import type { A } from 'foo'; + `, + parser, + options: [ + { + groups: ['external', 'index', 'type'], + alphabetize: { order: 'asc' }, + }, + ], + errors: semver.satisfies(eslintPkg.version, '< 3') ? [ + { message: '`Bar` import should occur before import of `bar`' }, + { message: '`Bar` import should occur before import of `foo`' }, + ] : [ + { message: /(`Bar` import should occur before import of `bar`)|(`bar` import should occur after import of `Bar`)/ }, + { message: /(`Bar` import should occur before import of `foo`)|(`foo` import should occur after import of `Bar`)/ }, + ], + }, + parserConfig, + ), + // Option alphabetize: {order: 'desc'} with type group + test( + { + code: ` + import a from 'foo'; + import c from 'Bar'; + import b from 'bar'; + + import index from './'; + + import type { C } from 'Bar'; + import type { A } from 'foo'; + `, + output: ` + import a from 'foo'; + import b from 'bar'; + import c from 'Bar'; + + import index from './'; + + import type { A } from 'foo'; + import type { C } from 'Bar'; + `, + parser, + options: [ + { + groups: ['external', 'index', 'type'], + alphabetize: { order: 'desc' }, + }, + ], + errors: semver.satisfies(eslintPkg.version, '< 3') ? [ + { message: '`bar` import should occur before import of `Bar`' }, + { message: '`foo` import should occur before import of `Bar`' }, + ] : [ + { message: /(`bar` import should occur before import of `Bar`)|(`Bar` import should occur after import of `bar`)/ }, + { message: /(`foo` import should occur before import of `Bar`)|(`Bar` import should occur after import of `foo`)/ }, + ], + }, + parserConfig, + ), + // warns for out of order unassigned imports (warnOnUnassignedImports enabled) + test({ + code: ` + import './local1'; + import global from 'global1'; + import local from './local2'; + import 'global2'; + `, + output: ` + import './local1'; + import global from 'global1'; + import local from './local2'; + import 'global2'; + `, + errors: [ + { + message: '`global1` import should occur before import of `./local1`', + }, + { + message: '`global2` import should occur before import of `./local1`', + }, + ], + options: [{ warnOnUnassignedImports: true }], + }), + // fix cannot move below unassigned import (warnOnUnassignedImports enabled) + test({ + code: ` + import local from './local'; + + import 'global1'; + + import global2 from 'global2'; + import global3 from 'global3'; + `, + output: ` + import local from './local'; + + import 'global1'; + + import global2 from 'global2'; + import global3 from 'global3'; + `, + errors: [{ + message: '`./local` import should occur after import of `global3`', + }], + options: [{ warnOnUnassignedImports: true }], + }), ], - }) - }) -}) + }); + }); +}); diff --git a/tests/src/rules/prefer-default-export.js b/tests/src/rules/prefer-default-export.js index 9e38cea926..4efa47f5fc 100644 --- a/tests/src/rules/prefer-default-export.js +++ b/tests/src/rules/prefer-default-export.js @@ -1,9 +1,9 @@ -import { test, getNonDefaultParsers } from '../utils' +import { test, getNonDefaultParsers } from '../utils'; -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('../../../src/rules/prefer-default-export') +const ruleTester = new RuleTester(); +const rule = require('../../../src/rules/prefer-default-export'); ruleTester.run('prefer-default-export', rule, { valid: [ @@ -11,72 +11,72 @@ ruleTester.run('prefer-default-export', rule, { code: ` export const foo = 'foo'; export const bar = 'bar';`, - }), + }), test({ code: ` export default function bar() {};`, - }), + }), test({ code: ` export const foo = 'foo'; export function bar() {};`, - }), + }), test({ code: ` export const foo = 'foo'; export default bar;`, - }), + }), test({ code: ` let foo, bar; export { foo, bar }`, - }), + }), test({ code: ` export const { foo, bar } = item;`, - }), + }), test({ code: ` export const { foo, bar: baz } = item;`, - }), + }), test({ code: ` export const { foo: { bar, baz } } = item;`, - }), + }), test({ code: ` export const [a, b] = item;`, - }), + }), test({ code: ` let item; export const foo = item; export { item };`, - }), + }), test({ code: ` let foo; export { foo as default }`, - }), + }), test({ code: ` export * from './foo';`, - }), + }), test({ code: `export Memory, { MemoryValue } from './Memory'`, parser: require.resolve('babel-eslint'), - }), + }), // no exports at all test({ code: ` import * as foo from './foo';`, - }), + }), test({ code: `export type UserId = number;`, parser: require.resolve('babel-eslint'), - }), + }), // issue #653 test({ @@ -88,6 +88,12 @@ ruleTester.run('prefer-default-export', rule, { parser: require.resolve('babel-eslint'), }), // ...SYNTAX_CASES, + test({ + code: ` + export const [CounterProvider,, withCounter] = func();; + `, + parser: require.resolve('babel-eslint'), + }), ], invalid: [ test({ @@ -140,7 +146,7 @@ ruleTester.run('prefer-default-export', rule, { }], }), ], -}) +}); context('TypeScript', function() { getNonDefaultParsers().forEach((parser) => { @@ -150,7 +156,7 @@ context('TypeScript', function() { 'import/parsers': { [parser]: ['.ts'] }, 'import/resolver': { 'eslint-import-resolver-typescript': true }, }, - } + }; ruleTester.run('prefer-default-export', rule, { valid: [ @@ -187,14 +193,14 @@ context('TypeScript', function() { }, parserConfig, ), - test ( + test( { code: 'export interface foo { bar: string; }', parser, }, parserConfig, ), - test ( + test( { code: 'export interface foo { bar: string; }; export function goo() {}', parser, @@ -203,6 +209,6 @@ context('TypeScript', function() { ), ], invalid: [], - }) - }) -}) + }); + }); +}); diff --git a/tests/src/rules/unambiguous.js b/tests/src/rules/unambiguous.js index 705ce79d10..72e6b10828 100644 --- a/tests/src/rules/unambiguous.js +++ b/tests/src/rules/unambiguous.js @@ -1,7 +1,7 @@ -import { RuleTester } from 'eslint' +import { RuleTester } from 'eslint'; -const ruleTester = new RuleTester() - , rule = require('rules/unambiguous') +const ruleTester = new RuleTester(); +const rule = require('rules/unambiguous'); ruleTester.run('unambiguous', rule, { valid: [ @@ -54,4 +54,4 @@ ruleTester.run('unambiguous', rule, { errors: ['This module could be parsed as a valid script.'], }, ], -}) +}); diff --git a/tests/src/utils.js b/tests/src/utils.js index 4bc8f0119a..a76826de51 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -1,34 +1,34 @@ -import path from 'path' -import eslintPkg from 'eslint/package.json' -import semver from 'semver' +import path from 'path'; +import eslintPkg from 'eslint/package.json'; +import semver from 'semver'; // warms up the module cache. this import takes a while (>500ms) -import 'babel-eslint' +import 'babel-eslint'; export function testFilePath(relativePath) { - return path.join(process.cwd(), './tests/files', relativePath) + return path.join(process.cwd(), './tests/files', relativePath); } export function getTSParsers() { - const parsers = [] + const parsers = []; if (semver.satisfies(eslintPkg.version, '>=4.0.0 <6.0.0')) { - parsers.push(require.resolve('typescript-eslint-parser')) + parsers.push(require.resolve('typescript-eslint-parser')); } if (semver.satisfies(eslintPkg.version, '>5.0.0')) { - parsers.push(require.resolve('@typescript-eslint/parser')) + parsers.push(require.resolve('@typescript-eslint/parser')); } - return parsers + return parsers; } export function getNonDefaultParsers() { - return getTSParsers().concat(require.resolve('babel-eslint')) + return getTSParsers().concat(require.resolve('babel-eslint')); } -export const FILENAME = testFilePath('foo.js') +export const FILENAME = testFilePath('foo.js'); export function testVersion(specifier, t) { - return semver.satisfies(eslintPkg.version, specifier) && test(t()) + return semver.satisfies(eslintPkg.version, specifier) && test(t()); } export function test(t) { @@ -37,18 +37,18 @@ export function test(t) { }, t, { parserOptions: Object.assign({ sourceType: 'module', - ecmaVersion: 6, + ecmaVersion: 9, }, t.parserOptions), - }) + }); } export function testContext(settings) { - return { getFilename: function () { return FILENAME } - , settings: settings || {} } + return { getFilename: function () { return FILENAME; }, + settings: settings || {} }; } export function getFilename(file) { - return path.join(__dirname, '..', 'files', file || 'foo.js') + return path.join(__dirname, '..', 'files', file || 'foo.js'); } /** @@ -116,4 +116,4 @@ export const SYNTAX_CASES = [ code: 'import { foo } from "./ignore.invalid.extension"', }), -] +]; diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index f337d3850a..949fa8d582 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -5,10 +5,20 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +## v2.6.1 - 2021-05-13 + +### Fixed +- `no-unresolved`: check `import()` ([#2026], thanks [@aladdin-add]) +- Add fix for Windows Subsystem for Linux ([#1786], thanks [@manuth]) + +### Changed +- [deps] update `debug` +- [Refactor] use `Array.isArray` instead of `instanceof Array` + ## v2.6.0 - 2020-03-28 ### Added -[New] Print more helpful info if parsing fails ([#1671], thanks [@kaiyoma]) +- Print more helpful info if parsing fails ([#1671], thanks [@kaiyoma]) ## v2.5.2 - 2020-01-12 @@ -75,6 +85,8 @@ Yanked due to critical issue with cache key resulting from #839. ### Fixed - `unambiguous.test()` regex is now properly in multiline mode +[#2026]: https://github.com/benmosher/eslint-plugin-import/pull/2026 +[#1786]: https://github.com/benmosher/eslint-plugin-import/pull/1786 [#1671]: https://github.com/benmosher/eslint-plugin-import/pull/1671 [#1606]: https://github.com/benmosher/eslint-plugin-import/pull/1606 [#1602]: https://github.com/benmosher/eslint-plugin-import/pull/1602 @@ -101,3 +113,5 @@ Yanked due to critical issue with cache key resulting from #839. [@sompylasar]: https://github.com/sompylasar [@iamnapo]: https://github.com/iamnapo [@kaiyoma]: https://github.com/kaiyoma +[@manuth]: https://github.com/manuth +[@aladdin-add]: https://github.com/aladdin-add \ No newline at end of file diff --git a/utils/ModuleCache.js b/utils/ModuleCache.js index b910a5810a..a06616de9b 100644 --- a/utils/ModuleCache.js +++ b/utils/ModuleCache.js @@ -1,11 +1,11 @@ -'use strict' -exports.__esModule = true +'use strict'; +exports.__esModule = true; -const log = require('debug')('eslint-module-utils:ModuleCache') +const log = require('debug')('eslint-module-utils:ModuleCache'); class ModuleCache { constructor(map) { - this.map = map || new Map() + this.map = map || new Map(); } /** @@ -14,19 +14,19 @@ class ModuleCache { * @param {[type]} result [description] */ set(cacheKey, result) { - this.map.set(cacheKey, { result, lastSeen: process.hrtime() }) - log('setting entry for', cacheKey) - return result + this.map.set(cacheKey, { result, lastSeen: process.hrtime() }); + log('setting entry for', cacheKey); + return result; } get(cacheKey, settings) { if (this.map.has(cacheKey)) { - const f = this.map.get(cacheKey) + const f = this.map.get(cacheKey); // check freshness - if (process.hrtime(f.lastSeen)[0] < settings.lifetime) return f.result - } else log('cache miss for', cacheKey) + if (process.hrtime(f.lastSeen)[0] < settings.lifetime) return f.result; + } else log('cache miss for', cacheKey); // cache miss - return undefined + return undefined; } } @@ -34,14 +34,14 @@ class ModuleCache { ModuleCache.getSettings = function (settings) { const cacheSettings = Object.assign({ lifetime: 30, // seconds - }, settings['import/cache']) + }, settings['import/cache']); // parse infinity if (cacheSettings.lifetime === '∞' || cacheSettings.lifetime === 'Infinity') { - cacheSettings.lifetime = Infinity + cacheSettings.lifetime = Infinity; } - return cacheSettings -} + return cacheSettings; +}; -exports.default = ModuleCache +exports.default = ModuleCache; diff --git a/utils/declaredScope.js b/utils/declaredScope.js index 904279ad79..ded2131e49 100644 --- a/utils/declaredScope.js +++ b/utils/declaredScope.js @@ -1,14 +1,9 @@ -'use strict' -exports.__esModule = true +'use strict'; +exports.__esModule = true; exports.default = function declaredScope(context, name) { - let references = context.getScope().references - , i - for (i = 0; i < references.length; i++) { - if (references[i].identifier.name === name) { - break - } - } - if (!references[i]) return undefined - return references[i].resolved.scope.type -} + const references = context.getScope().references; + const reference = references.find(x => x.identifier.name === name); + if (!reference) return undefined; + return reference.resolved.scope.type; +}; diff --git a/utils/hash.js b/utils/hash.js index d69dd4df5f..fcf00de38c 100644 --- a/utils/hash.js +++ b/utils/hash.js @@ -2,58 +2,58 @@ * utilities for hashing config objects. * basically iteratively updates hash with a JSON-like format */ -'use strict' -exports.__esModule = true +'use strict'; +exports.__esModule = true; -const createHash = require('crypto').createHash +const createHash = require('crypto').createHash; -const stringify = JSON.stringify +const stringify = JSON.stringify; function hashify(value, hash) { - if (!hash) hash = createHash('sha256') + if (!hash) hash = createHash('sha256'); - if (value instanceof Array) { - hashArray(value, hash) + if (Array.isArray(value)) { + hashArray(value, hash); } else if (value instanceof Object) { - hashObject(value, hash) + hashObject(value, hash); } else { - hash.update(stringify(value) || 'undefined') + hash.update(stringify(value) || 'undefined'); } - return hash + return hash; } -exports.default = hashify +exports.default = hashify; function hashArray(array, hash) { - if (!hash) hash = createHash('sha256') + if (!hash) hash = createHash('sha256'); - hash.update('[') + hash.update('['); for (let i = 0; i < array.length; i++) { - hashify(array[i], hash) - hash.update(',') + hashify(array[i], hash); + hash.update(','); } - hash.update(']') + hash.update(']'); - return hash + return hash; } -hashify.array = hashArray -exports.hashArray = hashArray +hashify.array = hashArray; +exports.hashArray = hashArray; function hashObject(object, hash) { - if (!hash) hash = createHash('sha256') + if (!hash) hash = createHash('sha256'); - hash.update('{') + hash.update('{'); Object.keys(object).sort().forEach(key => { - hash.update(stringify(key)) - hash.update(':') - hashify(object[key], hash) - hash.update(',') - }) - hash.update('}') - - return hash + hash.update(stringify(key)); + hash.update(':'); + hashify(object[key], hash); + hash.update(','); + }); + hash.update('}'); + + return hash; } -hashify.object = hashObject -exports.hashObject = hashObject +hashify.object = hashObject; +exports.hashObject = hashObject; diff --git a/utils/ignore.js b/utils/ignore.js index 47af8122dd..32bbbc6249 100644 --- a/utils/ignore.js +++ b/utils/ignore.js @@ -1,60 +1,60 @@ -'use strict' -exports.__esModule = true +'use strict'; +exports.__esModule = true; -const extname = require('path').extname +const extname = require('path').extname; -const log = require('debug')('eslint-plugin-import:utils:ignore') +const log = require('debug')('eslint-plugin-import:utils:ignore'); // one-shot memoized -let cachedSet, lastSettings +let cachedSet; let lastSettings; function validExtensions(context) { if (cachedSet && context.settings === lastSettings) { - return cachedSet + return cachedSet; } - lastSettings = context.settings - cachedSet = makeValidExtensionSet(context.settings) - return cachedSet + lastSettings = context.settings; + cachedSet = makeValidExtensionSet(context.settings); + return cachedSet; } function makeValidExtensionSet(settings) { // start with explicit JS-parsed extensions - const exts = new Set(settings['import/extensions'] || [ '.js' ]) + const exts = new Set(settings['import/extensions'] || [ '.js' ]); // all alternate parser extensions are also valid if ('import/parsers' in settings) { - for (let parser in settings['import/parsers']) { - const parserSettings = settings['import/parsers'][parser] + for (const parser in settings['import/parsers']) { + const parserSettings = settings['import/parsers'][parser]; if (!Array.isArray(parserSettings)) { - throw new TypeError('"settings" for ' + parser + ' must be an array') + throw new TypeError('"settings" for ' + parser + ' must be an array'); } - parserSettings.forEach(ext => exts.add(ext)) + parserSettings.forEach(ext => exts.add(ext)); } } - return exts + return exts; } -exports.getFileExtensions = makeValidExtensionSet +exports.getFileExtensions = makeValidExtensionSet; exports.default = function ignore(path, context) { // check extension whitelist first (cheap) - if (!hasValidExtension(path, context)) return true + if (!hasValidExtension(path, context)) return true; - if (!('import/ignore' in context.settings)) return false - const ignoreStrings = context.settings['import/ignore'] + if (!('import/ignore' in context.settings)) return false; + const ignoreStrings = context.settings['import/ignore']; for (let i = 0; i < ignoreStrings.length; i++) { - const regex = new RegExp(ignoreStrings[i]) + const regex = new RegExp(ignoreStrings[i]); if (regex.test(path)) { - log(`ignoring ${path}, matched pattern /${ignoreStrings[i]}/`) - return true + log(`ignoring ${path}, matched pattern /${ignoreStrings[i]}/`); + return true; } } - return false -} + return false; +}; function hasValidExtension(path, context) { - return validExtensions(context).has(extname(path)) + return validExtensions(context).has(extname(path)); } -exports.hasValidExtension = hasValidExtension +exports.hasValidExtension = hasValidExtension; diff --git a/utils/module-require.js b/utils/module-require.js index 689450658c..70e5510621 100644 --- a/utils/module-require.js +++ b/utils/module-require.js @@ -1,30 +1,30 @@ -'use strict' -exports.__esModule = true +'use strict'; +exports.__esModule = true; -const Module = require('module') -const path = require('path') +const Module = require('module'); +const path = require('path'); // borrowed from babel-eslint function createModule(filename) { - const mod = new Module(filename) - mod.filename = filename - mod.paths = Module._nodeModulePaths(path.dirname(filename)) - return mod + const mod = new Module(filename); + mod.filename = filename; + mod.paths = Module._nodeModulePaths(path.dirname(filename)); + return mod; } exports.default = function moduleRequire(p) { try { // attempt to get espree relative to eslint - const eslintPath = require.resolve('eslint') - const eslintModule = createModule(eslintPath) - return require(Module._resolveFilename(p, eslintModule)) + const eslintPath = require.resolve('eslint'); + const eslintModule = createModule(eslintPath); + return require(Module._resolveFilename(p, eslintModule)); } catch(err) { /* ignore */ } try { // try relative to entry point - return require.main.require(p) + return require.main.require(p); } catch(err) { /* ignore */ } // finally, try from here - return require(p) -} + return require(p); +}; diff --git a/utils/moduleVisitor.js b/utils/moduleVisitor.js index bc8c91b0af..69269985bd 100644 --- a/utils/moduleVisitor.js +++ b/utils/moduleVisitor.js @@ -1,5 +1,5 @@ -'use strict' -exports.__esModule = true +'use strict'; +exports.__esModule = true; /** * Returns an object of node visitors that will call @@ -12,95 +12,103 @@ exports.__esModule = true */ exports.default = function visitModules(visitor, options) { // if esmodule is not explicitly disabled, it is assumed to be enabled - options = Object.assign({ esmodule: true }, options) + options = Object.assign({ esmodule: true }, options); - let ignoreRegExps = [] + let ignoreRegExps = []; if (options.ignore != null) { - ignoreRegExps = options.ignore.map(p => new RegExp(p)) + ignoreRegExps = options.ignore.map(p => new RegExp(p)); } function checkSourceValue(source, importer) { - if (source == null) return //? + if (source == null) return; //? // handle ignore - if (ignoreRegExps.some(re => re.test(source.value))) return + if (ignoreRegExps.some(re => re.test(source.value))) return; // fire visitor - visitor(source, importer) + visitor(source, importer); } // for import-y declarations function checkSource(node) { - checkSourceValue(node.source, node) + checkSourceValue(node.source, node); } // for esmodule dynamic `import()` calls function checkImportCall(node) { - if (node.callee.type !== 'Import') return - if (node.arguments.length !== 1) return + let modulePath; + // refs https://github.com/estree/estree/blob/master/es2020.md#importexpression + if (node.type === 'ImportExpression') { + modulePath = node.source; + } else if (node.type === 'CallExpression') { + if (node.callee.type !== 'Import') return; + if (node.arguments.length !== 1) return; + + modulePath = node.arguments[0]; + } - const modulePath = node.arguments[0] - if (modulePath.type !== 'Literal') return - if (typeof modulePath.value !== 'string') return + if (modulePath.type !== 'Literal') return; + if (typeof modulePath.value !== 'string') return; - checkSourceValue(modulePath, node) + checkSourceValue(modulePath, node); } // for CommonJS `require` calls // adapted from @mctep: http://git.io/v4rAu function checkCommon(call) { - if (call.callee.type !== 'Identifier') return - if (call.callee.name !== 'require') return - if (call.arguments.length !== 1) return + if (call.callee.type !== 'Identifier') return; + if (call.callee.name !== 'require') return; + if (call.arguments.length !== 1) return; - const modulePath = call.arguments[0] - if (modulePath.type !== 'Literal') return - if (typeof modulePath.value !== 'string') return + const modulePath = call.arguments[0]; + if (modulePath.type !== 'Literal') return; + if (typeof modulePath.value !== 'string') return; - checkSourceValue(modulePath, call) + checkSourceValue(modulePath, call); } function checkAMD(call) { - if (call.callee.type !== 'Identifier') return + if (call.callee.type !== 'Identifier') return; if (call.callee.name !== 'require' && - call.callee.name !== 'define') return - if (call.arguments.length !== 2) return + call.callee.name !== 'define') return; + if (call.arguments.length !== 2) return; - const modules = call.arguments[0] - if (modules.type !== 'ArrayExpression') return + const modules = call.arguments[0]; + if (modules.type !== 'ArrayExpression') return; - for (let element of modules.elements) { - if (element.type !== 'Literal') continue - if (typeof element.value !== 'string') continue + for (const element of modules.elements) { + if (element.type !== 'Literal') continue; + if (typeof element.value !== 'string') continue; if (element.value === 'require' || - element.value === 'exports') continue // magic modules: http://git.io/vByan + element.value === 'exports') continue; // magic modules: http://git.io/vByan - checkSourceValue(element, element) + checkSourceValue(element, element); } } - const visitors = {} + const visitors = {}; if (options.esmodule) { Object.assign(visitors, { 'ImportDeclaration': checkSource, 'ExportNamedDeclaration': checkSource, 'ExportAllDeclaration': checkSource, 'CallExpression': checkImportCall, - }) + 'ImportExpression': checkImportCall, + }); } if (options.commonjs || options.amd) { - const currentCallExpression = visitors['CallExpression'] + const currentCallExpression = visitors['CallExpression']; visitors['CallExpression'] = function (call) { - if (currentCallExpression) currentCallExpression(call) - if (options.commonjs) checkCommon(call) - if (options.amd) checkAMD(call) - } + if (currentCallExpression) currentCallExpression(call); + if (options.commonjs) checkCommon(call); + if (options.amd) checkAMD(call); + }; } - return visitors -} + return visitors; +}; /** * make an options schema for the module visitor, optionally @@ -121,21 +129,21 @@ function makeOptionsSchema(additionalProperties) { }, }, 'additionalProperties': false, - } + }; if (additionalProperties){ - for (let key in additionalProperties) { - base.properties[key] = additionalProperties[key] + for (const key in additionalProperties) { + base.properties[key] = additionalProperties[key]; } } - return base + return base; } -exports.makeOptionsSchema = makeOptionsSchema +exports.makeOptionsSchema = makeOptionsSchema; /** * json schema object for options parameter. can be used to build * rule options schema object. * @type {Object} */ -exports.optionsSchema = makeOptionsSchema() +exports.optionsSchema = makeOptionsSchema(); diff --git a/utils/package.json b/utils/package.json index 6e8ebddfb9..2ec00e60a4 100644 --- a/utils/package.json +++ b/utils/package.json @@ -1,6 +1,6 @@ { "name": "eslint-module-utils", - "version": "2.6.0", + "version": "2.6.1", "description": "Core utilities to support eslint-plugin-import and other module-related plugins.", "engines": { "node": ">=4" @@ -26,7 +26,7 @@ }, "homepage": "https://github.com/benmosher/eslint-plugin-import#readme", "dependencies": { - "debug": "^2.6.9", + "debug": "^3.2.7", "pkg-dir": "^2.0.0" } } diff --git a/utils/parse.js b/utils/parse.js index b3a469221d..9cc2380b3a 100644 --- a/utils/parse.js +++ b/utils/parse.js @@ -1,82 +1,82 @@ -'use strict' -exports.__esModule = true +'use strict'; +exports.__esModule = true; -const moduleRequire = require('./module-require').default -const extname = require('path').extname +const moduleRequire = require('./module-require').default; +const extname = require('path').extname; -const log = require('debug')('eslint-plugin-import:parse') +const log = require('debug')('eslint-plugin-import:parse'); exports.default = function parse(path, content, context) { - if (context == null) throw new Error('need context to parse properly') + if (context == null) throw new Error('need context to parse properly'); - let parserOptions = context.parserOptions - const parserPath = getParserPath(path, context) + let parserOptions = context.parserOptions; + const parserPath = getParserPath(path, context); - if (!parserPath) throw new Error('parserPath is required!') + if (!parserPath) throw new Error('parserPath is required!'); // hack: espree blows up with frozen options - parserOptions = Object.assign({}, parserOptions) - parserOptions.ecmaFeatures = Object.assign({}, parserOptions.ecmaFeatures) + parserOptions = Object.assign({}, parserOptions); + parserOptions.ecmaFeatures = Object.assign({}, parserOptions.ecmaFeatures); // always include comments and tokens (for doc parsing) - parserOptions.comment = true - parserOptions.attachComment = true // keeping this for backward-compat with older parsers - parserOptions.tokens = true + parserOptions.comment = true; + parserOptions.attachComment = true; // keeping this for backward-compat with older parsers + parserOptions.tokens = true; // attach node locations - parserOptions.loc = true - parserOptions.range = true + parserOptions.loc = true; + parserOptions.range = true; // provide the `filePath` like eslint itself does, in `parserOptions` // https://github.com/eslint/eslint/blob/3ec436ee/lib/linter.js#L637 - parserOptions.filePath = path - + parserOptions.filePath = path; + // @typescript-eslint/parser will parse the entire project with typechecking if you provide // "project" or "projects" in parserOptions. Removing these options means the parser will // only parse one file in isolate mode, which is much, much faster. // https://github.com/benmosher/eslint-plugin-import/issues/1408#issuecomment-509298962 - delete parserOptions.project - delete parserOptions.projects - + delete parserOptions.project; + delete parserOptions.projects; + // require the parser relative to the main module (i.e., ESLint) - const parser = moduleRequire(parserPath) + const parser = moduleRequire(parserPath); if (typeof parser.parseForESLint === 'function') { - let ast + let ast; try { - ast = parser.parseForESLint(content, parserOptions).ast + ast = parser.parseForESLint(content, parserOptions).ast; } catch (e) { - console.warn() - console.warn('Error while parsing ' + parserOptions.filePath) - console.warn('Line ' + e.lineNumber + ', column ' + e.column + ': ' + e.message) + console.warn(); + console.warn('Error while parsing ' + parserOptions.filePath); + console.warn('Line ' + e.lineNumber + ', column ' + e.column + ': ' + e.message); } if (!ast || typeof ast !== 'object') { console.warn( '`parseForESLint` from parser `' + parserPath + '` is invalid and will just be ignored' - ) + ); } else { - return ast + return ast; } } - return parser.parse(content, parserOptions) -} + return parser.parse(content, parserOptions); +}; function getParserPath(path, context) { - const parsers = context.settings['import/parsers'] + const parsers = context.settings['import/parsers']; if (parsers != null) { - const extension = extname(path) - for (let parserPath in parsers) { + const extension = extname(path); + for (const parserPath in parsers) { if (parsers[parserPath].indexOf(extension) > -1) { // use this alternate parser - log('using alt parser:', parserPath) - return parserPath + log('using alt parser:', parserPath); + return parserPath; } } } // default to use ESLint parser - return context.parserPath + return context.parserPath; } diff --git a/utils/resolve.js b/utils/resolve.js index fc8f85de9a..ea5bf5a150 100644 --- a/utils/resolve.js +++ b/utils/resolve.js @@ -1,211 +1,211 @@ -'use strict' -exports.__esModule = true +'use strict'; +exports.__esModule = true; -const pkgDir = require('pkg-dir') +const pkgDir = require('pkg-dir'); -const fs = require('fs') -const Module = require('module') -const path = require('path') +const fs = require('fs'); +const Module = require('module'); +const path = require('path'); -const hashObject = require('./hash').hashObject - , ModuleCache = require('./ModuleCache').default +const hashObject = require('./hash').hashObject; +const ModuleCache = require('./ModuleCache').default; -const CASE_SENSITIVE_FS = !fs.existsSync(path.join(__dirname.toUpperCase(), 'reSOLVE.js')) -exports.CASE_SENSITIVE_FS = CASE_SENSITIVE_FS +const CASE_SENSITIVE_FS = !fs.existsSync(path.join(__dirname.toUpperCase(), 'reSOLVE.js')); +exports.CASE_SENSITIVE_FS = CASE_SENSITIVE_FS; -const ERROR_NAME = 'EslintPluginImportResolveError' +const ERROR_NAME = 'EslintPluginImportResolveError'; -const fileExistsCache = new ModuleCache() +const fileExistsCache = new ModuleCache(); // Polyfill Node's `Module.createRequireFromPath` if not present (added in Node v10.12.0) // Use `Module.createRequire` if available (added in Node v12.2.0) const createRequire = Module.createRequire || Module.createRequireFromPath || function (filename) { - const mod = new Module(filename, null) - mod.filename = filename - mod.paths = Module._nodeModulePaths(path.dirname(filename)) + const mod = new Module(filename, null); + mod.filename = filename; + mod.paths = Module._nodeModulePaths(path.dirname(filename)); - mod._compile(`module.exports = require;`, filename) + mod._compile(`module.exports = require;`, filename); - return mod.exports -} + return mod.exports; +}; function tryRequire(target, sourceFile) { - let resolved + let resolved; try { // Check if the target exists if (sourceFile != null) { try { - resolved = createRequire(path.resolve(sourceFile)).resolve(target) + resolved = createRequire(path.resolve(sourceFile)).resolve(target); } catch (e) { - resolved = require.resolve(target) + resolved = require.resolve(target); } } else { - resolved = require.resolve(target) + resolved = require.resolve(target); } } catch(e) { // If the target does not exist then just return undefined - return undefined + return undefined; } // If the target exists then return the loaded module - return require(resolved) + return require(resolved); } // http://stackoverflow.com/a/27382838 exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings) { // don't care if the FS is case-sensitive - if (CASE_SENSITIVE_FS) return true + if (CASE_SENSITIVE_FS) return true; // null means it resolved to a builtin - if (filepath === null) return true - if (filepath.toLowerCase() === process.cwd().toLowerCase()) return true - const parsedPath = path.parse(filepath) - , dir = parsedPath.dir + if (filepath === null) return true; + if (filepath.toLowerCase() === process.cwd().toLowerCase()) return true; + const parsedPath = path.parse(filepath); + const dir = parsedPath.dir; - let result = fileExistsCache.get(filepath, cacheSettings) - if (result != null) return result + let result = fileExistsCache.get(filepath, cacheSettings); + if (result != null) return result; // base case if (dir === '' || parsedPath.root === filepath) { - result = true + result = true; } else { - const filenames = fs.readdirSync(dir) + const filenames = fs.readdirSync(dir); if (filenames.indexOf(parsedPath.base) === -1) { - result = false + result = false; } else { - result = fileExistsWithCaseSync(dir, cacheSettings) + result = fileExistsWithCaseSync(dir, cacheSettings); } } - fileExistsCache.set(filepath, result) - return result -} + fileExistsCache.set(filepath, result); + return result; +}; function relative(modulePath, sourceFile, settings) { - return fullResolve(modulePath, sourceFile, settings).path + return fullResolve(modulePath, sourceFile, settings).path; } function fullResolve(modulePath, sourceFile, settings) { // check if this is a bonus core module - const coreSet = new Set(settings['import/core-modules']) - if (coreSet.has(modulePath)) return { found: true, path: null } + const coreSet = new Set(settings['import/core-modules']); + if (coreSet.has(modulePath)) return { found: true, path: null }; - const sourceDir = path.dirname(sourceFile) - , cacheKey = sourceDir + hashObject(settings).digest('hex') + modulePath + const sourceDir = path.dirname(sourceFile); + const cacheKey = sourceDir + hashObject(settings).digest('hex') + modulePath; - const cacheSettings = ModuleCache.getSettings(settings) + const cacheSettings = ModuleCache.getSettings(settings); - const cachedPath = fileExistsCache.get(cacheKey, cacheSettings) - if (cachedPath !== undefined) return { found: true, path: cachedPath } + const cachedPath = fileExistsCache.get(cacheKey, cacheSettings); + if (cachedPath !== undefined) return { found: true, path: cachedPath }; function cache(resolvedPath) { - fileExistsCache.set(cacheKey, resolvedPath) + fileExistsCache.set(cacheKey, resolvedPath); } function withResolver(resolver, config) { function v1() { try { - const resolved = resolver.resolveImport(modulePath, sourceFile, config) - if (resolved === undefined) return { found: false } - return { found: true, path: resolved } + const resolved = resolver.resolveImport(modulePath, sourceFile, config); + if (resolved === undefined) return { found: false }; + return { found: true, path: resolved }; } catch (err) { - return { found: false } + return { found: false }; } } function v2() { - return resolver.resolve(modulePath, sourceFile, config) + return resolver.resolve(modulePath, sourceFile, config); } switch (resolver.interfaceVersion) { - case 2: - return v2() + case 2: + return v2(); - default: - case 1: - return v1() + default: + case 1: + return v1(); } } const configResolvers = (settings['import/resolver'] - || { 'node': settings['import/resolve'] }) // backward compatibility + || { 'node': settings['import/resolve'] }); // backward compatibility - const resolvers = resolverReducer(configResolvers, new Map()) + const resolvers = resolverReducer(configResolvers, new Map()); - for (let pair of resolvers) { - let name = pair[0] - , config = pair[1] - const resolver = requireResolver(name, sourceFile) - , resolved = withResolver(resolver, config) + for (const pair of resolvers) { + const name = pair[0]; + const config = pair[1]; + const resolver = requireResolver(name, sourceFile); + const resolved = withResolver(resolver, config); - if (!resolved.found) continue + if (!resolved.found) continue; // else, counts - cache(resolved.path) - return resolved + cache(resolved.path); + return resolved; } // failed // cache(undefined) - return { found: false } + return { found: false }; } -exports.relative = relative +exports.relative = relative; function resolverReducer(resolvers, map) { - if (resolvers instanceof Array) { - resolvers.forEach(r => resolverReducer(r, map)) - return map + if (Array.isArray(resolvers)) { + resolvers.forEach(r => resolverReducer(r, map)); + return map; } if (typeof resolvers === 'string') { - map.set(resolvers, null) - return map + map.set(resolvers, null); + return map; } if (typeof resolvers === 'object') { - for (let key in resolvers) { - map.set(key, resolvers[key]) + for (const key in resolvers) { + map.set(key, resolvers[key]); } - return map + return map; } - const err = new Error('invalid resolver config') - err.name = ERROR_NAME - throw err + const err = new Error('invalid resolver config'); + err.name = ERROR_NAME; + throw err; } function getBaseDir(sourceFile) { - return pkgDir.sync(sourceFile) || process.cwd() + return pkgDir.sync(sourceFile) || process.cwd(); } function requireResolver(name, sourceFile) { // Try to resolve package with conventional name - let resolver = tryRequire(`eslint-import-resolver-${name}`, sourceFile) || + const resolver = tryRequire(`eslint-import-resolver-${name}`, sourceFile) || tryRequire(name, sourceFile) || - tryRequire(path.resolve(getBaseDir(sourceFile), name)) + tryRequire(path.resolve(getBaseDir(sourceFile), name)); if (!resolver) { - const err = new Error(`unable to load resolver "${name}".`) - err.name = ERROR_NAME - throw err + const err = new Error(`unable to load resolver "${name}".`); + err.name = ERROR_NAME; + throw err; } if (!isResolverValid(resolver)) { - const err = new Error(`${name} with invalid interface loaded as resolver`) - err.name = ERROR_NAME - throw err + const err = new Error(`${name} with invalid interface loaded as resolver`); + err.name = ERROR_NAME; + throw err; } - return resolver + return resolver; } function isResolverValid(resolver) { if (resolver.interfaceVersion === 2) { - return resolver.resolve && typeof resolver.resolve === 'function' + return resolver.resolve && typeof resolver.resolve === 'function'; } else { - return resolver.resolveImport && typeof resolver.resolveImport === 'function' + return resolver.resolveImport && typeof resolver.resolveImport === 'function'; } } -const erroredContexts = new Set() +const erroredContexts = new Set(); /** * Given @@ -218,24 +218,24 @@ const erroredContexts = new Set() function resolve(p, context) { try { return relative( p - , context.getFilename() - , context.settings - ) + , context.getFilename() + , context.settings + ); } catch (err) { if (!erroredContexts.has(context)) { // The `err.stack` string starts with `err.name` followed by colon and `err.message`. // We're filtering out the default `err.name` because it adds little value to the message. - let errMessage = err.message + let errMessage = err.message; if (err.name !== ERROR_NAME && err.stack) { - errMessage = err.stack.replace(/^Error: /, '') + errMessage = err.stack.replace(/^Error: /, ''); } context.report({ message: `Resolve error: ${errMessage}`, loc: { line: 1, column: 0 }, - }) - erroredContexts.add(context) + }); + erroredContexts.add(context); } } } -resolve.relative = relative -exports.default = resolve +resolve.relative = relative; +exports.default = resolve; diff --git a/utils/unambiguous.js b/utils/unambiguous.js index 1dae1d6167..1446632f39 100644 --- a/utils/unambiguous.js +++ b/utils/unambiguous.js @@ -1,8 +1,8 @@ -'use strict' -exports.__esModule = true +'use strict'; +exports.__esModule = true; -const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*=]))/m +const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*=]))/m; /** * detect possible imports/exports without a full parse. * @@ -14,11 +14,11 @@ const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*=]))/m * @type {RegExp} */ exports.test = function isMaybeUnambiguousModule(content) { - return pattern.test(content) -} + return pattern.test(content); +}; // future-/Babel-proof at the expense of being a little loose -const unambiguousNodeType = /^(?:(?:Exp|Imp)ort.*Declaration|TSExportAssignment)$/ +const unambiguousNodeType = /^(?:(?:Exp|Imp)ort.*Declaration|TSExportAssignment)$/; /** * Given an AST, return true if the AST unambiguously represents a module. @@ -26,5 +26,5 @@ const unambiguousNodeType = /^(?:(?:Exp|Imp)ort.*Declaration|TSExportAssignment) * @return {Boolean} */ exports.isModule = function isUnambiguousModule(ast) { - return ast.body.some(node => unambiguousNodeType.test(node.type)) -} + return ast.body.some(node => unambiguousNodeType.test(node.type)); +};