From 1c1f9f46b05ff8cf777c6555994b30c0e47da8ef Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 1 Feb 2024 22:22:41 +1030 Subject: [PATCH 1/9] feat(eslint-plugin): [consistent-type-imports] ignore files with decorators, experimentalDecorators, and emitDecoratorMetadata --- docs/packages/Parser.mdx | 7 + .../docs/rules/consistent-type-imports.md | 65 +- .../src/rules/consistent-type-imports.ts | 589 +-- .../rules/consistent-type-imports.test.ts | 3907 ++++++++--------- packages/parser/src/parser.ts | 13 +- packages/rule-tester/package.json | 3 +- .../rule-tester/src/types/InvalidTestCase.ts | 2 +- .../rule-tester/src/types/ValidTestCase.ts | 2 +- packages/scope-manager/src/ScopeManager.ts | 2 +- packages/scope-manager/src/analyze.ts | 8 +- .../src/referencer/ClassVisitor.ts | 69 +- .../src/referencer/Referencer.ts | 10 +- .../tests/eslint-scope/references.test.ts | 116 - packages/scope-manager/tests/fixtures.test.ts | 1 - .../class/emit-metadata/accessor-deco.ts | 23 - .../class/emit-metadata/accessor-deco.ts.shot | 470 -- .../class/emit-metadata/method-deco.ts | 11 - .../class/emit-metadata/method-deco.ts.shot | 291 -- .../emit-metadata/method-return-generic.ts | 9 - .../method-return-generic.ts.shot | 191 - .../class/emit-metadata/nested-class-both.ts | 15 - .../emit-metadata/nested-class-both.ts.shot | 298 -- .../class/emit-metadata/nested-class-inner.ts | 14 - .../emit-metadata/nested-class-inner.ts.shot | 288 -- .../class/emit-metadata/nested-class-outer.ts | 14 - .../emit-metadata/nested-class-outer.ts.shot | 289 -- .../class/emit-metadata/parameters-deco.ts | 14 - .../emit-metadata/parameters-deco.ts.shot | 344 -- .../class/emit-metadata/property-deco.ts | 13 - .../class/emit-metadata/property-deco.ts.shot | 205 - packages/types/src/parser-options.ts | 2 + .../src/createParserServices.ts | 17 +- .../typescript-estree/src/parser-options.ts | 10 +- .../lib/__snapshots__/parse.test.ts.snap | 36 + .../tests/lib/semanticInfo-singleRun.test.ts | 3 + .../src/components/linter/createParser.ts | 4 + yarn.lock | 1 + 37 files changed, 2325 insertions(+), 5031 deletions(-) delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts.shot delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/method-return-generic.ts delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/method-return-generic.ts.shot delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts.shot delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts.shot delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts.shot delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts.shot delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts delete mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts.shot diff --git a/docs/packages/Parser.mdx b/docs/packages/Parser.mdx index 5d5860493103..a77a4ff0d93e 100644 --- a/docs/packages/Parser.mdx +++ b/docs/packages/Parser.mdx @@ -40,6 +40,7 @@ interface ParserOptions { }; ecmaVersion?: number | 'latest'; emitDecoratorMetadata?: boolean; + experimentalDecorators?: boolean; extraFileExtensions?: string[]; jsDocParsingMode?: 'all' | 'none' | 'type-info'; jsxFragmentName?: string | null; @@ -128,6 +129,12 @@ Specifies the version of ECMAScript syntax you want to use. This is used by the This option allow you to tell parser to act as if `emitDecoratorMetadata: true` is set in `tsconfig.json`, but without [type-aware linting](../linting/Typed_Linting.mdx). In other words, you don't have to specify `parserOptions.project` in this case, making the linting process faster. +### `experimentalDecorators` + +> Default `undefined`. + +This option allow you to tell parser to act as if `experimentalDecorators: true` is set in `tsconfig.json`, but without [type-aware linting](../linting/Typed_Linting.mdx). In other words, you don't have to specify `parserOptions.project` in this case, making the linting process faster. + ### `extraFileExtensions` > Default `undefined`. diff --git a/packages/eslint-plugin/docs/rules/consistent-type-imports.md b/packages/eslint-plugin/docs/rules/consistent-type-imports.md index 496172f342e3..e4853e6f1430 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-imports.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-imports.md @@ -88,11 +88,70 @@ type T = import('Foo').Foo; const x: import('Bar') = 1; ``` -## Usage with `emitDecoratorMetadata` +## Caveat: `@decorators` + `experimentalDecorators: true` + `emitDecoratorMetadata: true` -The `emitDecoratorMetadata` compiler option changes the code the TypeScript emits. In short - it causes TypeScript to create references to value imports when they are used in a type-only location. If you are using `emitDecoratorMetadata` then our tooling will require additional information in order for the rule to work correctly. +> TL;DR - the rule will **_not_** report any errors in files _that contain decorators_ when **both** `experimentalDecorators` and `emitDecoratorMetadata` are turned on. -If you are using [type-aware linting](https://typescript-eslint.io/linting/typed-linting), then you just need to ensure that the `tsconfig.json` you've configured for `parserOptions.project` has `emitDecoratorMetadata` turned on. Otherwise you can explicitly tell our tooling to analyze your code as if the compiler option was turned on [by setting `parserOptions.emitDecoratorMetadata` to `true`](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/README.md#parseroptionsemitdecoratormetadata). +When both the compiler options `experimentalDecorators` and `emitDecoratorMetadata` are turned on and a class is annotated with decorators, TypeScript will emit runtime metadata for the class that captures the property types, method parameter types, and method return types. This runtime code is derived using type information - meaning that the runtime code emitted changes based on the cross-file type information that TypeScript has computed. + +This behavior is problematic for the style enforced by this lint rule because it also means that annotating an import with `type` can change the runtime metadata! For example in the following snippet TS: + +```ts +import Foo from 'foo'; +import decorator from 'decorator'; + +class Clazz { + @decorator + method(arg: Foo) {} +} +``` + +TS will emit metadata for `method`'s `arg` to annotate its type: + +- If `Foo` is a value (eg a class declaration), then TS will annotate the type of `arg` as `Foo` + - Put another way - there is an invisible runtime reference to `Foo`! +- If `Foo` is a type (eg an interface), then TS will annotate the type of `arg` as either `Object`, `String`, etc - depending on what the type is. + +Syntactically it _looks_ like the `Foo` import is a type-only import because it's only used in a type location. However if we annotate the `Foo` import as `type` then it means that the runtime code always falls into the latter case of a type - which changes the runtime metadata! This is a problem for the rule because the rule is not type-aware - so it can't determine if an import is a type or a value - it works purely based on how the import is used. + +In the past we tried to solve this problem by enforcing that imported names that are used in decorator metadata are specifically _not_ marked as type-only imports to ensure that values are correctly emitted in the runtime code. + +However things get further complicated if you also turn on `isolatedModules` because TS will start enforcing that your imported types are marked as type-only. So to satisfy this the rule needs to enforce that imported values used in decorator metadata are imported _without_ a `type` qualifier, and imported types that are used in decorator metadata are imported _with_ a `type` qualifier. So our above solution of always removing the `type` doesn't work, and instead we need type-information to correctly report errors and fix code. + +Ultimately we decided to just opt-out of handling this use-case entirely to avoid accidentally reporting the wrong thing and fixing to code that either fails to compile or alters the emitted runtime metadata. If you'd like more information please [check out the related issue and its discussion](https://github.com/typescript-eslint/typescript-eslint/issues/5468). + +To be clear this means the following - the rule will **_not_** report any errors in files _that contain decorators_ when **both** `experimentalDecorators` and `emitDecoratorMetadata` are turned on. + +If you are working in such a workspace and want to correctly have your imports consistently marked with `type`, we suggest using the [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax) compiler option which will use type information to correctly enforce that types are marked with `type` and values are not when they are used in decorator metadata. + +### Note: TypeScript v5.0 (+ v5.2) decorators + +[TypeScript v5.0](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators) added support for the latest stable decorator proposal. [TypeScript v5.2](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata) added support for the latest stable decorator metadata proposal. If you are writing decorators on TS v5.2+ with `emitDecoratorMetadata: true` and `experimentalDecorators: false` then you are using the new decorator metadata support. + +The new decorator metadata does not include any annotations derived from types -- meaning it does not have any runtime side-effects. This means that the rule will report on these files as normal because changing an import to a type-only import will not have side-effects on the runtime code. + +### Configuring `experimentalDecorators` and `emitDecoratorMetadata` for the rule + +If you are using [type-aware linting](https://typescript-eslint.io/linting/typed-linting) then we will automatically infer your setup from your tsconfig and you should not need to configure anything. + +Otherwise you can explicitly tell our tooling to analyze your code as if the compiler option was turned on by setting both [`parserOptions.emitDecoratorMetadata = true`](https://typescript-eslint.io/packages/parser/#emitdecoratormetadata) and [`parserOptions.experimentalDecorators = true`](https://typescript-eslint.io/packages/parser/#experimentaldecorators). + +## Comparison with `importsNotUsedAsValues` / `verbatimModuleSyntax` + +[`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax) was introduced in TypeScript v5.0 (as a replacement for `importsNotUsedAsValues`). That being said we **do not** recommend that you use both this rule and `verbatimModuleSyntax` / `importsNotUsedAsValues` together at the same time. + +As a comparison both this rule and `verbatimModuleSyntax` should _mostly_ behave in the same way. The main differences being: + +- This rule includes an auto-fixer which can be applied from the ESLint CLI `--fix` flag or via your IDE's auto-fix-on-save feature. +- `verbatimModuleSyntax` will error on unused imports, where as this rule will ignore them +- `verbatimModuleSyntax` will work correctly when paired with both `experimentalDecorators` and `emitDecoratorMetadata` +- `verbatimModuleSyntax` will break your build if you have [`noEmitOnError`](https://www.typescriptlang.org/tsconfig#noEmitOnError) turned on +- `verbatimModuleSyntax` can change how imports are emitted from your build. + - Given the code `import { type T } from 'T';`: + - With `verbatimModuleSyntax: true` TS will emit `import {} from 'T'`. + - With `verbatimModuleSyntax: false` TS will emit nothing (it will "elide" the entire import). + +Because there are some differences - using both this rule and `verbatimModuleSyntax` at the same time can lead to conflicting errors. As such we recommend that you only ever use one _or_ the other -- never both. ## When Not To Use It diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index 4ac3c011746d..f8bfd1fb2f56 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -1,5 +1,6 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import type { RuleListener } from '@typescript-eslint/utils/eslint-utils'; import { getDeclaredVariables, getSourceCode, @@ -8,6 +9,7 @@ import { import { createRule, formatWordList, + getParserServices, isClosingBraceToken, isCommaToken, isImportKeyword, @@ -47,11 +49,9 @@ interface ReportValueImport { } type MessageIds = - | 'aImportInDecoMeta' | 'aImportIsOnlyTypes' | 'noImportTypeAnnotations' | 'someImportsAreOnlyTypes' - | 'someImportsInDecoMeta' | 'typeOverValue' | 'valueOverType'; export default createRule({ @@ -67,10 +67,6 @@ export default createRule({ someImportsAreOnlyTypes: 'Imports {{typeImports}} are only used as types.', aImportIsOnlyTypes: 'Import {{typeImports}} is only used as types.', - someImportsInDecoMeta: - 'Type imports {{typeImports}} are used by decorator metadata.', - aImportInDecoMeta: - 'Type import {{typeImports}} is used by decorator metadata.', valueOverType: 'Use an `import` instead of an `import type`.', noImportTypeAnnotations: '`import()` type annotations are forbidden.', }, @@ -107,305 +103,334 @@ export default createRule({ create(context, [option]) { const prefer = option.prefer ?? 'type-imports'; const disallowTypeAnnotations = option.disallowTypeAnnotations !== false; - const fixStyle = option.fixStyle ?? 'separate-type-imports'; const sourceCode = getSourceCode(context); + const selectors: RuleListener = {}; + + if (disallowTypeAnnotations) { + selectors.TSImportType = (node): void => { + context.report({ + node, + messageId: 'noImportTypeAnnotations', + }); + }; + } + + if (prefer === 'no-type-imports') { + // prefer no type imports + return { + ...selectors, + 'ImportDeclaration[importKind = "type"]'( + node: TSESTree.ImportDeclaration, + ): void { + context.report({ + node, + messageId: 'valueOverType', + fix(fixer) { + return fixRemoveTypeSpecifierFromImportDeclaration(fixer, node); + }, + }); + }, + 'ImportSpecifier[importKind = "type"]'( + node: TSESTree.ImportSpecifier, + ): void { + context.report({ + node, + messageId: 'valueOverType', + fix(fixer) { + return fixRemoveTypeSpecifierFromImportSpecifier(fixer, node); + }, + }); + }, + }; + } + + // prefer type imports + const fixStyle = option.fixStyle ?? 'separate-type-imports'; + + let hasDecoratorMetadata = false; const sourceImportsMap: Record = {}; + const emitDecoratorMetadata = + getParserServices(context, true).emitDecoratorMetadata ?? false; + const experimentalDecorators = + getParserServices(context, true).experimentalDecorators ?? false; + if (experimentalDecorators && emitDecoratorMetadata) { + selectors.Decorator = (): void => { + hasDecoratorMetadata = true; + }; + } + return { - ...(prefer === 'type-imports' - ? { - // prefer type imports - ImportDeclaration(node): void { - const source = node.source.value; - // sourceImports is the object containing all the specifics for a particular import source, type or value - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - sourceImportsMap[source] ??= { - source, - reportValueImports: [], // if there is a mismatch where type importKind but value specifiers - typeOnlyNamedImport: null, // if only type imports - valueOnlyNamedImport: null, // if only value imports with named specifiers - valueImport: null, // if only value imports - }; - const sourceImports = sourceImportsMap[source]; - if (node.importKind === 'type') { - if ( - !sourceImports.typeOnlyNamedImport && - node.specifiers.every( - specifier => - specifier.type === AST_NODE_TYPES.ImportSpecifier, - ) - ) { - // definitely import type { TypeX } - sourceImports.typeOnlyNamedImport = node; - } - } else { - if ( - !sourceImports.valueOnlyNamedImport && - node.specifiers.every( - specifier => - specifier.type === AST_NODE_TYPES.ImportSpecifier, - ) - ) { - sourceImports.valueOnlyNamedImport = node; - sourceImports.valueImport = node; - } else if ( - !sourceImports.valueImport && - node.specifiers.some( - specifier => - specifier.type === AST_NODE_TYPES.ImportDefaultSpecifier, - ) - ) { - sourceImports.valueImport = node; - } - } + ...selectors, + + ImportDeclaration(node): void { + const source = node.source.value; + // sourceImports is the object containing all the specifics for a particular import source, type or value + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + sourceImportsMap[source] ??= { + source, + reportValueImports: [], // if there is a mismatch where type importKind but value specifiers + typeOnlyNamedImport: null, // if only type imports + valueOnlyNamedImport: null, // if only value imports with named specifiers + valueImport: null, // if only value imports + }; + const sourceImports = sourceImportsMap[source]; + if (node.importKind === 'type') { + if ( + !sourceImports.typeOnlyNamedImport && + node.specifiers.every( + specifier => specifier.type === AST_NODE_TYPES.ImportSpecifier, + ) + ) { + // definitely import type { TypeX } + sourceImports.typeOnlyNamedImport = node; + } + } else { + if ( + !sourceImports.valueOnlyNamedImport && + node.specifiers.every( + specifier => specifier.type === AST_NODE_TYPES.ImportSpecifier, + ) + ) { + sourceImports.valueOnlyNamedImport = node; + sourceImports.valueImport = node; + } else if ( + !sourceImports.valueImport && + node.specifiers.some( + specifier => + specifier.type === AST_NODE_TYPES.ImportDefaultSpecifier, + ) + ) { + sourceImports.valueImport = node; + } + } - const typeSpecifiers: TSESTree.ImportClause[] = []; - const inlineTypeSpecifiers: TSESTree.ImportSpecifier[] = []; - const valueSpecifiers: TSESTree.ImportClause[] = []; - const unusedSpecifiers: TSESTree.ImportClause[] = []; - for (const specifier of node.specifiers) { - if ( - specifier.type === AST_NODE_TYPES.ImportSpecifier && - specifier.importKind === 'type' - ) { - inlineTypeSpecifiers.push(specifier); - continue; + const typeSpecifiers: TSESTree.ImportClause[] = []; + const inlineTypeSpecifiers: TSESTree.ImportSpecifier[] = []; + const valueSpecifiers: TSESTree.ImportClause[] = []; + const unusedSpecifiers: TSESTree.ImportClause[] = []; + for (const specifier of node.specifiers) { + if ( + specifier.type === AST_NODE_TYPES.ImportSpecifier && + specifier.importKind === 'type' + ) { + inlineTypeSpecifiers.push(specifier); + continue; + } + + const [variable] = getDeclaredVariables(context, specifier); + if (variable.references.length === 0) { + unusedSpecifiers.push(specifier); + } else { + const onlyHasTypeReferences = variable.references.every(ref => { + /** + * keep origin import kind when export + * export { Type } + * export default Type; + */ + if ( + ref.identifier.parent.type === AST_NODE_TYPES.ExportSpecifier || + ref.identifier.parent.type === + AST_NODE_TYPES.ExportDefaultDeclaration || + ref.identifier.parent.type === AST_NODE_TYPES.TSExportAssignment + ) { + if (ref.isValueReference && ref.isTypeReference) { + return node.importKind === 'type'; } + } + if (ref.isValueReference) { + let parent = ref.identifier.parent as TSESTree.Node | undefined; + let child: TSESTree.Node = ref.identifier; + while (parent) { + switch (parent.type) { + // CASE 1: + // `type T = typeof foo` will create a value reference because "foo" must be a value type + // however this value reference is safe to use with type-only imports + case AST_NODE_TYPES.TSTypeQuery: + return true; - const [variable] = getDeclaredVariables(context, specifier); - if (variable.references.length === 0) { - unusedSpecifiers.push(specifier); - } else { - const onlyHasTypeReferences = variable.references.every( - ref => { - /** - * keep origin import kind when export - * export { Type } - * export default Type; - * export = Type; - */ - if ( - ref.identifier.parent.type === - AST_NODE_TYPES.ExportSpecifier || - ref.identifier.parent.type === - AST_NODE_TYPES.ExportDefaultDeclaration || - ref.identifier.parent.type === - AST_NODE_TYPES.TSExportAssignment - ) { - if (ref.isValueReference && ref.isTypeReference) { - return node.importKind === 'type'; - } + case AST_NODE_TYPES.TSQualifiedName: + // TSTypeQuery must have a TSESTree.EntityName as its child, so we can filter here and break early + if (parent.left !== child) { + return false; } - if (ref.isValueReference) { - let parent = ref.identifier.parent as - | TSESTree.Node - | undefined; - let child: TSESTree.Node = ref.identifier; - while (parent) { - switch (parent.type) { - // CASE 1: - // `type T = typeof foo` will create a value reference because "foo" must be a value type - // however this value reference is safe to use with type-only imports - case AST_NODE_TYPES.TSTypeQuery: - return true; - - case AST_NODE_TYPES.TSQualifiedName: - // TSTypeQuery must have a TSESTree.EntityName as its child, so we can filter here and break early - if (parent.left !== child) { - return false; - } - child = parent; - parent = parent.parent; - continue; - // END CASE 1 - - ////////////// - - // CASE 2: - // `type T = { [foo]: string }` will create a value reference because "foo" must be a value type - // however this value reference is safe to use with type-only imports. - // Also this is represented as a non-type AST - hence it uses MemberExpression - case AST_NODE_TYPES.TSPropertySignature: - return parent.key === child; - - case AST_NODE_TYPES.MemberExpression: - if (parent.object !== child) { - return false; - } - child = parent; - parent = parent.parent; - continue; - // END CASE 2 - - default: - return false; - } - } + child = parent; + parent = parent.parent; + continue; + // END CASE 1 + + ////////////// + + // CASE 2: + // `type T = { [foo]: string }` will create a value reference because "foo" must be a value type + // however this value reference is safe to use with type-only imports. + // Also this is represented as a non-type AST - hence it uses MemberExpression + case AST_NODE_TYPES.TSPropertySignature: + return parent.key === child; + + case AST_NODE_TYPES.MemberExpression: + if (parent.object !== child) { + return false; } + child = parent; + parent = parent.parent; + continue; + // END CASE 2 - return ref.isTypeReference; - }, - ); - if (onlyHasTypeReferences) { - typeSpecifiers.push(specifier); - } else { - valueSpecifiers.push(specifier); + default: + return false; } } } - if ( - (node.importKind === 'value' && typeSpecifiers.length) || - (node.importKind === 'type' && valueSpecifiers.length) - ) { - sourceImports.reportValueImports.push({ - node, - typeSpecifiers, - valueSpecifiers, - unusedSpecifiers, - inlineTypeSpecifiers, + return ref.isTypeReference; + }); + if (onlyHasTypeReferences) { + typeSpecifiers.push(specifier); + } else { + valueSpecifiers.push(specifier); + } + } + } + + if ( + (node.importKind === 'value' && typeSpecifiers.length) || + (node.importKind === 'type' && valueSpecifiers.length) + ) { + sourceImports.reportValueImports.push({ + node, + typeSpecifiers, + valueSpecifiers, + unusedSpecifiers, + inlineTypeSpecifiers, + }); + } + }, + + 'Program:exit'(): void { + if (hasDecoratorMetadata) { + // Decorator metadata is bowl of poop that cannot be supported based + // on pure syntactic analysis. + // + // So we can do one of two things: + // 1) add type-information to the rule in a breaking change and + // prevent users from using it so that we can fully support this + // case. + // 2) make the rule ignore all imports that are used in a file that + // might have decorator metadata. + // + // (1) is has huge impact and prevents the rule from being used by 99% + // of users Frankly - it's a straight-up bad option. So instead we + // choose with option (2) and just avoid reporting on any imports in a + // file with both emitDecoratorMetadata AND decorators + // + // For more context see the discussion in this issue and its linked + // issues: + // https://github.com/typescript-eslint/typescript-eslint/issues/5468 + // + // + // NOTE - in TS 5.0 `experimentalDecorators` became the legacy option, + // replaced with un-flagged, stable decorators and thus the type-aware + // emitDecoratorMetadata implementation also became legacy. in TS 5.2 + // support for the new, stable decorator metadata proposal was added - + // however this proposal does not include type information + // + // + // PHEW. So TL;DR what does all this mean? + // - if you use experimentalDecorators:true, + // emitDecoratorMetadata:true, and have a decorator in the file - + // the rule will do nothing in the file out of an abundance of + // caution. + // - else the rule will work as normal. + return; + } + + for (const sourceImports of Object.values(sourceImportsMap)) { + if (sourceImports.reportValueImports.length === 0) { + // nothing to fix. value specifiers and type specifiers are correctly written + continue; + } + for (const report of sourceImports.reportValueImports) { + if ( + report.valueSpecifiers.length === 0 && + report.unusedSpecifiers.length === 0 && + report.node.importKind !== 'type' + ) { + /** + * checks if import has type assertions + * ``` + * import * as type from 'mod' assert { type: 'json' }; + * ``` + * https://github.com/typescript-eslint/typescript-eslint/issues/7527 + */ + if (report.node.attributes.length === 0) { + context.report({ + node: report.node, + messageId: 'typeOverValue', + *fix(fixer) { + yield* fixToTypeImportDeclaration( + fixer, + report, + sourceImports, + ); + }, }); } - }, - 'Program:exit'(): void { - for (const sourceImports of Object.values(sourceImportsMap)) { - if (sourceImports.reportValueImports.length === 0) { - // nothing to fix. value specifiers and type specifiers are correctly written - continue; + } else { + const isTypeImport = report.node.importKind === 'type'; + + // we have a mixed type/value import or just value imports, so we need to split them out into multiple imports if separate-type-imports is configured + const importNames = ( + isTypeImport + ? report.valueSpecifiers // import type { A } from 'roo'; // WHERE A is used in value position + : report.typeSpecifiers + ) // import { A, B } from 'roo'; // WHERE A is used in type position and B is in value position + .map(specifier => `"${specifier.local.name}"`); + + const message = ((): { + messageId: MessageIds; + data: Record; + } => { + const typeImports = formatWordList(importNames); + + if (importNames.length === 1) { + return { + messageId: 'aImportIsOnlyTypes', + data: { typeImports }, + }; } - for (const report of sourceImports.reportValueImports) { - if ( - report.valueSpecifiers.length === 0 && - report.unusedSpecifiers.length === 0 && - report.node.importKind !== 'type' - ) { - /** - * checks if import has type assertions - * ``` - * import * as type from 'mod' assert { type: 'json' }; - * ``` - * https://github.com/typescript-eslint/typescript-eslint/issues/7527 - */ - if (report.node.attributes.length === 0) { - context.report({ - node: report.node, - messageId: 'typeOverValue', - *fix(fixer) { - yield* fixToTypeImportDeclaration( - fixer, - report, - sourceImports, - ); - }, - }); - } + return { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports }, // typeImports are all the type specifiers in the value position + }; + })(); + + context.report({ + node: report.node, + ...message, + *fix(fixer) { + if (isTypeImport) { + // take all the valueSpecifiers and put them on a new line + yield* fixToValueImportDeclaration( + fixer, + report, + sourceImports, + ); } else { - const isTypeImport = report.node.importKind === 'type'; - - // we have a mixed type/value import or just value imports, so we need to split them out into multiple imports if separate-type-imports is configured - const importNames = ( - isTypeImport - ? report.valueSpecifiers // import type { A } from 'roo'; // WHERE A is used in value position - : report.typeSpecifiers - ) // import { A, B } from 'roo'; // WHERE A is used in type position and B is in value position - .map(specifier => `"${specifier.local.name}"`); - - const message = ((): { - messageId: MessageIds; - data: Record; - } => { - const typeImports = formatWordList(importNames); - - if (importNames.length === 1) { - if (isTypeImport) { - return { - messageId: 'aImportInDecoMeta', - data: { typeImports }, - }; - } - return { - messageId: 'aImportIsOnlyTypes', - data: { typeImports }, - }; - } - if (isTypeImport) { - return { - messageId: 'someImportsInDecoMeta', - data: { typeImports }, // typeImports are all the value specifiers that are in the type position - }; - } - return { - messageId: 'someImportsAreOnlyTypes', - data: { typeImports }, // typeImports are all the type specifiers in the value position - }; - })(); - - context.report({ - node: report.node, - ...message, - *fix(fixer) { - if (isTypeImport) { - // take all the valueSpecifiers and put them on a new line - yield* fixToValueImportDeclaration( - fixer, - report, - sourceImports, - ); - } else { - // take all the typeSpecifiers and put them on a new line - yield* fixToTypeImportDeclaration( - fixer, - report, - sourceImports, - ); - } - }, - }); + // take all the typeSpecifiers and put them on a new line + yield* fixToTypeImportDeclaration( + fixer, + report, + sourceImports, + ); } - } - } - }, - } - : { - // prefer no type imports - 'ImportDeclaration[importKind = "type"]'( - node: TSESTree.ImportDeclaration, - ): void { - context.report({ - node, - messageId: 'valueOverType', - fix(fixer) { - return fixRemoveTypeSpecifierFromImportDeclaration( - fixer, - node, - ); }, }); - }, - 'ImportSpecifier[importKind = "type"]'( - node: TSESTree.ImportSpecifier, - ): void { - context.report({ - node, - messageId: 'valueOverType', - fix(fixer) { - return fixRemoveTypeSpecifierFromImportSpecifier(fixer, node); - }, - }); - }, - }), - ...(disallowTypeAnnotations - ? { - // disallow `import()` type - TSImportType(node: TSESTree.TSImportType): void { - context.report({ - node, - messageId: 'noImportTypeAnnotations', - }); - }, + } } - : {}), + } + }, }; function classifySpecifier(node: TSESTree.ImportDeclaration): { diff --git a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts index a9956f09fc4f..82a3fb3dad4d 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -1,1085 +1,842 @@ import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/consistent-type-imports'; -import { getFixturesRootDir } from '../RuleTester'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', +const PARSER_OPTION_COMBOS = [ + { + experimentalDecorators: false, + emitDecoratorMetadata: false, }, - // type-only imports were first added in TS3.8 - dependencyConstraints: { - typescript: '3.8', + { + experimentalDecorators: false, + emitDecoratorMetadata: true, }, -}); - -const withMetaParserOptions = { - EXPERIMENTAL_useProjectService: false, - tsconfigRootDir: getFixturesRootDir(), - project: './tsconfig-withmeta.json', -}; - -const withMetaConfigParserOptions = { - emitDecoratorMetadata: true, -}; - -ruleTester.run('consistent-type-imports', rule, { - valid: [ - ` - import Foo from 'foo'; - const foo: Foo = new Foo(); - `, - ` - import foo from 'foo'; - const foo: foo.Foo = foo.fn(); - `, - ` - import { A, B } from 'foo'; - const foo: A = B(); - const bar = new A(); - `, - ` - import Foo from 'foo'; - `, - ` - import Foo from 'foo'; - type T = Foo; // shadowing - `, - ` - import Foo from 'foo'; - function fn() { - type Foo = {}; // shadowing - let foo: Foo; - } - `, - ` - import { A, B } from 'foo'; - const b = B; - `, - ` - import { A, B, C as c } from 'foo'; - const d = c; - `, - ` - import {} from 'foo'; // empty - `, - { - code: ` - let foo: import('foo'); - let bar: import('foo').Bar; - `, - options: [{ disallowTypeAnnotations: false }], - }, - { - code: ` - import Foo from 'foo'; - let foo: Foo; - `, - options: [{ prefer: 'no-type-imports' }], - }, - // type queries - ` - import type Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - ` - import type { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - ` - import type * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - { - code: ` - import Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - }, - { - code: ` - import { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - }, - { - code: ` - import * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - }, - { - code: ` - import * as Type from 'foo' assert { type: 'json' }; - const a: typeof Type = Type; - `, - options: [{ prefer: 'no-type-imports' }], - dependencyConstraints: { - typescript: '4.5', - }, - }, - ` - import { type A } from 'foo'; - type T = A; - `, - ` - import { type A, B } from 'foo'; - type T = A; - const b = B; - `, - ` - import { type A, type B } from 'foo'; - type T = A; - type Z = B; - `, - ` - import { B } from 'foo'; - import { type A } from 'foo'; - type T = A; - const b = B; - `, - { - code: ` - import { B, type A } from 'foo'; - type T = A; - const b = B; - `, - options: [{ fixStyle: 'inline-type-imports' }], - }, - { - code: ` - import { B } from 'foo'; - import type A from 'baz'; - type T = A; - const b = B; - `, - options: [{ fixStyle: 'inline-type-imports' }], - }, - { - code: ` - import { type B } from 'foo'; - import type { A } from 'foo'; - type T = A; - const b = B; - `, - options: [{ fixStyle: 'inline-type-imports' }], - }, - { - code: ` - import { B, type C } from 'foo'; - import type A from 'baz'; - type T = A; - type Z = C; - const b = B; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - }, - { - code: ` - import { B } from 'foo'; - import type { A } from 'foo'; - type T = A; - const b = B; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - }, - { - code: ` - import { B } from 'foo'; - import { A } from 'foo'; - type T = A; - const b = B; - `, - options: [{ prefer: 'no-type-imports', fixStyle: 'inline-type-imports' }], + { + experimentalDecorators: true, + emitDecoratorMetadata: false, + }, +]; +for (const parserOptions of PARSER_OPTION_COMBOS) { + describe(`experimentalDecorators: ${parserOptions.experimentalDecorators} + emitDecoratorMetadata: ${parserOptions.emitDecoratorMetadata}`, () => { + const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + // type-only imports were first added in TS3.8 dependencyConstraints: { - typescript: '4.5', - }, - }, - // exports - ` - import Type from 'foo'; - - export { Type }; // is a value export - export default Type; // is a value export - `, - ` - import type Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - ` - import { Type } from 'foo'; - - export { Type }; // is a value export - export default Type; // is a value export - `, - ` - import type { Type } from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - ` - import * as Type from 'foo'; - - export { Type }; // is a value export - export default Type; // is a value export - `, - ` - import type * as Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - - { - code: ` - import Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - }, - { - code: ` - import { Type } from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - }, - { - code: ` - import * as Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - }, - // https://github.com/typescript-eslint/typescript-eslint/issues/2455 - { - code: ` - import React from 'react'; - - export const ComponentFoo: React.FC = () => { - return
Foo Foo
; - }; - `, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, + typescript: '3.8', }, - }, - { - code: ` - import { h } from 'some-other-jsx-lib'; - - export const ComponentFoo: h.FC = () => { - return
Foo Foo
; - }; - `, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - jsxPragma: 'h', - }, - }, - { - code: ` - import { Fragment } from 'react'; - - export const ComponentFoo: Fragment = () => { - return <>Foo Foo; - }; - `, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - jsxFragmentName: 'Fragment', - }, - }, - ` - import Default, * as Rest from 'module'; - const a: typeof Default = Default; - const b: typeof Rest = Rest; - `, - { - code: ` - import Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo: Foo; - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo(foo: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo(): Foo {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - foo(@deco foo: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - set foo(value: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - get foo() {} - - set foo(value: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - get foo() {} - - set ['foo'](value: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import type { Foo } from 'foo'; - const key = 'k'; - class A { - @deco - get [key]() {} - - set [key](value: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import * as foo from 'foo'; - @deco - class A { - constructor(foo: foo.Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo: Foo; - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo(foo: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo(): Foo {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - foo(@deco foo: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - set foo(value: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - get foo() {} - - set foo(value: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - get foo() {} - - set ['foo'](value: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import type { Foo } from 'foo'; - const key = 'k'; - class A { - @deco - get [key]() {} - - set [key](value: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import * as foo from 'foo'; - @deco - class A { - constructor(foo: foo.Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - - // https://github.com/typescript-eslint/typescript-eslint/issues/7327 - { - code: ` - import type { ClassA } from './classA'; - - export class ClassB { - public constructor(node: ClassA) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - - // https://github.com/typescript-eslint/typescript-eslint/issues/2989 - ` -import type * as constants from './constants'; - -export type Y = { - [constants.X]: ReadonlyArray; -}; - `, - ` - import A from 'foo'; - export = A; - `, - ` - import type A from 'foo'; - export = A; - `, - ` - import type A from 'foo'; - export = {} as A; - `, - ` - import { type A } from 'foo'; - export = {} as A; - `, - ], - invalid: [ - { - code: ` - import Foo from 'foo'; - let foo: Foo; - type Bar = Foo; - interface Baz { - foo: Foo; - } - function fn(a: Foo): Foo {} - `, - output: ` - import type Foo from 'foo'; - let foo: Foo; - type Bar = Foo; - interface Baz { - foo: Foo; - } - function fn(a: Foo): Foo {} - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, + parserOptions, + }); + + ruleTester.run('consistent-type-imports', rule, { + valid: [ + ` + import Foo from 'foo'; + const foo: Foo = new Foo(); + `, + ` + import foo from 'foo'; + const foo: foo.Foo = foo.fn(); + `, + ` + import { A, B } from 'foo'; + const foo: A = B(); + const bar = new A(); + `, + ` + import Foo from 'foo'; + `, + ` + import Foo from 'foo'; + type T = Foo; // shadowing + `, + ` + import Foo from 'foo'; + function fn() { + type Foo = {}; // shadowing + let foo: Foo; + } + `, + ` + import { A, B } from 'foo'; + const b = B; + `, + ` + import { A, B, C as c } from 'foo'; + const d = c; + `, + ` + import {} from 'foo'; // empty + `, + { + code: ` +let foo: import('foo'); +let bar: import('foo').Bar; + `, + options: [{ disallowTypeAnnotations: false }], + }, + { + code: ` +import Foo from 'foo'; +let foo: Foo; + `, + options: [{ prefer: 'no-type-imports' }], + }, + // type queries + ` + import type Type from 'foo'; + + type T = typeof Type; + type T = typeof Type.foo; + `, + ` + import type { Type } from 'foo'; + + type T = typeof Type; + type T = typeof Type.foo; + `, + ` + import type * as Type from 'foo'; + + type T = typeof Type; + type T = typeof Type.foo; + `, + { + code: ` +import Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + }, + { + code: ` +import { Type } from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + }, + { + code: ` +import * as Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + }, + { + code: ` +import * as Type from 'foo' assert { type: 'json' }; +const a: typeof Type = Type; + `, + options: [{ prefer: 'no-type-imports' }], + dependencyConstraints: { + typescript: '4.5', + }, + }, + ` + import { type A } from 'foo'; + type T = A; + `, + ` + import { type A, B } from 'foo'; + type T = A; + const b = B; + `, + ` + import { type A, type B } from 'foo'; + type T = A; + type Z = B; + `, + ` + import { B } from 'foo'; + import { type A } from 'foo'; + type T = A; + const b = B; + `, + { + code: ` +import { B, type A } from 'foo'; +type T = A; +const b = B; + `, + options: [{ fixStyle: 'inline-type-imports' }], }, - ], - }, - { - code: ` - import Foo from 'foo'; - let foo: Foo; - `, - output: ` - import type Foo from 'foo'; - let foo: Foo; - `, - options: [{ prefer: 'type-imports' }], - errors: [ { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import Foo from 'foo'; - let foo: Foo; - `, - output: ` - import type Foo from 'foo'; - let foo: Foo; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, + code: ` +import { B } from 'foo'; +import type A from 'baz'; +type T = A; +const b = B; + `, + options: [{ fixStyle: 'inline-type-imports' }], }, - ], - }, - { - code: ` - import { A, B } from 'foo'; - let foo: A; - let bar: B; - `, - output: ` - import type { A, B } from 'foo'; - let foo: A; - let bar: B; - `, - errors: [ { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { A as a, B as b } from 'foo'; - let foo: a; - let bar: b; - `, - output: ` - import type { A as a, B as b } from 'foo'; - let foo: a; - let bar: b; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, + code: ` +import { type B } from 'foo'; +import type { A } from 'foo'; +type T = A; +const b = B; + `, + options: [{ fixStyle: 'inline-type-imports' }], }, - ], - }, - { - code: ` - import Foo from 'foo'; - type Bar = typeof Foo; // TSTypeQuery - `, - output: ` - import type Foo from 'foo'; - type Bar = typeof Foo; // TSTypeQuery - `, - errors: [ { - messageId: 'typeOverValue', - line: 2, - column: 9, + code: ` +import { B, type C } from 'foo'; +import type A from 'baz'; +type T = A; +type Z = C; +const b = B; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], }, - ], - }, - { - code: ` - import foo from 'foo'; - type Bar = foo.Bar; // TSQualifiedName - `, - output: ` - import type foo from 'foo'; - type Bar = foo.Bar; // TSQualifiedName - `, - errors: [ { - messageId: 'typeOverValue', - line: 2, - column: 9, + code: ` +import { B } from 'foo'; +import type { A } from 'foo'; +type T = A; +const b = B; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], }, - ], - }, - { - code: ` - import foo from 'foo'; - type Baz = (typeof foo.bar)['Baz']; // TSQualifiedName & TSTypeQuery - `, - output: ` - import type foo from 'foo'; - type Baz = (typeof foo.bar)['Baz']; // TSQualifiedName & TSTypeQuery - `, - errors: [ { - messageId: 'typeOverValue', - line: 2, - column: 9, + code: ` +import { B } from 'foo'; +import { A } from 'foo'; +type T = A; +const b = B; + `, + options: [ + { prefer: 'no-type-imports', fixStyle: 'inline-type-imports' }, + ], + dependencyConstraints: { + typescript: '4.5', + }, + }, + // exports + ` + import Type from 'foo'; + + export { Type }; // is a value export + export default Type; // is a value export + `, + ` + import type Type from 'foo'; + + export { Type }; // is a type-only export + export default Type; // is a type-only export + export type { Type }; // is a type-only export + `, + ` + import { Type } from 'foo'; + + export { Type }; // is a value export + export default Type; // is a value export + `, + ` + import type { Type } from 'foo'; + + export { Type }; // is a type-only export + export default Type; // is a type-only export + export type { Type }; // is a type-only export + `, + ` + import * as Type from 'foo'; + + export { Type }; // is a value export + export default Type; // is a value export + `, + ` + import type * as Type from 'foo'; + + export { Type }; // is a type-only export + export default Type; // is a type-only export + export type { Type }; // is a type-only export + `, + + { + code: ` +import Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + }, + { + code: ` +import { Type } from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + }, + { + code: ` +import * as Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + }, + // https://github.com/typescript-eslint/typescript-eslint/issues/2455 + { + code: ` +import React from 'react'; + +export const ComponentFoo: React.FC = () => { + return
Foo Foo
; +}; + `, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, }, - ], - }, - { - code: ` - import * as A from 'foo'; - let foo: A.Foo; - `, - output: ` - import type * as A from 'foo'; - let foo: A.Foo; - `, - errors: [ { - messageId: 'typeOverValue', - line: 2, - column: 9, + code: ` +import { h } from 'some-other-jsx-lib'; + +export const ComponentFoo: h.FC = () => { + return
Foo Foo
; +}; + `, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + jsxPragma: 'h', + }, }, - ], - }, - { - // default and named - code: ` + { + code: ` +import { Fragment } from 'react'; + +export const ComponentFoo: Fragment = () => { + return <>Foo Foo; +}; + `, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + jsxFragmentName: 'Fragment', + }, + }, + ` + import Default, * as Rest from 'module'; + const a: typeof Default = Default; + const b: typeof Rest = Rest; + `, + + // https://github.com/typescript-eslint/typescript-eslint/issues/2989 + ` + import type * as constants from './constants'; + + export type Y = { + [constants.X]: ReadonlyArray; + }; + `, + ` + import A from 'foo'; + export = A; + `, + ` + import type A from 'foo'; + export = A; + `, + ` + import type A from 'foo'; + export = {} as A; + `, + ` + import { type A } from 'foo'; + export = {} as A; + `, + ], + invalid: [ + { + code: ` +import Foo from 'foo'; +let foo: Foo; +type Bar = Foo; +interface Baz { + foo: Foo; +} +function fn(a: Foo): Foo {} + `, + output: ` +import type Foo from 'foo'; +let foo: Foo; +type Bar = Foo; +interface Baz { + foo: Foo; +} +function fn(a: Foo): Foo {} + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import Foo from 'foo'; +let foo: Foo; + `, + output: ` +import type Foo from 'foo'; +let foo: Foo; + `, + options: [{ prefer: 'type-imports' }], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import Foo from 'foo'; +let foo: Foo; + `, + output: ` +import type Foo from 'foo'; +let foo: Foo; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { A, B } from 'foo'; +let foo: A; +let bar: B; + `, + output: ` +import type { A, B } from 'foo'; +let foo: A; +let bar: B; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { A as a, B as b } from 'foo'; +let foo: a; +let bar: b; + `, + output: ` +import type { A as a, B as b } from 'foo'; +let foo: a; +let bar: b; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import Foo from 'foo'; +type Bar = typeof Foo; // TSTypeQuery + `, + output: ` +import type Foo from 'foo'; +type Bar = typeof Foo; // TSTypeQuery + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import foo from 'foo'; +type Bar = foo.Bar; // TSQualifiedName + `, + output: ` +import type foo from 'foo'; +type Bar = foo.Bar; // TSQualifiedName + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import foo from 'foo'; +type Baz = (typeof foo.bar)['Baz']; // TSQualifiedName & TSTypeQuery + `, + output: ` +import type foo from 'foo'; +type Baz = (typeof foo.bar)['Baz']; // TSQualifiedName & TSTypeQuery + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import * as A from 'foo'; +let foo: A.Foo; + `, + output: ` +import type * as A from 'foo'; +let foo: A.Foo; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + // default and named + code: ` import A, { B } from 'foo'; let foo: A; let bar: B; - `, - output: ` + `, + output: ` import type { B } from 'foo'; import type A from 'foo'; let foo: A; let bar: B; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], }, - ], - }, - { - code: noFormat` - import A, {} from 'foo'; - let foo: A; - `, - output: ` - import type A from 'foo'; - let foo: A; - `, - errors: [ { - messageId: 'typeOverValue', - line: 2, - column: 9, + code: noFormat` +import A, {} from 'foo'; +let foo: A; + `, + output: ` +import type A from 'foo'; +let foo: A; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], }, - ], - }, - { - code: ` + { + code: ` import { A, B } from 'foo'; const foo: A = B(); - `, - output: ` + `, + output: ` import type { A} from 'foo'; import { B } from 'foo'; const foo: A = B(); - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"A"' }, - line: 2, - column: 1, + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"A"' }, + line: 2, + }, + ], }, - ], - }, - { - code: ` + { + code: ` import { A, B, C } from 'foo'; const foo: A = B(); let bar: C; - `, - output: ` + `, + output: ` import type { A, C } from 'foo'; import { B } from 'foo'; const foo: A = B(); let bar: C; - `, - errors: [ - { - messageId: 'someImportsAreOnlyTypes', - data: { typeImports: '"A" and "C"' }, - line: 2, - column: 1, + `, + errors: [ + { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports: '"A" and "C"' }, + line: 2, + }, + ], }, - ], - }, - { - code: ` + { + code: ` import { A, B, C, D } from 'foo'; const foo: A = B(); type T = { bar: C; baz: D }; - `, - output: ` + `, + output: ` import type { A, C, D } from 'foo'; import { B } from 'foo'; const foo: A = B(); type T = { bar: C; baz: D }; - `, - errors: [ - { - messageId: 'someImportsAreOnlyTypes', - data: { typeImports: '"A", "C" and "D"' }, - line: 2, - column: 1, + `, + errors: [ + { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports: '"A", "C" and "D"' }, + line: 2, + }, + ], }, - ], - }, - { - code: ` + { + code: ` import A, { B, C, D } from 'foo'; B(); type T = { foo: A; bar: C; baz: D }; - `, - output: ` + `, + output: ` import type { C, D } from 'foo'; import type A from 'foo'; import { B } from 'foo'; B(); type T = { foo: A; bar: C; baz: D }; - `, - errors: [ - { - messageId: 'someImportsAreOnlyTypes', - data: { typeImports: '"A", "C" and "D"' }, - line: 2, - column: 1, + `, + errors: [ + { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports: '"A", "C" and "D"' }, + line: 2, + }, + ], }, - ], - }, - { - code: ` + { + code: ` import A, { B } from 'foo'; B(); type T = A; - `, - output: ` + `, + output: ` import type A from 'foo'; import { B } from 'foo'; B(); type T = A; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"A"' }, - line: 2, - column: 1, - }, - ], - }, - { - code: ` - import type Already1Def from 'foo'; - import type { Already1 } from 'foo'; - import A, { B } from 'foo'; - import { C, D, E } from 'bar'; - import type { Already2 } from 'bar'; - type T = { b: B; c: C; d: D }; - `, - output: ` - import type Already1Def from 'foo'; - import type { Already1 , B } from 'foo'; - import A from 'foo'; - import { E } from 'bar'; - import type { Already2 , C, D} from 'bar'; - type T = { b: B; c: C; d: D }; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"B"' }, - line: 4, - column: 9, - }, - { - messageId: 'someImportsAreOnlyTypes', - data: { typeImports: '"C" and "D"' }, - line: 5, - column: 9, - }, - ], - }, - { - code: ` + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"A"' }, + line: 2, + }, + ], + }, + { + code: ` +import type Already1Def from 'foo'; +import type { Already1 } from 'foo'; +import A, { B } from 'foo'; +import { C, D, E } from 'bar'; +import type { Already2 } from 'bar'; +type T = { b: B; c: C; d: D }; + `, + output: ` +import type Already1Def from 'foo'; +import type { Already1 , B } from 'foo'; +import A from 'foo'; +import { E } from 'bar'; +import type { Already2 , C, D} from 'bar'; +type T = { b: B; c: C; d: D }; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"B"' }, + line: 4, + }, + { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports: '"C" and "D"' }, + line: 5, + }, + ], + }, + { + code: ` import A, { /* comment */ B } from 'foo'; type T = B; - `, - output: ` + `, + output: ` import type { /* comment */ B } from 'foo'; import A from 'foo'; type T = B; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"B"' }, - line: 2, - column: 1, + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"B"' }, + line: 2, + }, + ], }, - ], - }, - { - code: noFormat` + { + code: noFormat` import { A, B, C } from 'foo'; import { D, E, F, } from 'bar'; type T = A | D; - `, - output: ` + `, + output: ` import type { A} from 'foo'; import { B, C } from 'foo'; import type { D} from 'bar'; import { E, F, } from 'bar'; type T = A | D; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"A"' }, - line: 2, - column: 1, - }, - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"D"' }, - line: 3, - column: 1, - }, - ], - }, - { - code: noFormat` + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"A"' }, + line: 2, + }, + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"D"' }, + line: 3, + }, + ], + }, + { + code: noFormat` import { A, B, C } from 'foo'; import { D, E, F, } from 'bar'; type T = B | E; - `, - output: ` + `, + output: ` import type { B} from 'foo'; import { A, C } from 'foo'; import type { E} from 'bar'; import { D, F, } from 'bar'; type T = B | E; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"B"' }, - line: 2, - column: 1, - }, - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"E"' }, - line: 3, - column: 1, - }, - ], - }, - { - code: noFormat` + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"B"' }, + line: 2, + }, + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"E"' }, + line: 3, + }, + ], + }, + { + code: noFormat` import { A, B, C } from 'foo'; import { D, E, F, } from 'bar'; type T = C | F; - `, - output: ` + `, + output: ` import type { C } from 'foo'; import { A, B } from 'foo'; import type { F} from 'bar'; import { D, E } from 'bar'; type T = C | F; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"C"' }, - line: 2, - column: 1, - }, - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"F"' }, - line: 3, - column: 1, - }, - ], - }, - { - // all type fix cases - code: ` + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"C"' }, + line: 2, + }, + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"F"' }, + line: 3, + }, + ], + }, + { + // all type fix cases + code: ` import { Type1, Type2 } from 'named_types'; import Type from 'default_type'; import * as Types from 'namespace_type'; import Default, { Named } from 'default_and_named_type'; type T = Type1 | Type2 | Type | Types.A | Default | Named; - `, - output: ` + `, + output: ` import type { Type1, Type2 } from 'named_types'; import type Type from 'default_type'; import type * as Types from 'namespace_type'; import type { Named } from 'default_and_named_type'; import type Default from 'default_and_named_type'; type T = Type1 | Type2 | Type | Types.A | Default | Named; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, - }, - { - messageId: 'typeOverValue', - line: 3, - column: 1, - }, - { - messageId: 'typeOverValue', - line: 4, - column: 1, - }, - { - messageId: 'typeOverValue', - line: 5, - column: 1, - }, - ], - }, - { - // some type fix cases - code: ` + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + { + messageId: 'typeOverValue', + line: 3, + }, + { + messageId: 'typeOverValue', + line: 4, + }, + { + messageId: 'typeOverValue', + line: 5, + }, + ], + }, + { + // some type fix cases + code: ` import { Value1, Type1 } from 'named_import'; import Type2, { Value2 } from 'default_import'; import Value3, { Type3 } from 'default_import2'; import Type4, { Type5, Value4 } from 'default_and_named_import'; type T = Type1 | Type2 | Type3 | Type4 | Type5; - `, - output: ` + `, + output: ` import type { Type1 } from 'named_import'; import { Value1 } from 'named_import'; import type Type2 from 'default_import'; @@ -1090,1197 +847,1245 @@ import type { Type5} from 'default_and_named_import'; import type Type4 from 'default_and_named_import'; import { Value4 } from 'default_and_named_import'; type T = Type1 | Type2 | Type3 | Type4 | Type5; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"Type1"' }, - line: 2, - column: 1, - }, - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"Type2"' }, - line: 3, - column: 1, - }, - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"Type3"' }, - line: 4, - column: 1, - }, - { - messageId: 'someImportsAreOnlyTypes', - data: { typeImports: '"Type4" and "Type5"' }, - line: 5, - column: 1, - }, - ], - }, - // type annotations - { - code: ` - let foo: import('foo'); - let bar: import('foo').Bar; - `, - output: null, - errors: [ - { - messageId: 'noImportTypeAnnotations', - line: 2, - column: 18, - }, - { - messageId: 'noImportTypeAnnotations', - line: 3, - column: 18, - }, - ], - }, - { - code: ` - let foo: import('foo'); - `, - output: null, - options: [{ prefer: 'type-imports' }], - errors: [ - { - messageId: 'noImportTypeAnnotations', - line: 2, - column: 18, - }, - ], - }, - { - code: ` - import type Foo from 'foo'; - let foo: Foo; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import Foo from 'foo'; - let foo: Foo; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type { Foo } from 'foo'; - let foo: Foo; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import { Foo } from 'foo'; - let foo: Foo; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - // type queries - { - code: ` - import Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - output: ` - import type Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - output: ` - import type { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - output: ` - import type * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - // exports - { - code: ` - import Type from 'foo'; - - export type { Type }; // is a type-only export - `, - output: ` - import type Type from 'foo'; - - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { Type } from 'foo'; - - export type { Type }; // is a type-only export - `, - output: ` - import type { Type } from 'foo'; - - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import * as Type from 'foo'; - - export type { Type }; // is a type-only export - `, - output: ` - import type * as Type from 'foo'; - - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type { Type } from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import { Type } from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type * as Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import * as Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - // type with comments - code: noFormat` + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"Type1"' }, + line: 2, + }, + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"Type2"' }, + line: 3, + }, + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"Type3"' }, + line: 4, + }, + { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports: '"Type4" and "Type5"' }, + line: 5, + }, + ], + }, + // type annotations + { + code: ` +let foo: import('foo'); +let bar: import('foo').Bar; + `, + output: null, + errors: [ + { + messageId: 'noImportTypeAnnotations', + line: 2, + }, + { + messageId: 'noImportTypeAnnotations', + line: 3, + }, + ], + }, + { + code: ` +let foo: import('foo'); + `, + output: null, + options: [{ prefer: 'type-imports' }], + errors: [ + { + messageId: 'noImportTypeAnnotations', + line: 2, + }, + ], + }, + { + code: ` +import type Foo from 'foo'; +let foo: Foo; + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import Foo from 'foo'; +let foo: Foo; + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + code: ` +import type { Foo } from 'foo'; +let foo: Foo; + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import { Foo } from 'foo'; +let foo: Foo; + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + // type queries + { + code: ` +import Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + output: ` +import type Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { Type } from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + output: ` +import type { Type } from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import * as Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + output: ` +import type * as Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import type Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + code: ` +import type { Type } from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import { Type } from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + code: ` +import type * as Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import * as Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + // exports + { + code: ` +import Type from 'foo'; + +export type { Type }; // is a type-only export + `, + output: ` +import type Type from 'foo'; + +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { Type } from 'foo'; + +export type { Type }; // is a type-only export + `, + output: ` +import type { Type } from 'foo'; + +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import * as Type from 'foo'; + +export type { Type }; // is a type-only export + `, + output: ` +import type * as Type from 'foo'; + +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import type Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + code: ` +import type { Type } from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import { Type } from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + code: ` +import type * as Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import * as Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + // type with comments + code: noFormat` import type /*comment*/ * as AllType from 'foo'; import type // comment DefType from 'foo'; import type /*comment*/ { Type } from 'foo'; type T = { a: AllType; b: DefType; c: Type }; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` + `, + options: [{ prefer: 'no-type-imports' }], + output: ` import /*comment*/ * as AllType from 'foo'; import // comment DefType from 'foo'; import /*comment*/ { Type } from 'foo'; type T = { a: AllType; b: DefType; c: Type }; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 1, - }, - { - messageId: 'valueOverType', - line: 3, - column: 1, - }, - { - messageId: 'valueOverType', - line: 5, - column: 1, - }, - ], - }, - { - // https://github.com/typescript-eslint/typescript-eslint/issues/2775 - code: ` + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + { + messageId: 'valueOverType', + line: 3, + }, + { + messageId: 'valueOverType', + line: 5, + }, + ], + }, + { + // https://github.com/typescript-eslint/typescript-eslint/issues/2775 + code: ` import Default, * as Rest from 'module'; const a: Rest.A = ''; - `, - options: [{ prefer: 'type-imports' }], - output: ` + `, + options: [{ prefer: 'type-imports' }], + output: ` import type * as Rest from 'module'; import Default from 'module'; const a: Rest.A = ''; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 1, + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], }, - ], - }, - { - code: ` + { + code: ` import Default, * as Rest from 'module'; const a: Default = ''; - `, - options: [{ prefer: 'type-imports' }], - output: ` + `, + options: [{ prefer: 'type-imports' }], + output: ` import type Default from 'module'; import * as Rest from 'module'; const a: Default = ''; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 1, + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], }, - ], - }, - { - code: ` + { + code: ` import Default, * as Rest from 'module'; const a: Default = ''; const b: Rest.A = ''; - `, - options: [{ prefer: 'type-imports' }], - output: ` + `, + options: [{ prefer: 'type-imports' }], + output: ` import type * as Rest from 'module'; import type Default from 'module'; const a: Default = ''; const b: Rest.A = ''; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], }, - ], - }, - { - // type with comments - code: ` + { + // type with comments + code: ` import Default, /*comment*/ * as Rest from 'module'; const a: Default = ''; - `, - options: [{ prefer: 'type-imports' }], - output: ` + `, + options: [{ prefer: 'type-imports' }], + output: ` import type Default from 'module'; import /*comment*/ * as Rest from 'module'; const a: Default = ''; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 1, + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], }, - ], - }, - { - // type with comments - code: noFormat` + { + // type with comments + code: noFormat` import Default /*comment1*/, /*comment2*/ { Data } from 'module'; const a: Default = ''; - `, - options: [{ prefer: 'type-imports' }], - output: ` + `, + options: [{ prefer: 'type-imports' }], + output: ` import type Default /*comment1*/ from 'module'; import /*comment2*/ { Data } from 'module'; const a: Default = ''; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 1, - }, - ], - }, - { - code: ` - import Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import type Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: ` - import type { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: noFormat` - import type { Type } from 'foo'; - import { Foo, Bar } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - type T = Bar; - `, - output: ` - import type { Type , Bar } from 'foo'; - import { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - type T = Bar; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"Bar"' }, - line: 3, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: ` - import { V } from 'foo'; - import type { Foo, Bar, T } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - foo(@deco bar: Bar) {} - } - `, - output: ` - import { V , Foo, Bar} from 'foo'; - import type { T } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - foo(@deco bar: Bar) {} - } - `, - errors: [ - { - messageId: 'someImportsInDecoMeta', - data: { typeImports: '"Foo" and "Bar"' }, - line: 3, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: ` - import type { Foo, T } from 'foo'; - import { V } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import type { T } from 'foo'; - import { V , Foo} from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: ` - import type * as Type from 'foo'; - @deco - class A { - constructor(foo: Type.Foo) {} - } - `, - output: ` - import * as Type from 'foo'; - @deco - class A { - constructor(foo: Type.Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Type"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: ` - import type Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import type { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: noFormat` - import type { Type } from 'foo'; - import { Foo, Bar } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - type T = Bar; - `, - output: ` - import type { Type , Bar } from 'foo'; - import { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - type T = Bar; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"Bar"' }, - line: 3, - column: 9, - }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import { V } from 'foo'; - import type { Foo, Bar, T } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - foo(@deco bar: Bar) {} - } - `, - output: ` - import { V , Foo, Bar} from 'foo'; - import type { T } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - foo(@deco bar: Bar) {} - } - `, - errors: [ - { - messageId: 'someImportsInDecoMeta', - data: { typeImports: '"Foo" and "Bar"' }, - line: 3, - column: 9, - }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import type { Foo, T } from 'foo'; - import { V } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import type { T } from 'foo'; - import { V , Foo} from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import type * as Type from 'foo'; - @deco - class A { - constructor(foo: Type.Foo) {} - } - `, - output: ` - import * as Type from 'foo'; - @deco - class A { - constructor(foo: Type.Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Type"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` -import { type A, B } from 'foo'; -type T = A; -const b = B; - `, - output: ` -import { A, B } from 'foo'; -type T = A; -const b = B; - `, - dependencyConstraints: { - typescript: '4.5', - }, - options: [{ prefer: 'no-type-imports' }], - errors: [ - { - messageId: 'valueOverType', - line: 2, - }, - ], - }, - { - code: ` + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` +import Foo from 'foo'; +@deco +class A { + constructor(foo: Foo) {} +} + `, + output: ` +import type Foo from 'foo'; +@deco +class A { + constructor(foo: Foo) {} +} + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { type A, B } from 'foo'; +type T = A; +const b = B; + `, + output: ` +import { A, B } from 'foo'; +type T = A; +const b = B; + `, + dependencyConstraints: { + typescript: '4.5', + }, + options: [{ prefer: 'no-type-imports' }], + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + code: ` import { A, B, type C } from 'foo'; type T = A | C; const b = B; - `, - output: ` + `, + output: ` import type { A} from 'foo'; import { B, type C } from 'foo'; type T = A | C; const b = B; - `, - dependencyConstraints: { - typescript: '4.5', - }, - options: [{ prefer: 'type-imports' }], - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"A"' }, - line: 2, - }, - ], - }, - - // inline-type-imports - { - code: ` - import { A, B } from 'foo'; - let foo: A; - let bar: B; - `, - output: ` - import { type A, type B } from 'foo'; - let foo: A; - let bar: B; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { A, B } from 'foo'; + `, + dependencyConstraints: { + typescript: '4.5', + }, + options: [{ prefer: 'type-imports' }], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"A"' }, + line: 2, + }, + ], + }, + + // inline-type-imports + { + code: ` +import { A, B } from 'foo'; +let foo: A; +let bar: B; + `, + output: ` +import { type A, type B } from 'foo'; +let foo: A; +let bar: B; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { A, B } from 'foo'; - let foo: A; - B(); - `, - output: ` - import { type A, B } from 'foo'; +let foo: A; +B(); + `, + output: ` +import { type A, B } from 'foo'; - let foo: A; - B(); - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { A, B } from 'foo'; - type T = A; - B(); - `, - output: ` - import { type A, B } from 'foo'; - type T = A; - B(); - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { A } from 'foo'; - import { B } from 'foo'; - type T = A; - type U = B; - `, - output: ` - import { type A } from 'foo'; - import { type B } from 'foo'; - type T = A; - type U = B; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - { - messageId: 'typeOverValue', - line: 3, - column: 9, - }, - ], - }, - { - code: ` - import { A } from 'foo'; - import B from 'foo'; - type T = A; - type U = B; - `, - output: ` - import { type A } from 'foo'; - import type B from 'foo'; - type T = A; - type U = B; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - { - messageId: 'typeOverValue', - line: 3, - column: 9, - }, - ], - }, - { - code: ` +let foo: A; +B(); + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` +import { A, B } from 'foo'; +type T = A; +B(); + `, + output: ` +import { type A, B } from 'foo'; +type T = A; +B(); + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` +import { A } from 'foo'; +import { B } from 'foo'; +type T = A; +type U = B; + `, + output: ` +import { type A } from 'foo'; +import { type B } from 'foo'; +type T = A; +type U = B; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + { + messageId: 'typeOverValue', + line: 3, + }, + ], + }, + { + code: ` +import { A } from 'foo'; +import B from 'foo'; +type T = A; +type U = B; + `, + output: ` +import { type A } from 'foo'; +import type B from 'foo'; +type T = A; +type U = B; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + { + messageId: 'typeOverValue', + line: 3, + }, + ], + }, + { + code: ` import A, { B, C } from 'foo'; type T = B; type U = C; A(); - `, - output: ` + `, + output: ` import A, { type B, type C } from 'foo'; type T = B; type U = C; A(); - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'someImportsAreOnlyTypes', - line: 2, - column: 1, - }, - ], - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'someImportsAreOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` import A, { B, C } from 'foo'; type T = B; type U = C; type V = A; - `, - output: ` + `, + output: ` import {type B, type C} from 'foo'; import type A from 'foo'; type T = B; type U = C; type V = A; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, - }, - ], - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` import A, { B, C as D } from 'foo'; type T = B; type U = D; type V = A; - `, - output: ` + `, + output: ` import {type B, type C as D} from 'foo'; import type A from 'foo'; type T = B; type U = D; type V = A; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, - }, - ], - }, - { - code: ` - import { /* comment */ A, B } from 'foo'; - type T = A; - `, - output: ` - import { /* comment */ type A, B } from 'foo'; - type T = A; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { B, /* comment */ A } from 'foo'; - type T = A; - `, - output: ` - import { B, /* comment */ type A } from 'foo'; - type T = A; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 9, - }, - ], - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { /* comment */ A, B } from 'foo'; +type T = A; + `, + output: ` +import { /* comment */ type A, B } from 'foo'; +type T = A; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` +import { B, /* comment */ A } from 'foo'; +type T = A; + `, + output: ` +import { B, /* comment */ type A } from 'foo'; +type T = A; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` import { A, B, C } from 'foo'; import type { D } from 'deez'; const foo: A = B(); let bar: C; let baz: D; - `, - output: ` + `, + output: ` import { type A, B, type C } from 'foo'; import type { D } from 'deez'; const foo: A = B(); let bar: C; let baz: D; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'someImportsAreOnlyTypes', - line: 2, - column: 1, - }, - ], - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'someImportsAreOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` import { A, B, type C } from 'foo'; import type { D } from 'deez'; const foo: A = B(); let bar: C; let baz: D; - `, - output: ` + `, + output: ` import { type A, B, type C } from 'foo'; import type { D } from 'deez'; const foo: A = B(); let bar: C; let baz: D; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 1, - }, - ], - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` import A from 'foo'; export = {} as A; - `, - output: ` + `, + output: ` import type A from 'foo'; export = {} as A; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, - }, - ], - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` import { A } from 'foo'; export = {} as A; - `, - output: ` + `, + output: ` import { type A } from 'foo'; export = {} as A; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + foo: Foo; + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + foo: Foo; + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + foo(foo: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + foo(foo: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + foo(): Foo {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + foo(): Foo {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + foo(@deco foo: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + foo(@deco foo: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + set foo(value: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + set foo(value: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + get foo() {} + + set foo(value: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + get foo() {} + + set foo(value: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + get foo() {} + + set ['foo'](value: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + get foo() {} + + set ['foo'](value: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import * as foo from 'foo'; + @deco + class A { + constructor(foo: foo.Foo) {} + } + `, + output: ` + import type * as foo from 'foo'; + @deco + class A { + constructor(foo: foo.Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + ], + }); + }); +} + +// the special ignored config case +describe('experimentalDecorators: true + emitDecoratorMetadata: true', () => { + const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + // type-only imports were first added in TS3.8 + dependencyConstraints: { + typescript: '3.8', + }, + parserOptions: { + experimentalDecorators: true, + emitDecoratorMetadata: true, + }, + }); + + ruleTester.run('consistent-type-imports', rule, { + valid: [ + ` + import Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, - }, - ], - }, - ], + + ` + import Foo from 'foo'; + class A { + @deco + foo: Foo; + } + `, + + ` + import Foo from 'foo'; + class A { + @deco + foo(foo: Foo) {} + } + `, + + ` + import Foo from 'foo'; + class A { + @deco + foo(): Foo {} + } + `, + + ` + import Foo from 'foo'; + class A { + foo(@deco foo: Foo) {} + } + `, + + ` + import Foo from 'foo'; + class A { + @deco + set foo(value: Foo) {} + } + `, + + ` + import Foo from 'foo'; + class A { + @deco + get foo() {} + + set foo(value: Foo) {} + } + `, + + ` + import Foo from 'foo'; + class A { + @deco + get foo() {} + + set ['foo'](value: Foo) {} + } + `, + + ` + import type { Foo } from 'foo'; + const key = 'k'; + class A { + @deco + get [key]() {} + + set [key](value: Foo) {} + } + `, + + ` + import * as foo from 'foo'; + @deco + class A { + constructor(foo: foo.Foo) {} + } + `, + + // https://github.com/typescript-eslint/typescript-eslint/issues/7327 + ` + import type { ClassA } from './classA'; + + export class ClassB { + public constructor(node: ClassA) {} + } + `, + + ` + import type Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + ` + import type { Foo } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + ` + import type { Type } from 'foo'; + import { Foo, Bar } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + type T = Bar; + `, + ` + import { V } from 'foo'; + import type { Foo, Bar, T } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + foo(@deco bar: Bar) {} + } + `, + ` + import type { Foo, T } from 'foo'; + import { V } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + ` + import type * as Type from 'foo'; + @deco + class A { + constructor(foo: Type.Foo) {} + } + `, + ], + invalid: [ + { + code: ` + import Foo from 'foo'; + export type T = Foo; + `, + output: ` + import type Foo from 'foo'; + export type T = Foo; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + ], + }); }); diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index 8577dd61bccd..32ed786ab2eb 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -10,6 +10,7 @@ import type { TSESTreeOptions, } from '@typescript-eslint/typescript-estree'; import { parseAndGenerateServices } from '@typescript-eslint/typescript-estree'; +import type { VisitorKeys } from '@typescript-eslint/visitor-keys'; import { visitorKeys } from '@typescript-eslint/visitor-keys'; import debug from 'debug'; import type * as ts from 'typescript'; @@ -24,7 +25,7 @@ interface ParseForESLintResult { comments?: TSESTree.Comment[]; }; services: ParserServices; - visitorKeys: typeof visitorKeys; + visitorKeys: VisitorKeys; scopeManager: ScopeManager; } @@ -129,7 +130,6 @@ function parseForESLint( const { ast, services } = parseAndGenerateServices(code, parserOptions); ast.sourceType = options.sourceType; - let emitDecoratorMetadata = options.emitDecoratorMetadata === true; if (services.program) { // automatically apply the options configured for the program const compilerOptions = services.program.getCompilerOptions(); @@ -160,14 +160,11 @@ function parseForESLint( analyzeOptions.jsxFragmentName, ); } - if (compilerOptions.emitDecoratorMetadata === true) { - emitDecoratorMetadata = true; - } } - if (emitDecoratorMetadata) { - analyzeOptions.emitDecoratorMetadata = true; - } + // if not defined - override from the parserOptions + services.emitDecoratorMetadata ??= options.emitDecoratorMetadata === true; + services.experimentalDecorators ??= options.experimentalDecorators === true; const scopeManager = analyze(ast, analyzeOptions); diff --git a/packages/rule-tester/package.json b/packages/rule-tester/package.json index c2cfbe6ab2ec..7dc4487dfa41 100644 --- a/packages/rule-tester/package.json +++ b/packages/rule-tester/package.json @@ -63,7 +63,8 @@ "chai": "^4.3.7", "mocha": "^10.0.0", "sinon": "^16.0.0", - "source-map-support": "^0.5.21" + "source-map-support": "^0.5.21", + "typescript": "*" }, "funding": { "type": "opencollective", diff --git a/packages/rule-tester/src/types/InvalidTestCase.ts b/packages/rule-tester/src/types/InvalidTestCase.ts index 1bef9e2b89fb..5e62f9dc86b6 100644 --- a/packages/rule-tester/src/types/InvalidTestCase.ts +++ b/packages/rule-tester/src/types/InvalidTestCase.ts @@ -63,7 +63,7 @@ export interface TestCaseError { export interface InvalidTestCase< TMessageIds extends string, - TOptions extends Readonly, + TOptions extends readonly unknown[], > extends ValidTestCase { /** * Expected errors. diff --git a/packages/rule-tester/src/types/ValidTestCase.ts b/packages/rule-tester/src/types/ValidTestCase.ts index 11b6c9e7e4b0..eff17919227b 100644 --- a/packages/rule-tester/src/types/ValidTestCase.ts +++ b/packages/rule-tester/src/types/ValidTestCase.ts @@ -6,7 +6,7 @@ import type { import type { DependencyConstraint } from './DependencyConstraint'; -export interface ValidTestCase> { +export interface ValidTestCase { /** * Name for the test case. */ diff --git a/packages/scope-manager/src/ScopeManager.ts b/packages/scope-manager/src/ScopeManager.ts index c77bcfe62e92..5814b6a78034 100644 --- a/packages/scope-manager/src/ScopeManager.ts +++ b/packages/scope-manager/src/ScopeManager.ts @@ -27,8 +27,8 @@ import type { Variable } from './variable'; interface ScopeManagerOptions { globalReturn?: boolean; - sourceType?: SourceType; impliedStrict?: boolean; + sourceType?: SourceType; } /** diff --git a/packages/scope-manager/src/analyze.ts b/packages/scope-manager/src/analyze.ts index 450283553b24..12e6b4406ede 100644 --- a/packages/scope-manager/src/analyze.ts +++ b/packages/scope-manager/src/analyze.ts @@ -57,9 +57,9 @@ interface AnalyzeOptions { */ sourceType?: SourceType; + // TODO - remove this in v7 /** - * Emit design-type metadata for decorated declarations in source. - * Defaults to `false`. + * This option no longer does anything and will be removed in a future major release. */ emitDecoratorMetadata?: boolean; } @@ -96,9 +96,7 @@ function analyze( providedOptions?.jsxFragmentName ?? DEFAULT_OPTIONS.jsxFragmentName, sourceType: providedOptions?.sourceType ?? DEFAULT_OPTIONS.sourceType, lib: providedOptions?.lib ?? ['esnext'], - emitDecoratorMetadata: - providedOptions?.emitDecoratorMetadata ?? - DEFAULT_OPTIONS.emitDecoratorMetadata, + emitDecoratorMetadata: false, }; // ensure the option is lower cased diff --git a/packages/scope-manager/src/referencer/ClassVisitor.ts b/packages/scope-manager/src/referencer/ClassVisitor.ts index 33b48c7a103b..94cb7ab59815 100644 --- a/packages/scope-manager/src/referencer/ClassVisitor.ts +++ b/packages/scope-manager/src/referencer/ClassVisitor.ts @@ -9,29 +9,21 @@ import { Visitor } from './Visitor'; class ClassVisitor extends Visitor { readonly #classNode: TSESTree.ClassDeclaration | TSESTree.ClassExpression; readonly #referencer: Referencer; - readonly #emitDecoratorMetadata: boolean; constructor( referencer: Referencer, node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, - emitDecoratorMetadata: boolean, ) { super(referencer); this.#referencer = referencer; this.#classNode = node; - this.#emitDecoratorMetadata = emitDecoratorMetadata; } static visit( referencer: Referencer, node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, - emitDecoratorMetadata: boolean, ): void { - const classVisitor = new ClassVisitor( - referencer, - node, - emitDecoratorMetadata, - ); + const classVisitor = new ClassVisitor(referencer, node); classVisitor.visitClass(node); } @@ -96,25 +88,21 @@ class ClassVisitor extends Visitor { * foo: Type; * } */ - this.visitMetadataType(node.typeAnnotation, !!node.decorators.length); + this.visitType(node.typeAnnotation); } protected visitFunctionParameterTypeAnnotation( node: TSESTree.Parameter, - withDecorators: boolean, ): void { switch (node.type) { case AST_NODE_TYPES.AssignmentPattern: - this.visitMetadataType(node.left.typeAnnotation, withDecorators); + this.visitType(node.left.typeAnnotation); break; case AST_NODE_TYPES.TSParameterProperty: - this.visitFunctionParameterTypeAnnotation( - node.parameter, - withDecorators, - ); + this.visitFunctionParameterTypeAnnotation(node.parameter); break; default: - this.visitMetadataType(node.typeAnnotation, withDecorators); + this.visitType(node.typeAnnotation); } } @@ -217,11 +205,11 @@ class ClassVisitor extends Visitor { }, { processRightHandNodes: true }, ); - this.visitFunctionParameterTypeAnnotation(param, withMethodDecorators); + this.visitFunctionParameterTypeAnnotation(param); param.decorators.forEach(d => this.visit(d)); } - this.visitMetadataType(node.returnType, withMethodDecorators); + this.visitType(node.returnType); this.visitType(node.typeParameters); this.#referencer.visitChildren(node.body); @@ -284,49 +272,6 @@ class ClassVisitor extends Visitor { TypeVisitor.visit(this.#referencer, node); } - protected visitMetadataType( - node: TSESTree.TSTypeAnnotation | null | undefined, - withDecorators: boolean, - ): void { - if (!node) { - return; - } - // emit decorators metadata only work for TSTypeReference in ClassDeclaration - if ( - this.#classNode.type === AST_NODE_TYPES.ClassDeclaration && - !this.#classNode.declare && - node.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && - this.#emitDecoratorMetadata - ) { - let entityName: TSESTree.Identifier | TSESTree.ThisExpression; - if ( - node.typeAnnotation.typeName.type === AST_NODE_TYPES.TSQualifiedName - ) { - let iter = node.typeAnnotation.typeName; - while (iter.left.type === AST_NODE_TYPES.TSQualifiedName) { - iter = iter.left; - } - entityName = iter.left; - } else { - entityName = node.typeAnnotation.typeName; - } - - if (withDecorators) { - if (entityName.type === AST_NODE_TYPES.Identifier) { - this.#referencer.currentScope().referenceDualValueType(entityName); - } - - if (node.typeAnnotation.typeArguments) { - this.visitType(node.typeAnnotation.typeArguments); - } - - // everything is handled now - return; - } - } - this.visitType(node); - } - ///////////////////// // Visit selectors // ///////////////////// diff --git a/packages/scope-manager/src/referencer/Referencer.ts b/packages/scope-manager/src/referencer/Referencer.ts index 6082f4846611..dac19c71a2c7 100644 --- a/packages/scope-manager/src/referencer/Referencer.ts +++ b/packages/scope-manager/src/referencer/Referencer.ts @@ -29,7 +29,6 @@ interface ReferencerOptions extends VisitorOptions { jsxPragma: string | null; jsxFragmentName: string | null; lib: Lib[]; - emitDecoratorMetadata: boolean; } // Referencing variables and creating bindings. @@ -39,7 +38,6 @@ class Referencer extends Visitor { #hasReferencedJsxFactory = false; #hasReferencedJsxFragmentFactory = false; #lib: Lib[]; - readonly #emitDecoratorMetadata: boolean; public readonly scopeManager: ScopeManager; constructor(options: ReferencerOptions, scopeManager: ScopeManager) { @@ -48,7 +46,6 @@ class Referencer extends Visitor { this.#jsxPragma = options.jsxPragma; this.#jsxFragmentName = options.jsxFragmentName; this.#lib = options.lib; - this.#emitDecoratorMetadata = options.emitDecoratorMetadata; } public currentScope(): Scope; @@ -152,7 +149,7 @@ class Referencer extends Visitor { protected visitClass( node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, ): void { - ClassVisitor.visit(this, node, this.#emitDecoratorMetadata); + ClassVisitor.visit(this, node); } protected visitForIn( @@ -445,6 +442,11 @@ class Referencer extends Visitor { protected TSExportAssignment(node: TSESTree.TSExportAssignment): void { if (node.expression.type === AST_NODE_TYPES.Identifier) { + // this is a special case - you can `export = T` where `T` is a type OR a + // value however `T[U]` is illegal when `T` is a type and `T.U` is illegal + // when `T.U` is a type + // i.e. if the expression is JUST an Identifier - it could be either ref + // kind; otherwise the standard rules apply this.currentScope().referenceDualValueType(node.expression); } else { this.visit(node.expression); diff --git a/packages/scope-manager/tests/eslint-scope/references.test.ts b/packages/scope-manager/tests/eslint-scope/references.test.ts index 0b49cab0f7f5..f290efd185d3 100644 --- a/packages/scope-manager/tests/eslint-scope/references.test.ts +++ b/packages/scope-manager/tests/eslint-scope/references.test.ts @@ -540,120 +540,4 @@ describe('References:', () => { }), ); }); - - describe('When emitDecoratorMetadata is true', () => { - it('check type referenced by decorator metadata', () => { - const { scopeManager } = parseAndAnalyze( - ` - @deco - class A { - property: Type1; - @deco - propertyWithDeco: a.Foo; - - set foo(@deco a: SetterType) {} - - constructor(foo: b.Foo) {} - - foo1(@deco a: Type2, b: Type0) {} - - @deco - foo2(a: Type3) {} - - @deco - foo3(): Type4 {} - - set ['a'](a: Type5) {} - set [0](a: Type6) {} - @deco - get a() {} - @deco - get [0]() {} - } - - const keyName = 'foo'; - class B { - constructor(@deco foo: c.Foo) {} - - set [keyName](a: Type) {} - @deco - get [keyName]() {} - } - - declare class C { - @deco - foo(): TypeC {}; - } - `, - { - emitDecoratorMetadata: true, - }, - ); - - const classAScope = scopeManager.globalScope!.childScopes[0]; - const propertyTypeRef = classAScope.references[2]; - expect(propertyTypeRef.identifier.name).toBe('a'); - expect(propertyTypeRef.isTypeReference).toBe(true); - expect(propertyTypeRef.isValueReference).toBe(true); - - const setterParamTypeRef = classAScope.childScopes[0].references[0]; - expect(setterParamTypeRef.identifier.name).toBe('SetterType'); - expect(setterParamTypeRef.isTypeReference).toBe(true); - expect(setterParamTypeRef.isValueReference).toBe(false); - - const constructorParamTypeRef = classAScope.childScopes[1].references[0]; - expect(constructorParamTypeRef.identifier.name).toBe('b'); - expect(constructorParamTypeRef.isTypeReference).toBe(true); - expect(constructorParamTypeRef.isValueReference).toBe(true); - - const methodParamTypeRef = classAScope.childScopes[2].references[0]; - expect(methodParamTypeRef.identifier.name).toBe('Type2'); - expect(methodParamTypeRef.isTypeReference).toBe(true); - expect(methodParamTypeRef.isValueReference).toBe(true); - const methodParamTypeRef0 = classAScope.childScopes[2].references[2]; - expect(methodParamTypeRef0.identifier.name).toBe('Type0'); - expect(methodParamTypeRef0.isTypeReference).toBe(true); - expect(methodParamTypeRef0.isValueReference).toBe(true); - - const methodParamTypeRef1 = classAScope.childScopes[3].references[0]; - expect(methodParamTypeRef1.identifier.name).toBe('Type3'); - expect(methodParamTypeRef1.isTypeReference).toBe(true); - expect(methodParamTypeRef1.isValueReference).toBe(true); - - const methodReturnTypeRef = classAScope.childScopes[4].references[0]; - expect(methodReturnTypeRef.identifier.name).toBe('Type4'); - expect(methodReturnTypeRef.isTypeReference).toBe(true); - expect(methodReturnTypeRef.isValueReference).toBe(true); - - const setterParamTypeRef1 = classAScope.childScopes[5].references[0]; - expect(setterParamTypeRef1.identifier.name).toBe('Type5'); - expect(setterParamTypeRef1.isTypeReference).toBe(true); - expect(setterParamTypeRef1.isValueReference).toBe(true); - - const setterParamTypeRef2 = classAScope.childScopes[6].references[0]; - expect(setterParamTypeRef2.identifier.name).toBe('Type6'); - expect(setterParamTypeRef2.isTypeReference).toBe(true); - expect(setterParamTypeRef2.isValueReference).toBe(true); - - const classBScope = scopeManager.globalScope!.childScopes[1]; - - const constructorParamTypeRef1 = classBScope.childScopes[0].references[0]; - expect(constructorParamTypeRef1.identifier.name).toBe('c'); - expect(constructorParamTypeRef1.isTypeReference).toBe(true); - expect(constructorParamTypeRef1.isValueReference).toBe(true); - - const setterParamTypeRef3 = classBScope.childScopes[1].references[0]; - // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum - expect(setterParamTypeRef3.identifier.name).toBe('Type'); - expect(setterParamTypeRef3.isTypeReference).toBe(true); - expect(setterParamTypeRef3.isValueReference).toBe(false); - - const classCScope = scopeManager.globalScope!.childScopes[2]; - - const methodReturnTypeRef1 = classCScope.childScopes[0].references[0]; - expect(methodReturnTypeRef1.identifier.name).toBe('TypeC'); - expect(methodReturnTypeRef1.isTypeReference).toBe(true); - expect(methodReturnTypeRef1.isValueReference).toBe(false); - }); - }); }); diff --git a/packages/scope-manager/tests/fixtures.test.ts b/packages/scope-manager/tests/fixtures.test.ts index 9593a9a45a6a..dd25a1278617 100644 --- a/packages/scope-manager/tests/fixtures.test.ts +++ b/packages/scope-manager/tests/fixtures.test.ts @@ -47,7 +47,6 @@ const ALLOWED_OPTIONS: Map = new Map< ['jsxPragma', ['string']], ['jsxFragmentName', ['string']], ['sourceType', ['string', new Set(['module', 'script'])]], - ['emitDecoratorMetadata', ['boolean']], ]); function nestDescribe( diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts b/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts deleted file mode 100644 index 41c40b287703..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts +++ /dev/null @@ -1,23 +0,0 @@ -//// @emitDecoratorMetadata = true - -function deco(...param: any) {} - -class T {} -const keyName = 'foo'; - -class A { - @deco - set b(b: T) {} - - set ['a'](a: T) {} - @deco - get a() {} - - set [0](a: T) {} - @deco - get [0]() {} - - set [keyName](a: T) {} - @deco - get [keyName]() {} -} diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot b/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot deleted file mode 100644 index 8051ca3382c9..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot +++ /dev/null @@ -1,470 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`class emit-metadata accessor-deco 1`] = ` -ScopeManager { - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2 { - defs: [ - FunctionNameDefinition$1 { - name: Identifier<"deco">, - node: FunctionDeclaration$1, - }, - ], - name: "deco", - references: [ - Reference$3 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$5 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$7 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$11 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - ], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$3 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$4 { - defs: [ - ParameterDefinition$2 { - name: Identifier<"param">, - node: FunctionDeclaration$1, - }, - ], - name: "param", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$5 { - defs: [ - ClassNameDefinition$3 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [ - Reference$2 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - Reference$4 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - Reference$6 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - Reference$9 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: false, - isWrite: false, - resolved: Variable$5, - }, - ], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$6 { - defs: [ - ClassNameDefinition$4 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$7 { - defs: [ - VariableDefinition$5 { - name: Identifier<"keyName">, - node: VariableDeclarator$3, - }, - ], - name: "keyName", - references: [ - Reference$1 { - identifier: Identifier<"keyName">, - init: true, - isRead: false, - isTypeReference: false, - isValueReference: true, - isWrite: true, - resolved: Variable$7, - writeExpr: Literal$4, - }, - Reference$8 { - identifier: Identifier<"keyName">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$7, - }, - Reference$10 { - identifier: Identifier<"keyName">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$7, - }, - ], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$8 { - defs: [ - ClassNameDefinition$6 { - name: Identifier<"A">, - node: ClassDeclaration$5, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$9 { - defs: [ - ClassNameDefinition$7 { - name: Identifier<"A">, - node: ClassDeclaration$5, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$10 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$11 { - defs: [ - ParameterDefinition$8 { - name: Identifier<"b">, - node: FunctionExpression$6, - }, - ], - name: "b", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$12 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$13 { - defs: [ - ParameterDefinition$9 { - name: Identifier<"a">, - node: FunctionExpression$7, - }, - ], - name: "a", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$14 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$15 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$16 { - defs: [ - ParameterDefinition$10 { - name: Identifier<"a">, - node: FunctionExpression$8, - }, - ], - name: "a", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$17 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$18 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$19 { - defs: [ - ParameterDefinition$11 { - name: Identifier<"a">, - node: FunctionExpression$9, - }, - ], - name: "a", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$20 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - ], - scopes: [ - GlobalScope$1 { - block: Program$10, - isStrict: false, - references: [ - Reference$1, - ], - set: Map { - "const" => ImplicitGlobalConstTypeVariable, - "deco" => Variable$2, - "T" => Variable$5, - "keyName" => Variable$7, - "A" => Variable$8, - }, - type: "global", - upper: null, - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2, - Variable$5, - Variable$7, - Variable$8, - ], - }, - FunctionScope$2 { - block: FunctionDeclaration$1, - isStrict: false, - references: [], - set: Map { - "arguments" => Variable$3, - "param" => Variable$4, - }, - type: "function", - upper: GlobalScope$1, - variables: [ - Variable$3, - Variable$4, - ], - }, - ClassScope$3 { - block: ClassDeclaration$2, - isStrict: true, - references: [], - set: Map { - "T" => Variable$6, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$6, - ], - }, - ClassScope$4 { - block: ClassDeclaration$5, - isStrict: true, - references: [ - Reference$3, - Reference$5, - Reference$7, - Reference$8, - Reference$10, - Reference$11, - ], - set: Map { - "A" => Variable$9, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$9, - ], - }, - FunctionScope$5 { - block: FunctionExpression$6, - isStrict: true, - references: [ - Reference$2, - ], - set: Map { - "arguments" => Variable$10, - "b" => Variable$11, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$10, - Variable$11, - ], - }, - FunctionScope$6 { - block: FunctionExpression$7, - isStrict: true, - references: [ - Reference$4, - ], - set: Map { - "arguments" => Variable$12, - "a" => Variable$13, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$12, - Variable$13, - ], - }, - FunctionScope$7 { - block: FunctionExpression$11, - isStrict: true, - references: [], - set: Map { - "arguments" => Variable$14, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$14, - ], - }, - FunctionScope$8 { - block: FunctionExpression$8, - isStrict: true, - references: [ - Reference$6, - ], - set: Map { - "arguments" => Variable$15, - "a" => Variable$16, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$15, - Variable$16, - ], - }, - FunctionScope$9 { - block: FunctionExpression$12, - isStrict: true, - references: [], - set: Map { - "arguments" => Variable$17, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$17, - ], - }, - FunctionScope$10 { - block: FunctionExpression$9, - isStrict: true, - references: [ - Reference$9, - ], - set: Map { - "arguments" => Variable$18, - "a" => Variable$19, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$18, - Variable$19, - ], - }, - FunctionScope$11 { - block: FunctionExpression$13, - isStrict: true, - references: [], - set: Map { - "arguments" => Variable$20, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$20, - ], - }, - ], -} -`; diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts b/packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts deleted file mode 100644 index ab030a5b0b9a..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts +++ /dev/null @@ -1,11 +0,0 @@ -//// @emitDecoratorMetadata = true - -function deco(...param: any) {} - -class T {} - -class A { - foo(a: T): T {} - @deco - foo1(a: T, b: T): T {} -} diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts.shot b/packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts.shot deleted file mode 100644 index 567fbceb9d51..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts.shot +++ /dev/null @@ -1,291 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`class emit-metadata method-deco 1`] = ` -ScopeManager { - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2 { - defs: [ - FunctionNameDefinition$1 { - name: Identifier<"deco">, - node: FunctionDeclaration$1, - }, - ], - name: "deco", - references: [ - Reference$6 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - ], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$3 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$4 { - defs: [ - ParameterDefinition$2 { - name: Identifier<"param">, - node: FunctionDeclaration$1, - }, - ], - name: "param", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$5 { - defs: [ - ClassNameDefinition$3 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [ - Reference$1 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: false, - isWrite: false, - resolved: Variable$5, - }, - Reference$2 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: false, - isWrite: false, - resolved: Variable$5, - }, - Reference$3 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - Reference$4 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - Reference$5 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - ], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$6 { - defs: [ - ClassNameDefinition$4 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$7 { - defs: [ - ClassNameDefinition$5 { - name: Identifier<"A">, - node: ClassDeclaration$3, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$8 { - defs: [ - ClassNameDefinition$6 { - name: Identifier<"A">, - node: ClassDeclaration$3, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$9 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$10 { - defs: [ - ParameterDefinition$7 { - name: Identifier<"a">, - node: FunctionExpression$4, - }, - ], - name: "a", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$11 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$12 { - defs: [ - ParameterDefinition$8 { - name: Identifier<"a">, - node: FunctionExpression$5, - }, - ], - name: "a", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$13 { - defs: [ - ParameterDefinition$9 { - name: Identifier<"b">, - node: FunctionExpression$5, - }, - ], - name: "b", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - ], - scopes: [ - GlobalScope$1 { - block: Program$6, - isStrict: false, - references: [], - set: Map { - "const" => ImplicitGlobalConstTypeVariable, - "deco" => Variable$2, - "T" => Variable$5, - "A" => Variable$7, - }, - type: "global", - upper: null, - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2, - Variable$5, - Variable$7, - ], - }, - FunctionScope$2 { - block: FunctionDeclaration$1, - isStrict: false, - references: [], - set: Map { - "arguments" => Variable$3, - "param" => Variable$4, - }, - type: "function", - upper: GlobalScope$1, - variables: [ - Variable$3, - Variable$4, - ], - }, - ClassScope$3 { - block: ClassDeclaration$2, - isStrict: true, - references: [], - set: Map { - "T" => Variable$6, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$6, - ], - }, - ClassScope$4 { - block: ClassDeclaration$3, - isStrict: true, - references: [ - Reference$6, - ], - set: Map { - "A" => Variable$8, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$8, - ], - }, - FunctionScope$5 { - block: FunctionExpression$4, - isStrict: true, - references: [ - Reference$1, - Reference$2, - ], - set: Map { - "arguments" => Variable$9, - "a" => Variable$10, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$9, - Variable$10, - ], - }, - FunctionScope$6 { - block: FunctionExpression$5, - isStrict: true, - references: [ - Reference$3, - Reference$4, - Reference$5, - ], - set: Map { - "arguments" => Variable$11, - "a" => Variable$12, - "b" => Variable$13, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$11, - Variable$12, - Variable$13, - ], - }, - ], -} -`; diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/method-return-generic.ts b/packages/scope-manager/tests/fixtures/class/emit-metadata/method-return-generic.ts deleted file mode 100644 index ea8c1a53790d..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/method-return-generic.ts +++ /dev/null @@ -1,9 +0,0 @@ -//// @emitDecoratorMetadata = true - -import { TestGeneric, Test } from 'fake-module'; - -declare function deco(..._param: any): any; -export class TestClass { - @deco - public test(): TestGeneric {} -} diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/method-return-generic.ts.shot b/packages/scope-manager/tests/fixtures/class/emit-metadata/method-return-generic.ts.shot deleted file mode 100644 index 5812d8360b7f..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/method-return-generic.ts.shot +++ /dev/null @@ -1,191 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`class emit-metadata method-return-generic 1`] = ` -ScopeManager { - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2 { - defs: [ - ImportBindingDefinition$1 { - name: Identifier<"TestGeneric">, - node: ImportSpecifier$1, - }, - ], - name: "TestGeneric", - references: [ - Reference$1 { - identifier: Identifier<"TestGeneric">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - ], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$3 { - defs: [ - ImportBindingDefinition$2 { - name: Identifier<"Test">, - node: ImportSpecifier$2, - }, - ], - name: "Test", - references: [ - Reference$2 { - identifier: Identifier<"Test">, - isRead: true, - isTypeReference: true, - isValueReference: false, - isWrite: false, - resolved: Variable$3, - }, - ], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$4 { - defs: [ - FunctionNameDefinition$3 { - name: Identifier<"deco">, - node: TSDeclareFunction$3, - }, - ], - name: "deco", - references: [ - Reference$3 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$4, - }, - ], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$5 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$6 { - defs: [ - ParameterDefinition$4 { - name: Identifier<"_param">, - node: TSDeclareFunction$3, - }, - ], - name: "_param", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$7 { - defs: [ - ClassNameDefinition$5 { - name: Identifier<"TestClass">, - node: ClassDeclaration$4, - }, - ], - name: "TestClass", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$8 { - defs: [ - ClassNameDefinition$6 { - name: Identifier<"TestClass">, - node: ClassDeclaration$4, - }, - ], - name: "TestClass", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$9 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - ], - scopes: [ - GlobalScope$1 { - block: Program$5, - isStrict: false, - references: [], - set: Map { - "const" => ImplicitGlobalConstTypeVariable, - "TestGeneric" => Variable$2, - "Test" => Variable$3, - "deco" => Variable$4, - "TestClass" => Variable$7, - }, - type: "global", - upper: null, - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2, - Variable$3, - Variable$4, - Variable$7, - ], - }, - FunctionScope$2 { - block: TSDeclareFunction$3, - isStrict: false, - references: [], - set: Map { - "arguments" => Variable$5, - "_param" => Variable$6, - }, - type: "function", - upper: GlobalScope$1, - variables: [ - Variable$5, - Variable$6, - ], - }, - ClassScope$3 { - block: ClassDeclaration$4, - isStrict: true, - references: [ - Reference$3, - ], - set: Map { - "TestClass" => Variable$8, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$8, - ], - }, - FunctionScope$4 { - block: FunctionExpression$6, - isStrict: true, - references: [ - Reference$1, - Reference$2, - ], - set: Map { - "arguments" => Variable$9, - }, - type: "function", - upper: ClassScope$3, - variables: [ - Variable$9, - ], - }, - ], -} -`; diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts deleted file mode 100644 index 39fa3c402325..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts +++ /dev/null @@ -1,15 +0,0 @@ -//// @emitDecoratorMetadata = true - -function deco(...param: any) {} - -class T {} - -@deco -class A { - constructor(foo: T) { - @deco - class B { - constructor(bar: T) {} - } - } -} diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts.shot b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts.shot deleted file mode 100644 index 0e6e399e45e4..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts.shot +++ /dev/null @@ -1,298 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`class emit-metadata nested-class-both 1`] = ` -ScopeManager { - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2 { - defs: [ - FunctionNameDefinition$1 { - name: Identifier<"deco">, - node: FunctionDeclaration$1, - }, - ], - name: "deco", - references: [ - Reference$1 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$3 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - ], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$3 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$4 { - defs: [ - ParameterDefinition$2 { - name: Identifier<"param">, - node: FunctionDeclaration$1, - }, - ], - name: "param", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$5 { - defs: [ - ClassNameDefinition$3 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [ - Reference$2 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - Reference$4 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - ], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$6 { - defs: [ - ClassNameDefinition$4 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$7 { - defs: [ - ClassNameDefinition$5 { - name: Identifier<"A">, - node: ClassDeclaration$3, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$8 { - defs: [ - ClassNameDefinition$6 { - name: Identifier<"A">, - node: ClassDeclaration$3, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$9 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$10 { - defs: [ - ParameterDefinition$7 { - name: Identifier<"foo">, - node: FunctionExpression$4, - }, - ], - name: "foo", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$11 { - defs: [ - ClassNameDefinition$8 { - name: Identifier<"B">, - node: ClassDeclaration$5, - }, - ], - name: "B", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$12 { - defs: [ - ClassNameDefinition$9 { - name: Identifier<"B">, - node: ClassDeclaration$5, - }, - ], - name: "B", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$13 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$14 { - defs: [ - ParameterDefinition$10 { - name: Identifier<"bar">, - node: FunctionExpression$6, - }, - ], - name: "bar", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - ], - scopes: [ - GlobalScope$1 { - block: Program$7, - isStrict: false, - references: [ - Reference$1, - ], - set: Map { - "const" => ImplicitGlobalConstTypeVariable, - "deco" => Variable$2, - "T" => Variable$5, - "A" => Variable$7, - }, - type: "global", - upper: null, - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2, - Variable$5, - Variable$7, - ], - }, - FunctionScope$2 { - block: FunctionDeclaration$1, - isStrict: false, - references: [], - set: Map { - "arguments" => Variable$3, - "param" => Variable$4, - }, - type: "function", - upper: GlobalScope$1, - variables: [ - Variable$3, - Variable$4, - ], - }, - ClassScope$3 { - block: ClassDeclaration$2, - isStrict: true, - references: [], - set: Map { - "T" => Variable$6, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$6, - ], - }, - ClassScope$4 { - block: ClassDeclaration$3, - isStrict: true, - references: [], - set: Map { - "A" => Variable$8, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$8, - ], - }, - FunctionScope$5 { - block: FunctionExpression$4, - isStrict: true, - references: [ - Reference$2, - Reference$3, - ], - set: Map { - "arguments" => Variable$9, - "foo" => Variable$10, - "B" => Variable$11, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$9, - Variable$10, - Variable$11, - ], - }, - ClassScope$6 { - block: ClassDeclaration$5, - isStrict: true, - references: [], - set: Map { - "B" => Variable$12, - }, - type: "class", - upper: FunctionScope$5, - variables: [ - Variable$12, - ], - }, - FunctionScope$7 { - block: FunctionExpression$6, - isStrict: true, - references: [ - Reference$4, - ], - set: Map { - "arguments" => Variable$13, - "bar" => Variable$14, - }, - type: "function", - upper: ClassScope$6, - variables: [ - Variable$13, - Variable$14, - ], - }, - ], -} -`; diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts deleted file mode 100644 index d2fd5be90214..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts +++ /dev/null @@ -1,14 +0,0 @@ -//// @emitDecoratorMetadata = true - -function deco(...param: any) {} - -class T {} - -class A { - constructor(foo: T) { - @deco - class B { - constructor(bar: T) {} - } - } -} diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts.shot b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts.shot deleted file mode 100644 index f46cd16af2ef..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts.shot +++ /dev/null @@ -1,288 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`class emit-metadata nested-class-inner 1`] = ` -ScopeManager { - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2 { - defs: [ - FunctionNameDefinition$1 { - name: Identifier<"deco">, - node: FunctionDeclaration$1, - }, - ], - name: "deco", - references: [ - Reference$2 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - ], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$3 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$4 { - defs: [ - ParameterDefinition$2 { - name: Identifier<"param">, - node: FunctionDeclaration$1, - }, - ], - name: "param", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$5 { - defs: [ - ClassNameDefinition$3 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [ - Reference$1 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: false, - isWrite: false, - resolved: Variable$5, - }, - Reference$3 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - ], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$6 { - defs: [ - ClassNameDefinition$4 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$7 { - defs: [ - ClassNameDefinition$5 { - name: Identifier<"A">, - node: ClassDeclaration$3, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$8 { - defs: [ - ClassNameDefinition$6 { - name: Identifier<"A">, - node: ClassDeclaration$3, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$9 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$10 { - defs: [ - ParameterDefinition$7 { - name: Identifier<"foo">, - node: FunctionExpression$4, - }, - ], - name: "foo", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$11 { - defs: [ - ClassNameDefinition$8 { - name: Identifier<"B">, - node: ClassDeclaration$5, - }, - ], - name: "B", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$12 { - defs: [ - ClassNameDefinition$9 { - name: Identifier<"B">, - node: ClassDeclaration$5, - }, - ], - name: "B", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$13 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$14 { - defs: [ - ParameterDefinition$10 { - name: Identifier<"bar">, - node: FunctionExpression$6, - }, - ], - name: "bar", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - ], - scopes: [ - GlobalScope$1 { - block: Program$7, - isStrict: false, - references: [], - set: Map { - "const" => ImplicitGlobalConstTypeVariable, - "deco" => Variable$2, - "T" => Variable$5, - "A" => Variable$7, - }, - type: "global", - upper: null, - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2, - Variable$5, - Variable$7, - ], - }, - FunctionScope$2 { - block: FunctionDeclaration$1, - isStrict: false, - references: [], - set: Map { - "arguments" => Variable$3, - "param" => Variable$4, - }, - type: "function", - upper: GlobalScope$1, - variables: [ - Variable$3, - Variable$4, - ], - }, - ClassScope$3 { - block: ClassDeclaration$2, - isStrict: true, - references: [], - set: Map { - "T" => Variable$6, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$6, - ], - }, - ClassScope$4 { - block: ClassDeclaration$3, - isStrict: true, - references: [], - set: Map { - "A" => Variable$8, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$8, - ], - }, - FunctionScope$5 { - block: FunctionExpression$4, - isStrict: true, - references: [ - Reference$1, - Reference$2, - ], - set: Map { - "arguments" => Variable$9, - "foo" => Variable$10, - "B" => Variable$11, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$9, - Variable$10, - Variable$11, - ], - }, - ClassScope$6 { - block: ClassDeclaration$5, - isStrict: true, - references: [], - set: Map { - "B" => Variable$12, - }, - type: "class", - upper: FunctionScope$5, - variables: [ - Variable$12, - ], - }, - FunctionScope$7 { - block: FunctionExpression$6, - isStrict: true, - references: [ - Reference$3, - ], - set: Map { - "arguments" => Variable$13, - "bar" => Variable$14, - }, - type: "function", - upper: ClassScope$6, - variables: [ - Variable$13, - Variable$14, - ], - }, - ], -} -`; diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts deleted file mode 100644 index 2f5247067bf1..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts +++ /dev/null @@ -1,14 +0,0 @@ -//// @emitDecoratorMetadata = true - -function deco(...param: any) {} - -class T {} - -@deco -class A { - constructor(foo: T) { - class B { - constructor(bar: T) {} - } - } -} diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts.shot b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts.shot deleted file mode 100644 index 226235e83795..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts.shot +++ /dev/null @@ -1,289 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`class emit-metadata nested-class-outer 1`] = ` -ScopeManager { - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2 { - defs: [ - FunctionNameDefinition$1 { - name: Identifier<"deco">, - node: FunctionDeclaration$1, - }, - ], - name: "deco", - references: [ - Reference$1 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - ], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$3 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$4 { - defs: [ - ParameterDefinition$2 { - name: Identifier<"param">, - node: FunctionDeclaration$1, - }, - ], - name: "param", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$5 { - defs: [ - ClassNameDefinition$3 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [ - Reference$2 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - Reference$3 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: false, - isWrite: false, - resolved: Variable$5, - }, - ], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$6 { - defs: [ - ClassNameDefinition$4 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$7 { - defs: [ - ClassNameDefinition$5 { - name: Identifier<"A">, - node: ClassDeclaration$3, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$8 { - defs: [ - ClassNameDefinition$6 { - name: Identifier<"A">, - node: ClassDeclaration$3, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$9 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$10 { - defs: [ - ParameterDefinition$7 { - name: Identifier<"foo">, - node: FunctionExpression$4, - }, - ], - name: "foo", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$11 { - defs: [ - ClassNameDefinition$8 { - name: Identifier<"B">, - node: ClassDeclaration$5, - }, - ], - name: "B", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$12 { - defs: [ - ClassNameDefinition$9 { - name: Identifier<"B">, - node: ClassDeclaration$5, - }, - ], - name: "B", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$13 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$14 { - defs: [ - ParameterDefinition$10 { - name: Identifier<"bar">, - node: FunctionExpression$6, - }, - ], - name: "bar", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - ], - scopes: [ - GlobalScope$1 { - block: Program$7, - isStrict: false, - references: [ - Reference$1, - ], - set: Map { - "const" => ImplicitGlobalConstTypeVariable, - "deco" => Variable$2, - "T" => Variable$5, - "A" => Variable$7, - }, - type: "global", - upper: null, - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2, - Variable$5, - Variable$7, - ], - }, - FunctionScope$2 { - block: FunctionDeclaration$1, - isStrict: false, - references: [], - set: Map { - "arguments" => Variable$3, - "param" => Variable$4, - }, - type: "function", - upper: GlobalScope$1, - variables: [ - Variable$3, - Variable$4, - ], - }, - ClassScope$3 { - block: ClassDeclaration$2, - isStrict: true, - references: [], - set: Map { - "T" => Variable$6, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$6, - ], - }, - ClassScope$4 { - block: ClassDeclaration$3, - isStrict: true, - references: [], - set: Map { - "A" => Variable$8, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$8, - ], - }, - FunctionScope$5 { - block: FunctionExpression$4, - isStrict: true, - references: [ - Reference$2, - ], - set: Map { - "arguments" => Variable$9, - "foo" => Variable$10, - "B" => Variable$11, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$9, - Variable$10, - Variable$11, - ], - }, - ClassScope$6 { - block: ClassDeclaration$5, - isStrict: true, - references: [], - set: Map { - "B" => Variable$12, - }, - type: "class", - upper: FunctionScope$5, - variables: [ - Variable$12, - ], - }, - FunctionScope$7 { - block: FunctionExpression$6, - isStrict: true, - references: [ - Reference$3, - ], - set: Map { - "arguments" => Variable$13, - "bar" => Variable$14, - }, - type: "function", - upper: ClassScope$6, - variables: [ - Variable$13, - Variable$14, - ], - }, - ], -} -`; diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts b/packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts deleted file mode 100644 index fc0eb3c0525f..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts +++ /dev/null @@ -1,14 +0,0 @@ -//// @emitDecoratorMetadata = true - -function deco(...param: any) {} - -class T {} - -@deco -class A { - constructor(@deco foo: T) {} - - set foo(@deco a: T) {} - - foo1(@deco a: T, b: T) {} -} diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts.shot b/packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts.shot deleted file mode 100644 index 5a1ff4d6429a..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts.shot +++ /dev/null @@ -1,344 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`class emit-metadata parameters-deco 1`] = ` -ScopeManager { - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2 { - defs: [ - FunctionNameDefinition$1 { - name: Identifier<"deco">, - node: FunctionDeclaration$1, - }, - ], - name: "deco", - references: [ - Reference$1 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$3 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$5 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$7 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - ], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$3 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$4 { - defs: [ - ParameterDefinition$2 { - name: Identifier<"param">, - node: FunctionDeclaration$1, - }, - ], - name: "param", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$5 { - defs: [ - ClassNameDefinition$3 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [ - Reference$2 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - Reference$4 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: false, - isWrite: false, - resolved: Variable$5, - }, - Reference$6 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - Reference$8 { - identifier: Identifier<"T">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - ], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$6 { - defs: [ - ClassNameDefinition$4 { - name: Identifier<"T">, - node: ClassDeclaration$2, - }, - ], - name: "T", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$7 { - defs: [ - ClassNameDefinition$5 { - name: Identifier<"A">, - node: ClassDeclaration$3, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$8 { - defs: [ - ClassNameDefinition$6 { - name: Identifier<"A">, - node: ClassDeclaration$3, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$9 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$10 { - defs: [ - ParameterDefinition$7 { - name: Identifier<"foo">, - node: FunctionExpression$4, - }, - ], - name: "foo", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$11 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$12 { - defs: [ - ParameterDefinition$8 { - name: Identifier<"a">, - node: FunctionExpression$5, - }, - ], - name: "a", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$13 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$14 { - defs: [ - ParameterDefinition$9 { - name: Identifier<"a">, - node: FunctionExpression$6, - }, - ], - name: "a", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$15 { - defs: [ - ParameterDefinition$10 { - name: Identifier<"b">, - node: FunctionExpression$6, - }, - ], - name: "b", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - ], - scopes: [ - GlobalScope$1 { - block: Program$7, - isStrict: false, - references: [ - Reference$1, - ], - set: Map { - "const" => ImplicitGlobalConstTypeVariable, - "deco" => Variable$2, - "T" => Variable$5, - "A" => Variable$7, - }, - type: "global", - upper: null, - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2, - Variable$5, - Variable$7, - ], - }, - FunctionScope$2 { - block: FunctionDeclaration$1, - isStrict: false, - references: [], - set: Map { - "arguments" => Variable$3, - "param" => Variable$4, - }, - type: "function", - upper: GlobalScope$1, - variables: [ - Variable$3, - Variable$4, - ], - }, - ClassScope$3 { - block: ClassDeclaration$2, - isStrict: true, - references: [], - set: Map { - "T" => Variable$6, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$6, - ], - }, - ClassScope$4 { - block: ClassDeclaration$3, - isStrict: true, - references: [], - set: Map { - "A" => Variable$8, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$8, - ], - }, - FunctionScope$5 { - block: FunctionExpression$4, - isStrict: true, - references: [ - Reference$2, - Reference$3, - ], - set: Map { - "arguments" => Variable$9, - "foo" => Variable$10, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$9, - Variable$10, - ], - }, - FunctionScope$6 { - block: FunctionExpression$5, - isStrict: true, - references: [ - Reference$4, - Reference$5, - ], - set: Map { - "arguments" => Variable$11, - "a" => Variable$12, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$11, - Variable$12, - ], - }, - FunctionScope$7 { - block: FunctionExpression$6, - isStrict: true, - references: [ - Reference$6, - Reference$7, - Reference$8, - ], - set: Map { - "arguments" => Variable$13, - "a" => Variable$14, - "b" => Variable$15, - }, - type: "function", - upper: ClassScope$4, - variables: [ - Variable$13, - Variable$14, - Variable$15, - ], - }, - ], -} -`; diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts b/packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts deleted file mode 100644 index a4c17dcaa5d1..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts +++ /dev/null @@ -1,13 +0,0 @@ -//// @emitDecoratorMetadata = true - -function deco(...param: any) {} - -namespace a { - export class B {} -} - -class A { - property: a.B; - @deco - propertyWithDeco: a.B; -} diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts.shot b/packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts.shot deleted file mode 100644 index 91636155f5a9..000000000000 --- a/packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts.shot +++ /dev/null @@ -1,205 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`class emit-metadata property-deco 1`] = ` -ScopeManager { - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2 { - defs: [ - FunctionNameDefinition$1 { - name: Identifier<"deco">, - node: FunctionDeclaration$1, - }, - ], - name: "deco", - references: [ - Reference$2 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - ], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$3 { - defs: [], - name: "arguments", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$4 { - defs: [ - ParameterDefinition$2 { - name: Identifier<"param">, - node: FunctionDeclaration$1, - }, - ], - name: "param", - references: [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$5 { - defs: [ - TSModuleNameDefinition$3 { - name: Identifier<"a">, - node: TSModuleDeclaration$2, - }, - ], - name: "a", - references: [ - Reference$1 { - identifier: Identifier<"a">, - isRead: true, - isTypeReference: true, - isValueReference: false, - isWrite: false, - resolved: Variable$5, - }, - Reference$3 { - identifier: Identifier<"a">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: Variable$5, - }, - ], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$6 { - defs: [ - ClassNameDefinition$4 { - name: Identifier<"B">, - node: ClassDeclaration$3, - }, - ], - name: "B", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$7 { - defs: [ - ClassNameDefinition$5 { - name: Identifier<"B">, - node: ClassDeclaration$3, - }, - ], - name: "B", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$8 { - defs: [ - ClassNameDefinition$6 { - name: Identifier<"A">, - node: ClassDeclaration$4, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$9 { - defs: [ - ClassNameDefinition$7 { - name: Identifier<"A">, - node: ClassDeclaration$4, - }, - ], - name: "A", - references: [], - isValueVariable: true, - isTypeVariable: true, - }, - ], - scopes: [ - GlobalScope$1 { - block: Program$5, - isStrict: false, - references: [], - set: Map { - "const" => ImplicitGlobalConstTypeVariable, - "deco" => Variable$2, - "a" => Variable$5, - "A" => Variable$8, - }, - type: "global", - upper: null, - variables: [ - ImplicitGlobalConstTypeVariable, - Variable$2, - Variable$5, - Variable$8, - ], - }, - FunctionScope$2 { - block: FunctionDeclaration$1, - isStrict: false, - references: [], - set: Map { - "arguments" => Variable$3, - "param" => Variable$4, - }, - type: "function", - upper: GlobalScope$1, - variables: [ - Variable$3, - Variable$4, - ], - }, - TSModuleScope$3 { - block: TSModuleDeclaration$2, - isStrict: true, - references: [], - set: Map { - "B" => Variable$6, - }, - type: "tsModule", - upper: GlobalScope$1, - variables: [ - Variable$6, - ], - }, - ClassScope$4 { - block: ClassDeclaration$3, - isStrict: true, - references: [], - set: Map { - "B" => Variable$7, - }, - type: "class", - upper: TSModuleScope$3, - variables: [ - Variable$7, - ], - }, - ClassScope$5 { - block: ClassDeclaration$4, - isStrict: true, - references: [ - Reference$1, - Reference$2, - Reference$3, - ], - set: Map { - "A" => Variable$9, - }, - type: "class", - upper: GlobalScope$1, - variables: [ - Variable$9, - ], - }, - ], -} -`; diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index 885645f4bbae..9c2aea848f5e 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -50,6 +50,8 @@ interface ParserOptions { // use emitDecoratorMetadata without specifying parserOptions.project emitDecoratorMetadata?: boolean; + // use experimentalDecorators without specifying parserOptions.project + experimentalDecorators?: boolean; // typescript-estree specific comment?: boolean; diff --git a/packages/typescript-estree/src/createParserServices.ts b/packages/typescript-estree/src/createParserServices.ts index 1e62bdbe351e..de3070c2fe73 100644 --- a/packages/typescript-estree/src/createParserServices.ts +++ b/packages/typescript-estree/src/createParserServices.ts @@ -8,16 +8,25 @@ export function createParserServices( program: ts.Program | null, ): ParserServices { if (!program) { - // we always return the node maps because - // (a) they don't require type info and - // (b) they can be useful when using some of TS's internal non-type-aware AST utils - return { program, ...astMaps }; + return { + program, + emitDecoratorMetadata: undefined, + experimentalDecorators: undefined, + // we always return the node maps because + // (a) they don't require type info and + // (b) they can be useful when using some of TS's internal non-type-aware AST utils + ...astMaps, + }; } const checker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); return { program, + // not set in the config is the same as off + emitDecoratorMetadata: compilerOptions.emitDecoratorMetadata ?? false, + experimentalDecorators: compilerOptions.experimentalDecorators ?? false, ...astMaps, getSymbolAtLocation: node => checker.getSymbolAtLocation(astMaps.esTreeNodeToTSNodeMap.get(node)), diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index e86be6d50996..a95ffd1f21b9 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -243,18 +243,24 @@ export interface ParserWeakMapESTreeToTSNode< has(key: unknown): boolean; } +export interface ParserServicesBase { + emitDecoratorMetadata: boolean | undefined; + experimentalDecorators: boolean | undefined; +} export interface ParserServicesNodeMaps { esTreeNodeToTSNodeMap: ParserWeakMapESTreeToTSNode; tsNodeToESTreeNodeMap: ParserWeakMap; } export interface ParserServicesWithTypeInformation - extends ParserServicesNodeMaps { + extends ParserServicesNodeMaps, + ParserServicesBase { program: ts.Program; getSymbolAtLocation: (node: TSESTree.Node) => ts.Symbol | undefined; getTypeAtLocation: (node: TSESTree.Node) => ts.Type; } export interface ParserServicesWithoutTypeInformation - extends ParserServicesNodeMaps { + extends ParserServicesNodeMaps, + ParserServicesBase { program: null; } export type ParserServices = diff --git a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap index c4182c42113a..b20bb4029974 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap @@ -287,7 +287,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -581,7 +583,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -765,7 +769,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - witho "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -949,7 +955,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - witho "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -1169,7 +1177,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .json file - wit "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -1463,7 +1473,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -1757,7 +1769,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -1941,7 +1955,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -2125,7 +2141,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -2309,7 +2327,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .ts file - witho "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -2493,7 +2513,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .ts file - witho "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -2787,7 +2809,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -3081,7 +3105,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -3265,7 +3291,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -3449,7 +3477,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -3743,7 +3773,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .vue file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -3927,7 +3959,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .vue file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, @@ -4111,7 +4145,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .vue file - with "type": "Program", }, "services": { + "emitDecoratorMetadata": undefined, "esTreeNodeToTSNodeMap": WeakMap {}, + "experimentalDecorators": undefined, "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, diff --git a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts index 09daae795d61..1051b8d2f171 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts @@ -16,6 +16,9 @@ const mockProgram = { getTypeChecker(): void { return; }, + getCompilerOptions(): unknown { + return {}; + }, }; jest.mock('../../src/ast-converter', () => { diff --git a/packages/website/src/components/linter/createParser.ts b/packages/website/src/components/linter/createParser.ts index ee509fed0acf..3eba0c45e60a 100644 --- a/packages/website/src/components/linter/createParser.ts +++ b/packages/website/src/components/linter/createParser.ts @@ -77,6 +77,7 @@ export function createParser( }); const checker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); onUpdate(filePath, { storedAST: converted.estree, @@ -89,6 +90,9 @@ export function createParser( ast: converted.estree, services: { program, + emitDecoratorMetadata: compilerOptions.emitDecoratorMetadata ?? false, + experimentalDecorators: + compilerOptions.experimentalDecorators ?? false, esTreeNodeToTSNodeMap: converted.astMaps.esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap: converted.astMaps.tsNodeToESTreeNodeMap, getSymbolAtLocation: node => diff --git a/yarn.lock b/yarn.lock index a1c9a9cbec37..12c08dc4f015 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5540,6 +5540,7 @@ __metadata: semver: ^7.5.4 sinon: ^16.0.0 source-map-support: ^0.5.21 + typescript: "*" peerDependencies: "@eslint/eslintrc": ">=2" eslint: ">=8" From d786f7cdcc48d8b057e389f6598ac6b1433acf11 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 18 Mar 2024 21:37:05 +1030 Subject: [PATCH 2/9] fixup after merge --- .../src/rules/consistent-type-imports.ts | 97 +- .../rules/consistent-type-imports.test.ts | 4221 ++++++++--------- 2 files changed, 2060 insertions(+), 2258 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index 27a10c4d1660..43853ceee220 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -1,10 +1,6 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import type { RuleListener } from '@typescript-eslint/utils/eslint-utils'; -import { - getDeclaredVariables, - getSourceCode, -} from '@typescript-eslint/utils/eslint-utils'; import { createRule, @@ -103,7 +99,6 @@ export default createRule({ create(context, [option]) { const prefer = option.prefer ?? 'type-imports'; const disallowTypeAnnotations = option.disallowTypeAnnotations !== false; - const sourceCode = getSourceCode(context); const selectors: RuleListener = {}; @@ -167,7 +162,6 @@ export default createRule({ ImportDeclaration(node): void { const source = node.source.value; // sourceImports is the object containing all the specifics for a particular import source, type or value - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition sourceImportsMap[source] ??= { source, reportValueImports: [], // if there is a mismatch where type importKind but value specifiers @@ -220,7 +214,7 @@ export default createRule({ continue; } - const [variable] = getDeclaredVariables(context, specifier); + const [variable] = context.sourceCode.getDeclaredVariables(specifier); if (variable.references.length === 0) { unusedSpecifiers.push(specifier); } else { @@ -229,6 +223,7 @@ export default createRule({ * keep origin import kind when export * export { Type } * export default Type; + * export = Type; */ if ( ref.identifier.parent.type === AST_NODE_TYPES.ExportSpecifier || @@ -483,18 +478,18 @@ export default createRule({ // import Foo, {Type1, Type2} from 'foo' // import DefType, {Type1, Type2} from 'foo' const openingBraceToken = nullThrows( - sourceCode.getTokenBefore( + context.sourceCode.getTokenBefore( subsetNamedSpecifiers[0], isOpeningBraceToken, ), NullThrowsReasons.MissingToken('{', node.type), ); const commaToken = nullThrows( - sourceCode.getTokenBefore(openingBraceToken, isCommaToken), + context.sourceCode.getTokenBefore(openingBraceToken, isCommaToken), NullThrowsReasons.MissingToken(',', node.type), ); const closingBraceToken = nullThrows( - sourceCode.getFirstTokenBetween( + context.sourceCode.getFirstTokenBetween( openingBraceToken, node.source, isClosingBraceToken, @@ -509,7 +504,7 @@ export default createRule({ ); typeNamedSpecifiersTexts.push( - sourceCode.text.slice( + context.sourceCode.text.slice( openingBraceToken.range[1], closingBraceToken.range[0], ), @@ -535,7 +530,9 @@ export default createRule({ ); removeTypeNamedSpecifiers.push(fixer.removeRange(removeRange)); - typeNamedSpecifiersTexts.push(sourceCode.text.slice(...textRange)); + typeNamedSpecifiersTexts.push( + context.sourceCode.text.slice(...textRange), + ); } } return { @@ -558,7 +555,10 @@ export default createRule({ const last = namedSpecifierGroup[namedSpecifierGroup.length - 1]; const removeRange: TSESTree.Range = [first.range[0], last.range[1]]; const textRange: TSESTree.Range = [...removeRange]; - const before = sourceCode.getTokenBefore(first)!; + const before = nullThrows( + context.sourceCode.getTokenBefore(first), + NullThrowsReasons.MissingToken('token', 'first specifier'), + ); textRange[0] = before.range[1]; if (isCommaToken(before)) { removeRange[0] = before.range[0]; @@ -568,7 +568,10 @@ export default createRule({ const isFirst = allNamedSpecifiers[0] === first; const isLast = allNamedSpecifiers[allNamedSpecifiers.length - 1] === last; - const after = sourceCode.getTokenAfter(last)!; + const after = nullThrows( + context.sourceCode.getTokenAfter(last), + NullThrowsReasons.MissingToken('token', 'last specifier'), + ); textRange[1] = after.range[0]; if (isFirst || isLast) { if (isCommaToken(after)) { @@ -594,14 +597,20 @@ export default createRule({ insertText: string, ): TSESLint.RuleFix { const closingBraceToken = nullThrows( - sourceCode.getFirstTokenBetween( - sourceCode.getFirstToken(target), + context.sourceCode.getFirstTokenBetween( + nullThrows( + context.sourceCode.getFirstToken(target), + NullThrowsReasons.MissingToken('token before', 'import'), + ), target.source, isClosingBraceToken, ), NullThrowsReasons.MissingToken('}', target.type), ); - const before = sourceCode.getTokenBefore(closingBraceToken)!; + const before = nullThrows( + context.sourceCode.getTokenBefore(closingBraceToken), + NullThrowsReasons.MissingToken('token before', 'closing brace'), + ); if (!isCommaToken(before) && !isOpeningBraceToken(before)) { insertText = `,${insertText}`; } @@ -619,7 +628,7 @@ export default createRule({ typeSpecifiers: TSESTree.ImportSpecifier[], ): IterableIterator { for (const spec of typeSpecifiers) { - const insertText = sourceCode.text.slice(...spec.range); + const insertText = context.sourceCode.text.slice(...spec.range); yield fixer.replaceTextRange(spec.range, `type ${insertText}`); } } @@ -744,17 +753,21 @@ export default createRule({ node, `import {${typeNamedSpecifiers .map(spec => { - const insertText = sourceCode.text.slice(...spec.range); + const insertText = context.sourceCode.text.slice( + ...spec.range, + ); return `type ${insertText}`; }) - .join(', ')}} from ${sourceCode.getText(node.source)};\n`, + .join( + ', ', + )}} from ${context.sourceCode.getText(node.source)};\n`, ); } else { yield fixer.insertTextBefore( node, `import type {${ fixesNamedSpecifiers.typeNamedSpecifiersText - }} from ${sourceCode.getText(node.source)};\n`, + }} from ${context.sourceCode.getText(node.source)};\n`, ); } } @@ -769,7 +782,7 @@ export default createRule({ // import DefType, * as Type from 'foo' // import DefType, * as Type from 'foo' const commaToken = nullThrows( - sourceCode.getTokenBefore(namespaceSpecifier, isCommaToken), + context.sourceCode.getTokenBefore(namespaceSpecifier, isCommaToken), NullThrowsReasons.MissingToken(',', node.type), ); @@ -783,9 +796,9 @@ export default createRule({ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ insert yield fixer.insertTextBefore( node, - `import type ${sourceCode.getText( + `import type ${context.sourceCode.getText( namespaceSpecifier, - )} from ${sourceCode.getText(node.source)};\n`, + )} from ${context.sourceCode.getText(node.source)};\n`, ); } if ( @@ -794,7 +807,7 @@ export default createRule({ ) { if (report.typeSpecifiers.length === node.specifiers.length) { const importToken = nullThrows( - sourceCode.getFirstToken(node, isImportKeyword), + context.sourceCode.getFirstToken(node, isImportKeyword), NullThrowsReasons.MissingToken('import', node.type), ); // import type Type from 'foo' @@ -802,22 +815,24 @@ export default createRule({ yield fixer.insertTextAfter(importToken, ' type'); } else { const commaToken = nullThrows( - sourceCode.getTokenAfter(defaultSpecifier, isCommaToken), + context.sourceCode.getTokenAfter(defaultSpecifier, isCommaToken), NullThrowsReasons.MissingToken(',', defaultSpecifier.type), ); // import Type , {...} from 'foo' // ^^^^^ pick - const defaultText = sourceCode.text + const defaultText = context.sourceCode.text .slice(defaultSpecifier.range[0], commaToken.range[0]) .trim(); yield fixer.insertTextBefore( node, - `import type ${defaultText} from ${sourceCode.getText( + `import type ${defaultText} from ${context.sourceCode.getText( node.source, )};\n`, ); const afterToken = nullThrows( - sourceCode.getTokenAfter(commaToken, { includeComments: true }), + context.sourceCode.getTokenAfter(commaToken, { + includeComments: true, + }), NullThrowsReasons.MissingToken('any token', node.type), ); // import Type , {...} from 'foo' @@ -843,14 +858,14 @@ export default createRule({ // import type Foo from 'foo' // ^^^^^ insert const importToken = nullThrows( - sourceCode.getFirstToken(node, isImportKeyword), + context.sourceCode.getFirstToken(node, isImportKeyword), NullThrowsReasons.MissingToken('import', node.type), ); yield fixer.insertTextAfter(importToken, ' type'); if (isDefaultImport) { // Has default import - const openingBraceToken = sourceCode.getFirstTokenBetween( + const openingBraceToken = context.sourceCode.getFirstTokenBetween( importToken, node.source, isOpeningBraceToken, @@ -858,11 +873,11 @@ export default createRule({ if (openingBraceToken) { // Only braces. e.g. import Foo, {} from 'foo' const commaToken = nullThrows( - sourceCode.getTokenBefore(openingBraceToken, isCommaToken), + context.sourceCode.getTokenBefore(openingBraceToken, isCommaToken), NullThrowsReasons.MissingToken(',', node.type), ); const closingBraceToken = nullThrows( - sourceCode.getFirstTokenBetween( + context.sourceCode.getFirstTokenBetween( openingBraceToken, node.source, isClosingBraceToken, @@ -876,14 +891,14 @@ export default createRule({ commaToken.range[0], closingBraceToken.range[1], ]); - const specifiersText = sourceCode.text.slice( + const specifiersText = context.sourceCode.text.slice( commaToken.range[1], closingBraceToken.range[1], ); if (node.specifiers.length > 1) { yield fixer.insertTextAfter( node, - `\nimport type${specifiersText} from ${sourceCode.getText( + `\nimport type${specifiersText} from ${context.sourceCode.getText( node.source, )};`, ); @@ -971,7 +986,7 @@ export default createRule({ node, `import {${ fixesNamedSpecifiers.typeNamedSpecifiersText - }} from ${sourceCode.getText(node.source)};\n`, + }} from ${context.sourceCode.getText(node.source)};\n`, ); } } @@ -988,11 +1003,11 @@ export default createRule({ // import type Foo from 'foo' // ^^^^ remove const importToken = nullThrows( - sourceCode.getFirstToken(node, isImportKeyword), + context.sourceCode.getFirstToken(node, isImportKeyword), NullThrowsReasons.MissingToken('import', node.type), ); const typeToken = nullThrows( - sourceCode.getFirstTokenBetween( + context.sourceCode.getFirstTokenBetween( importToken, node.specifiers[0]?.local ?? node.source, isTypeKeyword, @@ -1000,7 +1015,7 @@ export default createRule({ NullThrowsReasons.MissingToken('type', node.type), ); const afterToken = nullThrows( - sourceCode.getTokenAfter(typeToken, { includeComments: true }), + context.sourceCode.getTokenAfter(typeToken, { includeComments: true }), NullThrowsReasons.MissingToken('any token', node.type), ); yield fixer.removeRange([typeToken.range[0], afterToken.range[0]]); @@ -1013,11 +1028,11 @@ export default createRule({ // import { type Foo } from 'foo' // ^^^^ remove const typeToken = nullThrows( - sourceCode.getFirstToken(node, isTypeKeyword), + context.sourceCode.getFirstToken(node, isTypeKeyword), NullThrowsReasons.MissingToken('type', node.type), ); const afterToken = nullThrows( - sourceCode.getTokenAfter(typeToken, { includeComments: true }), + context.sourceCode.getTokenAfter(typeToken, { includeComments: true }), NullThrowsReasons.MissingToken('any token', node.type), ); yield fixer.removeRange([typeToken.range[0], afterToken.range[0]]); diff --git a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts index 9e01955b4a5c..8d1cd5ff08d8 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -1,2313 +1,2100 @@ import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/consistent-type-imports'; -import { getFixturesRootDir } from '../RuleTester'; -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', +const PARSER_OPTION_COMBOS = [ + { + experimentalDecorators: false, + emitDecoratorMetadata: false, }, -}); - -const withMetaParserOptions = { - EXPERIMENTAL_useProjectService: false, - tsconfigRootDir: getFixturesRootDir(), - project: './tsconfig-withmeta.json', -}; - -const withMetaConfigParserOptions = { - emitDecoratorMetadata: true, -}; - -ruleTester.run('consistent-type-imports', rule, { - valid: [ - ` - import Foo from 'foo'; - const foo: Foo = new Foo(); - `, - ` - import foo from 'foo'; - const foo: foo.Foo = foo.fn(); - `, - ` - import { A, B } from 'foo'; - const foo: A = B(); - const bar = new A(); - `, - ` - import Foo from 'foo'; - `, - ` - import Foo from 'foo'; - type T = Foo; // shadowing - `, - ` - import Foo from 'foo'; - function fn() { - type Foo = {}; // shadowing - let foo: Foo; - } - `, - ` - import { A, B } from 'foo'; - const b = B; - `, - ` - import { A, B, C as c } from 'foo'; - const d = c; - `, - ` - import {} from 'foo'; // empty - `, - { - code: ` - let foo: import('foo'); - let bar: import('foo').Bar; - `, - options: [{ disallowTypeAnnotations: false }], - }, - { - code: ` - import Foo from 'foo'; - let foo: Foo; - `, - options: [{ prefer: 'no-type-imports' }], - }, - // type queries - ` - import type Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - ` - import type { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - ` - import type * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - { - code: ` - import Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - }, - { - code: ` - import { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - }, - { - code: ` - import * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - }, - { - code: ` - import * as Type from 'foo' assert { type: 'json' }; - const a: typeof Type = Type; - `, - options: [{ prefer: 'no-type-imports' }], - }, - ` - import { type A } from 'foo'; - type T = A; - `, - ` - import { type A, B } from 'foo'; - type T = A; - const b = B; - `, - ` - import { type A, type B } from 'foo'; - type T = A; - type Z = B; - `, - ` - import { B } from 'foo'; - import { type A } from 'foo'; - type T = A; - const b = B; - `, - { - code: ` - import { B, type A } from 'foo'; - type T = A; - const b = B; - `, - options: [{ fixStyle: 'inline-type-imports' }], - }, - { - code: ` - import { B } from 'foo'; - import type A from 'baz'; - type T = A; - const b = B; - `, - options: [{ fixStyle: 'inline-type-imports' }], - }, - { - code: ` - import { type B } from 'foo'; - import type { A } from 'foo'; - type T = A; - const b = B; - `, - options: [{ fixStyle: 'inline-type-imports' }], - }, - { - code: ` - import { B, type C } from 'foo'; - import type A from 'baz'; - type T = A; - type Z = C; - const b = B; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - }, - { - code: ` - import { B } from 'foo'; - import type { A } from 'foo'; - type T = A; - const b = B; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - }, - { - code: ` - import { B } from 'foo'; - import { A } from 'foo'; - type T = A; - const b = B; - `, - options: [{ prefer: 'no-type-imports', fixStyle: 'inline-type-imports' }], - }, - // exports - ` - import Type from 'foo'; - - export { Type }; // is a value export - export default Type; // is a value export - `, - ` - import type Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - ` - import { Type } from 'foo'; - - export { Type }; // is a value export - export default Type; // is a value export - `, - ` - import type { Type } from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - ` - import * as Type from 'foo'; - - export { Type }; // is a value export - export default Type; // is a value export - `, - ` - import type * as Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - - { - code: ` - import Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - }, - { - code: ` - import { Type } from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - }, - { - code: ` - import * as Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - }, - // https://github.com/typescript-eslint/typescript-eslint/issues/2455 - { - code: ` - import React from 'react'; - - export const ComponentFoo: React.FC = () => { - return
Foo Foo
; - }; - `, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - }, - { - code: ` - import { h } from 'some-other-jsx-lib'; - - export const ComponentFoo: h.FC = () => { - return
Foo Foo
; - }; - `, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - jsxPragma: 'h', - }, - }, - { - code: ` - import { Fragment } from 'react'; - - export const ComponentFoo: Fragment = () => { - return <>Foo Foo; - }; - `, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - jsxFragmentName: 'Fragment', - }, - }, - ` - import Default, * as Rest from 'module'; - const a: typeof Default = Default; - const b: typeof Rest = Rest; - `, - { - code: ` - import Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo: Foo; - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo(foo: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo(): Foo {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - foo(@deco foo: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - set foo(value: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - get foo() {} - - set foo(value: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - get foo() {} - - set ['foo'](value: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import type { Foo } from 'foo'; - const key = 'k'; - class A { - @deco - get [key]() {} - - set [key](value: Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import * as foo from 'foo'; - @deco - class A { - constructor(foo: foo.Foo) {} - } - `, - parserOptions: withMetaParserOptions, - }, - { - code: ` - import Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo: Foo; - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo(foo: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - foo(): Foo {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - foo(@deco foo: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - set foo(value: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - get foo() {} - - set foo(value: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import Foo from 'foo'; - class A { - @deco - get foo() {} - - set ['foo'](value: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import type { Foo } from 'foo'; - const key = 'k'; - class A { - @deco - get [key]() {} - - set [key](value: Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import * as foo from 'foo'; - @deco - class A { - constructor(foo: foo.Foo) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - - // https://github.com/typescript-eslint/typescript-eslint/issues/7327 - { - code: ` - import type { ClassA } from './classA'; - - export class ClassB { - public constructor(node: ClassA) {} - } - `, - parserOptions: withMetaConfigParserOptions, - }, - - // https://github.com/typescript-eslint/typescript-eslint/issues/2989 - ` -import type * as constants from './constants'; - -export type Y = { - [constants.X]: ReadonlyArray; -}; - `, - ` - import A from 'foo'; - export = A; - `, - ` - import type A from 'foo'; - export = A; - `, - ` - import type A from 'foo'; - export = {} as A; - `, - ` - import { type A } from 'foo'; - export = {} as A; - `, - ], - invalid: [ - { - code: ` - import Foo from 'foo'; - let foo: Foo; - type Bar = Foo; - interface Baz { - foo: Foo; - } - function fn(a: Foo): Foo {} - `, - output: ` - import type Foo from 'foo'; - let foo: Foo; - type Bar = Foo; - interface Baz { - foo: Foo; - } - function fn(a: Foo): Foo {} - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import Foo from 'foo'; - let foo: Foo; - `, - output: ` - import type Foo from 'foo'; - let foo: Foo; - `, - options: [{ prefer: 'type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import Foo from 'foo'; - let foo: Foo; - `, - output: ` - import type Foo from 'foo'; - let foo: Foo; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { A, B } from 'foo'; - let foo: A; - let bar: B; - `, - output: ` - import type { A, B } from 'foo'; - let foo: A; - let bar: B; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { A as a, B as b } from 'foo'; - let foo: a; - let bar: b; - `, - output: ` - import type { A as a, B as b } from 'foo'; - let foo: a; - let bar: b; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import Foo from 'foo'; - type Bar = typeof Foo; // TSTypeQuery - `, - output: ` - import type Foo from 'foo'; - type Bar = typeof Foo; // TSTypeQuery - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import foo from 'foo'; - type Bar = foo.Bar; // TSQualifiedName - `, - output: ` - import type foo from 'foo'; - type Bar = foo.Bar; // TSQualifiedName - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import foo from 'foo'; - type Baz = (typeof foo.bar)['Baz']; // TSQualifiedName & TSTypeQuery - `, - output: ` - import type foo from 'foo'; - type Baz = (typeof foo.bar)['Baz']; // TSQualifiedName & TSTypeQuery - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import * as A from 'foo'; - let foo: A.Foo; - `, - output: ` - import type * as A from 'foo'; - let foo: A.Foo; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - // default and named - code: ` -import A, { B } from 'foo'; -let foo: A; -let bar: B; - `, - output: ` -import type { B } from 'foo'; -import type A from 'foo'; -let foo: A; -let bar: B; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, - }, - ], - }, - { - code: noFormat` - import A, {} from 'foo'; - let foo: A; - `, - output: ` - import type A from 'foo'; - let foo: A; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` -import { A, B } from 'foo'; -const foo: A = B(); - `, - output: ` -import type { A} from 'foo'; -import { B } from 'foo'; -const foo: A = B(); - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"A"' }, - line: 2, - column: 1, - }, - ], - }, - { - code: ` -import { A, B, C } from 'foo'; -const foo: A = B(); -let bar: C; - `, - output: ` -import type { A, C } from 'foo'; -import { B } from 'foo'; -const foo: A = B(); -let bar: C; - `, - errors: [ - { - messageId: 'someImportsAreOnlyTypes', - data: { typeImports: '"A" and "C"' }, - line: 2, - column: 1, - }, - ], - }, - { - code: ` -import { A, B, C, D } from 'foo'; -const foo: A = B(); -type T = { bar: C; baz: D }; - `, - output: ` -import type { A, C, D } from 'foo'; -import { B } from 'foo'; -const foo: A = B(); -type T = { bar: C; baz: D }; - `, - errors: [ - { - messageId: 'someImportsAreOnlyTypes', - data: { typeImports: '"A", "C" and "D"' }, - line: 2, - column: 1, - }, - ], - }, - { - code: ` -import A, { B, C, D } from 'foo'; -B(); -type T = { foo: A; bar: C; baz: D }; - `, - output: ` -import type { C, D } from 'foo'; -import type A from 'foo'; -import { B } from 'foo'; -B(); -type T = { foo: A; bar: C; baz: D }; - `, - errors: [ - { - messageId: 'someImportsAreOnlyTypes', - data: { typeImports: '"A", "C" and "D"' }, - line: 2, - column: 1, - }, - ], - }, - { - code: ` -import A, { B } from 'foo'; -B(); -type T = A; - `, - output: ` -import type A from 'foo'; -import { B } from 'foo'; -B(); -type T = A; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"A"' }, - line: 2, - column: 1, - }, - ], - }, - { - code: ` - import type Already1Def from 'foo'; - import type { Already1 } from 'foo'; - import A, { B } from 'foo'; - import { C, D, E } from 'bar'; - import type { Already2 } from 'bar'; - type T = { b: B; c: C; d: D }; - `, - output: ` - import type Already1Def from 'foo'; - import type { Already1 , B } from 'foo'; - import A from 'foo'; - import { E } from 'bar'; - import type { Already2 , C, D} from 'bar'; - type T = { b: B; c: C; d: D }; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"B"' }, - line: 4, - column: 9, - }, - { - messageId: 'someImportsAreOnlyTypes', - data: { typeImports: '"C" and "D"' }, - line: 5, - column: 9, - }, - ], - }, - { - code: ` -import A, { /* comment */ B } from 'foo'; -type T = B; - `, - output: ` -import type { /* comment */ B } from 'foo'; -import A from 'foo'; -type T = B; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"B"' }, - line: 2, - column: 1, - }, - ], - }, - { - code: noFormat` -import { A, B, C } from 'foo'; -import { D, E, F, } from 'bar'; -type T = A | D; - `, - output: ` -import type { A} from 'foo'; -import { B, C } from 'foo'; -import type { D} from 'bar'; -import { E, F, } from 'bar'; -type T = A | D; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"A"' }, - line: 2, - column: 1, - }, - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"D"' }, - line: 3, - column: 1, - }, - ], - }, - { - code: noFormat` -import { A, B, C } from 'foo'; -import { D, E, F, } from 'bar'; -type T = B | E; - `, - output: ` -import type { B} from 'foo'; -import { A, C } from 'foo'; -import type { E} from 'bar'; -import { D, F, } from 'bar'; -type T = B | E; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"B"' }, - line: 2, - column: 1, - }, - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"E"' }, - line: 3, - column: 1, - }, - ], - }, - { - code: noFormat` -import { A, B, C } from 'foo'; -import { D, E, F, } from 'bar'; -type T = C | F; - `, - output: ` -import type { C } from 'foo'; -import { A, B } from 'foo'; -import type { F} from 'bar'; -import { D, E } from 'bar'; -type T = C | F; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"C"' }, - line: 2, - column: 1, - }, - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"F"' }, - line: 3, - column: 1, - }, - ], - }, - { - // all type fix cases - code: ` -import { Type1, Type2 } from 'named_types'; -import Type from 'default_type'; -import * as Types from 'namespace_type'; -import Default, { Named } from 'default_and_named_type'; -type T = Type1 | Type2 | Type | Types.A | Default | Named; - `, - output: ` -import type { Type1, Type2 } from 'named_types'; -import type Type from 'default_type'; -import type * as Types from 'namespace_type'; -import type { Named } from 'default_and_named_type'; -import type Default from 'default_and_named_type'; -type T = Type1 | Type2 | Type | Types.A | Default | Named; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, - }, - { - messageId: 'typeOverValue', - line: 3, - column: 1, - }, - { - messageId: 'typeOverValue', - line: 4, - column: 1, - }, - { - messageId: 'typeOverValue', - line: 5, - column: 1, - }, - ], - }, - { - // some type fix cases - code: ` -import { Value1, Type1 } from 'named_import'; -import Type2, { Value2 } from 'default_import'; -import Value3, { Type3 } from 'default_import2'; -import Type4, { Type5, Value4 } from 'default_and_named_import'; -type T = Type1 | Type2 | Type3 | Type4 | Type5; - `, - output: ` -import type { Type1 } from 'named_import'; -import { Value1 } from 'named_import'; -import type Type2 from 'default_import'; -import { Value2 } from 'default_import'; -import type { Type3 } from 'default_import2'; -import Value3 from 'default_import2'; -import type { Type5} from 'default_and_named_import'; -import type Type4 from 'default_and_named_import'; -import { Value4 } from 'default_and_named_import'; -type T = Type1 | Type2 | Type3 | Type4 | Type5; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"Type1"' }, - line: 2, - column: 1, - }, - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"Type2"' }, - line: 3, - column: 1, - }, - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"Type3"' }, - line: 4, - column: 1, - }, - { - messageId: 'someImportsAreOnlyTypes', - data: { typeImports: '"Type4" and "Type5"' }, - line: 5, - column: 1, - }, - ], - }, - // type annotations - { - code: ` - let foo: import('foo'); - let bar: import('foo').Bar; - `, - output: null, - errors: [ - { - messageId: 'noImportTypeAnnotations', - line: 2, - column: 18, - }, - { - messageId: 'noImportTypeAnnotations', - line: 3, - column: 18, - }, - ], - }, - { - code: ` - let foo: import('foo'); - `, - output: null, - options: [{ prefer: 'type-imports' }], - errors: [ - { - messageId: 'noImportTypeAnnotations', - line: 2, - column: 18, - }, - ], - }, - { - code: ` - import type Foo from 'foo'; - let foo: Foo; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import Foo from 'foo'; - let foo: Foo; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type { Foo } from 'foo'; - let foo: Foo; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import { Foo } from 'foo'; - let foo: Foo; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - // type queries - { - code: ` - import Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - output: ` - import type Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - output: ` - import type { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - output: ` - import type * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import { Type } from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import * as Type from 'foo'; - - type T = typeof Type; - type T = typeof Type.foo; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - // exports - { - code: ` - import Type from 'foo'; - - export type { Type }; // is a type-only export - `, - output: ` - import type Type from 'foo'; - - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { Type } from 'foo'; - - export type { Type }; // is a type-only export - `, - output: ` - import type { Type } from 'foo'; - - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import * as Type from 'foo'; - - export type { Type }; // is a type-only export - `, - output: ` - import type * as Type from 'foo'; - - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type { Type } from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import { Type } from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type * as Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - options: [{ prefer: 'no-type-imports' }], - output: ` - import * as Type from 'foo'; - - export { Type }; // is a type-only export - export default Type; // is a type-only export - export type { Type }; // is a type-only export - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 9, - }, - ], - }, - { - // type with comments - code: noFormat` -import type /*comment*/ * as AllType from 'foo'; -import type // comment -DefType from 'foo'; -import type /*comment*/ { Type } from 'foo'; - -type T = { a: AllType; b: DefType; c: Type }; - `, - options: [{ prefer: 'no-type-imports' }], - output: ` -import /*comment*/ * as AllType from 'foo'; -import // comment -DefType from 'foo'; -import /*comment*/ { Type } from 'foo'; - -type T = { a: AllType; b: DefType; c: Type }; - `, - errors: [ - { - messageId: 'valueOverType', - line: 2, - column: 1, - }, - { - messageId: 'valueOverType', - line: 3, - column: 1, - }, - { - messageId: 'valueOverType', - line: 5, - column: 1, - }, - ], - }, - { - // https://github.com/typescript-eslint/typescript-eslint/issues/2775 - code: ` -import Default, * as Rest from 'module'; -const a: Rest.A = ''; - `, - options: [{ prefer: 'type-imports' }], - output: ` -import type * as Rest from 'module'; -import Default from 'module'; -const a: Rest.A = ''; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 1, - }, - ], - }, - { - code: ` -import Default, * as Rest from 'module'; -const a: Default = ''; - `, - options: [{ prefer: 'type-imports' }], - output: ` -import type Default from 'module'; -import * as Rest from 'module'; -const a: Default = ''; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 1, - }, - ], - }, - { - code: ` -import Default, * as Rest from 'module'; -const a: Default = ''; -const b: Rest.A = ''; - `, - options: [{ prefer: 'type-imports' }], - output: ` -import type * as Rest from 'module'; -import type Default from 'module'; -const a: Default = ''; -const b: Rest.A = ''; - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, - }, - ], - }, - { - // type with comments - code: ` -import Default, /*comment*/ * as Rest from 'module'; -const a: Default = ''; - `, - options: [{ prefer: 'type-imports' }], - output: ` -import type Default from 'module'; -import /*comment*/ * as Rest from 'module'; -const a: Default = ''; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 1, - }, - ], - }, - { - // type with comments - code: noFormat` -import Default /*comment1*/, /*comment2*/ { Data } from 'module'; -const a: Default = ''; - `, - options: [{ prefer: 'type-imports' }], - output: ` -import type Default /*comment1*/ from 'module'; -import /*comment2*/ { Data } from 'module'; -const a: Default = ''; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 1, - }, - ], - }, - { - code: ` - import Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import type Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import type Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: ` - import type { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: noFormat` - import type { Type } from 'foo'; - import { Foo, Bar } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - type T = Bar; - `, - output: ` - import type { Type , Bar } from 'foo'; - import { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - type T = Bar; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"Bar"' }, - line: 3, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: ` - import { V } from 'foo'; - import type { Foo, Bar, T } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - foo(@deco bar: Bar) {} - } - `, - output: ` - import { V , Foo, Bar} from 'foo'; - import type { T } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - foo(@deco bar: Bar) {} - } - `, - errors: [ - { - messageId: 'someImportsInDecoMeta', - data: { typeImports: '"Foo" and "Bar"' }, - line: 3, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: ` - import type { Foo, T } from 'foo'; - import { V } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import type { T } from 'foo'; - import { V , Foo} from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: ` - import type * as Type from 'foo'; - @deco - class A { - constructor(foo: Type.Foo) {} - } - `, - output: ` - import * as Type from 'foo'; - @deco - class A { - constructor(foo: Type.Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Type"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaParserOptions, - }, - { - code: ` - import type Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import Foo from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import type { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ - { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, - }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: noFormat` - import type { Type } from 'foo'; - import { Foo, Bar } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - type T = Bar; - `, - output: ` - import type { Type , Bar } from 'foo'; - import { Foo } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - type T = Bar; - `, - errors: [ - { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"Bar"' }, - line: 3, - column: 9, - }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import { V } from 'foo'; - import type { Foo, Bar, T } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - foo(@deco bar: Bar) {} - } - `, - output: ` - import { V , Foo, Bar} from 'foo'; - import type { T } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - foo(@deco bar: Bar) {} - } - `, - errors: [ + { + experimentalDecorators: false, + emitDecoratorMetadata: true, + }, + { + experimentalDecorators: true, + emitDecoratorMetadata: false, + }, +]; +for (const parserOptions of PARSER_OPTION_COMBOS) { + describe(`experimentalDecorators: ${parserOptions.experimentalDecorators} + emitDecoratorMetadata: ${parserOptions.emitDecoratorMetadata}`, () => { + const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions, + }); + + ruleTester.run('consistent-type-imports', rule, { + valid: [ + ` + import Foo from 'foo'; + const foo: Foo = new Foo(); + `, + ` + import foo from 'foo'; + const foo: foo.Foo = foo.fn(); + `, + ` + import { A, B } from 'foo'; + const foo: A = B(); + const bar = new A(); + `, + ` + import Foo from 'foo'; + `, + ` + import Foo from 'foo'; + type T = Foo; // shadowing + `, + ` + import Foo from 'foo'; + function fn() { + type Foo = {}; // shadowing + let foo: Foo; + } + `, + ` + import { A, B } from 'foo'; + const b = B; + `, + ` + import { A, B, C as c } from 'foo'; + const d = c; + `, + ` + import {} from 'foo'; // empty + `, + { + code: ` +let foo: import('foo'); +let bar: import('foo').Bar; + `, + options: [{ disallowTypeAnnotations: false }], + }, + { + code: ` +import Foo from 'foo'; +let foo: Foo; + `, + options: [{ prefer: 'no-type-imports' }], + }, + // type queries + ` + import type Type from 'foo'; + + type T = typeof Type; + type T = typeof Type.foo; + `, + ` + import type { Type } from 'foo'; + + type T = typeof Type; + type T = typeof Type.foo; + `, + ` + import type * as Type from 'foo'; + + type T = typeof Type; + type T = typeof Type.foo; + `, + { + code: ` +import Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + }, + { + code: ` +import { Type } from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + }, + { + code: ` +import * as Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + }, + { + code: ` +import * as Type from 'foo' assert { type: 'json' }; +const a: typeof Type = Type; + `, + options: [{ prefer: 'no-type-imports' }], + }, + ` + import { type A } from 'foo'; + type T = A; + `, + ` + import { type A, B } from 'foo'; + type T = A; + const b = B; + `, + ` + import { type A, type B } from 'foo'; + type T = A; + type Z = B; + `, + ` + import { B } from 'foo'; + import { type A } from 'foo'; + type T = A; + const b = B; + `, + { + code: ` +import { B, type A } from 'foo'; +type T = A; +const b = B; + `, + options: [{ fixStyle: 'inline-type-imports' }], + }, { - messageId: 'someImportsInDecoMeta', - data: { typeImports: '"Foo" and "Bar"' }, - line: 3, - column: 9, + code: ` +import { B } from 'foo'; +import type A from 'baz'; +type T = A; +const b = B; + `, + options: [{ fixStyle: 'inline-type-imports' }], }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import type { Foo, T } from 'foo'; - import { V } from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - output: ` - import type { T } from 'foo'; - import { V , Foo} from 'foo'; - @deco - class A { - constructor(foo: Foo) {} - } - `, - errors: [ { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Foo"' }, - line: 2, - column: 9, + code: ` +import { type B } from 'foo'; +import type { A } from 'foo'; +type T = A; +const b = B; + `, + options: [{ fixStyle: 'inline-type-imports' }], }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` - import type * as Type from 'foo'; - @deco - class A { - constructor(foo: Type.Foo) {} - } - `, - output: ` - import * as Type from 'foo'; - @deco - class A { - constructor(foo: Type.Foo) {} - } - `, - errors: [ { - messageId: 'aImportInDecoMeta', - data: { typeImports: '"Type"' }, - line: 2, - column: 9, + code: ` +import { B, type C } from 'foo'; +import type A from 'baz'; +type T = A; +type Z = C; +const b = B; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], }, - ], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` -import { type A, B } from 'foo'; + { + code: ` +import { B } from 'foo'; +import type { A } from 'foo'; type T = A; const b = B; - `, - output: ` -import { A, B } from 'foo'; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + }, + { + code: ` +import { B } from 'foo'; +import { A } from 'foo'; type T = A; const b = B; - `, - options: [{ prefer: 'no-type-imports' }], - errors: [ + `, + options: [ + { prefer: 'no-type-imports', fixStyle: 'inline-type-imports' }, + ], + }, + // exports + ` + import Type from 'foo'; + + export { Type }; // is a value export + export default Type; // is a value export + `, + ` + import type Type from 'foo'; + + export { Type }; // is a type-only export + export default Type; // is a type-only export + export type { Type }; // is a type-only export + `, + ` + import { Type } from 'foo'; + + export { Type }; // is a value export + export default Type; // is a value export + `, + ` + import type { Type } from 'foo'; + + export { Type }; // is a type-only export + export default Type; // is a type-only export + export type { Type }; // is a type-only export + `, + ` + import * as Type from 'foo'; + + export { Type }; // is a value export + export default Type; // is a value export + `, + ` + import type * as Type from 'foo'; + + export { Type }; // is a type-only export + export default Type; // is a type-only export + export type { Type }; // is a type-only export + `, + + { + code: ` +import Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + }, + { + code: ` +import { Type } from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + }, + { + code: ` +import * as Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + }, + // https://github.com/typescript-eslint/typescript-eslint/issues/2455 + { + code: ` +import React from 'react'; + +export const ComponentFoo: React.FC = () => { + return
Foo Foo
; +}; + `, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, { - messageId: 'valueOverType', - line: 2, + code: ` +import { h } from 'some-other-jsx-lib'; + +export const ComponentFoo: h.FC = () => { + return
Foo Foo
; +}; + `, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + jsxPragma: 'h', + }, }, - ], - }, - { - code: ` -import { A, B, type C } from 'foo'; -type T = A | C; -const b = B; - `, - output: ` + { + code: ` +import { Fragment } from 'react'; + +export const ComponentFoo: Fragment = () => { + return <>Foo Foo; +}; + `, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + jsxFragmentName: 'Fragment', + }, + }, + ` + import Default, * as Rest from 'module'; + const a: typeof Default = Default; + const b: typeof Rest = Rest; + `, + + // https://github.com/typescript-eslint/typescript-eslint/issues/2989 + ` + import type * as constants from './constants'; + + export type Y = { + [constants.X]: ReadonlyArray; + }; + `, + ` + import A from 'foo'; + export = A; + `, + ` + import type A from 'foo'; + export = A; + `, + ` + import type A from 'foo'; + export = {} as A; + `, + ` + import { type A } from 'foo'; + export = {} as A; + `, + ], + invalid: [ + { + code: ` +import Foo from 'foo'; +let foo: Foo; +type Bar = Foo; +interface Baz { + foo: Foo; +} +function fn(a: Foo): Foo {} + `, + output: ` +import type Foo from 'foo'; +let foo: Foo; +type Bar = Foo; +interface Baz { + foo: Foo; +} +function fn(a: Foo): Foo {} + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import Foo from 'foo'; +let foo: Foo; + `, + output: ` +import type Foo from 'foo'; +let foo: Foo; + `, + options: [{ prefer: 'type-imports' }], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import Foo from 'foo'; +let foo: Foo; + `, + output: ` +import type Foo from 'foo'; +let foo: Foo; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { A, B } from 'foo'; +let foo: A; +let bar: B; + `, + output: ` +import type { A, B } from 'foo'; +let foo: A; +let bar: B; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { A as a, B as b } from 'foo'; +let foo: a; +let bar: b; + `, + output: ` +import type { A as a, B as b } from 'foo'; +let foo: a; +let bar: b; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import Foo from 'foo'; +type Bar = typeof Foo; // TSTypeQuery + `, + output: ` +import type Foo from 'foo'; +type Bar = typeof Foo; // TSTypeQuery + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import foo from 'foo'; +type Bar = foo.Bar; // TSQualifiedName + `, + output: ` +import type foo from 'foo'; +type Bar = foo.Bar; // TSQualifiedName + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import foo from 'foo'; +type Baz = (typeof foo.bar)['Baz']; // TSQualifiedName & TSTypeQuery + `, + output: ` +import type foo from 'foo'; +type Baz = (typeof foo.bar)['Baz']; // TSQualifiedName & TSTypeQuery + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import * as A from 'foo'; +let foo: A.Foo; + `, + output: ` +import type * as A from 'foo'; +let foo: A.Foo; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + // default and named + code: ` +import A, { B } from 'foo'; +let foo: A; +let bar: B; + `, + output: ` +import type { B } from 'foo'; +import type A from 'foo'; +let foo: A; +let bar: B; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: noFormat` +import A, {} from 'foo'; +let foo: A; + `, + output: ` +import type A from 'foo'; +let foo: A; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { A, B } from 'foo'; +const foo: A = B(); + `, + output: ` import type { A} from 'foo'; -import { B, type C } from 'foo'; -type T = A | C; -const b = B; - `, - options: [{ prefer: 'type-imports' }], - errors: [ +import { B } from 'foo'; +const foo: A = B(); + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"A"' }, + line: 2, + }, + ], + }, + { + code: ` +import { A, B, C } from 'foo'; +const foo: A = B(); +let bar: C; + `, + output: ` +import type { A, C } from 'foo'; +import { B } from 'foo'; +const foo: A = B(); +let bar: C; + `, + errors: [ + { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports: '"A" and "C"' }, + line: 2, + }, + ], + }, { - messageId: 'aImportIsOnlyTypes', - data: { typeImports: '"A"' }, - line: 2, + code: ` +import { A, B, C, D } from 'foo'; +const foo: A = B(); +type T = { bar: C; baz: D }; + `, + output: ` +import type { A, C, D } from 'foo'; +import { B } from 'foo'; +const foo: A = B(); +type T = { bar: C; baz: D }; + `, + errors: [ + { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports: '"A", "C" and "D"' }, + line: 2, + }, + ], }, - ], - }, - - // inline-type-imports - { - code: ` - import { A, B } from 'foo'; - let foo: A; - let bar: B; - `, - output: ` - import { type A, type B } from 'foo'; - let foo: A; - let bar: B; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ { - messageId: 'typeOverValue', - line: 2, - column: 9, + code: ` +import A, { B, C, D } from 'foo'; +B(); +type T = { foo: A; bar: C; baz: D }; + `, + output: ` +import type { C, D } from 'foo'; +import type A from 'foo'; +import { B } from 'foo'; +B(); +type T = { foo: A; bar: C; baz: D }; + `, + errors: [ + { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports: '"A", "C" and "D"' }, + line: 2, + }, + ], }, - ], - }, - { - code: ` - import { A, B } from 'foo'; + { + code: ` +import A, { B } from 'foo'; +B(); +type T = A; + `, + output: ` +import type A from 'foo'; +import { B } from 'foo'; +B(); +type T = A; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"A"' }, + line: 2, + }, + ], + }, + { + code: ` +import type Already1Def from 'foo'; +import type { Already1 } from 'foo'; +import A, { B } from 'foo'; +import { C, D, E } from 'bar'; +import type { Already2 } from 'bar'; +type T = { b: B; c: C; d: D }; + `, + output: ` +import type Already1Def from 'foo'; +import type { Already1 , B } from 'foo'; +import A from 'foo'; +import { E } from 'bar'; +import type { Already2 , C, D} from 'bar'; +type T = { b: B; c: C; d: D }; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"B"' }, + line: 4, + }, + { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports: '"C" and "D"' }, + line: 5, + }, + ], + }, + { + code: ` +import A, { /* comment */ B } from 'foo'; +type T = B; + `, + output: ` +import type { /* comment */ B } from 'foo'; +import A from 'foo'; +type T = B; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"B"' }, + line: 2, + }, + ], + }, + { + code: noFormat` +import { A, B, C } from 'foo'; +import { D, E, F, } from 'bar'; +type T = A | D; + `, + output: ` +import type { A} from 'foo'; +import { B, C } from 'foo'; +import type { D} from 'bar'; +import { E, F, } from 'bar'; +type T = A | D; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"A"' }, + line: 2, + }, + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"D"' }, + line: 3, + }, + ], + }, + { + code: noFormat` +import { A, B, C } from 'foo'; +import { D, E, F, } from 'bar'; +type T = B | E; + `, + output: ` +import type { B} from 'foo'; +import { A, C } from 'foo'; +import type { E} from 'bar'; +import { D, F, } from 'bar'; +type T = B | E; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"B"' }, + line: 2, + }, + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"E"' }, + line: 3, + }, + ], + }, + { + code: noFormat` +import { A, B, C } from 'foo'; +import { D, E, F, } from 'bar'; +type T = C | F; + `, + output: ` +import type { C } from 'foo'; +import { A, B } from 'foo'; +import type { F} from 'bar'; +import { D, E } from 'bar'; +type T = C | F; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"C"' }, + line: 2, + }, + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"F"' }, + line: 3, + }, + ], + }, + { + // all type fix cases + code: ` +import { Type1, Type2 } from 'named_types'; +import Type from 'default_type'; +import * as Types from 'namespace_type'; +import Default, { Named } from 'default_and_named_type'; +type T = Type1 | Type2 | Type | Types.A | Default | Named; + `, + output: ` +import type { Type1, Type2 } from 'named_types'; +import type Type from 'default_type'; +import type * as Types from 'namespace_type'; +import type { Named } from 'default_and_named_type'; +import type Default from 'default_and_named_type'; +type T = Type1 | Type2 | Type | Types.A | Default | Named; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + { + messageId: 'typeOverValue', + line: 3, + }, + { + messageId: 'typeOverValue', + line: 4, + }, + { + messageId: 'typeOverValue', + line: 5, + }, + ], + }, + { + // some type fix cases + code: ` +import { Value1, Type1 } from 'named_import'; +import Type2, { Value2 } from 'default_import'; +import Value3, { Type3 } from 'default_import2'; +import Type4, { Type5, Value4 } from 'default_and_named_import'; +type T = Type1 | Type2 | Type3 | Type4 | Type5; + `, + output: ` +import type { Type1 } from 'named_import'; +import { Value1 } from 'named_import'; +import type Type2 from 'default_import'; +import { Value2 } from 'default_import'; +import type { Type3 } from 'default_import2'; +import Value3 from 'default_import2'; +import type { Type5} from 'default_and_named_import'; +import type Type4 from 'default_and_named_import'; +import { Value4 } from 'default_and_named_import'; +type T = Type1 | Type2 | Type3 | Type4 | Type5; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"Type1"' }, + line: 2, + }, + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"Type2"' }, + line: 3, + }, + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"Type3"' }, + line: 4, + }, + { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports: '"Type4" and "Type5"' }, + line: 5, + }, + ], + }, + // type annotations + { + code: ` +let foo: import('foo'); +let bar: import('foo').Bar; + `, + output: null, + errors: [ + { + messageId: 'noImportTypeAnnotations', + line: 2, + }, + { + messageId: 'noImportTypeAnnotations', + line: 3, + }, + ], + }, + { + code: ` +let foo: import('foo'); + `, + output: null, + options: [{ prefer: 'type-imports' }], + errors: [ + { + messageId: 'noImportTypeAnnotations', + line: 2, + }, + ], + }, + { + code: ` +import type Foo from 'foo'; +let foo: Foo; + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import Foo from 'foo'; +let foo: Foo; + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + code: ` +import type { Foo } from 'foo'; +let foo: Foo; + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import { Foo } from 'foo'; +let foo: Foo; + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + // type queries + { + code: ` +import Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + output: ` +import type Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { Type } from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + output: ` +import type { Type } from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import * as Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + output: ` +import type * as Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import type Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + code: ` +import type { Type } from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import { Type } from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + code: ` +import type * as Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import * as Type from 'foo'; + +type T = typeof Type; +type T = typeof Type.foo; + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + // exports + { + code: ` +import Type from 'foo'; + +export type { Type }; // is a type-only export + `, + output: ` +import type Type from 'foo'; + +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { Type } from 'foo'; + +export type { Type }; // is a type-only export + `, + output: ` +import type { Type } from 'foo'; + +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import * as Type from 'foo'; + +export type { Type }; // is a type-only export + `, + output: ` +import type * as Type from 'foo'; + +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import type Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + code: ` +import type { Type } from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import { Type } from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + code: ` +import type * as Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import * as Type from 'foo'; + +export { Type }; // is a type-only export +export default Type; // is a type-only export +export type { Type }; // is a type-only export + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], + }, + { + // type with comments + code: noFormat` +import type /*comment*/ * as AllType from 'foo'; +import type // comment +DefType from 'foo'; +import type /*comment*/ { Type } from 'foo'; - let foo: A; - B(); - `, - output: ` - import { type A, B } from 'foo'; +type T = { a: AllType; b: DefType; c: Type }; + `, + options: [{ prefer: 'no-type-imports' }], + output: ` +import /*comment*/ * as AllType from 'foo'; +import // comment +DefType from 'foo'; +import /*comment*/ { Type } from 'foo'; - let foo: A; - B(); - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ +type T = { a: AllType; b: DefType; c: Type }; + `, + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + { + messageId: 'valueOverType', + line: 3, + }, + { + messageId: 'valueOverType', + line: 5, + }, + ], + }, + { + // https://github.com/typescript-eslint/typescript-eslint/issues/2775 + code: ` +import Default, * as Rest from 'module'; +const a: Rest.A = ''; + `, + options: [{ prefer: 'type-imports' }], + output: ` +import type * as Rest from 'module'; +import Default from 'module'; +const a: Rest.A = ''; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 9, + code: ` +import Default, * as Rest from 'module'; +const a: Default = ''; + `, + options: [{ prefer: 'type-imports' }], + output: ` +import type Default from 'module'; +import * as Rest from 'module'; +const a: Default = ''; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` +import Default, * as Rest from 'module'; +const a: Default = ''; +const b: Rest.A = ''; + `, + options: [{ prefer: 'type-imports' }], + output: ` +import type * as Rest from 'module'; +import type Default from 'module'; +const a: Default = ''; +const b: Rest.A = ''; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], }, - ], - }, - { - code: ` - import { A, B } from 'foo'; - type T = A; - B(); - `, - output: ` - import { type A, B } from 'foo'; - type T = A; - B(); - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 9, + // type with comments + code: ` +import Default, /*comment*/ * as Rest from 'module'; +const a: Default = ''; + `, + options: [{ prefer: 'type-imports' }], + output: ` +import type Default from 'module'; +import /*comment*/ * as Rest from 'module'; +const a: Default = ''; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + // type with comments + code: noFormat` +import Default /*comment1*/, /*comment2*/ { Data } from 'module'; +const a: Default = ''; + `, + options: [{ prefer: 'type-imports' }], + output: ` +import type Default /*comment1*/ from 'module'; +import /*comment2*/ { Data } from 'module'; +const a: Default = ''; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], }, - ], - }, - { - code: ` - import { A } from 'foo'; - import { B } from 'foo'; - type T = A; - type U = B; - `, - output: ` - import { type A } from 'foo'; - import { type B } from 'foo'; - type T = A; - type U = B; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ { - messageId: 'typeOverValue', - line: 2, - column: 9, + code: ` +import Foo from 'foo'; +@deco +class A { + constructor(foo: Foo) {} +} + `, + output: ` +import type Foo from 'foo'; +@deco +class A { + constructor(foo: Foo) {} +} + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], }, { - messageId: 'typeOverValue', - line: 3, - column: 9, + code: ` +import { type A, B } from 'foo'; +type T = A; +const b = B; + `, + output: ` +import { A, B } from 'foo'; +type T = A; +const b = B; + `, + options: [{ prefer: 'no-type-imports' }], + errors: [ + { + messageId: 'valueOverType', + line: 2, + }, + ], }, - ], - }, - { - code: ` - import { A } from 'foo'; - import B from 'foo'; - type T = A; - type U = B; - `, - output: ` - import { type A } from 'foo'; - import type B from 'foo'; - type T = A; - type U = B; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ { - messageId: 'typeOverValue', - line: 2, - column: 9, + code: ` +import { A, B, type C } from 'foo'; +type T = A | C; +const b = B; + `, + output: ` +import type { A} from 'foo'; +import { B, type C } from 'foo'; +type T = A | C; +const b = B; + `, + options: [{ prefer: 'type-imports' }], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"A"' }, + line: 2, + }, + ], }, + + // inline-type-imports { - messageId: 'typeOverValue', - line: 3, - column: 9, - }, - ], - }, - { - code: ` + code: ` +import { A, B } from 'foo'; +let foo: A; +let bar: B; + `, + output: ` +import { type A, type B } from 'foo'; +let foo: A; +let bar: B; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { A, B } from 'foo'; + +let foo: A; +B(); + `, + output: ` +import { type A, B } from 'foo'; + +let foo: A; +B(); + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` +import { A, B } from 'foo'; +type T = A; +B(); + `, + output: ` +import { type A, B } from 'foo'; +type T = A; +B(); + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` +import { A } from 'foo'; +import { B } from 'foo'; +type T = A; +type U = B; + `, + output: ` +import { type A } from 'foo'; +import { type B } from 'foo'; +type T = A; +type U = B; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + { + messageId: 'typeOverValue', + line: 3, + }, + ], + }, + { + code: ` +import { A } from 'foo'; +import B from 'foo'; +type T = A; +type U = B; + `, + output: ` +import { type A } from 'foo'; +import type B from 'foo'; +type T = A; +type U = B; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + { + messageId: 'typeOverValue', + line: 3, + }, + ], + }, + { + code: ` import A, { B, C } from 'foo'; type T = B; type U = C; A(); - `, - output: ` + `, + output: ` import A, { type B, type C } from 'foo'; type T = B; type U = C; A(); - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'someImportsAreOnlyTypes', - line: 2, - column: 1, - }, - ], - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'someImportsAreOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` import A, { B, C } from 'foo'; type T = B; type U = C; type V = A; - `, - output: ` + `, + output: ` import {type B, type C} from 'foo'; import type A from 'foo'; type T = B; type U = C; type V = A; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, - }, - ], - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` import A, { B, C as D } from 'foo'; type T = B; type U = D; type V = A; - `, - output: ` + `, + output: ` import {type B, type C as D} from 'foo'; import type A from 'foo'; type T = B; type U = D; type V = A; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, - }, - ], - }, - { - code: ` - import { /* comment */ A, B } from 'foo'; - type T = A; - `, - output: ` - import { /* comment */ type A, B } from 'foo'; - type T = A; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 9, - }, - ], - }, - { - code: ` - import { B, /* comment */ A } from 'foo'; - type T = A; - `, - output: ` - import { B, /* comment */ type A } from 'foo'; - type T = A; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 9, - }, - ], - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` +import { /* comment */ A, B } from 'foo'; +type T = A; + `, + output: ` +import { /* comment */ type A, B } from 'foo'; +type T = A; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` +import { B, /* comment */ A } from 'foo'; +type T = A; + `, + output: ` +import { B, /* comment */ type A } from 'foo'; +type T = A; + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` import { A, B, C } from 'foo'; import type { D } from 'deez'; const foo: A = B(); let bar: C; let baz: D; - `, - output: ` + `, + output: ` import { type A, B, type C } from 'foo'; import type { D } from 'deez'; const foo: A = B(); let bar: C; let baz: D; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'someImportsAreOnlyTypes', - line: 2, - column: 1, - }, - ], - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'someImportsAreOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` import { A, B, type C } from 'foo'; import type { D } from 'deez'; const foo: A = B(); let bar: C; let baz: D; - `, - output: ` + `, + output: ` import { type A, B, type C } from 'foo'; import type { D } from 'deez'; const foo: A = B(); let bar: C; let baz: D; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'aImportIsOnlyTypes', - line: 2, - column: 1, - }, - ], - }, - // https://github.com/typescript-eslint/typescript-eslint/issues/7209 - { - code: ` -import 'foo'; -import type { Foo, Bar } from 'foo'; -@deco -class A { - constructor(foo: Foo) {} -} - `, - output: ` -import 'foo'; -import { Foo} from 'foo'; -import type { Bar } from 'foo'; -@deco -class A { - constructor(foo: Foo) {} -} - `, - errors: [{ messageId: 'aImportInDecoMeta', line: 3, column: 1 }], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` -import {} from 'foo'; -import type { Foo, Bar } from 'foo'; -@deco -class A { - constructor(foo: Foo) {} -} - `, - output: ` -import {} from 'foo'; -import { Foo} from 'foo'; -import type { Bar } from 'foo'; -@deco -class A { - constructor(foo: Foo) {} -} - `, - errors: [{ messageId: 'aImportInDecoMeta', line: 3, column: 1 }], - parserOptions: withMetaConfigParserOptions, - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'aImportIsOnlyTypes', + line: 2, + }, + ], + }, + { + code: ` import A from 'foo'; export = {} as A; - `, - output: ` + `, + output: ` import type A from 'foo'; export = {} as A; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ - { - messageId: 'typeOverValue', - line: 2, - column: 1, - }, - ], - }, - { - code: ` + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` import { A } from 'foo'; export = {} as A; - `, - output: ` + `, + output: ` import { type A } from 'foo'; export = {} as A; - `, - options: [{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - errors: [ + `, + options: [ + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + foo: Foo; + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + foo: Foo; + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + foo(foo: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + foo(foo: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + foo(): Foo {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + foo(): Foo {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + foo(@deco foo: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + foo(@deco foo: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + set foo(value: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + set foo(value: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + get foo() {} + + set foo(value: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + get foo() {} + + set foo(value: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + get foo() {} + + set ['foo'](value: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + class A { + @deco + get foo() {} + + set ['foo'](value: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + { + code: ` + import * as foo from 'foo'; + @deco + class A { + constructor(foo: foo.Foo) {} + } + `, + output: ` + import type * as foo from 'foo'; + @deco + class A { + constructor(foo: foo.Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + // https://github.com/typescript-eslint/typescript-eslint/issues/7209 + { + code: ` +import 'foo'; +import { Foo, Bar } from 'foo'; +function test(foo: Foo) {} + `, + output: ` +import 'foo'; +import type { Foo} from 'foo'; +import { Bar } from 'foo'; +function test(foo: Foo) {} + `, + errors: [{ messageId: 'aImportIsOnlyTypes', line: 3, column: 1 }], + }, { - messageId: 'typeOverValue', - line: 2, - column: 1, + code: ` +import {} from 'foo'; +import { Foo, Bar } from 'foo'; +function test(foo: Foo) {} + `, + output: ` +import {} from 'foo'; +import type { Foo} from 'foo'; +import { Bar } from 'foo'; +function test(foo: Foo) {} + `, + errors: [{ messageId: 'aImportIsOnlyTypes', line: 3, column: 1 }], }, ], + }); + }); +} + +// the special ignored config case +describe('experimentalDecorators: true + emitDecoratorMetadata: true', () => { + const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + experimentalDecorators: true, + emitDecoratorMetadata: true, }, - ], + }); + + ruleTester.run('consistent-type-imports', rule, { + valid: [ + ` + import Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + + ` + import Foo from 'foo'; + class A { + @deco + foo: Foo; + } + `, + + ` + import Foo from 'foo'; + class A { + @deco + foo(foo: Foo) {} + } + `, + + ` + import Foo from 'foo'; + class A { + @deco + foo(): Foo {} + } + `, + + ` + import Foo from 'foo'; + class A { + foo(@deco foo: Foo) {} + } + `, + + ` + import Foo from 'foo'; + class A { + @deco + set foo(value: Foo) {} + } + `, + + ` + import Foo from 'foo'; + class A { + @deco + get foo() {} + + set foo(value: Foo) {} + } + `, + + ` + import Foo from 'foo'; + class A { + @deco + get foo() {} + + set ['foo'](value: Foo) {} + } + `, + + ` + import type { Foo } from 'foo'; + const key = 'k'; + class A { + @deco + get [key]() {} + + set [key](value: Foo) {} + } + `, + + ` + import * as foo from 'foo'; + @deco + class A { + constructor(foo: foo.Foo) {} + } + `, + + // https://github.com/typescript-eslint/typescript-eslint/issues/7327 + ` + import type { ClassA } from './classA'; + + export class ClassB { + public constructor(node: ClassA) {} + } + `, + + ` + import type Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + ` + import type { Foo } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + ` + import type { Type } from 'foo'; + import { Foo, Bar } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + type T = Bar; + `, + ` + import { V } from 'foo'; + import type { Foo, Bar, T } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + foo(@deco bar: Bar) {} + } + `, + ` + import type { Foo, T } from 'foo'; + import { V } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + ` + import type * as Type from 'foo'; + @deco + class A { + constructor(foo: Type.Foo) {} + } + `, + ], + invalid: [ + { + code: ` + import Foo from 'foo'; + export type T = Foo; + `, + output: ` + import type Foo from 'foo'; + export type T = Foo; + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + }, + ], + }, + ], + }); }); From 3673cd15dcf97560536a6f5086bc8ac45db59ee2 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 18 Mar 2024 23:54:56 +1030 Subject: [PATCH 3/9] updoots pt 1 --- docs/packages/Parser.mdx | 2 +- .../docs/rules/consistent-type-imports.mdx | 46 +----- .../src/rules/consistent-type-imports.ts | 149 ++++-------------- .../rules/consistent-type-imports.test.ts | 94 ++++++----- ...consistent-type-imports-with-decorators.md | 101 ++++++++++++ 5 files changed, 192 insertions(+), 200 deletions(-) create mode 100644 packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md diff --git a/docs/packages/Parser.mdx b/docs/packages/Parser.mdx index 43cc4b203ca1..cb7a57836842 100644 --- a/docs/packages/Parser.mdx +++ b/docs/packages/Parser.mdx @@ -133,7 +133,7 @@ This option allow you to tell parser to act as if `emitDecoratorMetadata: true` > Default `undefined`. -This option allow you to tell parser to act as if `experimentalDecorators: true` is set in `tsconfig.json`, but without [type-aware linting](../linting/Typed_Linting.mdx). In other words, you don't have to specify `parserOptions.project` in this case, making the linting process faster. +This option allow you to tell parser to act as if `experimentalDecorators: true` is set in `tsconfig.json`, but without [type-aware linting](../getting-started/Typed_Linting.mdx). In other words, you don't have to specify `parserOptions.project` in this case, making the linting process faster. ### `extraFileExtensions` diff --git a/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx b/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx index 7e5cad329fb0..4f5c105e2524 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx +++ b/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx @@ -95,50 +95,16 @@ const x: import('Bar') = 1; ## Caveat: `@decorators` + `experimentalDecorators: true` + `emitDecoratorMetadata: true` -> TL;DR - the rule will **_not_** report any errors in files _that contain decorators_ when **both** `experimentalDecorators` and `emitDecoratorMetadata` are turned on. +:::note +If you are using `experimentalDecorators: false` (eg [TypeScript v5.0's stable decorators](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators)) then the rule will always report errors as expected. +This caveat **only** applies to `experimentalDecorators: true` +::: -When both the compiler options `experimentalDecorators` and `emitDecoratorMetadata` are turned on and a class is annotated with decorators, TypeScript will emit runtime metadata for the class that captures the property types, method parameter types, and method return types. This runtime code is derived using type information - meaning that the runtime code emitted changes based on the cross-file type information that TypeScript has computed. +The rule will **_not_** report any errors in files _that contain decorators_ when **both** `experimentalDecorators` and `emitDecoratorMetadata` are turned on. -This behavior is problematic for the style enforced by this lint rule because it also means that annotating an import with `type` can change the runtime metadata! For example in the following snippet: - -```ts -import Foo from 'foo'; -import decorator from 'decorator'; - -class Clazz { - @decorator - method(arg: Foo) {} -} -``` - -TS will emit metadata for `method`'s `arg` to annotate its type: - -- If `Foo` is a value (eg a class declaration), then TS will annotate the type of `arg` as `Foo` - - Put another way - there is an invisible runtime reference to `Foo`! -- If `Foo` is a type (eg an interface), then TS will annotate the type of `arg` as either `Object`, `String`, etc - depending on what the type is. - -Syntactically it _looks_ like the `Foo` import is a type-only import because it's only used in a type location. However if we annotate the `Foo` import as `type` then it means that the runtime code always falls into the latter case of a type - which changes the runtime metadata! This is a problem for the rule because the rule is not type-aware - so it can't determine if an import is a type or a value - it works purely based on how the import is used. - -In the past we tried to solve this problem by enforcing that imported names that are used in decorator metadata are specifically _not_ marked as type-only imports to ensure that values are correctly emitted in the runtime code. - -However things get further complicated if you also turn on `isolatedModules` because TS will start enforcing that your imported types are marked as type-only. So to satisfy this the rule needs to enforce that imported values used in decorator metadata are imported _without_ a `type` qualifier, and imported types that are used in decorator metadata are imported _with_ a `type` qualifier. So our above solution of always removing the `type` doesn't work, and instead we need type-information to correctly report errors and fix code. - -Ultimately we decided to just opt-out of handling this use-case entirely to avoid accidentally reporting the wrong thing and fixing to code that either fails to compile or alters the emitted runtime metadata. If you'd like more information please [check out the related issue and its discussion](https://github.com/typescript-eslint/typescript-eslint/issues/5468). - -To be clear this means the following - the rule will **_not_** report any errors in files _that contain decorators_ when **both** `experimentalDecorators` and `emitDecoratorMetadata` are turned on. - -If you are working in such a workspace and want to correctly have your imports consistently marked with `type`, we suggest using the [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax) compiler option which will use type information to correctly enforce that types are marked with `type` and values are not when they are used in decorator metadata. - -### Note: TypeScript v5.0 (+ v5.2) decorators - -[TypeScript v5.0](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators) added support for the latest stable decorator proposal. [TypeScript v5.2](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata) added support for the latest stable decorator metadata proposal. If you are writing decorators on TS v5.2+ with `emitDecoratorMetadata: true` and `experimentalDecorators: false` then you are using the new decorator metadata support. - -The new decorator metadata does not include any annotations derived from types -- meaning it does not have any runtime side-effects. This means that the rule will report on these files as normal because changing an import to a type-only import will not have side-effects on the runtime code. - -### Configuring `experimentalDecorators` and `emitDecoratorMetadata` for the rule +> See [Blog > Changes to consistent-type-imports when used with legacy decorators and decorator metadata](/blog/changes-to-consistent-type-imports-with-decorators) for more details. If you are using [type-aware linting](https://typescript-eslint.io/linting/typed-linting) then we will automatically infer your setup from your tsconfig and you should not need to configure anything. - Otherwise you can explicitly tell our tooling to analyze your code as if the compiler option was turned on by setting both [`parserOptions.emitDecoratorMetadata = true`](https://typescript-eslint.io/packages/parser/#emitdecoratormetadata) and [`parserOptions.experimentalDecorators = true`](https://typescript-eslint.io/packages/parser/#experimentaldecorators). ## Comparison with `importsNotUsedAsValues` / `verbatimModuleSyntax` diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index 43853ceee220..f4e98d2d9e00 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -45,11 +45,10 @@ interface ReportValueImport { } type MessageIds = - | 'aImportIsOnlyTypes' - | 'noImportTypeAnnotations' - | 'someImportsAreOnlyTypes' | 'typeOverValue' - | 'valueOverType'; + | 'someImportsAreOnlyTypes' + | 'avoidImportType' + | 'noImportTypeAnnotations'; export default createRule({ name: 'consistent-type-imports', meta: { @@ -60,10 +59,9 @@ export default createRule({ messages: { typeOverValue: 'All imports in the declaration are only used as types. Use `import type`.', - someImportsAreOnlyTypes: - 'Imports {{typeImports}} are only used as types.', - aImportIsOnlyTypes: 'Import {{typeImports}} is only used as types.', - valueOverType: 'Use an `import` instead of an `import type`.', + someImportsAreOnlyTypes: 'Imports {{typeImports}} are only used as type.', + + avoidImportType: 'Use an `import` instead of an `import type`.', noImportTypeAnnotations: '`import()` type annotations are forbidden.', }, schema: [ @@ -112,7 +110,6 @@ export default createRule({ } if (prefer === 'no-type-imports') { - // prefer no type imports return { ...selectors, 'ImportDeclaration[importKind = "type"]'( @@ -120,7 +117,7 @@ export default createRule({ ): void { context.report({ node, - messageId: 'valueOverType', + messageId: 'avoidImportType', fix(fixer) { return fixRemoveTypeSpecifierFromImportDeclaration(fixer, node); }, @@ -131,7 +128,7 @@ export default createRule({ ): void { context.report({ node, - messageId: 'valueOverType', + messageId: 'avoidImportType', fix(fixer) { return fixRemoveTypeSpecifierFromImportSpecifier(fixer, node); }, @@ -290,10 +287,7 @@ export default createRule({ } } - if ( - (node.importKind === 'value' && typeSpecifiers.length) || - (node.importKind === 'type' && valueSpecifiers.length) - ) { + if (node.importKind === 'value' && typeSpecifiers.length) { sourceImports.reportValueImports.push({ node, typeSpecifiers, @@ -306,8 +300,8 @@ export default createRule({ 'Program:exit'(): void { if (hasDecoratorMetadata) { - // Decorator metadata is bowl of poop that cannot be supported based - // on pure syntactic analysis. + // Experimental decorator metadata is bowl of poop that cannot be + // supported based on pure syntactic analysis. // // So we can do one of two things: // 1) add type-information to the rule in a breaking change and @@ -374,15 +368,10 @@ export default createRule({ }); } } else { - const isTypeImport = report.node.importKind === 'type'; - // we have a mixed type/value import or just value imports, so we need to split them out into multiple imports if separate-type-imports is configured - const importNames = ( - isTypeImport - ? report.valueSpecifiers // import type { A } from 'roo'; // WHERE A is used in value position - : report.typeSpecifiers - ) // import { A, B } from 'roo'; // WHERE A is used in type position and B is in value position - .map(specifier => `"${specifier.local.name}"`); + const importNames = report.typeSpecifiers.map( + specifier => `"${specifier.local.name}"`, + ); const message = ((): { messageId: MessageIds; @@ -392,13 +381,17 @@ export default createRule({ if (importNames.length === 1) { return { - messageId: 'aImportIsOnlyTypes', - data: { typeImports }, + messageId: 'someImportsAreOnlyTypes', + data: { + typeImports, + }, }; } return { messageId: 'someImportsAreOnlyTypes', - data: { typeImports }, // typeImports are all the type specifiers in the value position + data: { + typeImports, + }, }; })(); @@ -406,21 +399,12 @@ export default createRule({ node: report.node, ...message, *fix(fixer) { - if (isTypeImport) { - // take all the valueSpecifiers and put them on a new line - yield* fixToValueImportDeclaration( - fixer, - report, - sourceImports, - ); - } else { - // take all the typeSpecifiers and put them on a new line - yield* fixToTypeImportDeclaration( - fixer, - report, - sourceImports, - ); - } + // take all the typeSpecifiers and put them on a new line + yield* fixToTypeImportDeclaration( + fixer, + report, + sourceImports, + ); }, }); } @@ -917,85 +901,6 @@ export default createRule({ } } - function* fixToValueImportDeclaration( - fixer: TSESLint.RuleFixer, - report: ReportValueImport, - sourceImports: SourceImports, - ): IterableIterator { - const { node } = report; - - const { defaultSpecifier, namespaceSpecifier, namedSpecifiers } = - classifySpecifier(node); - - if (namespaceSpecifier) { - // import type * as types from 'foo' - yield* fixRemoveTypeSpecifierFromImportDeclaration(fixer, node); - return; - } else if (defaultSpecifier) { - if ( - report.valueSpecifiers.includes(defaultSpecifier) && - namedSpecifiers.length === 0 - ) { - // import type Type from 'foo' - yield* fixRemoveTypeSpecifierFromImportDeclaration(fixer, node); - return; - } - } else { - if ( - namedSpecifiers.every(specifier => - report.valueSpecifiers.includes(specifier), - ) - ) { - // import type {Type1, Type2} from 'foo' - yield* fixRemoveTypeSpecifierFromImportDeclaration(fixer, node); - return; - } - } - - // we have some valueSpecifiers intermixed in types that need to be put on their own line - // import type { Type1, A } from 'foo' - // import type { A } from 'foo' - const valueNamedSpecifiers = namedSpecifiers.filter(specifier => - report.valueSpecifiers.includes(specifier), - ); - - const fixesNamedSpecifiers = getFixesNamedSpecifiers( - fixer, - node, - valueNamedSpecifiers, - namedSpecifiers, - ); - const afterFixes: TSESLint.RuleFix[] = []; - if (valueNamedSpecifiers.length) { - if (sourceImports.valueOnlyNamedImport) { - const insertTypeNamedSpecifiers = - fixInsertNamedSpecifiersInNamedSpecifierList( - fixer, - sourceImports.valueOnlyNamedImport, - fixesNamedSpecifiers.typeNamedSpecifiersText, - ); - if (sourceImports.valueOnlyNamedImport.range[1] <= node.range[0]) { - yield insertTypeNamedSpecifiers; - } else { - afterFixes.push(insertTypeNamedSpecifiers); - } - } else { - // some are types. - // Add new value import and later remove those value specifiers from import type - yield fixer.insertTextBefore( - node, - `import {${ - fixesNamedSpecifiers.typeNamedSpecifiersText - }} from ${context.sourceCode.getText(node.source)};\n`, - ); - } - } - - yield* fixesNamedSpecifiers.removeTypeNamedSpecifiers; - - yield* afterFixes; - } - function* fixRemoveTypeSpecifierFromImportDeclaration( fixer: TSESLint.RuleFixer, node: TSESTree.ImportDeclaration, diff --git a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts index 8d1cd5ff08d8..a751730cfd34 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -356,6 +356,22 @@ export const ComponentFoo: Fragment = () => { import { type A } from 'foo'; export = {} as A; `, + + // semantically these are insane but syntactically they are valid + // we don't want to handle them because it means changing invalid code + // to valid code which is dangerous "undefined" behavior. + ` +import type T from 'mod'; +const x = T; + `, + ` +import type { T } from 'mod'; +const x = T; + `, + ` +import { type T } from 'mod'; +const x = T; + `, ], invalid: [ { @@ -568,7 +584,7 @@ const foo: A = B(); `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"A"' }, line: 2, }, @@ -649,7 +665,7 @@ type T = A; `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"A"' }, line: 2, }, @@ -674,7 +690,7 @@ type T = { b: B; c: C; d: D }; `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"B"' }, line: 4, }, @@ -697,7 +713,7 @@ type T = B; `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"B"' }, line: 2, }, @@ -718,12 +734,12 @@ type T = A | D; `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"A"' }, line: 2, }, { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"D"' }, line: 3, }, @@ -744,12 +760,12 @@ type T = B | E; `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"B"' }, line: 2, }, { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"E"' }, line: 3, }, @@ -770,12 +786,12 @@ type T = C | F; `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"C"' }, line: 2, }, { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"F"' }, line: 3, }, @@ -840,17 +856,17 @@ type T = Type1 | Type2 | Type3 | Type4 | Type5; `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"Type1"' }, line: 2, }, { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"Type2"' }, line: 3, }, { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"Type3"' }, line: 4, }, @@ -904,7 +920,7 @@ let foo: Foo; `, errors: [ { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 2, }, ], @@ -921,7 +937,7 @@ let foo: Foo; `, errors: [ { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 2, }, ], @@ -1003,7 +1019,7 @@ type T = typeof Type.foo; `, errors: [ { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 2, }, ], @@ -1024,7 +1040,7 @@ type T = typeof Type.foo; `, errors: [ { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 2, }, ], @@ -1045,7 +1061,7 @@ type T = typeof Type.foo; `, errors: [ { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 2, }, ], @@ -1123,7 +1139,7 @@ export type { Type }; // is a type-only export `, errors: [ { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 2, }, ], @@ -1146,7 +1162,7 @@ export type { Type }; // is a type-only export `, errors: [ { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 2, }, ], @@ -1169,7 +1185,7 @@ export type { Type }; // is a type-only export `, errors: [ { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 2, }, ], @@ -1195,15 +1211,15 @@ type T = { a: AllType; b: DefType; c: Type }; `, errors: [ { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 2, }, { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 3, }, { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 5, }, ], @@ -1222,7 +1238,7 @@ const a: Rest.A = ''; `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', line: 2, }, ], @@ -1240,7 +1256,7 @@ const a: Default = ''; `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', line: 2, }, ], @@ -1279,7 +1295,7 @@ const a: Default = ''; `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', line: 2, }, ], @@ -1298,7 +1314,7 @@ const a: Default = ''; `, errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', line: 2, }, ], @@ -1339,7 +1355,7 @@ const b = B; options: [{ prefer: 'no-type-imports' }], errors: [ { - messageId: 'valueOverType', + messageId: 'avoidImportType', line: 2, }, ], @@ -1359,7 +1375,7 @@ const b = B; options: [{ prefer: 'type-imports' }], errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', data: { typeImports: '"A"' }, line: 2, }, @@ -1406,7 +1422,7 @@ B(); ], errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', line: 2, }, ], @@ -1427,7 +1443,7 @@ B(); ], errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', line: 2, }, ], @@ -1571,7 +1587,7 @@ type T = A; ], errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', line: 2, }, ], @@ -1590,7 +1606,7 @@ type T = A; ], errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', line: 2, }, ], @@ -1642,7 +1658,7 @@ let baz: D; ], errors: [ { - messageId: 'aImportIsOnlyTypes', + messageId: 'someImportsAreOnlyTypes', line: 2, }, ], @@ -1902,7 +1918,9 @@ import type { Foo} from 'foo'; import { Bar } from 'foo'; function test(foo: Foo) {} `, - errors: [{ messageId: 'aImportIsOnlyTypes', line: 3, column: 1 }], + errors: [ + { messageId: 'someImportsAreOnlyTypes', line: 3, column: 1 }, + ], }, { code: ` @@ -1916,7 +1934,9 @@ import type { Foo} from 'foo'; import { Bar } from 'foo'; function test(foo: Foo) {} `, - errors: [{ messageId: 'aImportIsOnlyTypes', line: 3, column: 1 }], + errors: [ + { messageId: 'someImportsAreOnlyTypes', line: 3, column: 1 }, + ], }, ], }); diff --git a/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md b/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md new file mode 100644 index 000000000000..9f06c3bb31dd --- /dev/null +++ b/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md @@ -0,0 +1,101 @@ +--- +authors: + - image_url: /img/team/bradzacher.jpg + name: Brad Zacher + title: typescript-eslint Maintainer + url: https://github.com/bradzacher +description: Changes to consistent-type-imports when used with decorators, experimentalDecorators, and emitDecoratorMetadata +slug: changes-to-consistent-type-imports-with-decorators +tags: + [ + consistent-type-imports, + experimentalDecorators, + emitDecoratorMetadata, + typescript-eslint, + ] +title: Changes to consistent-type-imports when used with legacy decorators and decorator metadata +--- + +We've made some changes to `consistent-type-imports` to fix some long-standing issues when used alongside `experimentalDecorators: true` and `emitDecoratorMetadata: true`. These changes increase safety and prevent invalid fixes when using decorator metadata. + + + +## Experimental Decorator Metadata + +The `experimentalDecorators` compiler option (referred to as "legacy decorators" from here on) turns on support for an old version of the decorator proposal that was never standardized. Syntactically this proposal isn't too dissimilar to the current [Stage 3 proposal](https://github.com/tc39/proposal-decorators), however they differ in one major way - the use of [metadata reflection](https://rbuckton.github.io/reflect-metadata/) when `emitDecoratorMetadata` is turned on. + +When using legacy decorators with decorator metadata and a class is annotated with decorators TypeScript will emit runtime metadata for the class that captures the property types, method parameter types, and method return types. This metadata is incredibly powerful and provides a bridge between the types (which are elided at compile time) and the runtime code. However this runtime code is generated at a cost as it is derived using type information - meaning that the runtime code emitted changes based on the cross-file type information that TypeScript has computed. + +To illustrate what this means consider the following snippet: + +```ts +import Foo from 'foo'; +import decorator from 'decorator'; + +class Clazz { + @decorator + method(arg: Foo) {} +} +``` + +If the imported name `Foo` resolves to... + +- a type then TS will emit metadata that annotates `arg` with one of `Function`, `Object`, `String`, `Number`, or `Boolean` depending on what that type resolves to. +- an enum then TS will emit metadata that annotates `arg` with one of `String`, `Number`, or `Object` depending on the type of the enum's members. +- a class declaration: + - and the import **_is NOT_** annotated as `import type` then TS will emit metadata that annotates the type of `arg` as `Foo`. + - and the import **_IS_** annotated as `import type` then TS will emit metadata that annotates the type of `arg` as `Function`. + +## `consistent-type-imports` caused runtime breakage + +The important piece is that last dot point - the handling of imported names that resolve to class declarations. If the import is not annotated as `import type` then TS emits a runtime reference to the imported name. This runtime reference is implicit and requires type information to derive - you cannot derive its existence purely based on single-file AST analysis. + +The `consistent-type-imports` rule was introduced to allow users to enforce that any imported names are annotated as `import type` if they are not used in a value location. How the rule makes this decision is based on single-file AST analysis; it scans the code using a technique called scope analysis so that it can find all references to the imported names and determine if each reference is a value reference. + +The issue arises with legacy decorators and decorator metadata - syntactically the only reference to `Foo` is a type reference. However the emitted code contains a hidden reference to `Foo`. When the rule relies upon the code it sees then it will report an error and attempt to mark `Foo` as `import type`. If the user applies this fix then that will cause their runtime code to change (`arg`'s metadata goes from `Foo` to `Function`) which can have downstream runtime impacts and cause broken code! + +## Past (Broken) Solution + +In the past we tried to solve this problem by enforcing that imported names that are used in decorator metadata are specifically _not_ marked as type-only imports to ensure that values are always correctly emitted in the runtime code. + +However this solution had a hidden pitfall; if the user also used `isolatedModules: true` then TS will enforce that all imported types are explicitly marked as `import type` for compatibility with single-file build tools. This lead to an unresolvable situation where `consistent-type-imports` would enforce that an imported name _must not be_ marked with `import type` so that we could ensure we don't break decorator metadata, and simultaneously TS would enforce that that same imported name _must be_ marked with `import type`. + +There have been a few attempts to fix this issue but the resolution we came to was that the only solution was to add type information to the rule so that it could correctly understand all of the above type-aware constraints. Adding type information to an existing rule is something we try to avoid because it is a major breaking change that restricts the rule to just users that leverage [type-aware linting](/getting-started/typed-linting). + +However we decided that adding type-information to the rule to handle this edge case was not a positive change for users or the ecosystem for two main reasons: + +1. It requires a specific combinations of compiler options to trigger it means that not everyone is impacted by the problem - so we'd be preventing a lot of un-impacted users from using the rule. +2. With the release of [TypeScript v5.0 and its stable decorators](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators) `experimentalDecorators` are now the legacy syntax. Whilst [TypeScript v5.2 added support for the latest stable decorator metadata proposal](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata) this proposal does not include type metadata - so it doesn't suffer the same drawbacks as its legacy counterpart. + +## The Final Solution + +Ultimately we decided the best solution was to just opt-out of handling this use-case entirely. This means that we can avoid accidentally reporting the wrong thing and fixing to code that either fails to compile or alters the emitted runtime metadata. To be clear this means that if you have **both** `experimentalDecorators: true` and `emitDecoratorMetadata: true` then rule will **_not_** report any errors within any files _that contain decorators_. + +All files without decorators will continue to report as expected. Similarly all projects that use `experimentalDecorators: false` and/or `emitDecoratorMetadata: false` will continue to report as expected. + +### Configuring the Linter to expect `experimentalDecorators: true` and `emitDecoratorMetadata: true` + +If you are using [type-aware linting](/linting/typed-linting) then we will automatically infer your setup from your tsconfig and you should not need to configure anything manually. + +Otherwise you can explicitly tell our tooling to analyze your code as if the compiler option was turned on by setting both [`parserOptions.emitDecoratorMetadata = true`](/packages/parser/#emitdecoratormetadata) and [`parserOptions.experimentalDecorators = true`](/packages/parser/#experimentaldecorators). For example: + +```js title="eslint.config.js" +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + ...tseslint.configs.recommended, + // Added lines start + { + languageOptions: { + parserOptions: { + emitDecoratorMetadata: true, + experimentalDecorators: true, + }, + }, + }, +); +``` + +## Alternatives for Impacted Users + +If you are working in a workspace that is impacted by this change and want to correctly have your imports consistently marked with `type`, we suggest using the [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax) compiler option which will use type information to correctly enforce that types are marked with `type` and values are not when they are used in decorator metadata. From 4d28e81474fb5200edb8c197a591362cee45a44a Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Tue, 19 Mar 2024 00:09:55 +1030 Subject: [PATCH 4/9] updoots pt 2 --- packages/scope-manager/src/analyze.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/scope-manager/src/analyze.ts b/packages/scope-manager/src/analyze.ts index 12e6b4406ede..9058fcafcf74 100644 --- a/packages/scope-manager/src/analyze.ts +++ b/packages/scope-manager/src/analyze.ts @@ -57,9 +57,9 @@ interface AnalyzeOptions { */ sourceType?: SourceType; - // TODO - remove this in v7 + // TODO - remove this in v8 /** - * This option no longer does anything and will be removed in a future major release. + * @deprecated This option no longer does anything and will be removed in a future major release. */ emitDecoratorMetadata?: boolean; } From 4f0d04e6e7f87055394f0d8e7a401d7cb4b5f3af Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Tue, 19 Mar 2024 00:16:19 +1030 Subject: [PATCH 5/9] docs --- .../docs/rules/consistent-type-imports.mdx | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx b/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx index 4f5c105e2524..20ca6c4ff19d 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx +++ b/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx @@ -109,20 +109,18 @@ Otherwise you can explicitly tell our tooling to analyze your code as if the com ## Comparison with `importsNotUsedAsValues` / `verbatimModuleSyntax` -[`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax) was introduced in TypeScript v5.0 (as a replacement for `importsNotUsedAsValues`). That being said we **do not** recommend that you use both this rule and `verbatimModuleSyntax` / `importsNotUsedAsValues` together at the same time. - -As a comparison both this rule and `verbatimModuleSyntax` should _mostly_ behave in the same way. The main differences being: - -- This rule includes an auto-fixer which can be applied from the ESLint CLI `--fix` flag or via your IDE's auto-fix-on-save feature. -- `verbatimModuleSyntax` will error on unused imports, where as this rule will ignore them -- `verbatimModuleSyntax` will work correctly when paired with both `experimentalDecorators` and `emitDecoratorMetadata` -- `verbatimModuleSyntax` will break your build if you have [`noEmitOnError`](https://www.typescriptlang.org/tsconfig#noEmitOnError) turned on -- `verbatimModuleSyntax` can change how imports are emitted from your build. - - Given the code `import { type T } from 'T';`: - - With `verbatimModuleSyntax: true` TS will emit `import {} from 'T'`. - - With `verbatimModuleSyntax: false` TS will emit nothing (it will "elide" the entire import). - -Because there are some differences - using both this rule and `verbatimModuleSyntax` at the same time can lead to conflicting errors. As such we recommend that you only ever use one _or_ the other -- never both. +[`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax) was introduced in TypeScript v5.0 (as a replacement for `importsNotUsedAsValues`). +This rule and `verbatimModuleSyntax` _mostly_ behave in the same way. +There are a few behavior differences: +| Situation | `consistent-type-imports` (ESLint) | `verbatimModuleSyntax` (TypeScript) | +| -------------------------------------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------- | +| Unused imports | Ignored (consider using [`@typescript-eslint/no-unused-vars`](/rules/no-unused-vars)) | Type error | +| Usage with `emitDecoratorMetadata` & `experimentalDecorations` | Ignores files that contain decorators | Reports on files that contain decorators | +| Failures detected | Does not fail `tsc` build; can be auto-fixed with `--fix` | Fails `tsc` build; cannot be auto-fixed on the command-line | +| `import { type T } from 'T';` | TypeScript will emit nothing (it "elides" the import) | TypeScript emits `import {} from 'T'` | + +Because there are some differences, using both this rule and `verbatimModuleSyntax` at the same time can lead to conflicting errors. +As such we recommend that you only ever use one _or_ the other -- never both. ## When Not To Use It From 1ec72cee7879a985d6c5e6d4105f759e1b9cfe40 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sun, 24 Mar 2024 14:31:39 +1030 Subject: [PATCH 6/9] review --- packages/scope-manager/src/analyze.ts | 2 +- ...consistent-type-imports-with-decorators.md | 62 ++++++++++++++----- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/packages/scope-manager/src/analyze.ts b/packages/scope-manager/src/analyze.ts index 9058fcafcf74..307adfe0905f 100644 --- a/packages/scope-manager/src/analyze.ts +++ b/packages/scope-manager/src/analyze.ts @@ -59,7 +59,7 @@ interface AnalyzeOptions { // TODO - remove this in v8 /** - * @deprecated This option no longer does anything and will be removed in a future major release. + * @deprecated This option never did what it was intended for and will be removed in a future major release. */ emitDecoratorMetadata?: boolean; } diff --git a/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md b/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md index 9f06c3bb31dd..a60d1d7d8ae7 100644 --- a/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md +++ b/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md @@ -13,18 +13,20 @@ tags: emitDecoratorMetadata, typescript-eslint, ] -title: Changes to consistent-type-imports when used with legacy decorators and decorator metadata +title: Changes to `consistent-type-imports` with Legacy Decorators and Decorator Metadata --- -We've made some changes to `consistent-type-imports` to fix some long-standing issues when used alongside `experimentalDecorators: true` and `emitDecoratorMetadata: true`. These changes increase safety and prevent invalid fixes when using decorator metadata. +We've made some changes to the [`consistent-type-imports` rule](/rules/consistent-type-imports) to fix some long-standing issues when used alongside `experimentalDecorators: true` and `emitDecoratorMetadata: true`. These changes increase safety and prevent invalid fixes when using decorator metadata. ## Experimental Decorator Metadata -The `experimentalDecorators` compiler option (referred to as "legacy decorators" from here on) turns on support for an old version of the decorator proposal that was never standardized. Syntactically this proposal isn't too dissimilar to the current [Stage 3 proposal](https://github.com/tc39/proposal-decorators), however they differ in one major way - the use of [metadata reflection](https://rbuckton.github.io/reflect-metadata/) when `emitDecoratorMetadata` is turned on. +TypeScript's [`experimentalDecorators` compiler option](https://aka.ms/tsconfig#experimentalDecorators) (referred to as "legacy decorators" from here on) turns on support for an old version of the [JavaScript TC39 decorator proposal](https://github.com/tc39/proposal-decorators) that was never standardized. TypeScript's legacy decorators are similar to the current proposal, but differ in that they use [metadata reflection](https://rbuckton.github.io/reflect-metadata/) when TypeScript's [`emitDecoratorMetadata` compiler option](https://aka.ms/tsconfig#emitDecoratorMetadata) is turned on. -When using legacy decorators with decorator metadata and a class is annotated with decorators TypeScript will emit runtime metadata for the class that captures the property types, method parameter types, and method return types. This metadata is incredibly powerful and provides a bridge between the types (which are elided at compile time) and the runtime code. However this runtime code is generated at a cost as it is derived using type information - meaning that the runtime code emitted changes based on the cross-file type information that TypeScript has computed. +When using legacy decorators with decorator metadata and a class is annotated with decorators, TypeScript will emit runtime metadata for the class (see the example below). That decorator metadata will capture property types, method parameter types, and method return types. Decorator metadata provides a bridge between the types (which are not available at compile time) and the runtime code. + +The downside of generating this runtime code that it is derived using type information: meaning that the runtime code emitted changes based on the cross-file type information that TypeScript has computed. Doing so violates a key [TypeScript design goal](https://github.com/microsoft/TypeScript/wiki/TypeScript-Design-Goals) of not changing runtime behavior based on type information. To illustrate what this means consider the following snippet: @@ -38,19 +40,46 @@ class Clazz { } ``` +TypeScript will transpile this code to the following: + + +```ts +var _a; +import { __decorate, __metadata } from "tslib"; +import Foo from 'foo'; +import decorator from 'decorator'; +class Clazz { + method(arg) { } +} +__decorate([ + decorator, + __metadata("design:type", Function), + __metadata("design:paramtypes", /* See below for what this value will be */), + __metadata("design:returntype", void 0) +], Clazz.prototype, "method", null); +``` + If the imported name `Foo` resolves to... -- a type then TS will emit metadata that annotates `arg` with one of `Function`, `Object`, `String`, `Number`, or `Boolean` depending on what that type resolves to. -- an enum then TS will emit metadata that annotates `arg` with one of `String`, `Number`, or `Object` depending on the type of the enum's members. +- a type then TS will emit `[Function]`, `[Object]`, `[String]`, `[Number]`, or `[Boolean]` depending on what that type resolves to. +- an enum then TS will emit one of `[String]`, `[Number]`, or `[Object]` depending on the type of the enum's members + - `[Object]` is used for an enum that has both string and number values. - a class declaration: - - and the import **_is NOT_** annotated as `import type` then TS will emit metadata that annotates the type of `arg` as `Foo`. - - and the import **_IS_** annotated as `import type` then TS will emit metadata that annotates the type of `arg` as `Function`. + - and the import **_is NOT_** annotated as `import type` then TS will emit `[Foo]`. + - and the import **_IS_** annotated as `import type` then TS will emit `[Function]`. + +In addition to requiring runtime type information, those metadata emit rules are confusing for developers to reason about. +They necessitate understanding edge cases specific to TypeScript's handling of decorators and type information. ## `consistent-type-imports` caused runtime breakage -The important piece is that last dot point - the handling of imported names that resolve to class declarations. If the import is not annotated as `import type` then TS emits a runtime reference to the imported name. This runtime reference is implicit and requires type information to derive - you cannot derive its existence purely based on single-file AST analysis. +The important piece is that last dot point above - the handling of imported names that resolve to class declarations. If the import is not annotated as `import type` then TS emits a runtime reference to the imported name. This runtime reference is implicit and requires type information to derive - you cannot derive its existence purely based on single-file AST analysis. + +The [`consistent-type-imports` rule](/rules/consistent-type-imports) was introduced to allow users to enforce that any imported names are annotated as `import type` if they are not used in a value location. How the rule makes this decision is based solely on the single file it's looking at. But another way the rule does not use any type information from TS and instead it scans the code using a technique called "scope analysis" so that it can find all references to the imported names and determine if each reference is a value reference. -The `consistent-type-imports` rule was introduced to allow users to enforce that any imported names are annotated as `import type` if they are not used in a value location. How the rule makes this decision is based on single-file AST analysis; it scans the code using a technique called scope analysis so that it can find all references to the imported names and determine if each reference is a value reference. +:::tip +See [ASTs and typescript-eslint](../asts-and-typescript-eslint) to understand how rules look at the syntax of files. +::: The issue arises with legacy decorators and decorator metadata - syntactically the only reference to `Foo` is a type reference. However the emitted code contains a hidden reference to `Foo`. When the rule relies upon the code it sees then it will report an error and attempt to mark `Foo` as `import type`. If the user applies this fix then that will cause their runtime code to change (`arg`'s metadata goes from `Foo` to `Function`) which can have downstream runtime impacts and cause broken code! @@ -62,18 +91,21 @@ However this solution had a hidden pitfall; if the user also used `isolatedModul There have been a few attempts to fix this issue but the resolution we came to was that the only solution was to add type information to the rule so that it could correctly understand all of the above type-aware constraints. Adding type information to an existing rule is something we try to avoid because it is a major breaking change that restricts the rule to just users that leverage [type-aware linting](/getting-started/typed-linting). -However we decided that adding type-information to the rule to handle this edge case was not a positive change for users or the ecosystem for two main reasons: +Adding type-information to the rule to handle this edge case would not be a positive change for users or the ecosystem: +1. Many users are unable to configure typed linting and/or unwilling to take its performance hit. Requiring type-aware linting for this rule to serve a very small subset of impacted users would reduce the linting ability of many more un-impacted users. 1. It requires a specific combinations of compiler options to trigger it means that not everyone is impacted by the problem - so we'd be preventing a lot of un-impacted users from using the rule. -2. With the release of [TypeScript v5.0 and its stable decorators](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators) `experimentalDecorators` are now the legacy syntax. Whilst [TypeScript v5.2 added support for the latest stable decorator metadata proposal](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata) this proposal does not include type metadata - so it doesn't suffer the same drawbacks as its legacy counterpart. +1. With the release of [TypeScript v5.0 and its stable decorators](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators) `experimentalDecorators` are now the legacy syntax. Whilst [TypeScript v5.2 added support for the latest stable decorator metadata proposal](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata) this proposal does not include type metadata - so it doesn't suffer the same drawbacks as its legacy counterpart. + +## Today's Solution - the Compromise -## The Final Solution +Ultimately we determined the best solution was to just opt-out of handling this use-case entirely. This means that we can avoid accidentally reporting the wrong thing and fixing to code that either fails to compile or alters the emitted runtime metadata. -Ultimately we decided the best solution was to just opt-out of handling this use-case entirely. This means that we can avoid accidentally reporting the wrong thing and fixing to code that either fails to compile or alters the emitted runtime metadata. To be clear this means that if you have **both** `experimentalDecorators: true` and `emitDecoratorMetadata: true` then rule will **_not_** report any errors within any files _that contain decorators_. +Now, if you have **both** `experimentalDecorators: true` and `emitDecoratorMetadata: true`, then the `consistent-type-imports` rule will **_not_** report any errors within any files _that contain decorators_. All files without decorators will continue to report as expected. Similarly all projects that use `experimentalDecorators: false` and/or `emitDecoratorMetadata: false` will continue to report as expected. -### Configuring the Linter to expect `experimentalDecorators: true` and `emitDecoratorMetadata: true` +### Configuring the linter to expect `experimentalDecorators: true` and `emitDecoratorMetadata: true` If you are using [type-aware linting](/linting/typed-linting) then we will automatically infer your setup from your tsconfig and you should not need to configure anything manually. From 69fbd1be3ce57d60c0b1e132651059f923f38b96 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sun, 24 Mar 2024 14:32:27 +1030 Subject: [PATCH 7/9] fix broken link --- ...3-25-changes-to-consistent-type-imports-with-decorators.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md b/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md index a60d1d7d8ae7..f14f033e82f5 100644 --- a/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md +++ b/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md @@ -93,7 +93,7 @@ There have been a few attempts to fix this issue but the resolution we came to w Adding type-information to the rule to handle this edge case would not be a positive change for users or the ecosystem: -1. Many users are unable to configure typed linting and/or unwilling to take its performance hit. Requiring type-aware linting for this rule to serve a very small subset of impacted users would reduce the linting ability of many more un-impacted users. +1. Many users are unable to configure typed linting and/or unwilling to take its performance hit. Requiring [type-aware linting](/getting-started/typed-linting) linting for this rule to serve a very small subset of impacted users would reduce the linting ability of many more un-impacted users. 1. It requires a specific combinations of compiler options to trigger it means that not everyone is impacted by the problem - so we'd be preventing a lot of un-impacted users from using the rule. 1. With the release of [TypeScript v5.0 and its stable decorators](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators) `experimentalDecorators` are now the legacy syntax. Whilst [TypeScript v5.2 added support for the latest stable decorator metadata proposal](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata) this proposal does not include type metadata - so it doesn't suffer the same drawbacks as its legacy counterpart. @@ -107,7 +107,7 @@ All files without decorators will continue to report as expected. Similarly all ### Configuring the linter to expect `experimentalDecorators: true` and `emitDecoratorMetadata: true` -If you are using [type-aware linting](/linting/typed-linting) then we will automatically infer your setup from your tsconfig and you should not need to configure anything manually. +If you are using [type-aware linting](/getting-started/typed-linting) then we will automatically infer your setup from your tsconfig and you should not need to configure anything manually. Otherwise you can explicitly tell our tooling to analyze your code as if the compiler option was turned on by setting both [`parserOptions.emitDecoratorMetadata = true`](/packages/parser/#emitdecoratormetadata) and [`parserOptions.experimentalDecorators = true`](/packages/parser/#experimentaldecorators). For example: From e85e9191c725df588ddc943126b2346a93fc3ad2 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 25 Mar 2024 09:35:25 +1030 Subject: [PATCH 8/9] Update 2024-03-25-changes-to-consistent-type-imports-with-decorators.md --- ...4-03-25-changes-to-consistent-type-imports-with-decorators.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md b/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md index f14f033e82f5..e6b12c84ef6b 100644 --- a/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md +++ b/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md @@ -44,7 +44,6 @@ TypeScript will transpile this code to the following: ```ts -var _a; import { __decorate, __metadata } from "tslib"; import Foo from 'foo'; import decorator from 'decorator'; From 5749a57df19579b6eaf77b38ea6230758cf9fe23 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 25 Mar 2024 09:37:55 +1030 Subject: [PATCH 9/9] Update 2024-03-25-changes-to-consistent-type-imports-with-decorators.md --- ...-03-25-changes-to-consistent-type-imports-with-decorators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md b/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md index e6b12c84ef6b..71337b19c174 100644 --- a/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md +++ b/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md @@ -77,7 +77,7 @@ The important piece is that last dot point above - the handling of imported name The [`consistent-type-imports` rule](/rules/consistent-type-imports) was introduced to allow users to enforce that any imported names are annotated as `import type` if they are not used in a value location. How the rule makes this decision is based solely on the single file it's looking at. But another way the rule does not use any type information from TS and instead it scans the code using a technique called "scope analysis" so that it can find all references to the imported names and determine if each reference is a value reference. :::tip -See [ASTs and typescript-eslint](../asts-and-typescript-eslint) to understand how rules look at the syntax of files. +See [ASTs and typescript-eslint](/blog/asts-and-typescript-eslint) to understand how rules look at the syntax of files. ::: The issue arises with legacy decorators and decorator metadata - syntactically the only reference to `Foo` is a type reference. However the emitted code contains a hidden reference to `Foo`. When the rule relies upon the code it sees then it will report an error and attempt to mark `Foo` as `import type`. If the user applies this fix then that will cause their runtime code to change (`arg`'s metadata goes from `Foo` to `Function`) which can have downstream runtime impacts and cause broken code!