diff --git a/docs/rules/no-unused-modules.md b/docs/rules/no-unused-modules.md index 32db6465fc..4302bc8458 100644 --- a/docs/rules/no-unused-modules.md +++ b/docs/rules/no-unused-modules.md @@ -105,7 +105,8 @@ export function doAnything() { export default 5 // will not be reported ``` - +#### Important Note +Exports from files listed as a main file (`main`, `browser`, or `bin` fields in `package.json`) will be ignored by default. This only applies if the `package.json` is not set to `private: true` ## When not to use diff --git a/package.json b/package.json index f7989b2d7a..488a441a1b 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "eslint-module-utils": "^2.4.0", "has": "^1.0.3", "minimatch": "^3.0.4", + "object.values": "^1.1.0", "read-pkg-up": "^2.0.0", "resolve": "^1.11.0" }, diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 16130d47d5..d39c4a964d 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -7,6 +7,9 @@ import Exports from '../ExportMap' import resolve from 'eslint-module-utils/resolve' import docsUrl from '../docsUrl' +import { dirname, join } from 'path' +import readPkgUp from 'read-pkg-up' +import values from 'object.values' // eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3 // and has been moved to eslint/lib/cli-engine/file-enumerator in version 6 @@ -80,7 +83,7 @@ const prepareImportsAndExports = (srcFiles, context) => { if (currentExports) { const { dependencies, reexports, imports: localImportList, namespace } = currentExports - // dependencies === export * from + // dependencies === export * from const currentExportAll = new Set() dependencies.forEach(value => { currentExportAll.add(value().path) @@ -146,7 +149,7 @@ const prepareImportsAndExports = (srcFiles, context) => { } /** - * traverse through all imports and add the respective path to the whereUsed-list + * traverse through all imports and add the respective path to the whereUsed-list * of the corresponding export */ const determineUsage = () => { @@ -201,6 +204,58 @@ const newNamespaceImportExists = specifiers => const newDefaultImportExists = specifiers => specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER) +const fileIsInPkg = file => { + const { path, pkg } = readPkgUp.sync({cwd: file, normalize: false}) + const basePath = dirname(path) + + const checkPkgFieldString = pkgField => { + if (join(basePath, pkgField) === file) { + return true + } + } + + const checkPkgFieldObject = pkgField => { + const pkgFieldFiles = values(pkgField).map(value => join(basePath, value)) + if (pkgFieldFiles.includes(file)) { + return true + } + } + + const checkPkgField = pkgField => { + if (typeof pkgField === 'string') { + return checkPkgFieldString(pkgField) + } + + if (typeof pkgField === 'object') { + return checkPkgFieldObject(pkgField) + } + } + + if (pkg.private === true) { + return false + } + + if (pkg.bin) { + if (checkPkgField(pkg.bin)) { + return true + } + } + + if (pkg.browser) { + if (checkPkgField(pkg.browser)) { + return true + } + } + + if (pkg.main) { + if (checkPkgFieldString(pkg.main)) { + return true + } + } + + return false +} + module.exports = { meta: { docs: { url: docsUrl('no-unused-modules') }, @@ -315,6 +370,10 @@ module.exports = { return } + if (fileIsInPkg(file)) { + return + } + // refresh list of source files const srcFiles = resolveFiles(getSrc(src), ignoreExports) @@ -325,7 +384,7 @@ module.exports = { exports = exportList.get(file) - // special case: export * from + // special case: export * from const exportAll = exports.get(EXPORT_ALL_DECLARATION) if (typeof exportAll !== 'undefined' && exportedValue !== IMPORT_DEFAULT_SPECIFIER) { if (exportAll.whereUsed.size > 0) { @@ -362,7 +421,7 @@ module.exports = { /** * only useful for tools like vscode-eslint - * + * * update lists of existing exports during runtime */ const updateExportUsage = node => { @@ -384,7 +443,7 @@ module.exports = { node.body.forEach(({ type, declaration, specifiers }) => { if (type === EXPORT_DEFAULT_DECLARATION) { newExportIdentifiers.add(IMPORT_DEFAULT_SPECIFIER) - } + } if (type === EXPORT_NAMED_DECLARATION) { if (specifiers.length > 0) { specifiers.forEach(specifier => { @@ -399,7 +458,7 @@ module.exports = { declaration.type === CLASS_DECLARATION ) { newExportIdentifiers.add(declaration.id.name) - } + } if (declaration.type === VARIABLE_DECLARATION) { declaration.declarations.forEach(({ id }) => { newExportIdentifiers.add(id.name) @@ -438,7 +497,7 @@ module.exports = { /** * only useful for tools like vscode-eslint - * + * * update lists of existing imports during runtime */ const updateImportUsage = node => { diff --git a/tests/files/no-unused-modules/bin.js b/tests/files/no-unused-modules/bin.js new file mode 100644 index 0000000000..c755d14027 --- /dev/null +++ b/tests/files/no-unused-modules/bin.js @@ -0,0 +1 @@ +export const bin = 'bin' diff --git a/tests/files/no-unused-modules/binObject/index.js b/tests/files/no-unused-modules/binObject/index.js new file mode 100644 index 0000000000..53cc33821f --- /dev/null +++ b/tests/files/no-unused-modules/binObject/index.js @@ -0,0 +1 @@ +export const binObject = 'binObject' diff --git a/tests/files/no-unused-modules/binObject/package.json b/tests/files/no-unused-modules/binObject/package.json new file mode 100644 index 0000000000..fa9c85326d --- /dev/null +++ b/tests/files/no-unused-modules/binObject/package.json @@ -0,0 +1,6 @@ +{ + "bin": { + "binObject": "./index.js" + }, + "private": false +} diff --git a/tests/files/no-unused-modules/browser.js b/tests/files/no-unused-modules/browser.js new file mode 100644 index 0000000000..daad8d2942 --- /dev/null +++ b/tests/files/no-unused-modules/browser.js @@ -0,0 +1 @@ +export const browser = 'browser' diff --git a/tests/files/no-unused-modules/browserObject/index.js b/tests/files/no-unused-modules/browserObject/index.js new file mode 100644 index 0000000000..92d09f8f11 --- /dev/null +++ b/tests/files/no-unused-modules/browserObject/index.js @@ -0,0 +1 @@ +export const browserObject = 'browserObject' diff --git a/tests/files/no-unused-modules/browserObject/package.json b/tests/files/no-unused-modules/browserObject/package.json new file mode 100644 index 0000000000..28272c6fef --- /dev/null +++ b/tests/files/no-unused-modules/browserObject/package.json @@ -0,0 +1,5 @@ +{ + "browser": { + "browserObject": "./index.js" + } +} diff --git a/tests/files/no-unused-modules/main/index.js b/tests/files/no-unused-modules/main/index.js new file mode 100644 index 0000000000..9eb0aade18 --- /dev/null +++ b/tests/files/no-unused-modules/main/index.js @@ -0,0 +1 @@ +export const main = 'main' diff --git a/tests/files/no-unused-modules/package.json b/tests/files/no-unused-modules/package.json new file mode 100644 index 0000000000..5aae42b811 --- /dev/null +++ b/tests/files/no-unused-modules/package.json @@ -0,0 +1,5 @@ +{ + "bin": "./bin.js", + "browser": "./browser.js", + "main": "./main/index.js" +} diff --git a/tests/files/no-unused-modules/privatePkg/index.js b/tests/files/no-unused-modules/privatePkg/index.js new file mode 100644 index 0000000000..c936cd4930 --- /dev/null +++ b/tests/files/no-unused-modules/privatePkg/index.js @@ -0,0 +1 @@ +export const privatePkg = 'privatePkg' diff --git a/tests/files/no-unused-modules/privatePkg/package.json b/tests/files/no-unused-modules/privatePkg/package.json new file mode 100644 index 0000000000..618c66d18c --- /dev/null +++ b/tests/files/no-unused-modules/privatePkg/package.json @@ -0,0 +1,4 @@ +{ + "main": "./index.js", + "private": true +} diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index cefa6ff214..8050b56935 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -610,4 +610,32 @@ ruleTester.run('no-unused-modules', rule, { filename: testFilePath('../jsx/named.jsx')}), ], invalid: [], -}) \ No newline at end of file +}) + +describe('do not report unused export for files mentioned in package.json', () => { + ruleTester.run('no-unused-modules', rule, { + valid: [ + test({ options: unusedExportsOptions, + code: 'export const bin = "bin"', + filename: testFilePath('./no-unused-modules/bin.js')}), + test({ options: unusedExportsOptions, + code: 'export const binObject = "binObject"', + filename: testFilePath('./no-unused-modules/binObject/index.js')}), + test({ options: unusedExportsOptions, + code: 'export const browser = "browser"', + filename: testFilePath('./no-unused-modules/browser.js')}), + test({ options: unusedExportsOptions, + code: 'export const browserObject = "browserObject"', + filename: testFilePath('./no-unused-modules/browserObject/index.js')}), + test({ options: unusedExportsOptions, + code: 'export const main = "main"', + filename: testFilePath('./no-unused-modules/main/index.js')}), + ], + invalid: [ + test({ options: unusedExportsOptions, + code: 'export const privatePkg = "privatePkg"', + filename: testFilePath('./no-unused-modules/privatePkg/index.js'), + errors: [error(`exported declaration 'privatePkg' not used within other modules`)]}), + ], + }) +})