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 cc841bcdfa5..ec78a1afd50 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -14,6 +14,10 @@ const withMetaParserOptions = { project: './tsconfig-withmeta.json', }; +const withMetaConfigParserOptions = { + emitDecoratorMetadata: true, +}; + ruleTester.run('consistent-type-imports', rule, { valid: [ ` @@ -346,6 +350,113 @@ ruleTester.run('consistent-type-imports', rule, { `, 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/2989 ` import type * as constants from './constants'; @@ -1522,6 +1633,166 @@ const a: Default = ''; ], parserOptions: withMetaParserOptions, }, + { + code: ` + import type Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + output: noFormat` + 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: noFormat` + 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: noFormat` + 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: noFormat` + import { V , Foo, Bar} from 'foo'; + import type { T } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + foo(@deco bar: Bar) {} + } + `, + errors: [ + { + messageId: 'someImportsInDecoMeta', + data: { typeImports: '"Foo" and "Bar"' }, + line: 3, + column: 9, + }, + ], + parserOptions: withMetaConfigParserOptions, + }, + { + code: ` + import type { Foo, T } from 'foo'; + import { V } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + output: noFormat` + import type { T } from 'foo'; + import { V , Foo} from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + errors: [ + { + messageId: 'aImportInDecoMeta', + data: { typeImports: '"Foo"' }, + line: 2, + column: 9, + }, + ], + parserOptions: withMetaConfigParserOptions, + }, + { + code: ` + import type * as Type from 'foo'; + @deco + class A { + constructor(foo: Type.Foo) {} + } + `, + output: noFormat` + import * as Type from 'foo'; + @deco + class A { + constructor(foo: Type.Foo) {} + } + `, + errors: [ + { + messageId: 'aImportInDecoMeta', + data: { typeImports: '"Type"' }, + line: 2, + column: 9, + }, + ], + parserOptions: withMetaConfigParserOptions, + }, { code: ` import { type A, B } from 'foo'; diff --git a/packages/parser/README.md b/packages/parser/README.md index 1859f99851a..81df0519497 100644 --- a/packages/parser/README.md +++ b/packages/parser/README.md @@ -67,6 +67,8 @@ interface ParserOptions { program?: import('typescript').Program; moduleResolver?: string; + + emitDecoratorMetadata?: boolean; } ``` @@ -246,6 +248,12 @@ interface ModuleResolver { Note that if you pass custom programs via `options.programs` this option will not have any effect over them (you can simply add the custom resolution on them directly). +### `parserOptions.emitDecoratorMetadata` + +Default `undefined`. + +This option allow you to tell parser to act as if `emitDecoratorMetadata: true` is set in `tsconfig.json`, but without [type-aware linting](https://typescript-eslint.io/docs/linting/type-linting). In other words, you don't have to specify `parserOptions.project` in this case, making the linting process faster. + ## Utilities ### `createProgram(configFile, projectDirectory)` diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index bbb0e3a04d7..365bff6648f 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -132,6 +132,7 @@ function parseForESLint( const { ast, services } = parseAndGenerateServices(code, parserOptions); ast.sourceType = options.sourceType; + let emitDecoratorMetadata = options.emitDecoratorMetadata === true; if (services.hasFullTypeInformation) { // automatically apply the options configured for the program const compilerOptions = services.program.getCompilerOptions(); @@ -165,11 +166,14 @@ function parseForESLint( } } if (compilerOptions.emitDecoratorMetadata === true) { - analyzeOptions.emitDecoratorMetadata = - compilerOptions.emitDecoratorMetadata; + emitDecoratorMetadata = true; } } + if (emitDecoratorMetadata) { + analyzeOptions.emitDecoratorMetadata = true; + } + const scopeManager = analyze(ast, analyzeOptions); return { ast, services, scopeManager, visitorKeys }; diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index 18ecbee7943..c54d38c73b9 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -37,6 +37,9 @@ interface ParserOptions { jsxFragmentName?: string | null; lib?: Lib[]; + // use emitDecoratorMetadata without specifying parserOptions.project + emitDecoratorMetadata?: boolean; + // typescript-estree specific comment?: boolean; debugLevel?: DebugLevel;