diff --git a/docs/packages/Parser.mdx b/docs/packages/Parser.mdx index 7724454ddafd..cb7a57836842 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](../getting-started/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](../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` > Default `undefined`. diff --git a/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx b/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx index 354b777c6605..20ca6c4ff19d 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx +++ b/packages/eslint-plugin/docs/rules/consistent-type-imports.mdx @@ -93,11 +93,34 @@ 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. +:::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` +::: -If you are using [type-aware linting](/getting-started/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). +The rule will **_not_** report any errors in files _that contain decorators_ when **both** `experimentalDecorators` and `emitDecoratorMetadata` are turned on. + +> 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` + +[`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 diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index c06097716fb5..f4e98d2d9e00 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -1,9 +1,11 @@ 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 { createRule, formatWordList, + getParserServices, isClosingBraceToken, isCommaToken, isImportKeyword, @@ -43,13 +45,10 @@ interface ReportValueImport { } type MessageIds = - | 'aImportInDecoMeta' - | 'aImportIsOnlyTypes' - | 'noImportTypeAnnotations' - | 'someImportsAreOnlyTypes' - | 'someImportsInDecoMeta' | 'typeOverValue' - | 'valueOverType'; + | 'someImportsAreOnlyTypes' + | 'avoidImportType' + | 'noImportTypeAnnotations'; export default createRule({ name: 'consistent-type-imports', meta: { @@ -60,14 +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.', - 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`.', + someImportsAreOnlyTypes: 'Imports {{typeImports}} are only used as type.', + + avoidImportType: 'Use an `import` instead of an `import type`.', noImportTypeAnnotations: '`import()` type annotations are forbidden.', }, schema: [ @@ -94,314 +88,329 @@ export default createRule({ defaultOptions: [ { + prefer: 'type-imports', disallowTypeAnnotations: true, fixStyle: 'separate-type-imports', - prefer: 'type-imports', }, ], create(context, [option]) { const prefer = option.prefer ?? 'type-imports'; const disallowTypeAnnotations = option.disallowTypeAnnotations !== false; + + const selectors: RuleListener = {}; + + if (disallowTypeAnnotations) { + selectors.TSImportType = (node): void => { + context.report({ + node, + messageId: 'noImportTypeAnnotations', + }); + }; + } + + if (prefer === 'no-type-imports') { + return { + ...selectors, + 'ImportDeclaration[importKind = "type"]'( + node: TSESTree.ImportDeclaration, + ): void { + context.report({ + node, + messageId: 'avoidImportType', + fix(fixer) { + return fixRemoveTypeSpecifierFromImportDeclaration(fixer, node); + }, + }); + }, + 'ImportSpecifier[importKind = "type"]'( + node: TSESTree.ImportSpecifier, + ): void { + context.report({ + node, + messageId: 'avoidImportType', + 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 - 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.length && - 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 + 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.length && + 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] = - context.sourceCode.getDeclaredVariables(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'; - } + const [variable] = context.sourceCode.getDeclaredVariables(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'; + } + } + 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; } - 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) { + sourceImports.reportValueImports.push({ + node, + typeSpecifiers, + valueSpecifiers, + unusedSpecifiers, + inlineTypeSpecifiers, + }); + } + }, + + 'Program:exit'(): void { + if (hasDecoratorMetadata) { + // 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 + // 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; - } - 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, - ); - }, - }); - } - } 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, - ); - } - }, - }); - } + } else { + // 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 = report.typeSpecifiers.map( + specifier => `"${specifier.local.name}"`, + ); + + const message = ((): { + messageId: MessageIds; + data: Record; + } => { + const typeImports = formatWordList(importNames); + + if (importNames.length === 1) { + return { + messageId: 'someImportsAreOnlyTypes', + data: { + typeImports, + }, + }; } - } - }, - } - : { - // prefer no type imports - 'ImportDeclaration[importKind = "type"]'( - node: TSESTree.ImportDeclaration, - ): void { + return { + messageId: 'someImportsAreOnlyTypes', + data: { + typeImports, + }, + }; + })(); + context.report({ - node, - messageId: 'valueOverType', - fix(fixer) { - return fixRemoveTypeSpecifierFromImportDeclaration( + node: report.node, + ...message, + *fix(fixer) { + // take all the typeSpecifiers and put them on a new line + yield* fixToTypeImportDeclaration( fixer, - node, + report, + sourceImports, ); }, }); - }, - '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): { @@ -586,7 +595,6 @@ export default createRule({ context.sourceCode.getTokenBefore(closingBraceToken), NullThrowsReasons.MissingToken('token before', 'closing brace'), ); - if (!isCommaToken(before) && !isOpeningBraceToken(before)) { insertText = `,${insertText}`; } @@ -893,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 9e01955b4a5c..a751730cfd34 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,2120 @@ 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; + `, + + // 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: [ + { + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + data: { typeImports: '"A"' }, + line: 2, + }, + { + messageId: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + data: { typeImports: '"B"' }, + line: 2, + }, + { + messageId: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + data: { typeImports: '"C"' }, + line: 2, + }, + { + messageId: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + data: { typeImports: '"Type1"' }, + line: 2, + }, + { + messageId: 'someImportsAreOnlyTypes', + data: { typeImports: '"Type2"' }, + line: 3, + }, + { + messageId: 'someImportsAreOnlyTypes', + 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: 'avoidImportType', + 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: 'avoidImportType', + 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: 'avoidImportType', + 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: 'avoidImportType', + 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: 'avoidImportType', + 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: 'avoidImportType', + 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: 'avoidImportType', + 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: 'avoidImportType', + 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: 'avoidImportType', + line: 2, + }, + { + messageId: 'avoidImportType', + line: 3, + }, + { + messageId: 'avoidImportType', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + 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: 'avoidImportType', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', + 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: 'someImportsAreOnlyTypes', 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: 'someImportsAreOnlyTypes', 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, + }, + ], + }, + ], + }); }); diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index 17c5a2ff61b9..32ed786ab2eb 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -130,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(); @@ -161,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 6beb99a0cc21..cb673dc1805a 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/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..307adfe0905f 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 v8 /** - * Emit design-type metadata for decorated declarations in source. - * Defaults to `false`. + * @deprecated This option never did what it was intended for 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 c7973556f269..3b00e3211f6b 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 94510960bd97..753b241b3383 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 477acb50cacb..4e44126bcaf1 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -51,6 +51,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 0aeb3e4a4356..5df5b5b0e271 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -245,18 +245,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/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..71337b19c174 --- /dev/null +++ b/packages/website/blog/2024-03-25-changes-to-consistent-type-imports-with-decorators.md @@ -0,0 +1,132 @@ +--- +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` with Legacy Decorators and 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 + +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 (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: + +```ts +import Foo from 'foo'; +import decorator from 'decorator'; + +class Clazz { + @decorator + method(arg: Foo) {} +} +``` + +TypeScript will transpile this code to the following: + + +```ts +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 `[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 `[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 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. + +:::tip +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! + +## 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). + +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](/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. + +## Today's Solution - the Compromise + +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. + +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` + +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: + +```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. diff --git a/packages/website/src/components/linter/createParser.ts b/packages/website/src/components/linter/createParser.ts index 71768bf61414..c8348c77da87 100644 --- a/packages/website/src/components/linter/createParser.ts +++ b/packages/website/src/components/linter/createParser.ts @@ -78,6 +78,7 @@ export function createParser( }); const checker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); onUpdate(filePath, { storedAST: converted.estree, @@ -90,6 +91,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 b02ff34fcf3d..eee7608dc8f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5619,6 +5619,7 @@ __metadata: semver: ^7.5.4 sinon: ^16.0.0 source-map-support: ^0.5.21 + typescript: "*" peerDependencies: "@eslint/eslintrc": ">=2" eslint: ^8.56.0