From 51655b93aadf6989dee2bb1db1462705d3e2ed22 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sun, 1 Oct 2023 01:38:37 -0400 Subject: [PATCH] feat(eslint-plugin): [no-restricted-imports] support import = require --- .../docs/rules/no-restricted-imports.md | 34 +++++++---- .../src/rules/no-restricted-imports.ts | 61 +++++++++++++------ .../tests/rules/no-restricted-imports.test.ts | 61 +++++++++++++++++++ 3 files changed, 127 insertions(+), 29 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-restricted-imports.md b/packages/eslint-plugin/docs/rules/no-restricted-imports.md index 551a2c25001a..226905a39259 100644 --- a/packages/eslint-plugin/docs/rules/no-restricted-imports.md +++ b/packages/eslint-plugin/docs/rules/no-restricted-imports.md @@ -6,7 +6,7 @@ description: 'Disallow specified modules when loaded by `import`.' > > See **https://typescript-eslint.io/rules/no-restricted-imports** for documentation. -This rule extends the base [`eslint/no-restricted-imports`](https://eslint.org/docs/rules/no-restricted-imports) rule. +This rule extends the base [`eslint/no-restricted-imports`](https://eslint.org/docs/rules/no-restricted-imports) rule. It adds support for the type import (`import type X from "..."`, `import { type X } from "..."`) and `import x = require("...")` syntaxes. ## Options @@ -19,17 +19,27 @@ This rule adds the following options: You can specify this option for a specific path or pattern as follows: ```jsonc -"@typescript-eslint/no-restricted-imports": ["error", { - "paths": [{ - "name": "import-foo", - "message": "Please use import-bar instead.", - "allowTypeImports": true - }, { - "name": "import-baz", - "message": "Please use import-quux instead.", - "allowTypeImports": true - }] -}] +{ + "rules": { + "@typescript-eslint/no-restricted-imports": [ + "error", + { + "paths": [ + { + "name": "import-foo", + "message": "Please use import-bar instead.", + "allowTypeImports": true + }, + { + "name": "import-baz", + "message": "Please use import-quux instead.", + "allowTypeImports": true + } + ] + } + ] + } +} ``` When set to `true`, the rule will allow [Type-Only Imports](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export). diff --git a/packages/eslint-plugin/src/rules/no-restricted-imports.ts b/packages/eslint-plugin/src/rules/no-restricted-imports.ts index c629a4095d13..af99250310ce 100644 --- a/packages/eslint-plugin/src/rules/no-restricted-imports.ts +++ b/packages/eslint-plugin/src/rules/no-restricted-imports.ts @@ -269,28 +269,55 @@ export default createRule({ ); } - return { - ImportDeclaration(node: TSESTree.ImportDeclaration): void { + function checkImportNode(node: TSESTree.ImportDeclaration): void { + if ( + node.importKind === 'type' || + (node.specifiers.length > 0 && + node.specifiers.every( + specifier => + specifier.type === AST_NODE_TYPES.ImportSpecifier && + specifier.importKind === 'type', + )) + ) { + const importSource = node.source.value.trim(); if ( - node.importKind === 'type' || - (node.specifiers.length > 0 && - node.specifiers.every( - specifier => - specifier.type === AST_NODE_TYPES.ImportSpecifier && - specifier.importKind === 'type', - )) + !isAllowedTypeImportPath(importSource) && + !isAllowedTypeImportPattern(importSource) ) { - const importSource = node.source.value.trim(); - if ( - !isAllowedTypeImportPath(importSource) && - !isAllowedTypeImportPattern(importSource) - ) { - return rules.ImportDeclaration(node); - } - } else { return rules.ImportDeclaration(node); } + } else { + return rules.ImportDeclaration(node); + } + } + + return { + TSImportEqualsDeclaration( + node: TSESTree.TSImportEqualsDeclaration, + ): void { + if ( + node.moduleReference.type === + AST_NODE_TYPES.TSExternalModuleReference && + node.moduleReference.expression.type === AST_NODE_TYPES.Literal && + typeof node.moduleReference.expression.value === 'string' + ) { + const synthesizedImport = { + ...node, + type: AST_NODE_TYPES.ImportDeclaration, + source: node.moduleReference.expression, + assertions: [], + specifiers: [ + { + ...node.id, + type: AST_NODE_TYPES.ImportDefaultSpecifier, + local: node.id, + }, + ], + } satisfies TSESTree.ImportDeclaration; + return checkImportNode(synthesizedImport); + } }, + ImportDeclaration: checkImportNode, 'ExportNamedDeclaration[source]'( node: TSESTree.ExportNamedDeclaration & { source: NonNullable; diff --git a/packages/eslint-plugin/tests/rules/no-restricted-imports.test.ts b/packages/eslint-plugin/tests/rules/no-restricted-imports.test.ts index 2113ad11601e..fcbdcd625caa 100644 --- a/packages/eslint-plugin/tests/rules/no-restricted-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/no-restricted-imports.test.ts @@ -10,11 +10,16 @@ const ruleTester = new RuleTester({ ruleTester.run('no-restricted-imports', rule, { valid: [ "import foo from 'foo';", + "import foo = require('foo');", "import 'foo';", { code: "import foo from 'foo';", options: ['import1', 'import2'], }, + { + code: "import foo = require('foo');", + options: ['import1', 'import2'], + }, { code: "export { foo } from 'foo';", options: ['import1', 'import2'], @@ -147,6 +152,20 @@ ruleTester.run('no-restricted-imports', rule, { }, ], }, + { + code: "import foo = require('foo');", + options: [ + { + paths: [ + { + name: 'foo', + importNames: ['foo'], + message: 'Please use Bar from /import-bar/baz/ instead.', + }, + ], + }, + ], + }, { code: "import type foo from 'import-foo';", options: [ @@ -161,6 +180,20 @@ ruleTester.run('no-restricted-imports', rule, { }, ], }, + { + code: "import type _ = require('import-foo');", + options: [ + { + paths: [ + { + name: 'import-foo', + message: 'Please use import-bar instead.', + allowTypeImports: true, + }, + ], + }, + ], + }, { code: "import type { Bar } from 'import-foo';", options: [ @@ -301,6 +334,15 @@ import type { foo } from 'import2/private/bar'; }, ], }, + { + code: "import foo = require('import1');", + options: ['import1', 'import2'], + errors: [ + { + messageId: 'path', + }, + ], + }, { code: "export { foo } from 'import1';", options: ['import1', 'import2'], @@ -552,6 +594,25 @@ import type { foo } from 'import2/private/bar'; }, ], }, + { + code: "import foo = require('import-foo');", + options: [ + { + paths: [ + { + name: 'import-foo', + message: 'Please use import-bar instead.', + allowTypeImports: true, + }, + ], + }, + ], + errors: [ + { + messageId: 'pathWithCustomMessage', + }, + ], + }, { code: "import { Bar } from 'import-foo';", options: [