diff --git a/CHANGELOG.md b/CHANGELOG.md index b918675a3..3452dd541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Fixed - [`no-duplicates`]: ensure autofix avoids excessive newlines ([#2028], thanks [@ertrzyiks]) - [`extensions`]: avoid crashing on partially typed import/export statements ([#2118], thanks [@ljharb]) +- [`no-extraneous-dependencies`]: add ESM intermediate package.json support] ([#2121], thanks [@paztis]) ## [2.23.4] - 2021-05-29 @@ -804,6 +805,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#2121]: https://github.com/benmosher/eslint-plugin-import/pull/2121 [#2099]: https://github.com/benmosher/eslint-plugin-import/pull/2099 [#2097]: https://github.com/benmosher/eslint-plugin-import/pull/2097 [#2090]: https://github.com/benmosher/eslint-plugin-import/pull/2090 diff --git a/src/core/packagePath.js b/src/core/packagePath.js index e95b06666..315ec0918 100644 --- a/src/core/packagePath.js +++ b/src/core/packagePath.js @@ -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; } diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 8a6af2f61..7e46ac34b 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -126,11 +126,19 @@ function getModuleRealName(resolved) { return getFilePackageName(resolved); } -function checkDependencyDeclaration(deps, packageName) { +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.split('/'); + const packageNameParts = packageName ? packageName.split('/') : []; packageNameParts.forEach((namePart, index) => { if (!namePart.startsWith('@')) { const ancestor = packageNameParts.slice(0, index + 1).join('/'); @@ -147,18 +155,16 @@ function checkDependencyDeclaration(deps, packageName) { isInBundledDeps: result.isInBundledDeps || deps.bundledDependencies.indexOf(ancestorName) !== -1, }; - }, { - isInDeps: false, - isInDevDeps: false, - isInOptDeps: false, - isInPeerDeps: false, - isInBundledDeps: false, - }); + }, 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; } @@ -170,48 +176,46 @@ function reportIfMissing(context, deps, depsOptions, node, name) { if (!resolved) { return; } const importPackageName = getModuleOriginalName(name); - const importPackageNameDeclaration = checkDependencyDeclaration(deps, importPackageName); - - if (importPackageNameDeclaration.isInDeps || - (depsOptions.allowDevDeps && importPackageNameDeclaration.isInDevDeps) || - (depsOptions.allowPeerDeps && importPackageNameDeclaration.isInPeerDeps) || - (depsOptions.allowOptDeps && importPackageNameDeclaration.isInOptDeps) || - (depsOptions.allowBundledDeps && importPackageNameDeclaration.isInBundledDeps) + 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; } // test the real name from the resolved package.json - // if not aliased imports (alias/react for example), importPackageName can be misinterpreted + // if not aliased imports (alias/react for example), importPackageName can be misinterpreted const realPackageName = getModuleRealName(resolved); - const realPackageNameDeclaration = checkDependencyDeclaration(deps, realPackageName); - - if (realPackageNameDeclaration.isInDeps || - (depsOptions.allowDevDeps && realPackageNameDeclaration.isInDevDeps) || - (depsOptions.allowPeerDeps && realPackageNameDeclaration.isInPeerDeps) || - (depsOptions.allowOptDeps && realPackageNameDeclaration.isInOptDeps) || - (depsOptions.allowBundledDeps && realPackageNameDeclaration.isInBundledDeps) - ) { - return; + 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 (( - importPackageNameDeclaration.isInDevDeps || - realPackageNameDeclaration.isInDevDeps - ) && !depsOptions.allowDevDeps) { - context.report(node, devDepErrorMessage(realPackageName)); + if (declarationStatus.isInDevDeps && !depsOptions.allowDevDeps) { + context.report(node, devDepErrorMessage(realPackageName || importPackageName)); return; } - if (( - importPackageNameDeclaration.isInOptDeps || - realPackageNameDeclaration.isInOptDeps - ) && !depsOptions.allowOptDeps) { - context.report(node, optDepErrorMessage(realPackageName)); + if (declarationStatus.isInOptDeps && !depsOptions.allowOptDeps) { + context.report(node, optDepErrorMessage(realPackageName || importPackageName)); return; } - context.report(node, missingErrorMessage(realPackageName)); + context.report(node, missingErrorMessage(realPackageName || importPackageName)); } function testConfig(config, filename) { diff --git a/tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/index.js b/tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/package.json b/tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/package.json new file mode 100644 index 000000000..0c58fec1b --- /dev/null +++ b/tests/files/node_modules/esm-package-not-in-pkg-json/esm-module/package.json @@ -0,0 +1,4 @@ +{ + "sideEffects": false, + "module": "./index.js" +} diff --git a/tests/files/node_modules/esm-package-not-in-pkg-json/index.js b/tests/files/node_modules/esm-package-not-in-pkg-json/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/node_modules/esm-package-not-in-pkg-json/package.json b/tests/files/node_modules/esm-package-not-in-pkg-json/package.json new file mode 100644 index 000000000..fa7f3c043 --- /dev/null +++ b/tests/files/node_modules/esm-package-not-in-pkg-json/package.json @@ -0,0 +1,5 @@ +{ + "name": "esm-package-not-in-pkg-json", + "main": "index.js", + "version": "1.0.0" +} diff --git a/tests/files/node_modules/esm-package/esm-module/index.js b/tests/files/node_modules/esm-package/esm-module/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/node_modules/esm-package/esm-module/package.json b/tests/files/node_modules/esm-package/esm-module/package.json new file mode 100644 index 000000000..0c58fec1b --- /dev/null +++ b/tests/files/node_modules/esm-package/esm-module/package.json @@ -0,0 +1,4 @@ +{ + "sideEffects": false, + "module": "./index.js" +} diff --git a/tests/files/node_modules/esm-package/index.js b/tests/files/node_modules/esm-package/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/node_modules/esm-package/package.json b/tests/files/node_modules/esm-package/package.json new file mode 100644 index 000000000..ddad1a531 --- /dev/null +++ b/tests/files/node_modules/esm-package/package.json @@ -0,0 +1,5 @@ +{ + "name": "esm-package", + "main": "index.js", + "version": "1.0.0" +} diff --git a/tests/files/package.json b/tests/files/package.json index 62bd3764a..de1d80275 100644 --- a/tests/files/package.json +++ b/tests/files/package.json @@ -9,6 +9,7 @@ }, "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", diff --git a/tests/files/webpack.config.js b/tests/files/webpack.config.js index 6a5dc0b88..bbe81b359 100644 --- a/tests/files/webpack.config.js +++ b/tests/files/webpack.config.js @@ -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 } }, } diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index 6bb84358a..7a1107468 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -154,6 +154,15 @@ ruleTester.run('no-extraneous-dependencies', rule, { test({ code: 'import "rxjs/operators"', }), + + test({ + code: 'import "esm-package/esm-module";', + }), + + test({ + code: 'import "alias/esm-package/esm-module";', + settings: { 'import/resolver': 'webpack' }, + }), ], invalid: [ test({ @@ -358,6 +367,13 @@ ruleTester.run('no-extraneous-dependencies', rule, { message: `'not-a-dependency' should be listed in the project's dependencies. Run 'npm i -S not-a-dependency' to add it`, }], }), + + test({ + code: 'import "esm-package-not-in-pkg-json/esm-module";', + errors: [{ + 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`, + }], + }), ], });