Skip to content

Commit

Permalink
[Fix] no-unused-modules: Exclude package "main"/"bin"/"browser" ent…
Browse files Browse the repository at this point in the history
…ry points

Fixes #1327.
  • Loading branch information
rfermann authored and ljharb committed Jul 2, 2019
1 parent 22d5440 commit 1caa402
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 9 deletions.
3 changes: 2 additions & 1 deletion docs/rules/no-unused-modules.md
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -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"
},
Expand Down
74 changes: 67 additions & 7 deletions src/rules/no-unused-modules.js
Expand Up @@ -7,6 +7,10 @@
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'
import includes from 'array-includes'

// eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3
// and has been moved to eslint/lib/cli-engine/file-enumerator in version 6
Expand Down Expand Up @@ -80,7 +84,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)
Expand Down Expand Up @@ -146,7 +150,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 = () => {
Expand Down Expand Up @@ -201,6 +205,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 (includes(pkgFieldFiles, 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') },
Expand Down Expand Up @@ -315,6 +371,10 @@ module.exports = {
return
}

if (fileIsInPkg(file)) {
return
}

// refresh list of source files
const srcFiles = resolveFiles(getSrc(src), ignoreExports)

Expand All @@ -325,7 +385,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) {
Expand Down Expand Up @@ -362,7 +422,7 @@ module.exports = {

/**
* only useful for tools like vscode-eslint
*
*
* update lists of existing exports during runtime
*/
const updateExportUsage = node => {
Expand All @@ -384,7 +444,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 => {
Expand All @@ -399,7 +459,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)
Expand Down Expand Up @@ -438,7 +498,7 @@ module.exports = {

/**
* only useful for tools like vscode-eslint
*
*
* update lists of existing imports during runtime
*/
const updateImportUsage = node => {
Expand Down
1 change: 1 addition & 0 deletions tests/files/no-unused-modules/bin.js
@@ -0,0 +1 @@
export const bin = 'bin'
1 change: 1 addition & 0 deletions tests/files/no-unused-modules/binObject/index.js
@@ -0,0 +1 @@
export const binObject = 'binObject'
6 changes: 6 additions & 0 deletions tests/files/no-unused-modules/binObject/package.json
@@ -0,0 +1,6 @@
{
"bin": {
"binObject": "./index.js"
},
"private": false
}
1 change: 1 addition & 0 deletions tests/files/no-unused-modules/browser.js
@@ -0,0 +1 @@
export const browser = 'browser'
1 change: 1 addition & 0 deletions tests/files/no-unused-modules/browserObject/index.js
@@ -0,0 +1 @@
export const browserObject = 'browserObject'
5 changes: 5 additions & 0 deletions tests/files/no-unused-modules/browserObject/package.json
@@ -0,0 +1,5 @@
{
"browser": {
"browserObject": "./index.js"
}
}
1 change: 1 addition & 0 deletions tests/files/no-unused-modules/main/index.js
@@ -0,0 +1 @@
export const main = 'main'
5 changes: 5 additions & 0 deletions tests/files/no-unused-modules/package.json
@@ -0,0 +1,5 @@
{
"bin": "./bin.js",
"browser": "./browser.js",
"main": "./main/index.js"
}
1 change: 1 addition & 0 deletions tests/files/no-unused-modules/privatePkg/index.js
@@ -0,0 +1 @@
export const privatePkg = 'privatePkg'
4 changes: 4 additions & 0 deletions tests/files/no-unused-modules/privatePkg/package.json
@@ -0,0 +1,4 @@
{
"main": "./index.js",
"private": true
}
30 changes: 29 additions & 1 deletion tests/src/rules/no-unused-modules.js
Expand Up @@ -610,4 +610,32 @@ ruleTester.run('no-unused-modules', rule, {
filename: testFilePath('../jsx/named.jsx')}),
],
invalid: [],
})
})

describe('do not report unused export for files mentioned in package.json', () => {
ruleTester.run('no-unused-modules', rule, {
valid: [
test({ options: unusedExportsOptions,
code: 'export const bin = "bin"',
filename: testFilePath('./no-unused-modules/bin.js')}),
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`)]}),
],
})
})

0 comments on commit 1caa402

Please sign in to comment.