Skip to content

Commit

Permalink
[New] no-unused-modules: Add ignoreUnusedTypeExports option
Browse files Browse the repository at this point in the history
Fixes #2694
  • Loading branch information
silverwind authored and ljharb committed May 28, 2024
1 parent fc361a9 commit 09476d7
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 14 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
### Added
- [`dynamic-import-chunkname`]: add `allowEmpty` option to allow empty leading comments ([#2942], thanks [@JiangWeixian])
- [`dynamic-import-chunkname`]: Allow empty chunk name when webpackMode: 'eager' is set; add suggestions to remove name in eager mode ([#3004], thanks [@amsardesai])
- [`no-unused-modules`]: Add `ignoreUnusedTypeExports` option ([#3011], thanks [@silverwind])

### Fixed
- [`no-extraneous-dependencies`]: allow wrong path ([#3012], thanks [@chabb])
Expand Down Expand Up @@ -1120,6 +1121,7 @@ for info on changes for earlier releases.
[`memo-parser`]: ./memo-parser/README.md

[#3012]: https://github.com/import-js/eslint-plugin-import/pull/3012
[#3011]: https://github.com/import-js/eslint-plugin-import/pull/3011
[#3004]: https://github.com/import-js/eslint-plugin-import/pull/3004
[#2991]: https://github.com/import-js/eslint-plugin-import/pull/2991
[#2989]: https://github.com/import-js/eslint-plugin-import/pull/2989
Expand Down Expand Up @@ -1915,6 +1917,7 @@ for info on changes for earlier releases.
[@sergei-startsev]: https://github.com/sergei-startsev
[@sharmilajesupaul]: https://github.com/sharmilajesupaul
[@sheepsteak]: https://github.com/sheepsteak
[@silverwind]: https://github.com/silverwind
[@silviogutierrez]: https://github.com/silviogutierrez
[@SimenB]: https://github.com/SimenB
[@simmo]: https://github.com/simmo
Expand Down
15 changes: 13 additions & 2 deletions docs/rules/no-unused-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ This rule takes the following option:

- **`missingExports`**: if `true`, files without any exports are reported (defaults to `false`)
- **`unusedExports`**: if `true`, exports without any static usage within other modules are reported (defaults to `false`)
- `src`: an array with files/paths to be analyzed. It only applies to unused exports. Defaults to `process.cwd()`, if not provided
- `ignoreExports`: an array with files/paths for which unused exports will not be reported (e.g module entry points in a published package)
- **`ignoreUnusedTypeExports`**: if `true`, TypeScript type exports without any static usage within other modules are reported (defaults to `false` and has no effect unless `unusedExports` is `true`)
- **`src`**: an array with files/paths to be analyzed. It only applies to unused exports. Defaults to `process.cwd()`, if not provided
- **`ignoreExports`**: an array with files/paths for which unused exports will not be reported (e.g module entry points in a published package)

### Example for missing exports

Expand Down Expand Up @@ -116,6 +117,16 @@ export function doAnything() {
export default 5 // will not be reported
```

### Unused exports with `ignoreUnusedTypeExports` set to `true`

The following will not be reported:

```ts
export type Foo = {}; // will not be reported
export interface Foo = {}; // will not be reported
export enum Foo {}; // 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`
Expand Down
35 changes: 23 additions & 12 deletions src/rules/no-unused-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,28 +83,30 @@ const DEFAULT = 'default';

function forEachDeclarationIdentifier(declaration, cb) {
if (declaration) {
const isTypeDeclaration = declaration.type === TS_INTERFACE_DECLARATION
|| declaration.type === TS_TYPE_ALIAS_DECLARATION
|| declaration.type === TS_ENUM_DECLARATION;

if (
declaration.type === FUNCTION_DECLARATION
|| declaration.type === CLASS_DECLARATION
|| declaration.type === TS_INTERFACE_DECLARATION
|| declaration.type === TS_TYPE_ALIAS_DECLARATION
|| declaration.type === TS_ENUM_DECLARATION
|| isTypeDeclaration
) {
cb(declaration.id.name);
cb(declaration.id.name, isTypeDeclaration);
} else if (declaration.type === VARIABLE_DECLARATION) {
declaration.declarations.forEach(({ id }) => {
if (id.type === OBJECT_PATTERN) {
recursivePatternCapture(id, (pattern) => {
if (pattern.type === IDENTIFIER) {
cb(pattern.name);
cb(pattern.name, false);
}
});
} else if (id.type === ARRAY_PATTERN) {
id.elements.forEach(({ name }) => {
cb(name);
cb(name, false);
});
} else {
cb(id.name);
cb(id.name, false);
}
});
}
Expand Down Expand Up @@ -443,6 +445,10 @@ module.exports = {
description: 'report exports without any usage',
type: 'boolean',
},
ignoreUnusedTypeExports: {
description: 'ignore type exports without any usage',
type: 'boolean',
},
},
anyOf: [
{
Expand Down Expand Up @@ -470,6 +476,7 @@ module.exports = {
ignoreExports = [],
missingExports,
unusedExports,
ignoreUnusedTypeExports,
} = context.options[0] || {};

if (unusedExports) {
Expand Down Expand Up @@ -502,11 +509,15 @@ module.exports = {
exportCount.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports);
};

const checkUsage = (node, exportedValue) => {
const checkUsage = (node, exportedValue, isTypeExport) => {
if (!unusedExports) {
return;
}

if (isTypeExport && ignoreUnusedTypeExports) {
return;
}

if (ignoredFiles.has(file)) {
return;
}
Expand Down Expand Up @@ -935,14 +946,14 @@ module.exports = {
checkExportPresence(node);
},
ExportDefaultDeclaration(node) {
checkUsage(node, IMPORT_DEFAULT_SPECIFIER);
checkUsage(node, IMPORT_DEFAULT_SPECIFIER, false);
},
ExportNamedDeclaration(node) {
node.specifiers.forEach((specifier) => {
checkUsage(specifier, specifier.exported.name || specifier.exported.value);
checkUsage(specifier, specifier.exported.name || specifier.exported.value, false);
});
forEachDeclarationIdentifier(node.declaration, (name) => {
checkUsage(node, name);
forEachDeclarationIdentifier(node.declaration, (name, isTypeExport) => {
checkUsage(node, name, isTypeExport);
});
},
};
Expand Down
67 changes: 67 additions & 0 deletions tests/src/rules/no-unused-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ const unusedExportsTypescriptOptions = [{
ignoreExports: undefined,
}];

const unusedExportsTypescriptIgnoreUnusedTypesOptions = [{
unusedExports: true,
ignoreUnusedTypeExports: true,
src: [testFilePath('./no-unused-modules/typescript')],
ignoreExports: undefined,
}];

const unusedExportsJsxOptions = [{
unusedExports: true,
src: [testFilePath('./no-unused-modules/jsx')],
Expand Down Expand Up @@ -1209,6 +1216,66 @@ context('TypeScript', function () {
});
});

describe('ignoreUnusedTypeExports', () => {
getTSParsers().forEach((parser) => {
typescriptRuleTester.run('no-unused-modules', rule, {
valid: [
// unused vars should not report
test({
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
code: `export interface c {};`,
parser,
filename: testFilePath(
'./no-unused-modules/typescript/file-ts-c-unused.ts',
),
}),
test({
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
code: `export type d = {};`,
parser,
filename: testFilePath(
'./no-unused-modules/typescript/file-ts-d-unused.ts',
),
}),
test({
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
code: `export enum e { f };`,
parser,
filename: testFilePath(
'./no-unused-modules/typescript/file-ts-e-unused.ts',
),
}),
// used vars should not report
test({
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
code: `export interface c {};`,
parser,
filename: testFilePath(
'./no-unused-modules/typescript/file-ts-c-used-as-type.ts',
),
}),
test({
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
code: `export type d = {};`,
parser,
filename: testFilePath(
'./no-unused-modules/typescript/file-ts-d-used-as-type.ts',
),
}),
test({
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
code: `export enum e { f };`,
parser,
filename: testFilePath(
'./no-unused-modules/typescript/file-ts-e-used-as-type.ts',
),
}),
],
invalid: [],
});
});
});

describe('correctly work with JSX only files', () => {
jsxRuleTester.run('no-unused-modules', rule, {
valid: [
Expand Down

0 comments on commit 09476d7

Please sign in to comment.