Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: import-js/eslint-plugin-import
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 20c373c509ad33e339b96fc38b0daaef8c5f6e9a
Choose a base ref
...
head repository: import-js/eslint-plugin-import
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 6171a7aa996fdb12fc7abc88bbdcd32b301ad906
Choose a head ref

Commits on Oct 25, 2018

  1. [New] named: add commonjs option

    vikr01 authored and ljharb committed Oct 25, 2018

    Verified

    This commit was signed with the committer’s verified signature.
    lifeofguenter Günter Grodotzki
    Copy the full SHA
    54d86c8 View commit details

Commits on Oct 26, 2018

  1. [New] no-dynamic-require: add option esmodule

    See #700.
    vikr01 authored and ljharb committed Oct 26, 2018

    Verified

    This commit was signed with the committer’s verified signature.
    lifeofguenter Günter Grodotzki
    Copy the full SHA
    7163824 View commit details

Commits on Oct 24, 2019

  1. [Tests] no-cycle: Restructure test files

    soryy708 authored and ljharb committed Oct 24, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    429f3f6 View commit details

Commits on Jul 6, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b743a65 View commit details

Commits on Apr 13, 2021

  1. Verified

    This commit was signed with the committer’s verified signature.
    lifeofguenter Günter Grodotzki
    Copy the full SHA
    7aea664 View commit details

Commits on May 25, 2021

  1. [Fix] no-extraneous-dependencies: fix package name algorithm

    - resolve nested package.json problems (a/b/c import will check a, a/b and a/b/c)
    - resolve renamed dependencies: checks the import name and the resolve package name
    
    Fixes #2066. Fixes #2065. Fixes #2058. Fixes #2078.
    jeromeh authored and ljharb committed May 25, 2021
    Copy the full SHA
    da8d584 View commit details

Commits on May 26, 2021

  1. Copy the full SHA
    81b9d24 View commit details

Commits on May 27, 2021

  1. [meta] fix changelog entries

    ljharb committed May 27, 2021
    Copy the full SHA
    ec10721 View commit details

Commits on May 29, 2021

  1. Bump to v2.23.4

    ljharb committed May 29, 2021
    Copy the full SHA
    998c300 View commit details

Commits on Jun 2, 2021

  1. [New] no-namespace: Add ignore option

    Closes #1916.
    Closes #1903.
    Arkadii Berezkin authored and ljharb committed Jun 2, 2021
    1
    Copy the full SHA
    bba59c4 View commit details

Commits on Jun 4, 2021

  1. Copy the full SHA
    4079482 View commit details

Commits on Jun 14, 2021

  1. [Fix] no-extraneous-dependencies: add ESM intermediate package.json…

    … support
    
    Fixes #2120.
    jeromeh authored and ljharb committed Jun 14, 2021
    Copy the full SHA
    b3d8c0c View commit details

Commits on Jun 25, 2021

  1. Copy the full SHA
    1012eb9 View commit details

Commits on Jul 6, 2021

  1. [Fix] extensions/importType: fix isScoped treating @/abc as scope…

    …d module
    
    Fixes #2145
    Rafael Perello authored and ljharb committed Jul 6, 2021
    Copy the full SHA
    00d7bc8 View commit details

Commits on Jul 19, 2021

  1. Copy the full SHA
    95e6011 View commit details

Commits on Jul 21, 2021

  1. Copy the full SHA
    bfab4cc View commit details

Commits on Jul 22, 2021

  1. [Docs] order: improve the documentation for the `pathGroupsExcluded…

    …ImportTypes` option
    liby authored and ljharb committed Jul 22, 2021
    Copy the full SHA
    b236748 View commit details

Commits on Jul 26, 2021

  1. [Docs] named: update docs with module info and deprecation notice…

    … for `jsnext:main`
    MustafaHaddara authored and ljharb committed Jul 26, 2021
    Copy the full SHA
    9521284 View commit details
  2. Copy the full SHA
    96e4332 View commit details

Commits on Jul 27, 2021

  1. Copy the full SHA
    5478a40 View commit details
  2. Copy the full SHA
    8dd13e8 View commit details

Commits on Aug 1, 2021

  1. [Tests] eslint v7.32 added a new fatalErrorCount property to `cli.e…

    …xecuteOnFiles` result
    ljharb committed Aug 1, 2021
    Copy the full SHA
    e20db4e View commit details

Commits on Aug 3, 2021

  1. Copy the full SHA
    c8876b1 View commit details
  2. [eslint] fix eslint failures

    ljharb committed Aug 3, 2021
    Copy the full SHA
    3761435 View commit details

Commits on Aug 5, 2021

  1. Copy the full SHA
    7626a14 View commit details

Commits on Aug 7, 2021

  1. Copy the full SHA
    32bf645 View commit details

Commits on Aug 8, 2021

  1. [meta] update repo URLs

    ljharb committed Aug 8, 2021
    Copy the full SHA
    794e869 View commit details
  2. [resolvers/node] v0.3.5

    ljharb committed Aug 8, 2021
    Copy the full SHA
    74fa6c9 View commit details
  3. utils: v2.6.2

    ljharb committed Aug 8, 2021
    Copy the full SHA
    546f87f View commit details
  4. Bump to v2.24.0

    ljharb committed Aug 8, 2021
    Copy the full SHA
    202e5e0 View commit details

Commits on Aug 9, 2021

  1. [Fix] ExportMap: Add default export when esModuleInterop is true an…

    …d anything is exported
    
    Fixes #2183. See #1689.
    Maxim-Mazurok authored and ljharb committed Aug 9, 2021
    Copy the full SHA
    ce540b6 View commit details
  2. Copy the full SHA
    750ba25 View commit details

Commits on Aug 13, 2021

  1. [meta] add Open Collective

    ljharb committed Aug 13, 2021
    Copy the full SHA
    ec825d0 View commit details
  2. [meta] fix some formatting

    ljharb committed Aug 13, 2021
    Copy the full SHA
    513bb0b View commit details

Commits on Aug 14, 2021

  1. [resolvers/node] [fix] when "module" does not exist, fall back to "main"

    Fixes #2186. This is actually exposing a bug with packages that are broken - that ship an invalid "module" field - but it‘s a more friendly and node-accurate behavior to still work when "main" works.
    ljharb committed Aug 14, 2021
    Copy the full SHA
    fa3192a View commit details
  2. [meta] fix some formatting

    ljharb committed Aug 14, 2021
    Copy the full SHA
    f0c1756 View commit details

Commits on Aug 15, 2021

  1. [resolvers/node] v0.3.6

    ljharb committed Aug 15, 2021
    Copy the full SHA
    8be2ec2 View commit details

Commits on Aug 16, 2021

  1. Copy the full SHA
    7610790 View commit details

Commits on Aug 17, 2021

  1. Copy the full SHA
    ce8b203 View commit details
  2. Copy the full SHA
    b2bf591 View commit details
  3. [fix] no-duplicates: correctly handle case of mixed default/named t…

    …ype imports
    
    Co-authored-by: Nathan Walters <nwalters@nerdwallet.com>
    Co-authored-by: Gord Pearson <gord.pearson@shopify.com>
    2 people authored and ljharb committed Aug 17, 2021
    Copy the full SHA
    712ee49 View commit details

Commits on Aug 18, 2021

  1. Copy the full SHA
    3ff4d77 View commit details
  2. Copy the full SHA
    3977c42 View commit details

Commits on Aug 19, 2021

  1. [Deps] update eslint-import-resolver-node, is-core-module, `objec…

    …t.values`, `tsconfig-paths`
    ljharb committed Aug 19, 2021
    Copy the full SHA
    c5d208d View commit details
  2. [Dev Deps] update coveralls

    ljharb committed Aug 19, 2021
    Copy the full SHA
    62cb4b5 View commit details
  3. Copy the full SHA
    4dc4651 View commit details
  4. Bump to v2.24.1

    ljharb committed Aug 19, 2021
    Copy the full SHA
    6171a7a View commit details
Showing with 2,836 additions and 1,507 deletions.
  1. +3 −3 .babelrc
  2. +1 −1 .eslintrc
  3. +1 −1 .github/FUNDING.yml
  4. +1 −1 .github/workflows/node-4+.yml
  5. +1 −1 .github/workflows/packages.yml
  6. +559 −421 CHANGELOG.md
  7. +6 −4 README.md
  8. +5 −5 RELEASE.md
  9. +5 −3 docs/rules/extensions.md
  10. +1 −1 docs/rules/first.md
  11. +1 −1 docs/rules/imports-first.md
  12. +30 −8 docs/rules/max-dependencies.md
  13. +5 −3 docs/rules/named.md
  14. +1 −1 docs/rules/no-cycle.md
  15. +1 −1 docs/rules/no-duplicates.md
  16. +11 −0 docs/rules/no-namespace.md
  17. +1 −1 docs/rules/no-unresolved.md
  18. +1 −1 docs/rules/no-unused-modules.md
  19. +1 −1 docs/rules/no-useless-path-segments.md
  20. +21 −0 docs/rules/order.md
  21. +3 −3 memo-parser/package.json
  22. +11 −10 package.json
  23. +17 −7 resolvers/node/CHANGELOG.md
  24. +26 −7 resolvers/node/index.js
  25. +4 −4 resolvers/node/package.json
  26. +4 −0 resolvers/node/test/package-mains/jsnext/package.json
  27. 0 resolvers/node/test/package-mains/jsnext/src/index.js
  28. +5 −0 resolvers/node/test/package-mains/module-and-jsnext/package.json
  29. 0 resolvers/node/test/package-mains/module-and-jsnext/src/index.js
  30. +1 −0 resolvers/node/test/package-mains/module-broken/main.js
  31. +4 −0 resolvers/node/test/package-mains/module-broken/package.json
  32. +4 −0 resolvers/node/test/package-mains/module/package.json
  33. 0 resolvers/node/test/package-mains/module/src/index.js
  34. +1 −0 resolvers/node/test/package-mains/package.json
  35. +32 −0 resolvers/node/test/packageMains.js
  36. +35 −35 resolvers/webpack/CHANGELOG.md
  37. +14 −15 resolvers/webpack/index.js
  38. +5 −5 resolvers/webpack/package.json
  39. +5 −0 resolvers/webpack/test/package-mains/module-and-jsnext/package.json
  40. 0 resolvers/webpack/test/package-mains/module-and-jsnext/src/index.js
  41. +1 −0 resolvers/webpack/test/package-mains/module-broken/main.js
  42. +4 −0 resolvers/webpack/test/package-mains/module-broken/package.json
  43. +18 −9 resolvers/webpack/test/packageMains.js
  44. +53 −35 src/ExportMap.js
  45. +1 −1 src/core/importType.js
  46. +7 −3 src/core/packagePath.js
  47. +1 −1 src/docsUrl.js
  48. +1 −1 src/rules/dynamic-import-chunkname.js
  49. +2 −2 src/rules/extensions.js
  50. +1 −1 src/rules/first.js
  51. +12 −6 src/rules/max-dependencies.js
  52. +90 −24 src/rules/named.js
  53. +6 −6 src/rules/namespace.js
  54. +3 −3 src/rules/newline-after-import.js
  55. +1 −1 src/rules/no-cycle.js
  56. +30 −7 src/rules/no-duplicates.js
  57. +30 −3 src/rules/no-dynamic-require.js
  58. +70 −24 src/rules/no-extraneous-dependencies.js
  59. +9 −3 src/rules/no-import-module-exports.js
  60. +24 −7 src/rules/no-namespace.js
  61. +1 −1 src/rules/no-relative-packages.js
  62. +1 −1 src/rules/no-relative-parent-imports.js
  63. +1 −1 src/rules/no-restricted-paths.js
  64. +1 −1 src/rules/no-self-import.js
  65. +1 −1 src/rules/no-unassigned-import.js
  66. +16 −15 src/rules/no-unresolved.js
  67. +2 −2 src/rules/no-unused-modules.js
  68. +1 −1 src/rules/no-useless-path-segments.js
  69. +2 −4 src/rules/order.js
  70. +1 −2 src/rules/prefer-default-export.js
  71. +9 −0 tests/.eslintrc
  72. +0 −8 tests/.eslintrc.yml
  73. +0 −2 tests/files/cycles/depth-one.js
  74. +2 −0 tests/files/cycles/es6/depth-one.js
  75. 0 tests/files/cycles/{ → es6}/depth-three-indirect.js
  76. 0 tests/files/cycles/{ → es6}/depth-three-star.js
  77. 0 tests/files/cycles/{ → es6}/depth-two.js
  78. +1 −1 tests/files/cycles/flow-types-depth-two.js
  79. +1 −0 tests/files/export-star-2/middle.js
  80. +1 −0 tests/files/export-star-2/upstream.js
  81. +1 −0 tests/files/export-star/extfield.js
  82. +1 −0 tests/files/export-star/extfield2.js
  83. +2 −0 tests/files/export-star/models.js
  84. +2 −4 tests/files/foo-bar-resolver-no-version.js
  85. +6 −8 tests/files/foo-bar-resolver-v1.js
  86. +4 −6 tests/files/foo-bar-resolver-v2.js
  87. +3 −0 tests/files/missing-entrypoint/package.json
  88. 0 tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/index.js
  89. +4 −0 tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/package.json
  90. 0 tests/files/node_modules/esm-package-not-in-pkg-json/index.js
  91. +5 −0 tests/files/node_modules/esm-package-not-in-pkg-json/package.json
  92. 0 tests/files/node_modules/esm-package/esm-module/index.js
  93. +4 −0 tests/files/node_modules/esm-package/esm-module/package.json
  94. 0 tests/files/node_modules/esm-package/index.js
  95. +5 −0 tests/files/node_modules/esm-package/package.json
  96. +1 −0 tests/files/node_modules/rxjs/index.js
  97. +1 −0 tests/files/node_modules/rxjs/operators/index.js
  98. +5 −0 tests/files/node_modules/rxjs/operators/package.json
  99. +5 −0 tests/files/node_modules/rxjs/package.json
  100. +3 −1 tests/files/package.json
  101. +19 −0 tests/files/typescript-export-react-test-renderer/index.d.ts
  102. +5 −0 tests/files/typescript-export-react-test-renderer/tsconfig.json
  103. +3 −3 tests/files/typescript.ts
  104. +2 −1 tests/files/webpack.config.js
  105. +9 −0 tests/src/cli.js
  106. +1 −1 tests/src/config/typescript.js
  107. +2 −2 tests/src/core/docsUrl.js
  108. +12 −5 tests/src/core/getExports.js
  109. +10 −1 tests/src/core/importType.js
  110. +154 −0 tests/src/core/resolve.js
  111. +11 −0 tests/src/rules/default.js
  112. +10 −0 tests/src/rules/extensions.js
  113. +74 −69 tests/src/rules/first.js
  114. +56 −1 tests/src/rules/max-dependencies.js
  115. +69 −2 tests/src/rules/named.js
  116. +13 −1 tests/src/rules/namespace.js
  117. +1 −1 tests/src/rules/newline-after-import.js
  118. +1 −1 tests/src/rules/no-amd.js
  119. +1 −1 tests/src/rules/no-commonjs.js
  120. +113 −103 tests/src/rules/no-cycle.js
  121. +93 −22 tests/src/rules/no-duplicates.js
  122. +84 −0 tests/src/rules/no-dynamic-require.js
  123. +21 −1 tests/src/rules/no-extraneous-dependencies.js
  124. +7 −0 tests/src/rules/no-import-module-exports.js
  125. +1 −0 tests/src/rules/no-namespace.js
  126. +3 −3 tests/src/rules/no-unresolved.js
  127. +366 −187 tests/src/rules/no-unused-modules.js
  128. +1 −1 tests/src/rules/no-useless-path-segments.js
  129. +333 −274 tests/src/rules/order.js
  130. +29 −47 tests/src/rules/prefer-default-export.js
  131. +3 −0 tests/src/utils.js
  132. +5 −0 utils/.eslintrc
  133. +32 −25 utils/CHANGELOG.md
  134. +1 −1 utils/moduleVisitor.js
  135. +4 −4 utils/package.json
  136. +2 −6 utils/parse.js
  137. +1 −4 utils/resolve.js
