Skip to content

Commit 09476d7

Browse files
silverwindljharb
authored andcommitted
[New] no-unused-modules: Add ignoreUnusedTypeExports option
Fixes #2694
1 parent fc361a9 commit 09476d7

File tree

4 files changed

+106
-14
lines changed

4 files changed

+106
-14
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
99
### Added
1010
- [`dynamic-import-chunkname`]: add `allowEmpty` option to allow empty leading comments ([#2942], thanks [@JiangWeixian])
1111
- [`dynamic-import-chunkname`]: Allow empty chunk name when webpackMode: 'eager' is set; add suggestions to remove name in eager mode ([#3004], thanks [@amsardesai])
12+
- [`no-unused-modules`]: Add `ignoreUnusedTypeExports` option ([#3011], thanks [@silverwind])
1213

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

11221123
[#3012]: https://github.com/import-js/eslint-plugin-import/pull/3012
1124+
[#3011]: https://github.com/import-js/eslint-plugin-import/pull/3011
11231125
[#3004]: https://github.com/import-js/eslint-plugin-import/pull/3004
11241126
[#2991]: https://github.com/import-js/eslint-plugin-import/pull/2991
11251127
[#2989]: https://github.com/import-js/eslint-plugin-import/pull/2989
@@ -1915,6 +1917,7 @@ for info on changes for earlier releases.
19151917
[@sergei-startsev]: https://github.com/sergei-startsev
19161918
[@sharmilajesupaul]: https://github.com/sharmilajesupaul
19171919
[@sheepsteak]: https://github.com/sheepsteak
1920+
[@silverwind]: https://github.com/silverwind
19181921
[@silviogutierrez]: https://github.com/silviogutierrez
19191922
[@SimenB]: https://github.com/SimenB
19201923
[@simmo]: https://github.com/simmo

docs/rules/no-unused-modules.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ This rule takes the following option:
2929

3030
- **`missingExports`**: if `true`, files without any exports are reported (defaults to `false`)
3131
- **`unusedExports`**: if `true`, exports without any static usage within other modules are reported (defaults to `false`)
32-
- `src`: an array with files/paths to be analyzed. It only applies to unused exports. Defaults to `process.cwd()`, if not provided
33-
- `ignoreExports`: an array with files/paths for which unused exports will not be reported (e.g module entry points in a published package)
32+
- **`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`)
33+
- **`src`**: an array with files/paths to be analyzed. It only applies to unused exports. Defaults to `process.cwd()`, if not provided
34+
- **`ignoreExports`**: an array with files/paths for which unused exports will not be reported (e.g module entry points in a published package)
3435

3536
### Example for missing exports
3637

@@ -116,6 +117,16 @@ export function doAnything() {
116117
export default 5 // will not be reported
117118
```
118119

120+
### Unused exports with `ignoreUnusedTypeExports` set to `true`
121+
122+
The following will not be reported:
123+
124+
```ts
125+
export type Foo = {}; // will not be reported
126+
export interface Foo = {}; // will not be reported
127+
export enum Foo {}; // will not be reported
128+
```
129+
119130
#### Important Note
120131

121132
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`

src/rules/no-unused-modules.js

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,28 +83,30 @@ const DEFAULT = 'default';
8383

8484
function forEachDeclarationIdentifier(declaration, cb) {
8585
if (declaration) {
86+
const isTypeDeclaration = declaration.type === TS_INTERFACE_DECLARATION
87+
|| declaration.type === TS_TYPE_ALIAS_DECLARATION
88+
|| declaration.type === TS_ENUM_DECLARATION;
89+
8690
if (
8791
declaration.type === FUNCTION_DECLARATION
8892
|| declaration.type === CLASS_DECLARATION
89-
|| declaration.type === TS_INTERFACE_DECLARATION
90-
|| declaration.type === TS_TYPE_ALIAS_DECLARATION
91-
|| declaration.type === TS_ENUM_DECLARATION
93+
|| isTypeDeclaration
9294
) {
93-
cb(declaration.id.name);
95+
cb(declaration.id.name, isTypeDeclaration);
9496
} else if (declaration.type === VARIABLE_DECLARATION) {
9597
declaration.declarations.forEach(({ id }) => {
9698
if (id.type === OBJECT_PATTERN) {
9799
recursivePatternCapture(id, (pattern) => {
98100
if (pattern.type === IDENTIFIER) {
99-
cb(pattern.name);
101+
cb(pattern.name, false);
100102
}
101103
});
102104
} else if (id.type === ARRAY_PATTERN) {
103105
id.elements.forEach(({ name }) => {
104-
cb(name);
106+
cb(name, false);
105107
});
106108
} else {
107-
cb(id.name);
109+
cb(id.name, false);
108110
}
109111
});
110112
}
@@ -443,6 +445,10 @@ module.exports = {
443445
description: 'report exports without any usage',
444446
type: 'boolean',
445447
},
448+
ignoreUnusedTypeExports: {
449+
description: 'ignore type exports without any usage',
450+
type: 'boolean',
451+
},
446452
},
447453
anyOf: [
448454
{
@@ -470,6 +476,7 @@ module.exports = {
470476
ignoreExports = [],
471477
missingExports,
472478
unusedExports,
479+
ignoreUnusedTypeExports,
473480
} = context.options[0] || {};
474481

475482
if (unusedExports) {
@@ -502,11 +509,15 @@ module.exports = {
502509
exportCount.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports);
503510
};
504511

505-
const checkUsage = (node, exportedValue) => {
512+
const checkUsage = (node, exportedValue, isTypeExport) => {
506513
if (!unusedExports) {
507514
return;
508515
}
509516

517+
if (isTypeExport && ignoreUnusedTypeExports) {
518+
return;
519+
}
520+
510521
if (ignoredFiles.has(file)) {
511522
return;
512523
}
@@ -935,14 +946,14 @@ module.exports = {
935946
checkExportPresence(node);
936947
},
937948
ExportDefaultDeclaration(node) {
938-
checkUsage(node, IMPORT_DEFAULT_SPECIFIER);
949+
checkUsage(node, IMPORT_DEFAULT_SPECIFIER, false);
939950
},
940951
ExportNamedDeclaration(node) {
941952
node.specifiers.forEach((specifier) => {
942-
checkUsage(specifier, specifier.exported.name || specifier.exported.value);
953+
checkUsage(specifier, specifier.exported.name || specifier.exported.value, false);
943954
});
944-
forEachDeclarationIdentifier(node.declaration, (name) => {
945-
checkUsage(node, name);
955+
forEachDeclarationIdentifier(node.declaration, (name, isTypeExport) => {
956+
checkUsage(node, name, isTypeExport);
946957
});
947958
},
948959
};

tests/src/rules/no-unused-modules.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ const unusedExportsTypescriptOptions = [{
3838
ignoreExports: undefined,
3939
}];
4040

41+
const unusedExportsTypescriptIgnoreUnusedTypesOptions = [{
42+
unusedExports: true,
43+
ignoreUnusedTypeExports: true,
44+
src: [testFilePath('./no-unused-modules/typescript')],
45+
ignoreExports: undefined,
46+
}];
47+
4148
const unusedExportsJsxOptions = [{
4249
unusedExports: true,
4350
src: [testFilePath('./no-unused-modules/jsx')],
@@ -1209,6 +1216,66 @@ context('TypeScript', function () {
12091216
});
12101217
});
12111218

1219+
describe('ignoreUnusedTypeExports', () => {
1220+
getTSParsers().forEach((parser) => {
1221+
typescriptRuleTester.run('no-unused-modules', rule, {
1222+
valid: [
1223+
// unused vars should not report
1224+
test({
1225+
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
1226+
code: `export interface c {};`,
1227+
parser,
1228+
filename: testFilePath(
1229+
'./no-unused-modules/typescript/file-ts-c-unused.ts',
1230+
),
1231+
}),
1232+
test({
1233+
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
1234+
code: `export type d = {};`,
1235+
parser,
1236+
filename: testFilePath(
1237+
'./no-unused-modules/typescript/file-ts-d-unused.ts',
1238+
),
1239+
}),
1240+
test({
1241+
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
1242+
code: `export enum e { f };`,
1243+
parser,
1244+
filename: testFilePath(
1245+
'./no-unused-modules/typescript/file-ts-e-unused.ts',
1246+
),
1247+
}),
1248+
// used vars should not report
1249+
test({
1250+
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
1251+
code: `export interface c {};`,
1252+
parser,
1253+
filename: testFilePath(
1254+
'./no-unused-modules/typescript/file-ts-c-used-as-type.ts',
1255+
),
1256+
}),
1257+
test({
1258+
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
1259+
code: `export type d = {};`,
1260+
parser,
1261+
filename: testFilePath(
1262+
'./no-unused-modules/typescript/file-ts-d-used-as-type.ts',
1263+
),
1264+
}),
1265+
test({
1266+
options: unusedExportsTypescriptIgnoreUnusedTypesOptions,
1267+
code: `export enum e { f };`,
1268+
parser,
1269+
filename: testFilePath(
1270+
'./no-unused-modules/typescript/file-ts-e-used-as-type.ts',
1271+
),
1272+
}),
1273+
],
1274+
invalid: [],
1275+
});
1276+
});
1277+
});
1278+
12121279
describe('correctly work with JSX only files', () => {
12131280
jsxRuleTester.run('no-unused-modules', rule, {
12141281
valid: [

0 commit comments

Comments
 (0)