From f06477209679362ee3968f1494c96dfe9e55e43e Mon Sep 17 00:00:00 2001 From: e020873 Date: Thu, 11 Jun 2020 18:20:11 +0200 Subject: [PATCH] [Fix] `no-unused-modules`: consider exported TypeScript interfaces, types and enums Fixes #1680 Co-authored-by: e020873 Co-authored-by: Jordan Harband --- CHANGELOG.md | 4 + src/rules/no-unused-modules.js | 57 ++++++++------- .../no-unused-modules/typescript/file-ts-a.ts | 7 +- .../no-unused-modules/typescript/file-ts-c.ts | 1 + .../no-unused-modules/typescript/file-ts-d.ts | 1 + .../no-unused-modules/typescript/file-ts-e.ts | 1 + tests/src/rules/no-unused-modules.js | 73 ++++++++++++++++++- 7 files changed, 116 insertions(+), 28 deletions(-) create mode 100644 tests/files/no-unused-modules/typescript/file-ts-c.ts create mode 100644 tests/files/no-unused-modules/typescript/file-ts-d.ts create mode 100644 tests/files/no-unused-modules/typescript/file-ts-e.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d583358ce..3f8c2e01c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] +### Fixed +- [`no-unused-modules`]: consider exported TypeScript interfaces, types and enums ([#1819], thanks [@nicolashenry]) ## [2.21.2] - 2020-06-09 ### Fixed @@ -702,6 +704,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#1819]: https://github.com/benmosher/eslint-plugin-import/pull/1819 [#1802]: https://github.com/benmosher/eslint-plugin-import/pull/1802 [#1801]: https://github.com/benmosher/eslint-plugin-import/issues/1801 [#1788]: https://github.com/benmosher/eslint-plugin-import/pull/1788 @@ -1216,3 +1219,4 @@ for info on changes for earlier releases. [@adjerbetian]: https://github.com/adjerbetian [@Maxim-Mazurok]: https://github.com/Maxim-Mazurok [@malykhinvi]: https://github.com/malykhinvi +[@nicolashenry]: https://github.com/nicolashenry diff --git a/src/rules/no-unused-modules.js b/src/rules/no-unused-modules.js index 25139b681f..d277ca9aed 100644 --- a/src/rules/no-unused-modules.js +++ b/src/rules/no-unused-modules.js @@ -63,8 +63,33 @@ const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier' const VARIABLE_DECLARATION = 'VariableDeclaration' const FUNCTION_DECLARATION = 'FunctionDeclaration' const CLASS_DECLARATION = 'ClassDeclaration' +const INTERFACE_DECLARATION = 'InterfaceDeclaration' +const TYPE_ALIAS = 'TypeAlias' +const TS_INTERFACE_DECLARATION = 'TSInterfaceDeclaration' +const TS_TYPE_ALIAS_DECLARATION = 'TSTypeAliasDeclaration' +const TS_ENUM_DECLARATION = 'TSEnumDeclaration' const DEFAULT = 'default' +function forEachDeclarationIdentifier(declaration, cb) { + if (declaration) { + if ( + declaration.type === FUNCTION_DECLARATION || + declaration.type === CLASS_DECLARATION || + declaration.type === INTERFACE_DECLARATION || + declaration.type === TYPE_ALIAS || + declaration.type === TS_INTERFACE_DECLARATION || + declaration.type === TS_TYPE_ALIAS_DECLARATION || + declaration.type === TS_ENUM_DECLARATION + ) { + cb(declaration.id.name) + } else if (declaration.type === VARIABLE_DECLARATION) { + declaration.declarations.forEach(({ id }) => { + cb(id.name) + }) + } + } +} + /** * List of imports per file. * @@ -559,19 +584,9 @@ module.exports = { } }) } - if (declaration) { - if ( - declaration.type === FUNCTION_DECLARATION || - declaration.type === CLASS_DECLARATION - ) { - newExportIdentifiers.add(declaration.id.name) - } - if (declaration.type === VARIABLE_DECLARATION) { - declaration.declarations.forEach(({ id }) => { - newExportIdentifiers.add(id.name) - }) - } - } + forEachDeclarationIdentifier(declaration, (name) => { + newExportIdentifiers.add(name) + }) } }) @@ -883,19 +898,9 @@ module.exports = { node.specifiers.forEach(specifier => { checkUsage(node, specifier.exported.name) }) - if (node.declaration) { - if ( - node.declaration.type === FUNCTION_DECLARATION || - node.declaration.type === CLASS_DECLARATION - ) { - checkUsage(node, node.declaration.id.name) - } - if (node.declaration.type === VARIABLE_DECLARATION) { - node.declaration.declarations.forEach(declaration => { - checkUsage(node, declaration.id.name) - }) - } - } + forEachDeclarationIdentifier(node.declaration, (name) => { + checkUsage(node, name) + }) }, } }, diff --git a/tests/files/no-unused-modules/typescript/file-ts-a.ts b/tests/files/no-unused-modules/typescript/file-ts-a.ts index a4272256e6..a5cc566715 100644 --- a/tests/files/no-unused-modules/typescript/file-ts-a.ts +++ b/tests/files/no-unused-modules/typescript/file-ts-a.ts @@ -1,3 +1,8 @@ import {b} from './file-ts-b'; +import {c} from './file-ts-c'; +import {d} from './file-ts-d'; +import {e} from './file-ts-e'; -export const a = b + 1; +export const a = b + 1 + e.f; +export const a2: c = {}; +export const a3: d = {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-c.ts b/tests/files/no-unused-modules/typescript/file-ts-c.ts new file mode 100644 index 0000000000..aedf4062be --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-c.ts @@ -0,0 +1 @@ +export interface c {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-d.ts b/tests/files/no-unused-modules/typescript/file-ts-d.ts new file mode 100644 index 0000000000..7679b3de03 --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-d.ts @@ -0,0 +1 @@ +export type d = {}; diff --git a/tests/files/no-unused-modules/typescript/file-ts-e.ts b/tests/files/no-unused-modules/typescript/file-ts-e.ts new file mode 100644 index 0000000000..d1787a11af --- /dev/null +++ b/tests/files/no-unused-modules/typescript/file-ts-e.ts @@ -0,0 +1 @@ +export enum e { f }; diff --git a/tests/src/rules/no-unused-modules.js b/tests/src/rules/no-unused-modules.js index ef2d3e66c2..74200fb0d9 100644 --- a/tests/src/rules/no-unused-modules.js +++ b/tests/src/rules/no-unused-modules.js @@ -1,4 +1,4 @@ -import { test, testFilePath } from '../utils' +import { test, testFilePath, getTSParsers } from '../utils' import jsxConfig from '../../../config/react' import typescriptConfig from '../../../config/typescript' @@ -736,10 +736,81 @@ describe('correctly work with Typescript only files', () => { error(`exported declaration 'b' not used within other modules`), ], }), + test({ + options: unusedExportsTypescriptOptions, + code: `export interface c {};`, + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/typescript/file-ts-c.ts'), + errors: [ + error(`exported declaration 'c' not used within other modules`), + ], + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export type d = {};`, + parser: require.resolve('babel-eslint'), + filename: testFilePath('./no-unused-modules/typescript/file-ts-d.ts'), + errors: [ + error(`exported declaration 'd' not used within other modules`), + ], + }), ], }) }) +context('TypeScript', function () { + getTSParsers().forEach((parser) => { + typescriptRuleTester.run('no-unused-modules', rule, { + valid: [ + test({ + options: unusedExportsTypescriptOptions, + code: 'import a from "file-ts-a";', + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-a.ts'), + }), + ], + invalid: [ + test({ + options: unusedExportsTypescriptOptions, + code: `export const b = 2;`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-b.ts'), + errors: [ + error(`exported declaration 'b' not used within other modules`), + ], + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export interface c {};`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-c.ts'), + errors: [ + error(`exported declaration 'c' not used within other modules`), + ], + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export type d = {};`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-d.ts'), + errors: [ + error(`exported declaration 'd' not used within other modules`), + ], + }), + test({ + options: unusedExportsTypescriptOptions, + code: `export enum e { f };`, + parser: parser, + filename: testFilePath('./no-unused-modules/typescript/file-ts-e.ts'), + errors: [ + error(`exported declaration 'e' not used within other modules`), + ], + }), + ], + }) + }) +}) + describe('correctly work with JSX only files', () => { jsxRuleTester.run('no-unused-modules', rule, { valid: [