Skip to content

Commit b3d8c0c

Browse files
jeromehljharb
jeromeh
authored andcommittedJun 14, 2021
[Fix] no-extraneous-dependencies: add ESM intermediate package.json support
Fixes #2120.
1 parent 4079482 commit b3d8c0c

File tree

14 files changed

+87
-41
lines changed

14 files changed

+87
-41
lines changed
 

‎CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
99
### Fixed
1010
- [`no-duplicates`]: ensure autofix avoids excessive newlines ([#2028], thanks [@ertrzyiks])
1111
- [`extensions`]: avoid crashing on partially typed import/export statements ([#2118], thanks [@ljharb])
12+
- [`no-extraneous-dependencies`]: add ESM intermediate package.json support] ([#2121], thanks [@paztis])
1213

1314
## [2.23.4] - 2021-05-29
1415

@@ -804,6 +805,7 @@ for info on changes for earlier releases.
804805

805806
[`memo-parser`]: ./memo-parser/README.md
806807

808+
[#2121]: https://github.com/benmosher/eslint-plugin-import/pull/2121
807809
[#2099]: https://github.com/benmosher/eslint-plugin-import/pull/2099
808810
[#2097]: https://github.com/benmosher/eslint-plugin-import/pull/2097
809811
[#2090]: https://github.com/benmosher/eslint-plugin-import/pull/2090

‎src/core/packagePath.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export function getFilePackagePath(filePath) {
1313
}
1414

1515
export function getFilePackageName(filePath) {
16-
const { pkg } = readPkgUp.sync({ cwd: filePath, normalize: false });
17-
return pkg && pkg.name;
16+
const { pkg, path } = readPkgUp.sync({ cwd: filePath, normalize: false });
17+
if (pkg) {
18+
// recursion in case of intermediate esm package.json without name found
19+
return pkg.name || getFilePackageName(dirname(dirname(path)));
20+
}
21+
return null;
1822
}

‎src/rules/no-extraneous-dependencies.js

+42-38
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,19 @@ function getModuleRealName(resolved) {
126126
return getFilePackageName(resolved);
127127
}
128128

129-
function checkDependencyDeclaration(deps, packageName) {
129+
function checkDependencyDeclaration(deps, packageName, declarationStatus) {
130+
const newDeclarationStatus = declarationStatus || {
131+
isInDeps: false,
132+
isInDevDeps: false,
133+
isInOptDeps: false,
134+
isInPeerDeps: false,
135+
isInBundledDeps: false,
136+
};
137+
130138
// in case of sub package.json inside a module
131139
// check the dependencies on all hierarchy
132140
const packageHierarchy = [];
133-
const packageNameParts = packageName.split('/');
141+
const packageNameParts = packageName ? packageName.split('/') : [];
134142
packageNameParts.forEach((namePart, index) => {
135143
if (!namePart.startsWith('@')) {
136144
const ancestor = packageNameParts.slice(0, index + 1).join('/');
@@ -147,18 +155,16 @@ function checkDependencyDeclaration(deps, packageName) {
147155
isInBundledDeps:
148156
result.isInBundledDeps || deps.bundledDependencies.indexOf(ancestorName) !== -1,
149157
};
150-
}, {
151-
isInDeps: false,
152-
isInDevDeps: false,
153-
isInOptDeps: false,
154-
isInPeerDeps: false,
155-
isInBundledDeps: false,
156-
});
158+
}, newDeclarationStatus);
157159
}
158160

159161
function reportIfMissing(context, deps, depsOptions, node, name) {
160162
// Do not report when importing types
161-
if (node.importKind === 'type' || (node.parent && node.parent.importKind === 'type') || node.importKind === 'typeof') {
163+
if (
164+
node.importKind === 'type' ||
165+
(node.parent && node.parent.importKind === 'type') ||
166+
node.importKind === 'typeof'
167+
) {
162168
return;
163169
}
164170

@@ -170,48 +176,46 @@ function reportIfMissing(context, deps, depsOptions, node, name) {
170176
if (!resolved) { return; }
171177

172178
const importPackageName = getModuleOriginalName(name);
173-
const importPackageNameDeclaration = checkDependencyDeclaration(deps, importPackageName);
174-
175-
if (importPackageNameDeclaration.isInDeps ||
176-
(depsOptions.allowDevDeps && importPackageNameDeclaration.isInDevDeps) ||
177-
(depsOptions.allowPeerDeps && importPackageNameDeclaration.isInPeerDeps) ||
178-
(depsOptions.allowOptDeps && importPackageNameDeclaration.isInOptDeps) ||
179-
(depsOptions.allowBundledDeps && importPackageNameDeclaration.isInBundledDeps)
179+
let declarationStatus = checkDependencyDeclaration(deps, importPackageName);
180+
181+
if (
182+
declarationStatus.isInDeps ||
183+
(depsOptions.allowDevDeps && declarationStatus.isInDevDeps) ||
184+
(depsOptions.allowPeerDeps && declarationStatus.isInPeerDeps) ||
185+
(depsOptions.allowOptDeps && declarationStatus.isInOptDeps) ||
186+
(depsOptions.allowBundledDeps && declarationStatus.isInBundledDeps)
180187
) {
181188
return;
182189
}
183190

184191
// test the real name from the resolved package.json
185-
// if not aliased imports (alias/react for example), importPackageName can be misinterpreted
192+
// if not aliased imports (alias/react for example), importPackageName can be misinterpreted
186193
const realPackageName = getModuleRealName(resolved);
187-
const realPackageNameDeclaration = checkDependencyDeclaration(deps, realPackageName);
188-
189-
if (realPackageNameDeclaration.isInDeps ||
190-
(depsOptions.allowDevDeps && realPackageNameDeclaration.isInDevDeps) ||
191-
(depsOptions.allowPeerDeps && realPackageNameDeclaration.isInPeerDeps) ||
192-
(depsOptions.allowOptDeps && realPackageNameDeclaration.isInOptDeps) ||
193-
(depsOptions.allowBundledDeps && realPackageNameDeclaration.isInBundledDeps)
194-
) {
195-
return;
194+
if (realPackageName && realPackageName !== importPackageName) {
195+
declarationStatus = checkDependencyDeclaration(deps, realPackageName, declarationStatus);
196+
197+
if (
198+
declarationStatus.isInDeps ||
199+
(depsOptions.allowDevDeps && declarationStatus.isInDevDeps) ||
200+
(depsOptions.allowPeerDeps && declarationStatus.isInPeerDeps) ||
201+
(depsOptions.allowOptDeps && declarationStatus.isInOptDeps) ||
202+
(depsOptions.allowBundledDeps && declarationStatus.isInBundledDeps)
203+
) {
204+
return;
205+
}
196206
}
197207

198-
if ((
199-
importPackageNameDeclaration.isInDevDeps ||
200-
realPackageNameDeclaration.isInDevDeps
201-
) && !depsOptions.allowDevDeps) {
202-
context.report(node, devDepErrorMessage(realPackageName));
208+
if (declarationStatus.isInDevDeps && !depsOptions.allowDevDeps) {
209+
context.report(node, devDepErrorMessage(realPackageName || importPackageName));
203210
return;
204211
}
205212

206-
if ((
207-
importPackageNameDeclaration.isInOptDeps ||
208-
realPackageNameDeclaration.isInOptDeps
209-
) && !depsOptions.allowOptDeps) {
210-
context.report(node, optDepErrorMessage(realPackageName));
213+
if (declarationStatus.isInOptDeps && !depsOptions.allowOptDeps) {
214+
context.report(node, optDepErrorMessage(realPackageName || importPackageName));
211215
return;
212216
}
213217

214-
context.report(node, missingErrorMessage(realPackageName));
218+
context.report(node, missingErrorMessage(realPackageName || importPackageName));
215219
}
216220

217221
function testConfig(config, filename) {

‎tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/index.js

Whitespace-only changes.

‎tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/package.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tests/files/node_modules/esm-package-not-in-pkg-json/index.js

Whitespace-only changes.

‎tests/files/node_modules/esm-package-not-in-pkg-json/package.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tests/files/node_modules/esm-package/esm-module/index.js

Whitespace-only changes.

‎tests/files/node_modules/esm-package/esm-module/package.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tests/files/node_modules/esm-package/index.js

Whitespace-only changes.

‎tests/files/node_modules/esm-package/package.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tests/files/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
},
1010
"dependencies": {
1111
"@org/package": "^1.0.0",
12+
"esm-package": "^1.0.0",
1213
"jquery": "^3.1.0",
1314
"lodash.cond": "^4.3.0",
1415
"pkg-up": "^1.0.0",

‎tests/files/webpack.config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ module.exports = {
33
extensions: ['', '.js', '.jsx'],
44
root: __dirname,
55
alias: {
6-
'alias/chai$': 'chai' // alias for no-extraneous-dependencies tests
6+
'alias/chai$': 'chai', // alias for no-extraneous-dependencies tests
7+
'alias/esm-package': 'esm-package' // alias for no-extraneous-dependencies tests
78
}
89
},
910
}

‎tests/src/rules/no-extraneous-dependencies.js

+16
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ ruleTester.run('no-extraneous-dependencies', rule, {
154154
test({
155155
code: 'import "rxjs/operators"',
156156
}),
157+
158+
test({
159+
code: 'import "esm-package/esm-module";',
160+
}),
161+
162+
test({
163+
code: 'import "alias/esm-package/esm-module";',
164+
settings: { 'import/resolver': 'webpack' },
165+
}),
157166
],
158167
invalid: [
159168
test({
@@ -358,6 +367,13 @@ ruleTester.run('no-extraneous-dependencies', rule, {
358367
message: `'not-a-dependency' should be listed in the project's dependencies. Run 'npm i -S not-a-dependency' to add it`,
359368
}],
360369
}),
370+
371+
test({
372+
code: 'import "esm-package-not-in-pkg-json/esm-module";',
373+
errors: [{
374+
message: `'esm-package-not-in-pkg-json' should be listed in the project's dependencies. Run 'npm i -S esm-package-not-in-pkg-json' to add it`,
375+
}],
376+
}),
361377
],
362378
});
363379

0 commit comments

Comments
 (0)
Please sign in to comment.