6 changes: 3 additions & 3 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"presets": [ "es2015-argon" ],
"presets": ["airbnb"],
"sourceMaps": "inline",
"retainLines": true,
"env": {
"test": {
"plugins": [
"istanbul",
[ "module-resolver", { "root": [ "./src/" ] } ]
["module-resolver", { "root": ["./src/"] }],
]
},
"testCompiled": {
"plugins": [
[ "module-resolver", { "root": [ "./lib/" ] } ]
["module-resolver", { "root": ["./lib/"] }],
]
}
}
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@
},
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 6,
"ecmaVersion": 2020,
},
"rules": {
"comma-dangle": [2, "always-multiline"],
2 changes: 1 addition & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

github: [ljharb]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
open_collective: eslint-plugin-import # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: npm/eslint-plugin-import
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
2 changes: 1 addition & 1 deletion .github/workflows/node-4+.yml
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ jobs:
with:
versionsAsRoot: true
type: majors
preset: '>= 6' # preset: '>=4' # see https://github.com/benmosher/eslint-plugin-import/issues/2053
preset: '>= 6' # preset: '>=4' # see https://github.com/import-js/eslint-plugin-import/issues/2053

latest:
needs: [matrix]
2 changes: 1 addition & 1 deletion .github/workflows/packages.yml
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ jobs:
id: set-matrix
with:
type: 'majors'
preset: '>= 6' # preset: '>=4' # see https://github.com/benmosher/eslint-plugin-import/issues/2053
preset: '>= 6' # preset: '>=4' # see https://github.com/import-js/eslint-plugin-import/issues/2053
versionsAsRoot: true

tests:
980 changes: 559 additions & 421 deletions CHANGELOG.md

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# eslint-plugin-import

[![build status](https://travis-ci.org/benmosher/eslint-plugin-import.svg?branch=master)](https://travis-ci.org/benmosher/eslint-plugin-import)
[![Coverage Status](https://coveralls.io/repos/github/benmosher/eslint-plugin-import/badge.svg?branch=master)](https://coveralls.io/github/benmosher/eslint-plugin-import?branch=master)
[![win32 build status](https://ci.appveyor.com/api/projects/status/3mw2fifalmjlqf56/branch/master?svg=true)](https://ci.appveyor.com/project/benmosher/eslint-plugin-import/branch/master)
[![build status](https://travis-ci.org/import-js/eslint-plugin-import.svg?branch=master)](https://travis-ci.org/import-js/eslint-plugin-import)
[![Coverage Status](https://coveralls.io/repos/github/import-js/eslint-plugin-import/badge.svg?branch=master)](https://coveralls.io/github/import-js/eslint-plugin-import?branch=master)
[![win32 build status](https://ci.appveyor.com/api/projects/status/3mw2fifalmjlqf56/branch/master?svg=true)](https://ci.appveyor.com/project/import-js/eslint-plugin-import/branch/master)
[![npm](https://img.shields.io/npm/v/eslint-plugin-import.svg)](https://www.npmjs.com/package/eslint-plugin-import)
[![npm downloads](https://img.shields.io/npm/dt/eslint-plugin-import.svg?maxAge=2592000)](http://www.npmtrends.com/eslint-plugin-import)

@@ -190,7 +190,7 @@ runtime (allowing some modules to be included more traditionally via script tags
In the interest of supporting both of these, v0.11 introduces resolvers.

Currently [Node] and [webpack] resolution have been implemented, but the
resolvers are just npm packages, so [third party packages are supported](https://github.com/benmosher/eslint-plugin-import/wiki/Resolvers) (and encouraged!).
resolvers are just npm packages, so [third party packages are supported](https://github.com/import-js/eslint-plugin-import/wiki/Resolvers) (and encouraged!).

You can reference resolvers in several ways (in order of precedence):

@@ -348,6 +348,8 @@ An array of folders. Resolved modules only from those folders will be considered

This option is also useful in a monorepo setup: list here all directories that contain monorepo's packages and they will be treated as external ones no matter which resolver is used.

If you are using `yarn` PnP as your package manager, add the `.yarn` folder and all your installed dependencies will be considered as `external`, instead of `internal`.

Each item in this array is either a folder's name, its subpath, or its absolute prefix path:

- `jspm_modules` will match any file or folder named `jspm_modules` or which has a direct or non-direct parent named `jspm_modules`, e.g. `/home/me/project/jspm_modules` or `/home/me/project/jspm_modules/some-pkg/index.js`.
10 changes: 5 additions & 5 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -13,16 +13,16 @@
at last version's tag.

```markdown
[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.1...HEAD
[2.0.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.0...v2.0.1
[Unreleased]: https://github.com/import-js/eslint-plugin-import/compare/v2.0.1...HEAD
[2.0.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.0.0...v2.0.1
```

becomes

```markdown
[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.1.0...HEAD
[2.1.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.1...v2.1.0
[2.0.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.0...v2.0.1
[Unreleased]: https://github.com/import-js/eslint-plugin-import/compare/v2.1.0...HEAD
[2.1.0]: https://github.com/import-js/eslint-plugin-import/compare/v2.0.1...v2.1.0
[2.0.1]: https://github.com/import-js/eslint-plugin-import/compare/v2.0.0...v2.0.1
```

Generally, don't use `npm version` for this because it creates a tag, which I normally
8 changes: 5 additions & 3 deletions docs/rules/extensions.md
Original file line number Diff line number Diff line change
@@ -110,7 +110,7 @@ import bar from './bar';

import Component from './Component';

import express from 'express';
import foo from '@/foo';
```

The following patterns are not considered problems when configuration set to "always":
@@ -122,9 +122,9 @@ import bar from './bar.json';

import Component from './Component.jsx';

import express from 'express/index.js';

import * as path from 'path';

import foo from '@/foo.js';
```

The following patterns are considered problems when configuration set to "ignorePackages":
@@ -149,6 +149,7 @@ import Component from './Component.jsx';

import express from 'express';

import foo from '@/foo'
```

The following patterns are not considered problems when configuration set to `['error', 'always', {ignorePackages: true} ]`:
@@ -160,6 +161,7 @@ import baz from 'foo/baz.js';

import express from 'express';

import foo from '@/foo';
```

## When Not To Use It
2 changes: 1 addition & 1 deletion docs/rules/first.md
Original file line number Diff line number Diff line change
@@ -67,4 +67,4 @@ enable this rule.
- Issue [#255]

[`import/order`]: ./order.md
[#255]: https://github.com/benmosher/eslint-plugin-import/issues/255
[#255]: https://github.com/import-js/eslint-plugin-import/issues/255
2 changes: 1 addition & 1 deletion docs/rules/imports-first.md
Original file line number Diff line number Diff line change
@@ -1,3 +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).
This rule was **deprecated** in eslint-plugin-import v2.0.0. Please use the corresponding rule [`first`](https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/first.md).
38 changes: 30 additions & 8 deletions docs/rules/max-dependencies.md
Original file line number Diff line number Diff line change
@@ -6,20 +6,20 @@ This is a useful rule because a module with too many dependencies is a code smel

Importing multiple named exports from a single module will only count once (e.g. `import {x, y, z} from './foo'` will only count as a single dependency).

### Options
## Options

This rule takes the following option:

`max`: The maximum number of dependencies allowed. Anything over will trigger the rule. **Default is 10** if the rule is enabled and no `max` is specified.

You can set the option like this:
This rule has the following options, with these defaults:

```js
"import/max-dependencies": ["error", {"max": 10}]
"import/max-dependencies": ["error", {
"max": 10,
"ignoreTypeImports": false,
}]
```

### `max`

## Example
This option sets the maximum number of dependencies allowed. Anything over will trigger the rule. **Default is 10** if the rule is enabled and no `max` is specified.

Given a max value of `{"max": 2}`:

@@ -39,6 +39,28 @@ const anotherA = require('./a'); // still 1
import {x, y, z} from './foo'; // 2
```

### `ignoreTypeImports`

Ignores `type` imports. Type imports are a feature released in TypeScript 3.8, you can [read more here](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export). Defaults to `false`.

Given `{"max": 2, "ignoreTypeImports": true}`:

### Fail

```ts
import a from './a';
import b from './b';
import c from './c';
```

### Pass

```ts
import a from './a';
import b from './b';
import type c from './c'; // Doesn't count against max
```

## When Not To Use It

If you don't care how many dependencies a module has.
8 changes: 5 additions & 3 deletions docs/rules/named.md
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ Verifies that all named imports are part of the set of named exports in the refe
For `export`, verifies that all named exports exist in the referenced module.

Note: for packages, the plugin will find exported names
from [`jsnext:main`], if present in `package.json`.
from [`jsnext:main`] (deprecated) or `module`, if present in `package.json`.
Redux's npm module includes this key, and thereby is lintable, for example.

A module path that is [ignored] or not [unambiguously an ES module] will not be reported when imported. Note that type imports and exports, as used by [Flow], are always ignored.
@@ -91,8 +91,10 @@ runtime, you will likely see false positives with this rule.
## Further Reading

- [`import/ignore`] setting
- [`jsnext:main`] (Rollup)
- [`jsnext:main`] deprecation
- [`pkg.module`] (Rollup)


[`jsnext:main`]: https://github.com/rollup/rollup/wiki/jsnext:main
[`jsnext:main`]: https://github.com/jsforum/jsforum/issues/5
[`pkg.module`]: https://github.com/rollup/rollup/wiki/pkg.module
[`import/ignore`]: ../../README.md#importignore
2 changes: 1 addition & 1 deletion docs/rules/no-cycle.md
Original file line number Diff line number Diff line change
@@ -83,7 +83,7 @@ this rule enabled.

## Further Reading

- [Original inspiring issue](https://github.com/benmosher/eslint-plugin-import/issues/941)
- [Original inspiring issue](https://github.com/import-js/eslint-plugin-import/issues/941)
- Rule to detect that module imports itself: [`no-self-import`]
- [`import/external-module-folders`] setting

2 changes: 1 addition & 1 deletion docs/rules/no-duplicates.md
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ ESLint core has a similar rule ([`no-duplicate-imports`](http://eslint.org/docs/
is different in two key ways:

1. the paths in the source code don't have to exactly match, they just have to point to the same module on the filesystem. (i.e. `./foo` and `./foo.js`)
2. this version distinguishes Flow `type` imports from standard imports. ([#334](https://github.com/benmosher/eslint-plugin-import/pull/334))
2. this version distinguishes Flow `type` imports from standard imports. ([#334](https://github.com/import-js/eslint-plugin-import/pull/334))

## Rule Details

11 changes: 11 additions & 0 deletions docs/rules/no-namespace.md
Original file line number Diff line number Diff line change
@@ -5,6 +5,12 @@ Enforce a convention of not using namespace (a.k.a. "wildcard" `*`) imports.
+(fixable) The `--fix` option on the [command line] automatically fixes problems reported by this rule, provided that the namespace object is only used for direct member access, e.g. `namespace.a`.
The `--fix` functionality for this rule requires ESLint 5 or newer.

### Options

This rule supports the following options:

- `ignore`: array of glob strings for modules that should be ignored by the rule.

## Rule Details

Valid:
@@ -15,6 +21,11 @@ import { a, b } from './bar'
import defaultExport, { a, b } from './foobar'
```

```js
/* eslint import/no-namespace: ["error", {ignore: ['*.ext']] */
import * as bar from './ignored-module.ext';
```

Invalid:

```js
2 changes: 1 addition & 1 deletion docs/rules/no-unresolved.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ as defined by standard Node `require.resolve` behavior.
See [settings](../../README.md#settings) for customization options for the resolution (i.e.
additional filetypes, `NODE_PATH`, etc.)

This rule can also optionally report on unresolved modules in CommonJS `require('./foo')` calls and AMD `require(['./foo'], function (foo){...})` and `define(['./foo'], function (foo){...})`.
This rule can also optionally report on unresolved modules in CommonJS `require('./foo')` calls and AMD `require(['./foo'], function (foo) {...})` and `define(['./foo'], function (foo) {...})`.

To enable this, send `{ commonjs: true/false, amd: true/false }` as a rule option.
Both are disabled by default.
2 changes: 1 addition & 1 deletion docs/rules/no-unused-modules.md
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ Note: dynamic imports are currently not supported.

### Usage

In order for this plugin to work, one of the options `missingExports` or `unusedExports` must be enabled (see "Options" section below). In the future, these options will be enabled by default (see https://github.com/benmosher/eslint-plugin-import/issues/1324)
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/import-js/eslint-plugin-import/issues/1324)

Example:
```
2 changes: 1 addition & 1 deletion docs/rules/no-useless-path-segments.md
Original file line number Diff line number Diff line change
@@ -72,7 +72,7 @@ import "./pages/index"; // should be "./pages" (auto-fixable)
import "./pages/index.js"; // should be "./pages" (auto-fixable)
```

Note: `noUselessIndex` only avoids ambiguous imports for `.js` files if you haven't specified other resolved file extensions. See [Settings: import/extensions](https://github.com/benmosher/eslint-plugin-import#importextensions) for details.
Note: `noUselessIndex` only avoids ambiguous imports for `.js` files if you haven't specified other resolved file extensions. See [Settings: import/extensions](https://github.com/import-js/eslint-plugin-import#importextensions) for details.

### commonjs

21 changes: 21 additions & 0 deletions docs/rules/order.md
Original file line number Diff line number Diff line change
@@ -148,6 +148,27 @@ Example:
}]
}
```

You can also use `patterns`(e.g., `react`, `react-router-dom`, etc).

Example:
```json
{
"import/order": [
"error",
{
"pathGroups": [
{
"pattern": "react",
"group": "builtin",
"position": "before"
}
],
"pathGroupsExcludedImportTypes": ["react"]
}
]
}
```
The default value is `["builtin", "external"]`.

### `newlines-between: [ignore|always|always-and-inside-groups|never]`:
6 changes: 3 additions & 3 deletions memo-parser/package.json
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/benmosher/eslint-plugin-import.git"
"url": "git+https://github.com/import-js/eslint-plugin-import.git"
},
"keywords": [
"eslint",
@@ -22,9 +22,9 @@
"author": "Ben Mosher (me@benmosher.com)",
"license": "MIT",
"bugs": {
"url": "https://github.com/benmosher/eslint-plugin-import/issues"
"url": "https://github.com/import-js/eslint-plugin-import/issues"
},
"homepage": "https://github.com/benmosher/eslint-plugin-import#readme",
"homepage": "https://github.com/import-js/eslint-plugin-import#readme",
"peerDependencies": {
"eslint": ">=3.5.0"
},
21 changes: 11 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-import",
"version": "2.23.3",
"version": "2.24.1",
"description": "Import with sanity.",
"engines": {
"node": ">=4"
@@ -36,7 +36,7 @@
},
"repository": {
"type": "git",
"url": "https://github.com/benmosher/eslint-plugin-import"
"url": "https://github.com/import-js/eslint-plugin-import"
},
"keywords": [
"eslint",
@@ -50,9 +50,9 @@
"author": "Ben Mosher <me@benmosher.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/benmosher/eslint-plugin-import/issues"
"url": "https://github.com/import-js/eslint-plugin-import/issues"
},
"homepage": "https://github.com/benmosher/eslint-plugin-import",
"homepage": "https://github.com/import-js/eslint-plugin-import",
"devDependencies": {
"@eslint/import-test-order-redirect-scoped": "file:./tests/files/order-redirect-scoped",
"@test-scope/some-module": "file:./tests/files/symlinked-module",
@@ -63,12 +63,13 @@
"babel-eslint": "=8.0.3 || ^8.2.6",
"babel-plugin-istanbul": "^4.1.6",
"babel-plugin-module-resolver": "^2.7.1",
"babel-preset-airbnb": "^2.6.0",
"babel-preset-es2015-argon": "latest",
"babel-preset-flow": "^6.23.0",
"babel-register": "^6.26.0",
"babylon": "^6.18.0",
"chai": "^4.3.4",
"coveralls": "^3.1.0",
"coveralls": "^3.1.1",
"cross-env": "^4.0.0",
"eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0",
"eslint-import-resolver-node": "file:./resolvers/node",
@@ -103,16 +104,16 @@
"array.prototype.flat": "^1.2.4",
"debug": "^2.6.9",
"doctrine": "^2.1.0",
"eslint-import-resolver-node": "^0.3.4",
"eslint-module-utils": "^2.6.1",
"eslint-import-resolver-node": "^0.3.6",
"eslint-module-utils": "^2.6.2",
"find-up": "^2.0.0",
"has": "^1.0.3",
"is-core-module": "^2.4.0",
"is-core-module": "^2.6.0",
"minimatch": "^3.0.4",
"object.values": "^1.1.3",
"object.values": "^1.1.4",
"pkg-up": "^2.0.0",
"read-pkg-up": "^3.0.0",
"resolve": "^1.20.0",
"tsconfig-paths": "^3.9.0"
"tsconfig-paths": "^3.10.1"
}
}
24 changes: 17 additions & 7 deletions resolvers/node/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -5,6 +5,14 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel

## Unreleased

## v0.3.6 - 2021-08-15
### Fixed
- when "module" does not exist, fall back to "main" ([#2186], thanks [@ljharb])

## v0.3.5 - 2021-08-08
### Added
- use "module" in the same spot as "jsnext:main" ([#2166], thanks [@MustafaHaddara])

## v0.3.4 - 2020-06-16
### Added
- add `.node` extension ([#1663])
@@ -48,16 +56,18 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
### Fixed
- find files with `.json` extensions (#333, thanks for noticing @jfmengels)

[#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
[#2186]: https://github.com/import-js/eslint-plugin-import/issues/2186
[#2166]: https://github.com/import-js/eslint-plugin-import/pull/2166
[#1663]: https://github.com/import-js/eslint-plugin-import/issues/1663
[#1595]: https://github.com/import-js/eslint-plugin-import/pull/1595
[#939]: https://github.com/import-js/eslint-plugin-import/issues/939
[#531]: https://github.com/import-js/eslint-plugin-import/issues/531
[#438]: https://github.com/import-js/eslint-plugin-import/pull/438
[#437]: https://github.com/import-js/eslint-plugin-import/issues/437

[@jasonkarns]: https://github.com/jasonkarns
[@lukeapage]: https://github.com/lukeapage
[@SkeLLLa]: https://github.com/SkeLLLa
[@ljharb]: https://github.com/ljharb
[@opichals]: https://github.com/opichals
[@MustafaHaddara]: https://github.com/MustafaHaddara
33 changes: 26 additions & 7 deletions resolvers/node/index.js
Original file line number Diff line number Diff line change
@@ -17,7 +17,8 @@ exports.resolve = function (source, file, config) {
}

try {
resolvedPath = resolve.sync(source, opts(file, config));
const cachedFilter = function (pkg, dir) { return packageFilter(pkg, dir, config); };
resolvedPath = resolve.sync(source, opts(file, config, cachedFilter));
log('Resolved to:', resolvedPath);
return { found: true, path: resolvedPath };
} catch (err) {
@@ -26,7 +27,7 @@ exports.resolve = function (source, file, config) {
}
};

function opts(file, config) {
function opts(file, config, packageFilter) {
return Object.assign({
// more closely matches Node (#333)
// plus 'mjs' for native modules! (#939)
@@ -36,14 +37,32 @@ function opts(file, config) {
{
// path.resolve will handle paths relative to CWD
basedir: path.dirname(path.resolve(file)),
packageFilter: packageFilter,

packageFilter,
});
}

function packageFilter(pkg) {
if (pkg['jsnext:main']) {
pkg['main'] = pkg['jsnext:main'];
function identity(x) { return x; }

function packageFilter(pkg, dir, config) {
let found = false;
const file = path.join(dir, 'dummy.js');
if (pkg.module) {
try {
resolve.sync(String(pkg.module).replace(/^(?:\.\/)?/, './'), opts(file, config, identity));
pkg.main = pkg.module;
found = true;
} catch (err) {
log('resolve threw error trying to find pkg.module:', err);
}
}
if (!found && pkg['jsnext:main']) {
try {
resolve.sync(String(pkg['jsnext:main']).replace(/^(?:\.\/)?/, './'), opts(file, config, identity));
pkg.main = pkg['jsnext:main'];
found = true;
} catch (err) {
log('resolve threw error trying to find pkg[\'jsnext:main\']:', err);
}
}
return pkg;
}
8 changes: 4 additions & 4 deletions resolvers/node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-import-resolver-node",
"version": "0.3.4",
"version": "0.3.6",
"description": "Node default behavior import resolution plugin for eslint-plugin-import.",
"main": "index.js",
"files": [
@@ -14,7 +14,7 @@
},
"repository": {
"type": "git",
"url": "https://github.com/benmosher/eslint-plugin-import"
"url": "https://github.com/import-js/eslint-plugin-import"
},
"keywords": [
"eslint",
@@ -26,9 +26,9 @@
"author": "Ben Mosher (me@benmosher.com)",
"license": "MIT",
"bugs": {
"url": "https://github.com/benmosher/eslint-plugin-import/issues"
"url": "https://github.com/import-js/eslint-plugin-import/issues"
},
"homepage": "https://github.com/benmosher/eslint-plugin-import",
"homepage": "https://github.com/import-js/eslint-plugin-import",
"dependencies": {
"debug": "^3.2.7",
"resolve": "^1.20.0"
4 changes: 4 additions & 0 deletions resolvers/node/test/package-mains/jsnext/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "lib/index.js",
"jsnext:main": "src/index.js"
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"main": "lib/index.js",
"module": "src/index.js",
"jsnext:main": "lib/index.js"
}
Empty file.
1 change: 1 addition & 0 deletions resolvers/node/test/package-mains/module-broken/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {};
4 changes: 4 additions & 0 deletions resolvers/node/test/package-mains/module-broken/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "./main.js",
"module": "./doesNotExist.js"
}
4 changes: 4 additions & 0 deletions resolvers/node/test/package-mains/module/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "lib/index.js",
"module": "src/index.js"
}
Empty file.
1 change: 1 addition & 0 deletions resolvers/node/test/package-mains/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "dummy": true }
32 changes: 32 additions & 0 deletions resolvers/node/test/packageMains.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

const chai = require('chai');
const expect = chai.expect;
const path = require('path');

const resolver = require('../');

const file = path.join(__dirname, 'package-mains', 'dummy.js');


describe('packageMains', function () {
it('captures module', function () {
expect(resolver.resolve('./module', file)).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js'));
});

it('captures jsnext', function () {
expect(resolver.resolve('./jsnext', file)).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js'));
});

it('captures module instead of jsnext', function () {
expect(resolver.resolve('./module-and-jsnext', file)).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'module-and-jsnext', 'src', 'index.js'));
});

it('falls back from a missing "module" to "main"', function () {
expect(resolver.resolve('./module-broken', file)).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'module-broken', 'main.js'));
});
});
70 changes: 35 additions & 35 deletions resolvers/webpack/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -150,41 +150,41 @@ 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
[#1220]: https://github.com/benmosher/eslint-plugin-import/pull/1220
[#1091]: https://github.com/benmosher/eslint-plugin-import/pull/1091
[#969]: https://github.com/benmosher/eslint-plugin-import/pull/969
[#968]: https://github.com/benmosher/eslint-plugin-import/pull/968
[#768]: https://github.com/benmosher/eslint-plugin-import/pull/768
[#683]: https://github.com/benmosher/eslint-plugin-import/pull/683
[#572]: https://github.com/benmosher/eslint-plugin-import/pull/572
[#569]: https://github.com/benmosher/eslint-plugin-import/pull/569
[#533]: https://github.com/benmosher/eslint-plugin-import/pull/533
[#413]: https://github.com/benmosher/eslint-plugin-import/pull/413
[#377]: https://github.com/benmosher/eslint-plugin-import/pull/377
[#363]: https://github.com/benmosher/eslint-plugin-import/pull/363
[#289]: https://github.com/benmosher/eslint-plugin-import/pull/289
[#287]: https://github.com/benmosher/eslint-plugin-import/pull/287
[#278]: https://github.com/benmosher/eslint-plugin-import/pull/278
[#181]: https://github.com/benmosher/eslint-plugin-import/pull/181
[#164]: https://github.com/benmosher/eslint-plugin-import/pull/164

[#1219]: https://github.com/benmosher/eslint-plugin-import/issues/1219
[#788]: https://github.com/benmosher/eslint-plugin-import/issues/788
[#767]: https://github.com/benmosher/eslint-plugin-import/issues/767
[#681]: https://github.com/benmosher/eslint-plugin-import/issues/681
[#435]: https://github.com/benmosher/eslint-plugin-import/issues/435
[#411]: https://github.com/benmosher/eslint-plugin-import/issues/411
[#357]: https://github.com/benmosher/eslint-plugin-import/issues/357
[#286]: https://github.com/benmosher/eslint-plugin-import/issues/286
[#283]: https://github.com/benmosher/eslint-plugin-import/issues/283
[#2023]: https://github.com/import-js/eslint-plugin-import/pull/2023
[#1967]: https://github.com/import-js/eslint-plugin-import/pull/1967
[#1962]: https://github.com/import-js/eslint-plugin-import/pull/1962
[#1705]: https://github.com/import-js/eslint-plugin-import/pull/1705
[#1595]: https://github.com/import-js/eslint-plugin-import/pull/1595
[#1503]: https://github.com/import-js/eslint-plugin-import/pull/1503
[#1297]: https://github.com/import-js/eslint-plugin-import/pull/1297
[#1261]: https://github.com/import-js/eslint-plugin-import/pull/1261
[#1220]: https://github.com/import-js/eslint-plugin-import/pull/1220
[#1091]: https://github.com/import-js/eslint-plugin-import/pull/1091
[#969]: https://github.com/import-js/eslint-plugin-import/pull/969
[#968]: https://github.com/import-js/eslint-plugin-import/pull/968
[#768]: https://github.com/import-js/eslint-plugin-import/pull/768
[#683]: https://github.com/import-js/eslint-plugin-import/pull/683
[#572]: https://github.com/import-js/eslint-plugin-import/pull/572
[#569]: https://github.com/import-js/eslint-plugin-import/pull/569
[#533]: https://github.com/import-js/eslint-plugin-import/pull/533
[#413]: https://github.com/import-js/eslint-plugin-import/pull/413
[#377]: https://github.com/import-js/eslint-plugin-import/pull/377
[#363]: https://github.com/import-js/eslint-plugin-import/pull/363
[#289]: https://github.com/import-js/eslint-plugin-import/pull/289
[#287]: https://github.com/import-js/eslint-plugin-import/pull/287
[#278]: https://github.com/import-js/eslint-plugin-import/pull/278
[#181]: https://github.com/import-js/eslint-plugin-import/pull/181
[#164]: https://github.com/import-js/eslint-plugin-import/pull/164

[#1219]: https://github.com/import-js/eslint-plugin-import/issues/1219
[#788]: https://github.com/import-js/eslint-plugin-import/issues/788
[#767]: https://github.com/import-js/eslint-plugin-import/issues/767
[#681]: https://github.com/import-js/eslint-plugin-import/issues/681
[#435]: https://github.com/import-js/eslint-plugin-import/issues/435
[#411]: https://github.com/import-js/eslint-plugin-import/issues/411
[#357]: https://github.com/import-js/eslint-plugin-import/issues/357
[#286]: https://github.com/import-js/eslint-plugin-import/issues/286
[#283]: https://github.com/import-js/eslint-plugin-import/issues/283

[@gausie]: https://github.com/gausie
[@jquense]: https://github.com/jquense
29 changes: 14 additions & 15 deletions resolvers/webpack/index.js
Original file line number Diff line number Diff line change
@@ -118,8 +118,7 @@ exports.resolve = function (source, file, settings) {

if (typeof configIndex !== 'undefined' && webpackConfig.length > configIndex) {
webpackConfig = webpackConfig[configIndex];
}
else {
} else {
webpackConfig = find(webpackConfig, function findFirstWithResolve(config) {
return !!config.resolve;
});
@@ -271,18 +270,18 @@ function createWebpack1ResolveSync(webpackRequire, resolveConfig, plugins) {
makeRootPlugin(ModulesInRootPlugin, 'module', resolveConfig.root),
new ModulesInDirectoriesPlugin(
'module',
resolveConfig.modulesDirectories || resolveConfig.modules || ['web_modules', 'node_modules']
resolveConfig.modulesDirectories || resolveConfig.modules || ['web_modules', 'node_modules'],
),
makeRootPlugin(ModulesInRootPlugin, 'module', resolveConfig.fallback),
new ModuleAsFilePlugin('module'),
new ModuleAsDirectoryPlugin('module'),
new DirectoryDescriptionFilePlugin(
'package.json',
['module', 'jsnext:main'].concat(resolveConfig.packageMains || webpack1DefaultMains)
['module', 'jsnext:main'].concat(resolveConfig.packageMains || webpack1DefaultMains),
),
new DirectoryDefaultFilePlugin(['index']),
new FileAppendPlugin(resolveConfig.extensions || ['', '.webpack.js', '.web.js', '.js']),
new ResultSymlinkPlugin()
new ResultSymlinkPlugin(),
);


@@ -311,16 +310,16 @@ function createWebpack1ResolveSync(webpackRequire, resolveConfig, plugins) {
/* eslint-disable */
// from https://github.com/webpack/webpack/blob/v1.13.0/lib/WebpackOptionsApply.js#L365
function makeRootPlugin(ModulesInRootPlugin, name, root) {
if(typeof root === "string")
if (typeof root === 'string') {
return new ModulesInRootPlugin(name, root);
else if(Array.isArray(root)) {
} else if (Array.isArray(root)) {
return function() {
root.forEach(function(root) {
root.forEach(function (root) {
this.apply(new ModulesInRootPlugin(name, root));
}, this);
};
}
return function() {};
return function () {};
}
/* eslint-enable */

@@ -422,7 +421,7 @@ function findConfigPath(configPath, packageDir) {
}

const maybePath = path.resolve(
path.join(packageDir, 'webpack.config' + maybeExtension)
path.join(packageDir, 'webpack.config' + maybeExtension),
);
if (fs.existsSync(maybePath)) {
configPath = maybePath;
@@ -436,17 +435,17 @@ function findConfigPath(configPath, packageDir) {
}

function registerCompiler(moduleDescriptor) {
if(moduleDescriptor) {
if(typeof moduleDescriptor === 'string') {
if (moduleDescriptor) {
if (typeof moduleDescriptor === 'string') {
require(moduleDescriptor);
} else if(!Array.isArray(moduleDescriptor)) {
} else if (!Array.isArray(moduleDescriptor)) {
moduleDescriptor.register(require(moduleDescriptor.module));
} else {
for(let i = 0; i < moduleDescriptor.length; i++) {
for (let i = 0; i < moduleDescriptor.length; i++) {
try {
registerCompiler(moduleDescriptor[i]);
break;
} catch(e) {
} catch (e) {
log('Failed to register compiler for moduleDescriptor[]:', i, moduleDescriptor);
}
}
10 changes: 5 additions & 5 deletions resolvers/webpack/package.json
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@
],
"repository": {
"type": "git",
"url": "git+https://github.com/benmosher/eslint-plugin-import.git"
"url": "git+https://github.com/import-js/eslint-plugin-import.git"
},
"keywords": [
"eslint-plugin-import",
@@ -28,18 +28,18 @@
"author": "Ben Mosher (me@benmosher.com)",
"license": "MIT",
"bugs": {
"url": "https://github.com/benmosher/eslint-plugin-import/issues"
"url": "https://github.com/import-js/eslint-plugin-import/issues"
},
"homepage": "https://github.com/benmosher/eslint-plugin-import/tree/master/resolvers/webpack",
"homepage": "https://github.com/import-js/eslint-plugin-import/tree/master/resolvers/webpack",
"dependencies": {
"array-find": "^1.0.0",
"debug": "^3.2.7",
"enhanced-resolve": "^0.9.1",
"find-root": "^1.1.0",
"has": "^1.0.3",
"interpret": "^1.4.0",
"is-core-module": "^2.4.0",
"is-regex": "^1.1.3",
"is-core-module": "^2.6.0",
"is-regex": "^1.1.4",
"lodash": "^4.17.21",
"resolve": "^1.20.0",
"semver": "^5.7.1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"main": "lib/index.js",
"module": "src/index.js",
"jsnext:main": "lib/index.js"
}
Empty file.
1 change: 1 addition & 0 deletions resolvers/webpack/test/package-mains/module-broken/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "./main.js",
"module": "./doesNotExist.js"
}
27 changes: 18 additions & 9 deletions resolvers/webpack/test/packageMains.js
Original file line number Diff line number Diff line change
@@ -4,46 +4,55 @@ const chai = require('chai');
const expect = chai.expect;
const path = require('path');

const webpack = require('../');
const resolver = require('../');

const file = path.join(__dirname, 'package-mains', 'dummy.js');


describe('packageMains', function () {

it('captures module', function () {
expect(webpack.resolve('./module', file)).property('path')
expect(resolver.resolve('./module', file)).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js'));
});

it('captures jsnext', function () {
expect(webpack.resolve('./jsnext', file)).property('path')
expect(resolver.resolve('./jsnext', file)).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js'));
});

it('captures module instead of jsnext', function () {
expect(resolver.resolve('./module-and-jsnext', file)).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'module-and-jsnext', 'src', 'index.js'));
});

it('falls back from a missing "module" to "main"', function () {
expect(resolver.resolve('./module-broken', file)).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'module-broken', 'main.js'));
});

it('captures webpack', function () {
expect(webpack.resolve('./webpack', file)).property('path')
expect(resolver.resolve('./webpack', file)).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'webpack', 'webpack.js'));
});

it('captures jam (array path)', function () {
expect(webpack.resolve('./jam', file)).property('path')
expect(resolver.resolve('./jam', file)).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'jam', 'jam.js'));
});

it('uses configured packageMains, if provided', function () {
expect(webpack.resolve('./webpack', file, { config: 'webpack.alt.config.js' })).property('path')
expect(resolver.resolve('./webpack', file, { config: 'webpack.alt.config.js' })).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'webpack', 'index.js'));
});

it('always defers to module, regardless of config', function () {
expect(webpack.resolve('./module', file, { config: 'webpack.alt.config.js' })).property('path')
expect(resolver.resolve('./module', file, { config: 'webpack.alt.config.js' })).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js'));
});

it('always defers to jsnext:main, regardless of config', function () {
expect(webpack.resolve('./jsnext', file, { config: 'webpack.alt.config.js' })).property('path')
expect(resolver.resolve('./jsnext', file, { config: 'webpack.alt.config.js' })).property('path')
.to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js'));
});

});
88 changes: 53 additions & 35 deletions src/ExportMap.js
Original file line number Diff line number Diff line change
@@ -351,6 +351,7 @@ ExportMap.for = function (context) {

ExportMap.parse = function (path, content, context) {
const m = new ExportMap(path);
const isEsModuleInteropTrue = isEsModuleInterop();

let ast;
try {
@@ -413,6 +414,39 @@ ExportMap.parse = function (path, content, context) {
return object;
}

function processSpecifier(s, n, m) {
const nsource = n.source && n.source.value;
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 'ExportAllDeclaration':
local = s.exported ? s.exported.name : s.local.name;
break;
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) });
}

function captureDependency({ source }, isOnlyImportingTypes, importedSpecifiers = new Set()) {
if (source == null) return null;

@@ -488,6 +522,9 @@ ExportMap.parse = function (path, content, context) {
if (n.type === 'ExportAllDeclaration') {
const getter = captureDependency(n, n.exportKind === 'type');
if (getter) m.dependencies.add(getter);
if (n.exported) {
processSpecifier(n, n.exported, m);
}
return;
}

@@ -545,39 +582,9 @@ ExportMap.parse = function (path, content, context) {
}
}

const nsource = n.source && n.source.value;
n.specifiers.forEach((s) => {
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;
}

// todo: JSDoc
m.reexports.set(s.exported.name, { local, getImport: () => resolveImport(nsource) });
});
n.specifiers.forEach((s) => processSpecifier(s, n, m));
}

const isEsModuleInteropTrue = isEsModuleInterop();

const exports = ['TSExportAssignment'];
if (isEsModuleInteropTrue) {
exports.push('TSNamespaceExportDeclaration');
@@ -606,8 +613,11 @@ ExportMap.parse = function (path, content, context) {
m.namespace.set('default', captureDoc(source, docStyleParsers, n));
return;
}
if (isEsModuleInteropTrue) {
m.namespace.set('default', {});
if (
isEsModuleInteropTrue // esModuleInterop is on in tsconfig
&& !m.namespace.has('default') // and default isn't added already
) {
m.namespace.set('default', {}); // add default export
}
exportedDecls.forEach((decl) => {
if (decl.type === 'TSModuleDeclaration') {
@@ -627,8 +637,8 @@ ExportMap.parse = function (path, content, context) {
namespaceDecl.declarations.forEach((d) =>
recursivePatternCapture(d.id, (id) => m.namespace.set(
id.name,
captureDoc(source, docStyleParsers, decl, namespaceDecl, moduleBlockNode)
))
captureDoc(source, docStyleParsers, decl, namespaceDecl, moduleBlockNode),
)),
);
} else {
m.namespace.set(
@@ -645,6 +655,14 @@ ExportMap.parse = function (path, content, context) {
}
});

if (
isEsModuleInteropTrue // esModuleInterop is on in tsconfig
&& m.namespace.size > 0 // anything is exported
&& !m.namespace.has('default') // and default isn't added already
) {
m.namespace.set('default', {}); // add default export
}

return m;
};

2 changes: 1 addition & 1 deletion src/core/importType.js
Original file line number Diff line number Diff line change
@@ -64,7 +64,7 @@ function isModuleMain(name) {
return name && moduleMainRegExp.test(name);
}

const scopedRegExp = /^@[^/]*\/?[^/]+/;
const scopedRegExp = /^@[^/]+\/?[^/]+/;
export function isScoped(name) {
return name && scopedRegExp.test(name);
}
10 changes: 7 additions & 3 deletions src/core/packagePath.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import readPkgUp from 'read-pkg-up';


export function getContextPackagePath(context) {
return getFilePackagePath(context.getFilename());
return getFilePackagePath(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename());
}

export function getFilePackagePath(filePath) {
@@ -13,6 +13,10 @@ export function getFilePackagePath(filePath) {
}

export function getFilePackageName(filePath) {
const { pkg } = readPkgUp.sync({ cwd: filePath, normalize: false });
return pkg && pkg.name;
const { pkg, path } = readPkgUp.sync({ cwd: filePath, normalize: false });
if (pkg) {
// recursion in case of intermediate esm package.json without name found
return pkg.name || getFilePackageName(dirname(dirname(path)));
}
return null;
}
2 changes: 1 addition & 1 deletion src/docsUrl.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pkg from '../package.json';

const repoUrl = 'https://github.com/benmosher/eslint-plugin-import';
const repoUrl = 'https://github.com/import-js/eslint-plugin-import';

export default function docsUrl(ruleName, commitish = `v${pkg.version}`) {
return `${repoUrl}/blob/${commitish}/docs/rules/${ruleName}.md`;
2 changes: 1 addition & 1 deletion src/rules/dynamic-import-chunkname.js
Original file line number Diff line number Diff line change
@@ -69,7 +69,7 @@ module.exports = {

try {
// just like webpack itself does
vm.runInNewContext(`(function(){return {${comment.value}}})()`);
vm.runInNewContext(`(function() {return {${comment.value}}})()`);
}
catch (error) {
context.report({
4 changes: 2 additions & 2 deletions src/rules/extensions.js
Original file line number Diff line number Diff line change
@@ -136,8 +136,8 @@ module.exports = {
}

function checkFileExtension(source) {
// bail if the declaration doesn't have a source, e.g. "export { foo };"
if (!source) return;
// bail if the declaration doesn't have a source, e.g. "export { foo };", or if it's only partially typed like in an editor
if (!source || !source.value) return;

const importPathWithQueryString = source.value;

2 changes: 1 addition & 1 deletion src/rules/first.js
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ module.exports = {
const errorInfos = [];
let shouldSort = true;
let lastSortNodesIndex = 0;
body.forEach(function (node, index){
body.forEach(function (node, index) {
if (!anyExpressions && isPossibleDirective(node)) {
return;
}
18 changes: 12 additions & 6 deletions src/rules/max-dependencies.js
Original file line number Diff line number Diff line change
@@ -2,15 +2,14 @@ import moduleVisitor from 'eslint-module-utils/moduleVisitor';
import docsUrl from '../docsUrl';

const DEFAULT_MAX = 10;
const DEFAULT_IGNORE_TYPE_IMPORTS = false;
const TYPE_IMPORT = 'type';

const countDependencies = (dependencies, lastNode, context) => {
const { max } = context.options[0] || { max: DEFAULT_MAX };

if (dependencies.size > max) {
context.report(
lastNode,
`Maximum number of dependencies (${max}) exceeded.`
);
context.report(lastNode, `Maximum number of dependencies (${max}) exceeded.`);
}
};

@@ -26,22 +25,29 @@ module.exports = {
'type': 'object',
'properties': {
'max': { 'type': 'number' },
'ignoreTypeImports': { 'type': 'boolean' },
},
'additionalProperties': false,
},
],
},

create: context => {
const {
ignoreTypeImports = DEFAULT_IGNORE_TYPE_IMPORTS,
} = context.options[0] || {};

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);
},
}, moduleVisitor((source) => {
dependencies.add(source.value);
}, moduleVisitor((source, { importKind }) => {
if (importKind !== TYPE_IMPORT || !ignoreTypeImports) {
dependencies.add(source.value);
}
lastNode = source;
}, { commonjs: true }));
},
114 changes: 90 additions & 24 deletions src/rules/named.js
Original file line number Diff line number Diff line change
@@ -8,65 +8,131 @@ module.exports = {
docs: {
url: docsUrl('named'),
},
schema: [],
schema: [
{
type: 'object',
properties: {
commonjs: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
},

create: function (context) {
const options = context.options[0] || {};

function checkSpecifiers(key, type, node) {
// ignore local exports and type imports/exports
if (node.source == null || node.importKind === 'type' ||
node.importKind === 'typeof' || node.exportKind === 'type') {
if (
node.source == null
|| node.importKind === 'type'
|| node.importKind === 'typeof'
|| node.exportKind === 'type'
) {
return;
}

if (!node.specifiers
.some(function (im) { return im.type === type; })) {
if (!node.specifiers.some((im) => im.type === type)) {
return; // no named imports/exports
}

const imports = Exports.get(node.source.value, context);
if (imports == null) return;
if (imports == null) {
return;
}

if (imports.errors.length) {
imports.reportErrors(context, node);
return;
}

node.specifiers.forEach(function (im) {
if (im.type !== type) return;

// ignore type imports
if (im.importKind === 'type' || im.importKind === 'typeof') return;
if (
im.type !== type
// ignore type imports
|| im.importKind === 'type' || im.importKind === 'typeof'
) {
return;
}

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.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()), i.path))
.join(' -> ');

context.report(im[key], `${im[key].name} not found via ${deepPath}`);
} else {
context.report(im[key], im[key].name + ' not found in \'' + node.source.value + '\'');
}
}
});
}

function checkRequire(node) {
if (
!options.commonjs
|| node.type !== 'VariableDeclarator'
// return if it's not an object destructure or it's an empty object destructure
|| !node.id || node.id.type !== 'ObjectPattern' || node.id.properties.length === 0
// return if there is no call expression on the right side
|| !node.init || node.init.type !== 'CallExpression'
) {
return;
}

const call = node.init;
const [source] = call.arguments;
const variableImports = node.id.properties;
const variableExports = Exports.get(source.value, context);

if (
// return if it's not a commonjs require statement
call.callee.type !== 'Identifier' || call.callee.name !== 'require' || call.arguments.length !== 1
// return if it's not a string source
|| source.type !== 'Literal'
|| variableExports == null
) {
return;
}

if (variableExports.errors.length) {
variableExports.reportErrors(context, node);
return;
}

variableImports.forEach(function (im) {
if (im.type !== 'Property' || !im.key || im.key.type !== 'Identifier') {
return;
}

const deepLookup = variableExports.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(' -> ');

context.report(im[key],
`${im[key].name} not found via ${deepPath}`);
context.report(im.key, `${im.key.name} not found via ${deepPath}`);
} else {
context.report(im[key],
im[key].name + ' not found in \'' + node.source.value + '\'');
context.report(im.key, im.key.name + ' not found in \'' + source.value + '\'');
}
}
});
}

return {
'ImportDeclaration': checkSpecifiers.bind( null
, 'imported'
, 'ImportSpecifier'
),

'ExportNamedDeclaration': checkSpecifiers.bind( null
, 'local'
, 'ExportSpecifier'
),
};
ImportDeclaration: checkSpecifiers.bind(null, 'imported', 'ImportSpecifier'),

ExportNamedDeclaration: checkSpecifiers.bind(null, 'local', 'ExportSpecifier'),

VariableDeclarator: checkRequire,
};
},
};
12 changes: 6 additions & 6 deletions src/rules/namespace.js
Original file line number Diff line number Diff line change
@@ -60,7 +60,7 @@ module.exports = {
if (!imports.size) {
context.report(
specifier,
`No exported names found in module '${declaration.source.value}'.`
`No exported names found in module '${declaration.source.value}'.`,
);
}
namespaces.set(specifier.local.name, imports);
@@ -69,7 +69,7 @@ module.exports = {
case 'ImportSpecifier': {
const meta = imports.get(
// default to 'default' for default http://i.imgur.com/nj6qAWy.jpg
specifier.imported ? specifier.imported.name : 'default'
specifier.imported ? specifier.imported.name : 'default',
);
if (!meta || !meta.namespace) { break; }
namespaces.set(specifier.local.name, meta.namespace);
@@ -96,7 +96,7 @@ module.exports = {
if (!imports.size) {
context.report(
namespace,
`No exported names found in module '${declaration.source.value}'.`
`No exported names found in module '${declaration.source.value}'.`,
);
}
},
@@ -111,7 +111,7 @@ module.exports = {
if (dereference.parent.type === 'AssignmentExpression' && dereference.parent.left === dereference) {
context.report(
dereference.parent,
`Assignment to member of namespace '${dereference.object.name}'.`
`Assignment to member of namespace '${dereference.object.name}'.`,
);
}

@@ -125,7 +125,7 @@ module.exports = {
if (!allowComputed) {
context.report(
dereference.property,
`Unable to validate computed reference to imported namespace '${dereference.object.name}'.`
`Unable to validate computed reference to imported namespace '${dereference.object.name}'.`,
);
}
return;
@@ -134,7 +134,7 @@ module.exports = {
if (!namespace.has(dereference.property.name)) {
context.report(
dereference.property,
makeMessage(dereference.property, namepath)
makeMessage(dereference.property, namepath),
);
break;
}
6 changes: 3 additions & 3 deletions src/rules/newline-after-import.js
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@ function isExportDefaultClass(node) {
}

function isExportNameClass(node) {

return node.type === 'ExportNamedDeclaration' && node.declaration && node.declaration.type === 'ClassDeclaration';
}

@@ -124,7 +124,7 @@ after ${type} statement not followed by another ${type}.`,
const { parent } = node;
const nodePosition = parent.body.indexOf(node);
const nextNode = parent.body[nodePosition + 1];

// skip "export import"s
if (node.type === 'TSImportEqualsDeclaration' && node.isExport) {
return;
@@ -144,7 +144,7 @@ after ${type} statement not followed by another ${type}.`,
}
},
'Program:exit': function () {
log('exit processing for', context.getFilename());
log('exit processing for', context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename());
const scopeBody = getScopeBody(context.getScope());
log('got scope:', scopeBody);

2 changes: 1 addition & 1 deletion src/rules/no-cycle.js
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ module.exports = {
},

create: function (context) {
const myPath = context.getFilename();
const myPath = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();
if (myPath === '<text>') return {}; // can't cycle-check a non-file

const options = context.options[0] || {};
37 changes: 30 additions & 7 deletions src/rules/no-duplicates.js
Original file line number Diff line number Diff line change
@@ -150,14 +150,27 @@ function getFix(first, rest, sourceCode) {

// Remove imports whose specifiers have been moved into the first import.
for (const specifier of specifiers) {
fixes.push(fixer.remove(specifier.importNode));
const importNode = specifier.importNode;
fixes.push(fixer.remove(importNode));

const charAfterImportRange = [importNode.range[1], importNode.range[1] + 1];
const charAfterImport = sourceCode.text.substring(charAfterImportRange[0], charAfterImportRange[1]);
if (charAfterImport === '\n') {
fixes.push(fixer.removeRange(charAfterImportRange));
}
}

// 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));

const charAfterImportRange = [node.range[1], node.range[1] + 1];
const charAfterImport = sourceCode.text.substring(charAfterImportRange[0], charAfterImportRange[1]);
if (charAfterImport === '\n') {
fixes.push(fixer.removeRange(charAfterImportRange));
}
}

return fixes;
@@ -263,13 +276,22 @@ module.exports = {

const imported = new Map();
const nsImported = new Map();
const typesImported = new Map();
const defaultTypesImported = new Map();
const namedTypesImported = new Map();

function getImportMap(n) {
if (n.importKind === 'type') {
return n.specifiers[0].type === 'ImportDefaultSpecifier' ? defaultTypesImported : namedTypesImported;
}

return hasNamespace(n) ? nsImported : imported;
}

return {
'ImportDeclaration': function (n) {
ImportDeclaration(n) {
// resolved path will cover aliased duplicates
const resolvedPath = resolver(n.source.value);
const importMap = n.importKind === 'type' ? typesImported :
(hasNamespace(n) ? nsImported : imported);
const importMap = getImportMap(n);

if (importMap.has(resolvedPath)) {
importMap.get(resolvedPath).push(n);
@@ -278,10 +300,11 @@ module.exports = {
}
},

'Program:exit': function () {
'Program:exit'() {
checkImports(imported, context);
checkImports(nsImported, context);
checkImports(typesImported, context);
checkImports(defaultTypesImported, context);
checkImports(namedTypesImported, context);
},
};
},
33 changes: 30 additions & 3 deletions src/rules/no-dynamic-require.js
Original file line number Diff line number Diff line change
@@ -8,6 +8,12 @@ function isRequire(node) {
node.arguments.length >= 1;
}

function isDynamicImport(node) {
return node &&
node.callee &&
node.callee.type === 'Import';
}

function isStaticValue(arg) {
return arg.type === 'Literal' ||
(arg.type === 'TemplateLiteral' && arg.expressions.length === 0);
@@ -19,18 +25,39 @@ module.exports = {
docs: {
url: docsUrl('no-dynamic-require'),
},
schema: [],
schema: [
{
type: 'object',
properties: {
esmodule: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
},

create: function (context) {
const options = context.options[0] || {};

return {
CallExpression(node) {
if (isRequire(node) && !isStaticValue(node.arguments[0])) {
context.report({
if (!node.arguments[0] || isStaticValue(node.arguments[0])) {
return;
}
if (isRequire(node)) {
return context.report({
node,
message: 'Calls to require() should use string literals',
});
}
if (options.esmodule && isDynamicImport(node)) {
return context.report({
node,
message: 'Calls to import() should use string literals',
});
}
},
};
},
94 changes: 70 additions & 24 deletions src/rules/no-extraneous-dependencies.js
Original file line number Diff line number Diff line change
@@ -69,7 +69,7 @@ function getDependencies(context, packageDir) {
Object.assign(
packageContent,
extractDepFields(
readPkgUp.sync({ cwd: context.getFilename(), normalize: false }).pkg
readPkgUp.sync({ cwd: context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(), normalize: false }).pkg
)
);
}
@@ -126,9 +126,45 @@ function getModuleRealName(resolved) {
return getFilePackageName(resolved);
}

function checkDependencyDeclaration(deps, packageName, declarationStatus) {
const newDeclarationStatus = declarationStatus || {
isInDeps: false,
isInDevDeps: false,
isInOptDeps: false,
isInPeerDeps: false,
isInBundledDeps: false,
};

// in case of sub package.json inside a module
// check the dependencies on all hierarchy
const packageHierarchy = [];
const packageNameParts = packageName ? 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,
};
}, newDeclarationStatus);
}

function reportIfMissing(context, deps, depsOptions, node, name) {
// Do not report when importing types
if (node.importKind === 'type' || (node.parent && node.parent.importKind === 'type') || node.importKind === 'typeof') {
if (
node.importKind === 'type' ||
(node.parent && node.parent.importKind === 'type') ||
node.importKind === 'typeof'
) {
return;
}

@@ -139,37 +175,47 @@ function reportIfMissing(context, deps, depsOptions, node, name) {
const resolved = resolve(name, context);
if (!resolved) { return; }

// get the real name from the resolved package.json
// if not aliased imports (alias/react for example) will not be correctly interpreted
// fallback on original name in case no package.json found
const packageName = getModuleRealName(resolved) || getModuleOriginalName(name);

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)
const importPackageName = getModuleOriginalName(name);
let declarationStatus = checkDependencyDeclaration(deps, importPackageName);

if (
declarationStatus.isInDeps ||
(depsOptions.allowDevDeps && declarationStatus.isInDevDeps) ||
(depsOptions.allowPeerDeps && declarationStatus.isInPeerDeps) ||
(depsOptions.allowOptDeps && declarationStatus.isInOptDeps) ||
(depsOptions.allowBundledDeps && declarationStatus.isInBundledDeps)
) {
return;
}

if (isInDevDeps && !depsOptions.allowDevDeps) {
context.report(node, devDepErrorMessage(packageName));
// 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);
if (realPackageName && realPackageName !== importPackageName) {
declarationStatus = checkDependencyDeclaration(deps, realPackageName, declarationStatus);

if (
declarationStatus.isInDeps ||
(depsOptions.allowDevDeps && declarationStatus.isInDevDeps) ||
(depsOptions.allowPeerDeps && declarationStatus.isInPeerDeps) ||
(depsOptions.allowOptDeps && declarationStatus.isInOptDeps) ||
(depsOptions.allowBundledDeps && declarationStatus.isInBundledDeps)
) {
return;
}
}

if (declarationStatus.isInDevDeps && !depsOptions.allowDevDeps) {
context.report(node, devDepErrorMessage(realPackageName || importPackageName));
return;
}

if (isInOptDeps && !depsOptions.allowOptDeps) {
context.report(node, optDepErrorMessage(packageName));
if (declarationStatus.isInOptDeps && !depsOptions.allowOptDeps) {
context.report(node, optDepErrorMessage(realPackageName || importPackageName));
return;
}

context.report(node, missingErrorMessage(packageName));
context.report(node, missingErrorMessage(realPackageName || importPackageName));
}

function testConfig(config, filename) {
@@ -208,7 +254,7 @@ module.exports = {

create: function (context) {
const options = context.options[0] || {};
const filename = context.getFilename();
const filename = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();
const deps = getDependencies(context, options.packageDir) || extractDepFields({});

const depsOptions = {
12 changes: 9 additions & 3 deletions src/rules/no-import-module-exports.js
Original file line number Diff line number Diff line change
@@ -3,8 +3,14 @@ import path from 'path';
import pkgUp from 'pkg-up';

function getEntryPoint(context) {
const pkgPath = pkgUp.sync(context.getFilename());
return require.resolve(path.dirname(pkgPath));
const pkgPath = pkgUp.sync(context.getPhysicalFilename ? context.getPhysicalFilename() : 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 = {
@@ -33,7 +39,7 @@ module.exports = {
let alreadyReported = false;

function report(node) {
const fileName = context.getFilename();
const fileName = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();
const isEntryPoint = entryPoint === fileName;
const isIdentifier = node.object.type === 'Identifier';
const hasKeywords = (/^(module|exports)$/).test(node.object.name);
31 changes: 24 additions & 7 deletions src/rules/no-namespace.js
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
* @author Radek Benkel
*/

import minimatch from 'minimatch';
import docsUrl from '../docsUrl';

//------------------------------------------------------------------------------
@@ -17,16 +18,32 @@ module.exports = {
url: docsUrl('no-namespace'),
},
fixable: 'code',
schema: [],
schema: [{
type: 'object',
properties: {
ignore: {
type: 'array',
items: {
type: 'string',
},
uniqueItems: true,
},
},
}],
},

create: function (context) {
const firstOption = context.options[0] || {};
const ignoreGlobs = firstOption.ignore;

return {
'ImportNamespaceSpecifier': function (node) {
ImportNamespaceSpecifier(node) {
if (ignoreGlobs && ignoreGlobs.find(glob => minimatch(node.parent.source.value, glob, { matchBase: true }))) {
return;
}

const scopeVariables = context.getScope().variables;
const namespaceVariable = scopeVariables.find((variable) =>
variable.defs[0].node === node
);
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);
@@ -63,11 +80,11 @@ module.exports = {
);

// Replace the ImportNamespaceSpecifier with a list of ImportSpecifiers
const namedImportSpecifiers = importNames.map((importName) =>
const namedImportSpecifiers = importNames.map((importName) => (
importName === importLocalNames[importName]
? importName
: `${importName} as ${importLocalNames[importName]}`
);
));
fixes.push(fixer.replaceText(node, `{ ${namedImportSpecifiers.join(', ')} }`));

// Pass 2: Replace references to the namespace with references to the named imports
2 changes: 1 addition & 1 deletion src/rules/no-relative-packages.js
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ function checkImportForRelativePackage(context, importPath, node) {
}

const resolvedImport = resolve(importPath, context);
const resolvedContext = context.getFilename();
const resolvedContext = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();

if (!resolvedImport || !resolvedContext) {
return;
2 changes: 1 addition & 1 deletion src/rules/no-relative-parent-imports.js
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ module.exports = {
},

create: function noRelativePackages(context) {
const myPath = context.getFilename();
const myPath = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();
if (myPath === '<text>') return {}; // can't check a non-file

function checkSourceValue(sourceNode) {
2 changes: 1 addition & 1 deletion src/rules/no-restricted-paths.js
Original file line number Diff line number Diff line change
@@ -52,7 +52,7 @@ module.exports = {
const options = context.options[0] || {};
const restrictedPaths = options.zones || [];
const basePath = options.basePath || process.cwd();
const currentFilename = context.getFilename();
const currentFilename = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();
const matchingZones = restrictedPaths.filter((zone) => {
const targetPath = path.resolve(basePath, zone.target);

2 changes: 1 addition & 1 deletion src/rules/no-self-import.js
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import moduleVisitor from 'eslint-module-utils/moduleVisitor';
import docsUrl from '../docsUrl';

function isImportingSelf(context, node, requireName) {
const filePath = context.getFilename();
const filePath = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();

// If the input is from stdin, this test can't fail
if (filePath !== '<text>' && filePath === resolve(requireName, context)) {
2 changes: 1 addition & 1 deletion src/rules/no-unassigned-import.js
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ function testIsAllow(globs, filename, source) {

function create(context) {
const options = context.options[0] || {};
const filename = context.getFilename();
const filename = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();
const isAllow = source => testIsAllow(options.allow, filename, source);

return {
31 changes: 16 additions & 15 deletions src/rules/no-unresolved.js
Original file line number Diff line number Diff line change
@@ -15,35 +15,36 @@ module.exports = {
url: docsUrl('no-unresolved'),
},

schema: [ makeOptionsSchema({
caseSensitive: { type: 'boolean', default: true },
})],
schema: [
makeOptionsSchema({
caseSensitive: { type: 'boolean', default: true },
}),
],
},

create: function (context) {

function checkSourceValue(source) {
const shouldCheckCase = !CASE_SENSITIVE_FS &&
(!context.options[0] || context.options[0].caseSensitive !== false);
const shouldCheckCase = !CASE_SENSITIVE_FS
&& (!context.options[0] || context.options[0].caseSensitive !== false);

const resolvedPath = resolve(source.value, context);

if (resolvedPath === undefined) {
context.report(source,
`Unable to resolve path to module '${source.value}'.`);
}

else if (shouldCheckCase) {
context.report(
source,
`Unable to resolve path to module '${source.value}'.`
);
} else if (shouldCheckCase) {
const cacheSettings = ModuleCache.getSettings(context.settings);
if (!fileExistsWithCaseSync(resolvedPath, cacheSettings)) {
context.report(source,
`Casing of ${source.value} does not match the underlying filesystem.`);
context.report(
source,
`Casing of ${source.value} does not match the underlying filesystem.`
);
}

}
}

return moduleVisitor(checkSourceValue, context.options[0]);

},
};
4 changes: 2 additions & 2 deletions src/rules/no-unused-modules.js
Original file line number Diff line number Diff line change
@@ -463,7 +463,7 @@ module.exports = {
doPreparation(src, ignoreExports, context);
}

const file = context.getFilename();
const file = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();

const checkExportPresence = node => {
if (!missingExports) {
@@ -540,7 +540,7 @@ module.exports = {

const value = exportsKey === IMPORT_DEFAULT_SPECIFIER ? DEFAULT : exportsKey;

if (typeof exportStatement !== 'undefined'){
if (typeof exportStatement !== 'undefined') {
if (exportStatement.whereUsed.size < 1) {
context.report(
node,
2 changes: 1 addition & 1 deletion src/rules/no-useless-path-segments.js
Original file line number Diff line number Diff line change
@@ -58,7 +58,7 @@ module.exports = {
},

create(context) {
const currentDir = path.dirname(context.getFilename());
const currentDir = path.dirname(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename());
const options = context.options[0];

function checkSourceValue(source) {
6 changes: 2 additions & 4 deletions src/rules/order.js
Original file line number Diff line number Diff line change
@@ -47,8 +47,7 @@ function takeTokensAfterWhile(sourceCode, node, condition) {
for (let i = 0; i < tokens.length; i++) {
if (condition(tokens[i])) {
result.push(tokens[i]);
}
else {
} else {
break;
}
}
@@ -61,8 +60,7 @@ function takeTokensBeforeWhile(sourceCode, node, condition) {
for (let i = tokens.length - 1; i >= 0; i--) {
if (condition(tokens[i])) {
result.push(tokens[i]);
}
else {
} else {
break;
}
}
3 changes: 1 addition & 2 deletions src/rules/prefer-default-export.js
Original file line number Diff line number Diff line change
@@ -69,8 +69,7 @@ module.exports = {
node.declaration.declarations.forEach(function(declaration) {
captureDeclaration(declaration.id);
});
}
else {
} else {
// captures 'export function foo() {}' syntax
specifierExportCount++;
}
9 changes: 9 additions & 0 deletions tests/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"env": {
"mocha": true,
},
"rules": {
"max-len": 0,
"import/default": 0,
},
}
8 changes: 0 additions & 8 deletions tests/.eslintrc.yml

This file was deleted.

2 changes: 0 additions & 2 deletions tests/files/cycles/depth-one.js

This file was deleted.

2 changes: 2 additions & 0 deletions tests/files/cycles/es6/depth-one.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import foo from "../depth-zero"
export { foo }
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/files/cycles/flow-types-depth-two.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import { foo } from './depth-one'
import { foo } from './es6/depth-one'
1 change: 1 addition & 0 deletions tests/files/export-star-2/middle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as myName from './upstream';
1 change: 1 addition & 0 deletions tests/files/export-star-2/upstream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const a = 1;
1 change: 1 addition & 0 deletions tests/files/export-star/extfield.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 42;
1 change: 1 addition & 0 deletions tests/files/export-star/extfield2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default NaN;
2 changes: 2 additions & 0 deletions tests/files/export-star/models.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * as ExtfieldModel from './extfield';
export * as Extfield2Model from './extfield2';
6 changes: 2 additions & 4 deletions tests/files/foo-bar-resolver-no-version.js
Original file line number Diff line number Diff line change
@@ -5,10 +5,8 @@ exports.resolveImport = function (modulePath, sourceFile, config) {
if (sourceFileName === 'foo.js') {
return path.join(__dirname, 'bar.jsx')
}
else if (sourceFileName === 'exception.js') {
if (sourceFileName === 'exception.js') {
throw new Error('foo-bar-resolver-v1 resolveImport test exception')
}
else {
return undefined
}
return undefined;
}
14 changes: 6 additions & 8 deletions tests/files/foo-bar-resolver-v1.js
Original file line number Diff line number Diff line change
@@ -3,14 +3,12 @@ var path = require('path')
exports.resolveImport = function (modulePath, sourceFile, config) {
var sourceFileName = path.basename(sourceFile)
if (sourceFileName === 'foo.js') {
return path.join(__dirname, 'bar.jsx')
return path.join(__dirname, 'bar.jsx');
}
else if (sourceFileName === 'exception.js') {
throw new Error('foo-bar-resolver-v1 resolveImport test exception')
if (sourceFileName === 'exception.js') {
throw new Error('foo-bar-resolver-v1 resolveImport test exception');
}
else {
return undefined
}
}
return undefined;
};

exports.interfaceVersion = 1
exports.interfaceVersion = 1;
10 changes: 4 additions & 6 deletions tests/files/foo-bar-resolver-v2.js
Original file line number Diff line number Diff line change
@@ -5,12 +5,10 @@ exports.resolve = function (modulePath, sourceFile, config) {
if (sourceFileName === 'foo.js') {
return { found: true, path: path.join(__dirname, 'bar.jsx') }
}
else if (sourceFileName === 'exception.js') {
if (sourceFileName === 'exception.js') {
throw new Error('foo-bar-resolver-v2 resolve test exception')
}
else {
return { found: false }
}
}
return { found: false };
};

exports.interfaceVersion = 2
exports.interfaceVersion = 2;
3 changes: 3 additions & 0 deletions tests/files/missing-entrypoint/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"bin": "./cli.js"
}
Empty file.
Empty file.
Empty file.
4 changes: 4 additions & 0 deletions tests/files/node_modules/esm-package/esm-module/package.json
Empty file.
5 changes: 5 additions & 0 deletions tests/files/node_modules/esm-package/package.json
1 change: 1 addition & 0 deletions tests/files/node_modules/rxjs/index.js
1 change: 1 addition & 0 deletions tests/files/node_modules/rxjs/operators/index.js
5 changes: 5 additions & 0 deletions tests/files/node_modules/rxjs/operators/package.json
5 changes: 5 additions & 0 deletions tests/files/node_modules/rxjs/package.json
4 changes: 3 additions & 1 deletion tests/files/package.json
Original file line number Diff line number Diff line change
@@ -9,9 +9,11 @@
},
"dependencies": {
"@org/package": "^1.0.0",
"esm-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"
19 changes: 19 additions & 0 deletions tests/files/typescript-export-react-test-renderer/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// case from @types/react-test-renderer

export {};

export interface ReactTestRendererJSON {
type: string;
props: { [propName: string]: any };
children: null | ReactTestRendererNode[];
}
export type ReactTestRendererNode = ReactTestRendererJSON | string;
export interface ReactTestRendererTree extends ReactTestRendererJSON {
nodeType: 'component' | 'host';
instance: any;
rendered: null | ReactTestRendererTree | ReactTestRendererTree[];
}

export function create(nextElement: any, options?: any): any;

export function act(callback: () => Promise<any>): Promise<undefined>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"esModuleInterop": true
}
}
6 changes: 3 additions & 3 deletions tests/files/typescript.ts
Original file line number Diff line number Diff line change
@@ -23,14 +23,14 @@ export function getFoo() : MyType {
}

export module MyModule {
export function ModuleFunction(){}
export function ModuleFunction() {}
}

export namespace MyNamespace {
export function NamespaceFunction(){}
export function NamespaceFunction() {}

export module NSModule {
export function NSModuleFunction(){}
export function NSModuleFunction() {}
}
}

3 changes: 2 additions & 1 deletion tests/files/webpack.config.js
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@ module.exports = {
extensions: ['', '.js', '.jsx'],
root: __dirname,
alias: {
'alias/chai$': 'chai' // alias for no-extraneous-dependencies tests
'alias/chai$': 'chai', // alias for no-extraneous-dependencies tests
'alias/esm-package': 'esm-package' // alias for no-extraneous-dependencies tests
}
},
}
9 changes: 9 additions & 0 deletions tests/src/cli.js
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import { expect } from 'chai';
import { CLIEngine } from 'eslint';
import eslintPkg from 'eslint/package.json';
import semver from 'semver';
import * as importPlugin from '../../src/index';

describe('CLI regression tests', function () {
describe('issue #210', function () {
@@ -20,6 +21,7 @@ describe('CLI regression tests', function () {
'named': 2,
},
});
cli.addPlugin('eslint-plugin-import', importPlugin);
});
it("doesn't throw an error on gratuitous, erroneous self-reference", function () {
expect(() => cli.executeOnFiles(['./tests/files/issue210.js'])).not.to.throw();
@@ -38,6 +40,7 @@ describe('CLI regression tests', function () {
rulePaths: ['./src/rules'],
ignore: false,
});
cli.addPlugin('eslint-plugin-import', importPlugin);
}
});

@@ -62,12 +65,18 @@ describe('CLI regression tests', function () {
},
],
errorCount: 1,
...(semver.satisfies(eslintPkg.version, '>= 7.32') && {
fatalErrorCount: 0,
}),
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
source: results.results[0].source, // NewLine-characters might differ depending on git-settings
},
],
...(semver.satisfies(eslintPkg.version, '>= 7.32') && {
fatalErrorCount: 0,
}),
errorCount: 1,
warningCount: 0,
fixableErrorCount: 0,
2 changes: 1 addition & 1 deletion tests/src/config/typescript.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import { expect } from 'chai';
const config = require(path.join(__dirname, '..', '..', '..', 'config', 'typescript'));

describe('config typescript', () => {
// https://github.com/benmosher/eslint-plugin-import/issues/1525
// https://github.com/import-js/eslint-plugin-import/issues/1525
it('should mark @types paths as external', () => {
const externalModuleFolders = config.settings['import/external-module-folders'];
expect(externalModuleFolders).to.exist;
4 changes: 2 additions & 2 deletions tests/src/core/docsUrl.js
Original file line number Diff line number Diff line change
@@ -5,10 +5,10 @@ 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/import-js/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/import-js/eslint-plugin-import/blob/bar/docs/rules/foo.md');
});
});
17 changes: 12 additions & 5 deletions tests/src/core/getExports.js
Original file line number Diff line number Diff line change
@@ -11,11 +11,18 @@ import { getFilename } from '../utils';
import * as unambiguous from 'eslint-module-utils/unambiguous';

describe('ExportMap', function () {
const fakeContext = {
getFilename: getFilename,
settings: {},
parserPath: 'babel-eslint',
};
const fakeContext = Object.assign(
semver.satisfies(eslintPkg.version, '>= 7.28') ? {
getFilename: function () { throw new Error('Should call getPhysicalFilename() instead of getFilename()'); },
getPhysicalFilename: getFilename,
} : {
getFilename,
},
{
settings: {},
parserPath: 'babel-eslint',
},
);

it('handles ExportAllDeclaration', function () {
let imports;
11 changes: 10 additions & 1 deletion tests/src/core/importType.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from 'chai';
import * as path from 'path';

import importType, { isExternalModule, isScopedModule } from 'core/importType';
import importType, { isExternalModule, isScopedModule, isScoped } from 'core/importType';

import { testContext, testFilePath } from '../utils';

@@ -241,6 +241,15 @@ describe('importType(name)', function () {

it('correctly identifies scoped modules with `isScopedModule`', () => {
expect(isScopedModule('@/abc')).to.equal(false);
expect(isScopedModule('@/abc/def')).to.equal(false);
expect(isScopedModule('@a/abc')).to.equal(true);
expect(isScopedModule('@a/abc/def')).to.equal(true);
});

it('correctly identifies scoped modules with `isScoped`', () => {
expect(isScoped('@/abc')).to.equal(false);
expect(isScoped('@/abc/def')).to.equal(false);
expect(isScoped('@a/abc')).to.equal(true);
expect(isScoped('@a/abc/def')).to.equal(true);
});
});
154 changes: 154 additions & 0 deletions tests/src/core/resolve.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { expect } from 'chai';
import eslintPkg from 'eslint/package.json';
import semver from 'semver';

import resolve, { CASE_SENSITIVE_FS, fileExistsWithCaseSync } from 'eslint-module-utils/resolve';
import ModuleCache from 'eslint-module-utils/ModuleCache';
@@ -162,6 +164,158 @@ describe('resolve', function () {
expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });
});

// context.getPhysicalFilename() is available in ESLint 7.28+
(semver.satisfies(eslintPkg.version, '>= 7.28') ? describe : describe.skip)('getPhysicalFilename()', () => {
function unexpectedCallToGetFilename() {
throw new Error('Expected to call to getPhysicalFilename() instead of getFilename()');
}

it('resolves via a custom resolver with interface version 1', function () {
const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v1' });

expect(resolve( '../files/foo'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }),
)).to.equal(utils.testFilePath('./bar.jsx'));

expect(resolve( '../files/exception'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('exception.js'); } }),
)).to.equal(undefined);

expect(resolve( '../files/not-found'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: 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' });

expect(resolve( '../files/foo'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }),
)).to.equal(utils.testFilePath('./bar.jsx'));

expect(resolve( '../files/exception'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('exception.js'); } }),
)).to.equal(undefined);

expect(resolve( '../files/not-found'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: 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 = [];
testContext.report = function (reportInfo) {
testContextReports.push(reportInfo);
};

expect(resolve( '../files/foo'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: function () { return utils.getFilename('foo.js'); } }),
)).to.equal(utils.testFilePath('./bar.jsx'));

testContextReports.length = 0;
expect(resolve( '../files/exception'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: 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<stack-was-here>');
expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });

testContextReports.length = 0;
expect(resolve( '../files/not-found'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: 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' ] });

expect(resolve( '../files/foo'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: 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': {} } });

expect(resolve( '../files/foo'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: 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': {} } ] });

expect(resolve( '../files/foo'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: 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': {} } });

expect(resolve( '../files/foo'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: 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 = [];
testContext.report = function (reportInfo) {
testContextReports.push(reportInfo);
};

testContextReports.length = 0;
expect(resolve( '../files/foo'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: 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 = [];
testContext.report = function (reportInfo) {
testContextReports.push(reportInfo);
};
testContextReports.length = 0;
expect(resolve( '../files/foo'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: 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'] } });

expect(resolve( './jsx/MyCoolComponent'
, 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 = [];
testContext.report = function (reportInfo) {
testContextReports.push(reportInfo);
};

expect(resolve( '../files/exception'
, Object.assign({}, testContext, { getFilename: unexpectedCallToGetFilename, getPhysicalFilename: 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<stack-was-here>');
expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 });
});
});

const caseDescribe = (!CASE_SENSITIVE_FS ? describe : describe.skip);
caseDescribe('case sensitivity', function () {
let file;
11 changes: 11 additions & 0 deletions tests/src/rules/default.js
Original file line number Diff line number Diff line change
@@ -220,6 +220,17 @@ context('TypeScript', function () {
tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-as-default-namespace/'),
},
}),
test({
code: `import Foo from "./typescript-export-react-test-renderer"`,
parser: parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
},
parserOptions: {
tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-react-test-renderer/'),
},
}),
test({
code: `import foobar from "./typescript-export-assign-property"`,
parser: parser,
10 changes: 10 additions & 0 deletions tests/src/rules/extensions.js
Original file line number Diff line number Diff line change
@@ -349,6 +349,11 @@ ruleTester.run('extensions', rule, {
line: 4,
column: 31,
},
{
message: 'Missing file extension for "@/configs/chart"',
line: 7,
column: 27,
},
],
}),

@@ -369,6 +374,11 @@ ruleTester.run('extensions', rule, {
line: 4,
column: 31,
},
{
message: 'Missing file extension for "@/configs/chart"',
line: 7,
column: 27,
},
],
}),

143 changes: 74 additions & 69 deletions tests/src/rules/first.js
Original file line number Diff line number Diff line change
@@ -7,60 +7,71 @@ 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';\
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'",
test({
code: "import { x } from './foo'; import { y } from 'bar'",
options: ['disable-absolute-first'],
}),
test({ code: "'use directive';\
import { x } from 'foo';" }),
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';\
export { x };",
test({
code: "import { x } from './foo';\
export { x };\
import { y } from './bar';",
errors: 1,
output: "import { x } from './foo';\
import { y } from './bar';\
export { x };",
}),
test({ code: "import { x } from './foo';\
export { x };\
import { y } from './bar';\
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';\
export { x };\
import { y } from './bar';\
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'",
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';\
'use directive';",
test({
code: "import { x } from 'foo';\
'use directive';\
import { y } from 'bar';",
errors: 1,
output: "import { x } from 'foo';\
import { y } from 'bar';\
'use directive';",
}),
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';\
var a = 1;\
if (true) { x() };\
import { x } from './foo';\
import { z } from './baz';",
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';\
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'",
test({
code: "if (true) { console.log(1) }import a from 'b'",
errors: 1,
output: "import a from 'b'\nif (true) { console.log(1) }",
}),
@@ -72,7 +83,7 @@ context('TypeScript', function () {
.filter((parser) => parser !== require.resolve('typescript-eslint-parser'))
.forEach((parser) => {
const parserConfig = {
parser: parser,
parser,
settings: {
'import/parsers': { [parser]: ['.ts'] },
'import/resolver': { 'eslint-import-resolver-typescript': true },
@@ -81,35 +92,29 @@ context('TypeScript', function () {

ruleTester.run('order', rule, {
valid: [
test(
{
code: `
import y = require('bar');
import { x } from 'foo';
import z = require('baz');
`,
parser,
},
parserConfig,
),
test({
code: `
import y = require('bar');
import { x } from 'foo';
import z = require('baz');
`,
...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,
),
test({
code: `
import { x } from './foo';
import y = require('bar');
`,
options: ['absolute-first'],
...parserConfig,
errors: [
{
message: 'Absolute imports should come before relative imports.',
},
],
}),
],
});
});
57 changes: 56 additions & 1 deletion tests/src/rules/max-dependencies.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { test } from '../utils';
import { test, getTSParsers } from '../utils';

import { RuleTester } from 'eslint';
import eslintPkg from 'eslint/package.json';
import semver from 'semver';

const ruleTester = new RuleTester();
const rule = require('rules/max-dependencies');
@@ -74,5 +76,58 @@ ruleTester.run('max-dependencies', rule, {
'Maximum number of dependencies (1) exceeded.',
],
}),

test({
code: 'import type { x } from \'./foo\'; import type { y } from \'./bar\'; import type { z } from \'./baz\'',
parser: require.resolve('babel-eslint'),
options: [{
max: 2,
ignoreTypeImports: false,
}],
errors: [
'Maximum number of dependencies (2) exceeded.',
],
}),
],
});

context('TypeScript', { skip: semver.satisfies(eslintPkg.version, '>5.0.0') }, () => {
getTSParsers().forEach((parser) => {
ruleTester.run(`max-dependencies (${parser.replace(process.cwd(), '.')})`, rule, {
valid: [
test({
code: 'import type { x } from \'./foo\'; import { y } from \'./bar\';',
parser: parser,
options: [{
max: 1,
ignoreTypeImports: true,
}],
}),
],
invalid: [
test({
code: 'import type { x } from \'./foo\'; import type { y } from \'./bar\'',
parser: parser,
options: [{
max: 1,
}],
errors: [
'Maximum number of dependencies (1) exceeded.',
],
}),

test({
code: 'import type { x } from \'./foo\'; import type { y } from \'./bar\'; import type { z } from \'./baz\'',
parser: parser,
options: [{
max: 2,
ignoreTypeImports: false,
}],
errors: [
'Maximum number of dependencies (2) exceeded.',
],
}),
],
});
});
});
71 changes: 69 additions & 2 deletions tests/src/rules/named.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, SYNTAX_CASES, getTSParsers } from '../utils';
import { test, SYNTAX_CASES, getTSParsers, testFilePath, testVersion } from '../utils';
import { RuleTester } from 'eslint';

import { CASE_SENSITIVE_FS } from 'eslint-module-utils/resolve';
@@ -146,11 +146,54 @@ ruleTester.run('named', rule, {
code: 'import { common } from "./re-export-default"',
}),

// destructured requires with commonjs option
test({
code: 'const { destructuredProp } = require("./named-exports")',
options: [{ commonjs: true }],
}),
test({
code: 'let { arrayKeyProp } = require("./named-exports")',
options: [{ commonjs: true }],
}),
test({
code: 'const { deepProp } = require("./named-exports")',
options: [{ commonjs: true }],
}),

test({
code: 'const { foo, bar } = require("./re-export-names")',
options: [{ commonjs: true }],
}),

test({
code: 'const { baz } = require("./bar")',
errors: [error('baz', './bar')],
}),

test({
code: 'const { baz } = require("./bar")',
errors: [error('baz', './bar')],
options: [{ commonjs: false }],
}),

test({
code: 'const { default: defExport } = require("./bar")',
options: [{ commonjs: true }],
}),

...SYNTAX_CASES,

...[].concat(testVersion('>= 6', () => ({
code: `import { ExtfieldModel, Extfield2Model } from './models';`,
filename: testFilePath('./export-star/downstream.js'),
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
},
})) || []),
],

invalid: [

test({ code: 'import { somethingElse } from "./test-module"',
errors: [ error('somethingElse', './test-module') ] }),

@@ -201,6 +244,30 @@ ruleTester.run('named', rule, {
errors: ['baz not found via broken-trampoline.js -> named-exports.js'],
}),

test({
code: 'const { baz } = require("./bar")',
errors: [error('baz', './bar')],
options: [{ commonjs: true }],
}),

test({
code: 'let { baz } = require("./bar")',
errors: [error('baz', './bar')],
options: [{ commonjs: true }],
}),

test({
code: 'const { baz: bar, bop } = require("./bar"), { a } = require("./re-export-names")',
errors: [error('baz', './bar'), error('bop', './bar'), error('a', './re-export-names')],
options: [{ commonjs: true }],
}),

test({
code: 'const { default: defExport } = require("./named-exports")',
errors: [error('default', './named-exports')],
options: [{ commonjs: true }],
}),

// parse errors
// test({
// code: "import { a } from './test.coffee';",
14 changes: 13 additions & 1 deletion tests/src/rules/namespace.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, SYNTAX_CASES, getTSParsers } from '../utils';
import { test, SYNTAX_CASES, getTSParsers, testVersion, testFilePath } from '../utils';
import { RuleTester } from 'eslint';
import flatMap from 'array.prototype.flatmap';

@@ -172,6 +172,18 @@ const valid = [
export const getExampleColor = () => color.example
`,
}),

...[].concat(testVersion('>= 6', () => ({
code: `
import * as middle from './middle';

console.log(middle.myName);
`,
filename: testFilePath('export-star-2/downstream.js'),
parserOptions: {
ecmaVersion: 2020,
},
})) || []),
];

const invalid = [
2 changes: 1 addition & 1 deletion tests/src/rules/newline-after-import.js
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), {
code: `const x = () => require('baz') && require('bar')`,
parserOptions: { ecmaVersion: 6 } ,
},
`function x(){ require('baz'); }`,
`function x() { require('baz'); }`,
`a(require('b'), require('c'), require('d'));`,
`function foo() {
switch (renderData.modalViewKey) {
2 changes: 1 addition & 1 deletion tests/src/rules/no-amd.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { RuleTester } from 'eslint';
import eslintPkg from 'eslint/package.json';
import semver from 'semver';

const ruleTester = new RuleTester();
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015, sourceType: 'module' } });

ruleTester.run('no-amd', require('rules/no-amd'), {
valid: [
2 changes: 1 addition & 1 deletion tests/src/rules/no-commonjs.js
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ import semver from 'semver';
const EXPORT_MESSAGE = 'Expected "export" or "export default"';
const IMPORT_MESSAGE = 'Expected "import" instead of "require()"';

const ruleTester = new RuleTester();
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015, sourceType: 'module' } });

ruleTester.run('no-commonjs', require('rules/no-commonjs'), {
valid: [
Loading