diff --git a/packages/eslint-plugin/docs/rules/consistent-type-imports.md b/packages/eslint-plugin/docs/rules/consistent-type-imports.md index a6d06aae851..c30e59b40b9 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-imports.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-imports.md @@ -25,7 +25,7 @@ const defaultOptions: Options = { This option defines the expected import kind for type-only imports. Valid values for `prefer` are: -- `type-imports` will enforce that you always use `import type Foo from '...'`. It is default. +- `type-imports` will enforce that you always use `import type Foo from '...'` except referenced by metadata of decorators. It is default. - `no-type-imports` will enforce that you always use `import Foo from '...'`. Examples of **correct** code with `{prefer: 'type-imports'}`, and **incorrect** code with `{prefer: 'no-type-imports'}`. diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index c4cb6586a00..5d14576a2e5 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -20,6 +20,8 @@ interface SourceImports { reportValueImports: ReportValueImport[]; // ImportDeclaration for type-only import only with named imports. typeOnlyNamedImport: TSESTree.ImportDeclaration | null; + // ImportDeclaration for value-only import only with named imports. + valueOnlyNamedImport: TSESTree.ImportDeclaration | null; } interface ReportValueImport { node: TSESTree.ImportDeclaration; @@ -39,12 +41,15 @@ function isTypeToken( ): token is TSESTree.IdentifierToken & { value: 'type' } { return token.type === AST_TOKEN_TYPES.Identifier && token.value === 'type'; } + type MessageIds = | 'typeOverValue' | 'someImportsAreOnlyTypes' | 'aImportIsOnlyTypes' | 'valueOverType' - | 'noImportTypeAnnotations'; + | 'noImportTypeAnnotations' + | 'someImportsInDecoMeta' + | 'aImportInDecoMeta'; export default util.createRule({ name: 'consistent-type-imports', meta: { @@ -59,6 +64,10 @@ export default util.createRule({ '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`.', noImportTypeAnnotations: '`import()` type annotations are forbidden.', }, @@ -105,6 +114,7 @@ export default util.createRule({ source, reportValueImports: [], typeOnlyNamedImport: null, + valueOnlyNamedImport: null, }); if (node.importKind === 'type') { if ( @@ -116,9 +126,18 @@ export default util.createRule({ ) { sourceImports.typeOnlyNamedImport = node; } - return; + } else { + if ( + !sourceImports.valueOnlyNamedImport && + node.specifiers.every( + specifier => + specifier.type === AST_NODE_TYPES.ImportSpecifier, + ) + ) { + sourceImports.valueOnlyNamedImport = node; + } } - // if importKind === 'value' + const typeSpecifiers: TSESTree.ImportClause[] = []; const valueSpecifiers: TSESTree.ImportClause[] = []; const unusedSpecifiers: TSESTree.ImportClause[] = []; @@ -129,6 +148,21 @@ export default util.createRule({ } else { const onlyHasTypeReferences = variable.references.every( ref => { + /** + * keep origin import kind when export + * export { Type } + * export default Type; + */ + if ( + ref.identifier.parent?.type === + AST_NODE_TYPES.ExportSpecifier || + ref.identifier.parent?.type === + AST_NODE_TYPES.ExportDefaultDeclaration + ) { + if (ref.isValueReference && ref.isTypeReference) { + return node.importKind === 'type'; + } + } if (ref.isValueReference) { // `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 @@ -157,7 +191,10 @@ export default util.createRule({ } } - if (typeSpecifiers.length) { + if ( + (node.importKind === 'value' && typeSpecifiers.length) || + (node.importKind === 'type' && valueSpecifiers.length) + ) { sourceImports.reportValueImports.push({ node, typeSpecifiers, @@ -185,27 +222,42 @@ export default util.createRule({ }, }); } else { + const isTypeImport = report.node.importKind === 'type'; + // we have a mixed type/value import, so we need to split them out into multiple exports - const typeImportNames: string[] = report.typeSpecifiers.map( - specifier => `"${specifier.local.name}"`, - ); + const importNames = (isTypeImport + ? report.valueSpecifiers + : report.typeSpecifiers + ).map(specifier => `"${specifier.local.name}"`); context.report({ node: report.node, messageId: - typeImportNames.length === 1 - ? 'aImportIsOnlyTypes' + importNames.length === 1 + ? isTypeImport + ? 'aImportInDecoMeta' + : 'aImportIsOnlyTypes' + : isTypeImport + ? 'someImportsInDecoMeta' : 'someImportsAreOnlyTypes', data: { typeImports: - typeImportNames.length === 1 - ? typeImportNames[0] + importNames.length === 1 + ? importNames[0] : [ - typeImportNames.slice(0, -1).join(', '), - typeImportNames.slice(-1)[0], + importNames.slice(0, -1).join(', '), + importNames.slice(-1)[0], ].join(' and '), }, *fix(fixer) { - yield* fixToTypeImport(fixer, report, sourceImports); + if (isTypeImport) { + yield* fixToValueImportInDecoMeta( + fixer, + report, + sourceImports, + ); + } else { + yield* fixToTypeImport(fixer, report, sourceImports); + } }, }); } @@ -240,13 +292,13 @@ export default util.createRule({ : {}), }; - function* fixToTypeImport( - fixer: TSESLint.RuleFixer, - report: ReportValueImport, - sourceImports: SourceImports, - ): IterableIterator { - const { node } = report; - + function classifySpecifier( + node: TSESTree.ImportDeclaration, + ): { + defaultSpecifier: TSESTree.ImportDefaultSpecifier | null; + namespaceSpecifier: TSESTree.ImportNamespaceSpecifier | null; + namedSpecifiers: TSESTree.ImportSpecifier[]; + } { const defaultSpecifier: TSESTree.ImportDefaultSpecifier | null = node.specifiers[0].type === AST_NODE_TYPES.ImportDefaultSpecifier ? node.specifiers[0] @@ -260,6 +312,175 @@ export default util.createRule({ (specifier): specifier is TSESTree.ImportSpecifier => specifier.type === AST_NODE_TYPES.ImportSpecifier, ); + return { + defaultSpecifier, + namespaceSpecifier, + namedSpecifiers, + }; + } + + /** + * Returns information for fixing named specifiers. + */ + function getFixesNamedSpecifiers( + fixer: TSESLint.RuleFixer, + node: TSESTree.ImportDeclaration, + typeNamedSpecifiers: TSESTree.ImportSpecifier[], + allNamedSpecifiers: TSESTree.ImportSpecifier[], + ): { + typeNamedSpecifiersText: string; + removeTypeNamedSpecifiers: TSESLint.RuleFix[]; + } { + if (allNamedSpecifiers.length === 0) { + return { + typeNamedSpecifiersText: '', + removeTypeNamedSpecifiers: [], + }; + } + const typeNamedSpecifiersTexts: string[] = []; + const removeTypeNamedSpecifiers: TSESLint.RuleFix[] = []; + if (typeNamedSpecifiers.length === allNamedSpecifiers.length) { + // e.g. + // import Foo, {Type1, Type2} from 'foo' + // import DefType, {Type1, Type2} from 'foo' + const openingBraceToken = util.nullThrows( + sourceCode.getTokenBefore( + typeNamedSpecifiers[0], + util.isOpeningBraceToken, + ), + util.NullThrowsReasons.MissingToken('{', node.type), + ); + const commaToken = util.nullThrows( + sourceCode.getTokenBefore(openingBraceToken, util.isCommaToken), + util.NullThrowsReasons.MissingToken(',', node.type), + ); + const closingBraceToken = util.nullThrows( + sourceCode.getFirstTokenBetween( + openingBraceToken, + node.source, + util.isClosingBraceToken, + ), + util.NullThrowsReasons.MissingToken('}', node.type), + ); + + // import DefType, {...} from 'foo' + // ^^^^^^^ remove + removeTypeNamedSpecifiers.push( + fixer.removeRange([commaToken.range[0], closingBraceToken.range[1]]), + ); + + typeNamedSpecifiersTexts.push( + sourceCode.text.slice( + openingBraceToken.range[1], + closingBraceToken.range[0], + ), + ); + } else { + const typeNamedSpecifierGroups: TSESTree.ImportSpecifier[][] = []; + let group: TSESTree.ImportSpecifier[] = []; + for (const namedSpecifier of allNamedSpecifiers) { + if (typeNamedSpecifiers.includes(namedSpecifier)) { + group.push(namedSpecifier); + } else if (group.length) { + typeNamedSpecifierGroups.push(group); + group = []; + } + } + if (group.length) { + typeNamedSpecifierGroups.push(group); + } + for (const namedSpecifiers of typeNamedSpecifierGroups) { + const { removeRange, textRange } = getNamedSpecifierRanges( + namedSpecifiers, + allNamedSpecifiers, + ); + removeTypeNamedSpecifiers.push(fixer.removeRange(removeRange)); + + typeNamedSpecifiersTexts.push(sourceCode.text.slice(...textRange)); + } + } + return { + typeNamedSpecifiersText: typeNamedSpecifiersTexts.join(','), + removeTypeNamedSpecifiers, + }; + } + + /** + * Returns ranges for fixing named specifier. + */ + function getNamedSpecifierRanges( + namedSpecifierGroup: TSESTree.ImportSpecifier[], + allNamedSpecifiers: TSESTree.ImportSpecifier[], + ): { + textRange: TSESTree.Range; + removeRange: TSESTree.Range; + } { + const first = namedSpecifierGroup[0]; + const last = namedSpecifierGroup[namedSpecifierGroup.length - 1]; + const removeRange: TSESTree.Range = [first.range[0], last.range[1]]; + const textRange: TSESTree.Range = [...removeRange]; + const before = sourceCode.getTokenBefore(first)!; + textRange[0] = before.range[1]; + if (util.isCommaToken(before)) { + removeRange[0] = before.range[0]; + } else { + removeRange[0] = before.range[1]; + } + + const isFirst = allNamedSpecifiers[0] === first; + const isLast = allNamedSpecifiers[allNamedSpecifiers.length - 1] === last; + const after = sourceCode.getTokenAfter(last)!; + textRange[1] = after.range[0]; + if (isFirst || isLast) { + if (util.isCommaToken(after)) { + removeRange[1] = after.range[1]; + } + } + + return { + textRange, + removeRange, + }; + } + + /** + * insert specifiers to named import node. + * e.g. + * import type { Already, Type1, Type2 } from 'foo' + * ^^^^^^^^^^^^^ insert + */ + function insertToNamedImport( + fixer: TSESLint.RuleFixer, + target: TSESTree.ImportDeclaration, + insertText: string, + ): TSESLint.RuleFix { + const closingBraceToken = util.nullThrows( + sourceCode.getFirstTokenBetween( + sourceCode.getFirstToken(target)!, + target.source, + util.isClosingBraceToken, + ), + util.NullThrowsReasons.MissingToken('}', target.type), + ); + const before = sourceCode.getTokenBefore(closingBraceToken)!; + if (!util.isCommaToken(before) && !util.isOpeningBraceToken(before)) { + insertText = ',' + insertText; + } + return fixer.insertTextBefore(closingBraceToken, insertText); + } + + function* fixToTypeImport( + fixer: TSESLint.RuleFixer, + report: ReportValueImport, + sourceImports: SourceImports, + ): IterableIterator { + const { node } = report; + + const { + defaultSpecifier, + namespaceSpecifier, + namedSpecifiers, + } = classifySpecifier(node); if (namespaceSpecifier && !defaultSpecifier) { // e.g. @@ -296,33 +517,18 @@ export default util.createRule({ ); const fixesNamedSpecifiers = getFixesNamedSpecifiers( + fixer, + node, typeNamedSpecifiers, namedSpecifiers, ); const afterFixes: TSESLint.RuleFix[] = []; if (typeNamedSpecifiers.length) { if (sourceImports.typeOnlyNamedImport) { - const closingBraceToken = util.nullThrows( - sourceCode.getFirstTokenBetween( - sourceCode.getFirstToken(sourceImports.typeOnlyNamedImport)!, - sourceImports.typeOnlyNamedImport.source, - util.isClosingBraceToken, - ), - util.NullThrowsReasons.MissingToken( - '}', - sourceImports.typeOnlyNamedImport.type, - ), - ); - let insertText = fixesNamedSpecifiers.typeNamedSpecifiersText; - const before = sourceCode.getTokenBefore(closingBraceToken)!; - if (!util.isCommaToken(before) && !util.isOpeningBraceToken(before)) { - insertText = ',' + insertText; - } - // import type { Already, Type1, Type2 } from 'foo' - // ^^^^^^^^^^^^^ insert - const insertTypeNamedSpecifiers = fixer.insertTextBefore( - closingBraceToken, - insertText, + const insertTypeNamedSpecifiers = insertToNamedImport( + fixer, + sourceImports.typeOnlyNamedImport, + fixesNamedSpecifiers.typeNamedSpecifiersText, ); if (sourceImports.typeOnlyNamedImport.range[1] <= node.range[0]) { yield insertTypeNamedSpecifiers; @@ -413,132 +619,6 @@ export default util.createRule({ yield* fixesRemoveTypeNamespaceSpecifier; yield* afterFixes; - - /** - * Returns information for fixing named specifiers. - */ - function getFixesNamedSpecifiers( - typeNamedSpecifiers: TSESTree.ImportSpecifier[], - allNamedSpecifiers: TSESTree.ImportSpecifier[], - ): { - typeNamedSpecifiersText: string; - removeTypeNamedSpecifiers: TSESLint.RuleFix[]; - } { - if (allNamedSpecifiers.length === 0) { - return { - typeNamedSpecifiersText: '', - removeTypeNamedSpecifiers: [], - }; - } - const typeNamedSpecifiersTexts: string[] = []; - const removeTypeNamedSpecifiers: TSESLint.RuleFix[] = []; - if (typeNamedSpecifiers.length === allNamedSpecifiers.length) { - // e.g. - // import Foo, {Type1, Type2} from 'foo' - // import DefType, {Type1, Type2} from 'foo' - const openingBraceToken = util.nullThrows( - sourceCode.getTokenBefore( - typeNamedSpecifiers[0], - util.isOpeningBraceToken, - ), - util.NullThrowsReasons.MissingToken('{', node.type), - ); - const commaToken = util.nullThrows( - sourceCode.getTokenBefore(openingBraceToken, util.isCommaToken), - util.NullThrowsReasons.MissingToken(',', node.type), - ); - const closingBraceToken = util.nullThrows( - sourceCode.getFirstTokenBetween( - openingBraceToken, - node.source, - util.isClosingBraceToken, - ), - util.NullThrowsReasons.MissingToken('}', node.type), - ); - - // import DefType, {...} from 'foo' - // ^^^^^^^ remove - removeTypeNamedSpecifiers.push( - fixer.removeRange([ - commaToken.range[0], - closingBraceToken.range[1], - ]), - ); - - typeNamedSpecifiersTexts.push( - sourceCode.text.slice( - openingBraceToken.range[1], - closingBraceToken.range[0], - ), - ); - } else { - const typeNamedSpecifierGroups: TSESTree.ImportSpecifier[][] = []; - let group: TSESTree.ImportSpecifier[] = []; - for (const namedSpecifier of allNamedSpecifiers) { - if (typeNamedSpecifiers.includes(namedSpecifier)) { - group.push(namedSpecifier); - } else if (group.length) { - typeNamedSpecifierGroups.push(group); - group = []; - } - } - if (group.length) { - typeNamedSpecifierGroups.push(group); - } - for (const namedSpecifiers of typeNamedSpecifierGroups) { - const { removeRange, textRange } = getNamedSpecifierRanges( - namedSpecifiers, - allNamedSpecifiers, - ); - removeTypeNamedSpecifiers.push(fixer.removeRange(removeRange)); - - typeNamedSpecifiersTexts.push(sourceCode.text.slice(...textRange)); - } - } - return { - typeNamedSpecifiersText: typeNamedSpecifiersTexts.join(','), - removeTypeNamedSpecifiers, - }; - } - - /** - * Returns ranges for fixing named specifier. - */ - function getNamedSpecifierRanges( - namedSpecifierGroup: TSESTree.ImportSpecifier[], - allNamedSpecifiers: TSESTree.ImportSpecifier[], - ): { - textRange: TSESTree.Range; - removeRange: TSESTree.Range; - } { - const first = namedSpecifierGroup[0]; - const last = namedSpecifierGroup[namedSpecifierGroup.length - 1]; - const removeRange: TSESTree.Range = [first.range[0], last.range[1]]; - const textRange: TSESTree.Range = [...removeRange]; - const before = sourceCode.getTokenBefore(first)!; - textRange[0] = before.range[1]; - if (util.isCommaToken(before)) { - removeRange[0] = before.range[0]; - } else { - removeRange[0] = before.range[1]; - } - - const isFirst = allNamedSpecifiers[0] === first; - const isLast = - allNamedSpecifiers[allNamedSpecifiers.length - 1] === last; - const after = sourceCode.getTokenAfter(last)!; - textRange[1] = after.range[0]; - if (isFirst || isLast) { - if (util.isCommaToken(after)) { - removeRange[1] = after.range[1]; - } - } - - return { - textRange, - removeRange, - }; - } } function* fixToTypeImportByInsertType( @@ -600,10 +680,89 @@ export default util.createRule({ } } - function fixToValueImport( + function* fixToValueImportInDecoMeta( + fixer: TSESLint.RuleFixer, + report: ReportValueImport, + sourceImports: SourceImports, + ): IterableIterator { + const { node } = report; + + const { + defaultSpecifier, + namespaceSpecifier, + namedSpecifiers, + } = classifySpecifier(node); + + if (namespaceSpecifier) { + // e.g. + // import type * as types from 'foo' + yield* fixToValueImport(fixer, node); + return; + } else if (defaultSpecifier) { + if ( + report.valueSpecifiers.includes(defaultSpecifier) && + namedSpecifiers.length === 0 + ) { + // e.g. + // import type Type from 'foo' + yield* fixToValueImport(fixer, node); + return; + } + } else { + if ( + namedSpecifiers.every(specifier => + report.valueSpecifiers.includes(specifier), + ) + ) { + // e.g. + // import type {Type1, Type2} from 'foo' + yield* fixToValueImport(fixer, node); + return; + } + } + + 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 = insertToNamedImport( + fixer, + sourceImports.valueOnlyNamedImport, + fixesNamedSpecifiers.typeNamedSpecifiersText, + ); + if (sourceImports.valueOnlyNamedImport.range[1] <= node.range[0]) { + yield insertTypeNamedSpecifiers; + } else { + afterFixes.push(insertTypeNamedSpecifiers); + } + } else { + yield fixer.insertTextBefore( + node, + `import {${ + fixesNamedSpecifiers.typeNamedSpecifiersText + }} from ${sourceCode.getText(node.source)};\n`, + ); + } + } + + yield* fixesNamedSpecifiers.removeTypeNamedSpecifiers; + + yield* afterFixes; + } + + function* fixToValueImport( fixer: TSESLint.RuleFixer, node: TSESTree.ImportDeclaration, - ): TSESLint.RuleFix { + ): IterableIterator { // import type Foo from 'foo' // ^^^^ remove const importToken = util.nullThrows( @@ -622,7 +781,7 @@ export default util.createRule({ sourceCode.getTokenAfter(typeToken, { includeComments: true }), util.NullThrowsReasons.MissingToken('any token', node.type), ); - return fixer.removeRange([typeToken.range[0], afterToken.range[0]]); + yield fixer.removeRange([typeToken.range[0], afterToken.range[0]]); } }, }); diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig-withmeta.json b/packages/eslint-plugin/tests/fixtures/tsconfig-withmeta.json new file mode 100644 index 00000000000..4987fc7e174 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/tsconfig-withmeta.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "emitDecoratorMetadata": true, + } +} \ No newline at end of file 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 8818225a024..83d803cf380 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/consistent-type-imports'; -import { RuleTester, noFormat } from '../RuleTester'; +import { RuleTester, noFormat, getFixturesRootDir } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -9,6 +9,11 @@ const ruleTester = new RuleTester({ }, }); +const withMetaParserOptions = { + tsconfigRootDir: getFixturesRootDir(), + project: './tsconfig-withmeta.json', +}; + ruleTester.run('consistent-type-imports', rule, { valid: [ ` @@ -230,6 +235,112 @@ ruleTester.run('consistent-type-imports', rule, { 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, + }, ], invalid: [ { @@ -1215,5 +1326,188 @@ const a: Default = ''; }, ], }, + { + 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: noFormat` + 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: noFormat` + 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: 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: withMetaParserOptions, + }, + { + 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: withMetaParserOptions, + }, + { + 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: withMetaParserOptions, + }, + { + 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: withMetaParserOptions, + }, ], }); diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index c1422dc261b..f304a837d8b 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -164,6 +164,10 @@ function parseForESLint( ); } } + if (compilerOptions.emitDecoratorMetadata === true) { + analyzeOptions.emitDecoratorMetadata = + compilerOptions.emitDecoratorMetadata; + } } const scopeManager = analyze(ast, analyzeOptions); diff --git a/packages/scope-manager/README.md b/packages/scope-manager/README.md index 24ccd839dff..7671214e8d7 100644 --- a/packages/scope-manager/README.md +++ b/packages/scope-manager/README.md @@ -83,6 +83,12 @@ interface AnalyzeOptions { * The source type of the script. */ sourceType?: 'script' | 'module'; + + /** + * Emit design-type metadata for decorated declarations in source. + * Defaults to `false`. + */ + emitDecoratorMetadata?: boolean; } ``` diff --git a/packages/scope-manager/src/analyze.ts b/packages/scope-manager/src/analyze.ts index 9e734925da1..1aaf874049f 100644 --- a/packages/scope-manager/src/analyze.ts +++ b/packages/scope-manager/src/analyze.ts @@ -61,6 +61,12 @@ interface AnalyzeOptions { * The source type of the script. */ sourceType?: 'script' | 'module'; + + /** + * Emit design-type metadata for decorated declarations in source. + * Defaults to `false`. + */ + emitDecoratorMetadata?: boolean; } const DEFAULT_OPTIONS: Required = { @@ -72,6 +78,7 @@ const DEFAULT_OPTIONS: Required = { jsxFragmentName: null, lib: ['es2018'], sourceType: 'script', + emitDecoratorMetadata: false, }; function mapEcmaVersion(version: EcmaVersion | undefined): Lib { @@ -106,6 +113,9 @@ function analyze( providedOptions?.jsxFragmentName ?? DEFAULT_OPTIONS.jsxFragmentName, sourceType: providedOptions?.sourceType ?? DEFAULT_OPTIONS.sourceType, lib: providedOptions?.lib ?? [mapEcmaVersion(ecmaVersion)], + emitDecoratorMetadata: + providedOptions?.emitDecoratorMetadata ?? + DEFAULT_OPTIONS.emitDecoratorMetadata, }; // ensure the option is lower cased diff --git a/packages/scope-manager/src/referencer/ClassVisitor.ts b/packages/scope-manager/src/referencer/ClassVisitor.ts new file mode 100644 index 00000000000..ff758a3ae8f --- /dev/null +++ b/packages/scope-manager/src/referencer/ClassVisitor.ts @@ -0,0 +1,362 @@ +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/types'; +import { ClassNameDefinition, ParameterDefinition } from '../definition'; +import { Referencer } from './Referencer'; +import { TypeVisitor } from './TypeVisitor'; +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, + ); + classVisitor.visitClass(node); + } + + /////////////////// + // Visit helpers // + /////////////////// + + protected visitClass( + node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, + ): void { + if (node.type === AST_NODE_TYPES.ClassDeclaration && node.id) { + this.#referencer + .currentScope() + .defineIdentifier(node.id, new ClassNameDefinition(node.id, node)); + } + + node.decorators?.forEach(d => this.visit(d)); + + this.#referencer.scopeManager.nestClassScope(node); + + if (node.id) { + // define the class name again inside the new scope + // references to the class should not resolve directly to the parent class + this.#referencer + .currentScope() + .defineIdentifier(node.id, new ClassNameDefinition(node.id, node)); + } + + this.#referencer.visit(node.superClass); + + // visit the type param declarations + this.visitType(node.typeParameters); + // then the usages + this.visitType(node.superTypeParameters); + node.implements?.forEach(imp => this.visitType(imp)); + + this.visit(node.body); + + this.#referencer.close(node); + } + + protected visitClassProperty( + node: TSESTree.TSAbstractClassProperty | TSESTree.ClassProperty, + ): void { + this.visitProperty(node); + /** + * class A { + * @meta // <--- check this + * foo: Type; + * } + */ + this.visitMetadataType(node.typeAnnotation, !!node.decorators); + } + + protected visitFunctionParameterTypeAnnotation( + node: TSESTree.Parameter, + withDecorators: boolean, + ): void { + if ('typeAnnotation' in node) { + this.visitMetadataType(node.typeAnnotation, withDecorators); + } else if (node.type === AST_NODE_TYPES.AssignmentPattern) { + this.visitMetadataType(node.left.typeAnnotation, withDecorators); + } else if (node.type === AST_NODE_TYPES.TSParameterProperty) { + this.visitFunctionParameterTypeAnnotation(node.parameter, withDecorators); + } + } + + protected visitMethodFunction( + node: TSESTree.FunctionExpression, + methodNode: TSESTree.MethodDefinition, + ): void { + if (node.id) { + // FunctionExpression with name creates its special scope; + // FunctionExpressionNameScope. + this.#referencer.scopeManager.nestFunctionExpressionNameScope(node); + } + + // Consider this function is in the MethodDefinition. + this.#referencer.scopeManager.nestFunctionScope(node, true); + + /** + * class A { + * @meta // <--- check this + * foo(a: Type) {} + * + * @meta // <--- check this + * foo(): Type {} + * } + */ + let withMethodDecorators = !!methodNode.decorators; + /** + * class A { + * foo( + * @meta // <--- check this + * a: Type + * ) {} + * + * set foo( + * @meta // <--- EXCEPT this. TS do nothing for this + * a: Type + * ) {} + * } + */ + withMethodDecorators = + withMethodDecorators || + (methodNode.kind !== 'set' && + node.params.some(param => param.decorators)); + if (!withMethodDecorators && methodNode.kind === 'set') { + const keyName = getLiteralMethodKeyName(methodNode); + + /** + * class A { + * @meta // <--- check this + * get a() {} + * set ['a'](v: Type) {} + * } + */ + if ( + keyName !== null && + this.#classNode.body.body.find( + (node): node is TSESTree.MethodDefinition => + node !== methodNode && + node.type === AST_NODE_TYPES.MethodDefinition && + // Node must both be static or not + node.static === methodNode.static && + getLiteralMethodKeyName(node) === keyName, + )?.decorators + ) { + withMethodDecorators = true; + } + } + + /** + * @meta // <--- check this + * class A { + * constructor(a: Type) {} + * } + */ + if ( + !withMethodDecorators && + methodNode.kind === 'constructor' && + this.#classNode.decorators + ) { + withMethodDecorators = true; + } + + // Process parameter declarations. + for (const param of node.params) { + this.visitPattern( + param, + (pattern, info) => { + this.#referencer + .currentScope() + .defineIdentifier( + pattern, + new ParameterDefinition(pattern, node, info.rest), + ); + + this.#referencer.referencingDefaultValue( + pattern, + info.assignments, + null, + true, + ); + }, + { processRightHandNodes: true }, + ); + this.visitFunctionParameterTypeAnnotation(param, withMethodDecorators); + param.decorators?.forEach(d => this.visit(d)); + } + + this.visitMetadataType(node.returnType, withMethodDecorators); + this.visitType(node.typeParameters); + + // In TypeScript there are a number of function-like constructs which have no body, + // so check it exists before traversing + if (node.body) { + // Skip BlockStatement to prevent creating BlockStatement scope. + if (node.body.type === AST_NODE_TYPES.BlockStatement) { + this.#referencer.visitChildren(node.body); + } else { + this.#referencer.visit(node.body); + } + } + + this.#referencer.close(node); + } + + protected visitProperty( + node: + | TSESTree.ClassProperty + | TSESTree.TSAbstractClassProperty + | TSESTree.TSAbstractMethodDefinition, + ): void { + if (node.computed) { + this.#referencer.visit(node.key); + } + + this.#referencer.visit(node.value); + + if ('decorators' in node) { + node.decorators?.forEach(d => this.#referencer.visit(d)); + } + } + + protected visitMethod(node: TSESTree.MethodDefinition): void { + if (node.computed) { + this.#referencer.visit(node.key); + } + + if (node.value.type === AST_NODE_TYPES.FunctionExpression) { + this.visitMethodFunction(node.value, node); + } else { + this.#referencer.visit(node.value); + } + + if ('decorators' in node) { + node.decorators?.forEach(d => this.#referencer.visit(d)); + } + } + + protected visitType(node: TSESTree.Node | null | undefined): void { + if (!node) { + return; + } + 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 identifier: TSESTree.Identifier; + 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; + } + identifier = iter.left; + } else { + identifier = node.typeAnnotation.typeName; + } + + if (withDecorators) { + return this.#referencer + .currentScope() + .referenceDualValueType(identifier); + } + } + this.visitType(node); + } + + ///////////////////// + // Visit selectors // + ///////////////////// + + protected ClassProperty(node: TSESTree.ClassProperty): void { + this.visitClassProperty(node); + } + + protected MethodDefinition(node: TSESTree.MethodDefinition): void { + this.visitMethod(node); + } + + protected TSAbstractClassProperty( + node: TSESTree.TSAbstractClassProperty, + ): void { + this.visitClassProperty(node); + } + + protected TSAbstractMethodDefinition( + node: TSESTree.TSAbstractMethodDefinition, + ): void { + this.visitProperty(node); + } + + protected Identifier(node: TSESTree.Identifier): void { + this.#referencer.visit(node); + } +} + +/** + * Only if key is one of [identifier, string, number], ts will combine metadata of accessors . + * class A { + * get a() {} + * set ['a'](v: Type) {} + * + * get [1]() {} + * set [1](v: Type) {} + * + * // Following won't be combined + * get [key]() {} + * set [key](v: Type) {} + * + * get [true]() {} + * set [true](v: Type) {} + * + * get ['a'+'b']() {} + * set ['a'+'b']() {} + * } + */ +function getLiteralMethodKeyName( + node: TSESTree.MethodDefinition, +): string | number | null { + if (node.computed && node.key.type === AST_NODE_TYPES.Literal) { + if ( + typeof node.key.value === 'string' || + typeof node.key.value === 'number' + ) { + return node.key.value; + } + } else if (!node.computed && node.key.type === AST_NODE_TYPES.Identifier) { + return node.key.name; + } + return null; +} + +export { ClassVisitor }; diff --git a/packages/scope-manager/src/referencer/Referencer.ts b/packages/scope-manager/src/referencer/Referencer.ts index a36dcfc9c1a..b0e7557ee7e 100644 --- a/packages/scope-manager/src/referencer/Referencer.ts +++ b/packages/scope-manager/src/referencer/Referencer.ts @@ -1,4 +1,5 @@ import { AST_NODE_TYPES, Lib, TSESTree } from '@typescript-eslint/types'; +import { ClassVisitor } from './ClassVisitor'; import { ExportVisitor } from './ExportVisitor'; import { ImportVisitor } from './ImportVisitor'; import { PatternVisitor } from './PatternVisitor'; @@ -9,7 +10,6 @@ import { Visitor, VisitorOptions } from './Visitor'; import { assert } from '../assert'; import { CatchClauseDefinition, - ClassNameDefinition, FunctionNameDefinition, ImportBindingDefinition, ParameterDefinition, @@ -25,16 +25,17 @@ interface ReferencerOptions extends VisitorOptions { jsxPragma: string; jsxFragmentName: string | null; lib: Lib[]; + emitDecoratorMetadata: boolean; } // Referencing variables and creating bindings. class Referencer extends Visitor { - #isInnerMethodDefinition: boolean; #jsxPragma: string; #jsxFragmentName: string | null; #hasReferencedJsxFactory = false; #hasReferencedJsxFragmentFactory = false; #lib: Lib[]; + readonly #emitDecoratorMetadata: boolean; public readonly scopeManager: ScopeManager; constructor(options: ReferencerOptions, scopeManager: ScopeManager) { @@ -43,7 +44,7 @@ class Referencer extends Visitor { this.#jsxPragma = options.jsxPragma; this.#jsxFragmentName = options.jsxFragmentName; this.#lib = options.lib; - this.#isInnerMethodDefinition = false; + this.#emitDecoratorMetadata = options.emitDecoratorMetadata; } public currentScope(): Scope; @@ -63,22 +64,7 @@ class Referencer extends Visitor { } } - protected pushInnerMethodDefinition( - isInnerMethodDefinition: boolean, - ): boolean { - const previous = this.#isInnerMethodDefinition; - - this.#isInnerMethodDefinition = isInnerMethodDefinition; - return previous; - } - - protected popInnerMethodDefinition( - isInnerMethodDefinition: boolean | undefined, - ): void { - this.#isInnerMethodDefinition = !!isInnerMethodDefinition; - } - - protected referencingDefaultValue( + public referencingDefaultValue( pattern: TSESTree.Identifier, assignments: (TSESTree.AssignmentExpression | TSESTree.AssignmentPattern)[], maybeImplicitGlobal: ReferenceImplicitGlobal | null, @@ -162,44 +148,7 @@ class Referencer extends Visitor { protected visitClass( node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, ): void { - if (node.type === AST_NODE_TYPES.ClassDeclaration && node.id) { - this.currentScope().defineIdentifier( - node.id, - new ClassNameDefinition(node.id, node), - ); - } - - node.decorators?.forEach(d => this.visit(d)); - - this.scopeManager.nestClassScope(node); - - if (node.id) { - // define the class name again inside the new scope - // references to the class should not resolve directly to the parent class - this.currentScope().defineIdentifier( - node.id, - new ClassNameDefinition(node.id, node), - ); - } - - this.visit(node.superClass); - - // visit the type param declarations - this.visitType(node.typeParameters); - // then the usages - this.visitType(node.superTypeParameters); - node.implements?.forEach(imp => this.visitType(imp)); - - this.visit(node.body); - - this.close(node); - } - - protected visitClassProperty( - node: TSESTree.TSAbstractClassProperty | TSESTree.ClassProperty, - ): void { - this.visitProperty(node); - this.visitType(node.typeAnnotation); + ClassVisitor.visit(this, node, this.#emitDecoratorMetadata); } protected visitForIn( @@ -296,7 +245,7 @@ class Referencer extends Visitor { } // Consider this function is in the MethodDefinition. - this.scopeManager.nestFunctionScope(node, this.#isInnerMethodDefinition); + this.scopeManager.nestFunctionScope(node, false); // Process parameter declarations. for (const param of node.params) { @@ -333,32 +282,12 @@ class Referencer extends Visitor { this.close(node); } - protected visitProperty( - node: - | TSESTree.ClassProperty - | TSESTree.MethodDefinition - | TSESTree.Property - | TSESTree.TSAbstractClassProperty - | TSESTree.TSAbstractMethodDefinition, - ): void { - let previous; - + protected visitProperty(node: TSESTree.Property): void { if (node.computed) { this.visit(node.key); } - const isMethodDefinition = node.type === AST_NODE_TYPES.MethodDefinition; - if (isMethodDefinition) { - previous = this.pushInnerMethodDefinition(true); - } this.visit(node.value); - if (isMethodDefinition) { - this.popInnerMethodDefinition(previous); - } - - if ('decorators' in node) { - node.decorators?.forEach(d => this.visit(d)); - } } protected visitType(node: TSESTree.Node | null | undefined): void { @@ -487,10 +416,6 @@ class Referencer extends Visitor { this.visitClass(node); } - protected ClassProperty(node: TSESTree.ClassProperty): void { - this.visitClassProperty(node); - } - protected ContinueStatement(): void { // don't reference the continue statement's label } @@ -622,10 +547,6 @@ class Referencer extends Visitor { // meta properties all builtin globals } - protected MethodDefinition(node: TSESTree.MethodDefinition): void { - this.visitProperty(node); - } - protected NewExpression(node: TSESTree.NewExpression): void { this.visitChildren(node, ['typeParameters']); this.visitType(node.typeParameters); @@ -682,18 +603,6 @@ class Referencer extends Visitor { this.visitType(node.typeParameters); } - protected TSAbstractClassProperty( - node: TSESTree.TSAbstractClassProperty, - ): void { - this.visitClassProperty(node); - } - - protected TSAbstractMethodDefinition( - node: TSESTree.TSAbstractMethodDefinition, - ): void { - this.visitProperty(node); - } - protected TSAsExpression(node: TSESTree.TSAsExpression): void { this.visitTypeAssertion(node); } diff --git a/packages/scope-manager/src/referencer/TypeVisitor.ts b/packages/scope-manager/src/referencer/TypeVisitor.ts index 75b5f614e32..155aea5c5d9 100644 --- a/packages/scope-manager/src/referencer/TypeVisitor.ts +++ b/packages/scope-manager/src/referencer/TypeVisitor.ts @@ -260,6 +260,11 @@ class TypeVisitor extends Visitor { this.#referencer.currentScope().referenceValue(expr); } } + + protected TSTypeAnnotation(node: TSESTree.TSTypeAnnotation): void { + // check + this.visitChildren(node); + } } export { TypeVisitor }; diff --git a/packages/scope-manager/tests/eslint-scope/references.test.ts b/packages/scope-manager/tests/eslint-scope/references.test.ts index 573d0ce84b3..5f07dd671e9 100644 --- a/packages/scope-manager/tests/eslint-scope/references.test.ts +++ b/packages/scope-manager/tests/eslint-scope/references.test.ts @@ -540,4 +540,119 @@ 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]; + 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 08eea4f4995..69833caa7fa 100644 --- a/packages/scope-manager/tests/fixtures.test.ts +++ b/packages/scope-manager/tests/fixtures.test.ts @@ -44,6 +44,7 @@ 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 new file mode 100644 index 00000000000..41c40b28770 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts @@ -0,0 +1,23 @@ +//// @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 new file mode 100644 index 00000000000..f535f8e61be --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot @@ -0,0 +1,470 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`class emit-metadata accessor-deco 1`] = ` +ScopeManager { + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: Array [ + FunctionNameDefinition$1 { + name: Identifier<"deco">, + node: FunctionDeclaration$1, + }, + ], + name: "deco", + references: Array [ + 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: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$4 { + defs: Array [ + ParameterDefinition$2 { + name: Identifier<"param">, + node: FunctionDeclaration$1, + }, + ], + name: "param", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$5 { + defs: Array [ + ClassNameDefinition$3 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [ + 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: Array [ + ClassNameDefinition$4 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [ + VariableDefinition$5 { + name: Identifier<"keyName">, + node: VariableDeclarator$3, + }, + ], + name: "keyName", + references: Array [ + 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: Array [ + ClassNameDefinition$6 { + name: Identifier<"A">, + node: ClassDeclaration$5, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$9 { + defs: Array [ + ClassNameDefinition$7 { + name: Identifier<"A">, + node: ClassDeclaration$5, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$10 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$11 { + defs: Array [ + ParameterDefinition$8 { + name: Identifier<"b">, + node: FunctionExpression$6, + }, + ], + name: "b", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$12 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$13 { + defs: Array [ + ParameterDefinition$9 { + name: Identifier<"a">, + node: FunctionExpression$7, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$14 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$15 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$16 { + defs: Array [ + ParameterDefinition$10 { + name: Identifier<"a">, + node: FunctionExpression$8, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$17 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$18 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$19 { + defs: Array [ + ParameterDefinition$11 { + name: Identifier<"a">, + node: FunctionExpression$9, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$20 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$10, + isStrict: false, + references: Array [ + Reference$1, + ], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "deco" => Variable$2, + "T" => Variable$5, + "keyName" => Variable$7, + "A" => Variable$8, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + Variable$5, + Variable$7, + Variable$8, + ], + }, + FunctionScope$2 { + block: FunctionDeclaration$1, + isStrict: false, + references: Array [], + set: Map { + "arguments" => Variable$3, + "param" => Variable$4, + }, + type: "function", + upper: GlobalScope$1, + variables: Array [ + Variable$3, + Variable$4, + ], + }, + ClassScope$3 { + block: ClassDeclaration$2, + isStrict: true, + references: Array [], + set: Map { + "T" => Variable$6, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$6, + ], + }, + ClassScope$4 { + block: ClassDeclaration$5, + isStrict: true, + references: Array [ + Reference$3, + Reference$5, + Reference$7, + Reference$8, + Reference$10, + Reference$11, + ], + set: Map { + "A" => Variable$9, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$9, + ], + }, + FunctionScope$5 { + block: FunctionExpression$6, + isStrict: true, + references: Array [ + Reference$2, + ], + set: Map { + "arguments" => Variable$10, + "b" => Variable$11, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$10, + Variable$11, + ], + }, + FunctionScope$6 { + block: FunctionExpression$7, + isStrict: true, + references: Array [ + Reference$4, + ], + set: Map { + "arguments" => Variable$12, + "a" => Variable$13, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$12, + Variable$13, + ], + }, + FunctionScope$7 { + block: FunctionExpression$11, + isStrict: true, + references: Array [], + set: Map { + "arguments" => Variable$14, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$14, + ], + }, + FunctionScope$8 { + block: FunctionExpression$8, + isStrict: true, + references: Array [ + Reference$6, + ], + set: Map { + "arguments" => Variable$15, + "a" => Variable$16, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$15, + Variable$16, + ], + }, + FunctionScope$9 { + block: FunctionExpression$12, + isStrict: true, + references: Array [], + set: Map { + "arguments" => Variable$17, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$17, + ], + }, + FunctionScope$10 { + block: FunctionExpression$9, + isStrict: true, + references: Array [ + Reference$9, + ], + set: Map { + "arguments" => Variable$18, + "a" => Variable$19, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$18, + Variable$19, + ], + }, + FunctionScope$11 { + block: FunctionExpression$13, + isStrict: true, + references: Array [], + set: Map { + "arguments" => Variable$20, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + 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 new file mode 100644 index 00000000000..ab030a5b0b9 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts @@ -0,0 +1,11 @@ +//// @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 new file mode 100644 index 00000000000..74707c85f35 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts.shot @@ -0,0 +1,291 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`class emit-metadata method-deco 1`] = ` +ScopeManager { + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: Array [ + FunctionNameDefinition$1 { + name: Identifier<"deco">, + node: FunctionDeclaration$1, + }, + ], + name: "deco", + references: Array [ + Reference$6 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$3 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$4 { + defs: Array [ + ParameterDefinition$2 { + name: Identifier<"param">, + node: FunctionDeclaration$1, + }, + ], + name: "param", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$5 { + defs: Array [ + ClassNameDefinition$3 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [ + 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: Array [ + ClassNameDefinition$4 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [ + ClassNameDefinition$5 { + name: Identifier<"A">, + node: ClassDeclaration$3, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$8 { + defs: Array [ + ClassNameDefinition$6 { + name: Identifier<"A">, + node: ClassDeclaration$3, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$9 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$10 { + defs: Array [ + ParameterDefinition$7 { + name: Identifier<"a">, + node: FunctionExpression$4, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$11 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$12 { + defs: Array [ + ParameterDefinition$8 { + name: Identifier<"a">, + node: FunctionExpression$5, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$13 { + defs: Array [ + ParameterDefinition$9 { + name: Identifier<"b">, + node: FunctionExpression$5, + }, + ], + name: "b", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$6, + isStrict: false, + references: Array [], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "deco" => Variable$2, + "T" => Variable$5, + "A" => Variable$7, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + Variable$5, + Variable$7, + ], + }, + FunctionScope$2 { + block: FunctionDeclaration$1, + isStrict: false, + references: Array [], + set: Map { + "arguments" => Variable$3, + "param" => Variable$4, + }, + type: "function", + upper: GlobalScope$1, + variables: Array [ + Variable$3, + Variable$4, + ], + }, + ClassScope$3 { + block: ClassDeclaration$2, + isStrict: true, + references: Array [], + set: Map { + "T" => Variable$6, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$6, + ], + }, + ClassScope$4 { + block: ClassDeclaration$3, + isStrict: true, + references: Array [ + Reference$6, + ], + set: Map { + "A" => Variable$8, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$8, + ], + }, + FunctionScope$5 { + block: FunctionExpression$4, + isStrict: true, + references: Array [ + Reference$1, + Reference$2, + ], + set: Map { + "arguments" => Variable$9, + "a" => Variable$10, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$9, + Variable$10, + ], + }, + FunctionScope$6 { + block: FunctionExpression$5, + isStrict: true, + references: Array [ + Reference$3, + Reference$4, + Reference$5, + ], + set: Map { + "arguments" => Variable$11, + "a" => Variable$12, + "b" => Variable$13, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$11, + Variable$12, + Variable$13, + ], + }, + ], +} +`; 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 new file mode 100644 index 00000000000..39fa3c40232 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts @@ -0,0 +1,15 @@ +//// @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 new file mode 100644 index 00000000000..01686cb2884 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts.shot @@ -0,0 +1,298 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`class emit-metadata nested-class-both 1`] = ` +ScopeManager { + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: Array [ + FunctionNameDefinition$1 { + name: Identifier<"deco">, + node: FunctionDeclaration$1, + }, + ], + name: "deco", + references: Array [ + 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: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$4 { + defs: Array [ + ParameterDefinition$2 { + name: Identifier<"param">, + node: FunctionDeclaration$1, + }, + ], + name: "param", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$5 { + defs: Array [ + ClassNameDefinition$3 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [ + 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: Array [ + ClassNameDefinition$4 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [ + ClassNameDefinition$5 { + name: Identifier<"A">, + node: ClassDeclaration$3, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$8 { + defs: Array [ + ClassNameDefinition$6 { + name: Identifier<"A">, + node: ClassDeclaration$3, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$9 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$10 { + defs: Array [ + ParameterDefinition$7 { + name: Identifier<"foo">, + node: FunctionExpression$4, + }, + ], + name: "foo", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$11 { + defs: Array [ + ClassNameDefinition$8 { + name: Identifier<"B">, + node: ClassDeclaration$5, + }, + ], + name: "B", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$12 { + defs: Array [ + ClassNameDefinition$9 { + name: Identifier<"B">, + node: ClassDeclaration$5, + }, + ], + name: "B", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$13 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$14 { + defs: Array [ + ParameterDefinition$10 { + name: Identifier<"bar">, + node: FunctionExpression$6, + }, + ], + name: "bar", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$7, + isStrict: false, + references: Array [ + Reference$1, + ], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "deco" => Variable$2, + "T" => Variable$5, + "A" => Variable$7, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + Variable$5, + Variable$7, + ], + }, + FunctionScope$2 { + block: FunctionDeclaration$1, + isStrict: false, + references: Array [], + set: Map { + "arguments" => Variable$3, + "param" => Variable$4, + }, + type: "function", + upper: GlobalScope$1, + variables: Array [ + Variable$3, + Variable$4, + ], + }, + ClassScope$3 { + block: ClassDeclaration$2, + isStrict: true, + references: Array [], + set: Map { + "T" => Variable$6, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$6, + ], + }, + ClassScope$4 { + block: ClassDeclaration$3, + isStrict: true, + references: Array [], + set: Map { + "A" => Variable$8, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$8, + ], + }, + FunctionScope$5 { + block: FunctionExpression$4, + isStrict: true, + references: Array [ + Reference$2, + Reference$3, + ], + set: Map { + "arguments" => Variable$9, + "foo" => Variable$10, + "B" => Variable$11, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$9, + Variable$10, + Variable$11, + ], + }, + ClassScope$6 { + block: ClassDeclaration$5, + isStrict: true, + references: Array [], + set: Map { + "B" => Variable$12, + }, + type: "class", + upper: FunctionScope$5, + variables: Array [ + Variable$12, + ], + }, + FunctionScope$7 { + block: FunctionExpression$6, + isStrict: true, + references: Array [ + Reference$4, + ], + set: Map { + "arguments" => Variable$13, + "bar" => Variable$14, + }, + type: "function", + upper: ClassScope$6, + variables: Array [ + 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 new file mode 100644 index 00000000000..d2fd5be9021 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts @@ -0,0 +1,14 @@ +//// @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 new file mode 100644 index 00000000000..963402a9036 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts.shot @@ -0,0 +1,288 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`class emit-metadata nested-class-inner 1`] = ` +ScopeManager { + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: Array [ + FunctionNameDefinition$1 { + name: Identifier<"deco">, + node: FunctionDeclaration$1, + }, + ], + name: "deco", + references: Array [ + Reference$2 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$3 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$4 { + defs: Array [ + ParameterDefinition$2 { + name: Identifier<"param">, + node: FunctionDeclaration$1, + }, + ], + name: "param", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$5 { + defs: Array [ + ClassNameDefinition$3 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [ + 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: Array [ + ClassNameDefinition$4 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [ + ClassNameDefinition$5 { + name: Identifier<"A">, + node: ClassDeclaration$3, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$8 { + defs: Array [ + ClassNameDefinition$6 { + name: Identifier<"A">, + node: ClassDeclaration$3, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$9 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$10 { + defs: Array [ + ParameterDefinition$7 { + name: Identifier<"foo">, + node: FunctionExpression$4, + }, + ], + name: "foo", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$11 { + defs: Array [ + ClassNameDefinition$8 { + name: Identifier<"B">, + node: ClassDeclaration$5, + }, + ], + name: "B", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$12 { + defs: Array [ + ClassNameDefinition$9 { + name: Identifier<"B">, + node: ClassDeclaration$5, + }, + ], + name: "B", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$13 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$14 { + defs: Array [ + ParameterDefinition$10 { + name: Identifier<"bar">, + node: FunctionExpression$6, + }, + ], + name: "bar", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$7, + isStrict: false, + references: Array [], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "deco" => Variable$2, + "T" => Variable$5, + "A" => Variable$7, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + Variable$5, + Variable$7, + ], + }, + FunctionScope$2 { + block: FunctionDeclaration$1, + isStrict: false, + references: Array [], + set: Map { + "arguments" => Variable$3, + "param" => Variable$4, + }, + type: "function", + upper: GlobalScope$1, + variables: Array [ + Variable$3, + Variable$4, + ], + }, + ClassScope$3 { + block: ClassDeclaration$2, + isStrict: true, + references: Array [], + set: Map { + "T" => Variable$6, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$6, + ], + }, + ClassScope$4 { + block: ClassDeclaration$3, + isStrict: true, + references: Array [], + set: Map { + "A" => Variable$8, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$8, + ], + }, + FunctionScope$5 { + block: FunctionExpression$4, + isStrict: true, + references: Array [ + Reference$1, + Reference$2, + ], + set: Map { + "arguments" => Variable$9, + "foo" => Variable$10, + "B" => Variable$11, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$9, + Variable$10, + Variable$11, + ], + }, + ClassScope$6 { + block: ClassDeclaration$5, + isStrict: true, + references: Array [], + set: Map { + "B" => Variable$12, + }, + type: "class", + upper: FunctionScope$5, + variables: Array [ + Variable$12, + ], + }, + FunctionScope$7 { + block: FunctionExpression$6, + isStrict: true, + references: Array [ + Reference$3, + ], + set: Map { + "arguments" => Variable$13, + "bar" => Variable$14, + }, + type: "function", + upper: ClassScope$6, + variables: Array [ + 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 new file mode 100644 index 00000000000..2f5247067bf --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts @@ -0,0 +1,14 @@ +//// @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 new file mode 100644 index 00000000000..5db83db9116 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts.shot @@ -0,0 +1,289 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`class emit-metadata nested-class-outer 1`] = ` +ScopeManager { + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: Array [ + FunctionNameDefinition$1 { + name: Identifier<"deco">, + node: FunctionDeclaration$1, + }, + ], + name: "deco", + references: Array [ + Reference$1 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$3 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$4 { + defs: Array [ + ParameterDefinition$2 { + name: Identifier<"param">, + node: FunctionDeclaration$1, + }, + ], + name: "param", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$5 { + defs: Array [ + ClassNameDefinition$3 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [ + 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: Array [ + ClassNameDefinition$4 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [ + ClassNameDefinition$5 { + name: Identifier<"A">, + node: ClassDeclaration$3, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$8 { + defs: Array [ + ClassNameDefinition$6 { + name: Identifier<"A">, + node: ClassDeclaration$3, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$9 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$10 { + defs: Array [ + ParameterDefinition$7 { + name: Identifier<"foo">, + node: FunctionExpression$4, + }, + ], + name: "foo", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$11 { + defs: Array [ + ClassNameDefinition$8 { + name: Identifier<"B">, + node: ClassDeclaration$5, + }, + ], + name: "B", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$12 { + defs: Array [ + ClassNameDefinition$9 { + name: Identifier<"B">, + node: ClassDeclaration$5, + }, + ], + name: "B", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$13 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$14 { + defs: Array [ + ParameterDefinition$10 { + name: Identifier<"bar">, + node: FunctionExpression$6, + }, + ], + name: "bar", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$7, + isStrict: false, + references: Array [ + Reference$1, + ], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "deco" => Variable$2, + "T" => Variable$5, + "A" => Variable$7, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + Variable$5, + Variable$7, + ], + }, + FunctionScope$2 { + block: FunctionDeclaration$1, + isStrict: false, + references: Array [], + set: Map { + "arguments" => Variable$3, + "param" => Variable$4, + }, + type: "function", + upper: GlobalScope$1, + variables: Array [ + Variable$3, + Variable$4, + ], + }, + ClassScope$3 { + block: ClassDeclaration$2, + isStrict: true, + references: Array [], + set: Map { + "T" => Variable$6, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$6, + ], + }, + ClassScope$4 { + block: ClassDeclaration$3, + isStrict: true, + references: Array [], + set: Map { + "A" => Variable$8, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$8, + ], + }, + FunctionScope$5 { + block: FunctionExpression$4, + isStrict: true, + references: Array [ + Reference$2, + ], + set: Map { + "arguments" => Variable$9, + "foo" => Variable$10, + "B" => Variable$11, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$9, + Variable$10, + Variable$11, + ], + }, + ClassScope$6 { + block: ClassDeclaration$5, + isStrict: true, + references: Array [], + set: Map { + "B" => Variable$12, + }, + type: "class", + upper: FunctionScope$5, + variables: Array [ + Variable$12, + ], + }, + FunctionScope$7 { + block: FunctionExpression$6, + isStrict: true, + references: Array [ + Reference$3, + ], + set: Map { + "arguments" => Variable$13, + "bar" => Variable$14, + }, + type: "function", + upper: ClassScope$6, + variables: Array [ + 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 new file mode 100644 index 00000000000..fc0eb3c0525 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts @@ -0,0 +1,14 @@ +//// @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 new file mode 100644 index 00000000000..ac1f8b41de5 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts.shot @@ -0,0 +1,344 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`class emit-metadata parameters-deco 1`] = ` +ScopeManager { + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: Array [ + FunctionNameDefinition$1 { + name: Identifier<"deco">, + node: FunctionDeclaration$1, + }, + ], + name: "deco", + references: Array [ + 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: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$4 { + defs: Array [ + ParameterDefinition$2 { + name: Identifier<"param">, + node: FunctionDeclaration$1, + }, + ], + name: "param", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$5 { + defs: Array [ + ClassNameDefinition$3 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [ + 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: Array [ + ClassNameDefinition$4 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [ + ClassNameDefinition$5 { + name: Identifier<"A">, + node: ClassDeclaration$3, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$8 { + defs: Array [ + ClassNameDefinition$6 { + name: Identifier<"A">, + node: ClassDeclaration$3, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$9 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$10 { + defs: Array [ + ParameterDefinition$7 { + name: Identifier<"foo">, + node: FunctionExpression$4, + }, + ], + name: "foo", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$11 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$12 { + defs: Array [ + ParameterDefinition$8 { + name: Identifier<"a">, + node: FunctionExpression$5, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$13 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$14 { + defs: Array [ + ParameterDefinition$9 { + name: Identifier<"a">, + node: FunctionExpression$6, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$15 { + defs: Array [ + ParameterDefinition$10 { + name: Identifier<"b">, + node: FunctionExpression$6, + }, + ], + name: "b", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$7, + isStrict: false, + references: Array [ + Reference$1, + ], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "deco" => Variable$2, + "T" => Variable$5, + "A" => Variable$7, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + Variable$5, + Variable$7, + ], + }, + FunctionScope$2 { + block: FunctionDeclaration$1, + isStrict: false, + references: Array [], + set: Map { + "arguments" => Variable$3, + "param" => Variable$4, + }, + type: "function", + upper: GlobalScope$1, + variables: Array [ + Variable$3, + Variable$4, + ], + }, + ClassScope$3 { + block: ClassDeclaration$2, + isStrict: true, + references: Array [], + set: Map { + "T" => Variable$6, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$6, + ], + }, + ClassScope$4 { + block: ClassDeclaration$3, + isStrict: true, + references: Array [], + set: Map { + "A" => Variable$8, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$8, + ], + }, + FunctionScope$5 { + block: FunctionExpression$4, + isStrict: true, + references: Array [ + Reference$2, + Reference$3, + ], + set: Map { + "arguments" => Variable$9, + "foo" => Variable$10, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$9, + Variable$10, + ], + }, + FunctionScope$6 { + block: FunctionExpression$5, + isStrict: true, + references: Array [ + Reference$4, + Reference$5, + ], + set: Map { + "arguments" => Variable$11, + "a" => Variable$12, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$11, + Variable$12, + ], + }, + FunctionScope$7 { + block: FunctionExpression$6, + isStrict: true, + references: Array [ + Reference$6, + Reference$7, + Reference$8, + ], + set: Map { + "arguments" => Variable$13, + "a" => Variable$14, + "b" => Variable$15, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + 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 new file mode 100644 index 00000000000..a4c17dcaa5d --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts @@ -0,0 +1,13 @@ +//// @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 new file mode 100644 index 00000000000..348c6009163 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts.shot @@ -0,0 +1,205 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`class emit-metadata property-deco 1`] = ` +ScopeManager { + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: Array [ + FunctionNameDefinition$1 { + name: Identifier<"deco">, + node: FunctionDeclaration$1, + }, + ], + name: "deco", + references: Array [ + Reference$2 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$3 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$4 { + defs: Array [ + ParameterDefinition$2 { + name: Identifier<"param">, + node: FunctionDeclaration$1, + }, + ], + name: "param", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$5 { + defs: Array [ + TSModuleNameDefinition$3 { + name: Identifier<"a">, + node: TSModuleDeclaration$2, + }, + ], + name: "a", + references: Array [ + 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: Array [ + ClassNameDefinition$4 { + name: Identifier<"B">, + node: ClassDeclaration$3, + }, + ], + name: "B", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [ + ClassNameDefinition$5 { + name: Identifier<"B">, + node: ClassDeclaration$3, + }, + ], + name: "B", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$8 { + defs: Array [ + ClassNameDefinition$6 { + name: Identifier<"A">, + node: ClassDeclaration$4, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$9 { + defs: Array [ + ClassNameDefinition$7 { + name: Identifier<"A">, + node: ClassDeclaration$4, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$5, + isStrict: false, + references: Array [], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "deco" => Variable$2, + "a" => Variable$5, + "A" => Variable$8, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + Variable$5, + Variable$8, + ], + }, + FunctionScope$2 { + block: FunctionDeclaration$1, + isStrict: false, + references: Array [], + set: Map { + "arguments" => Variable$3, + "param" => Variable$4, + }, + type: "function", + upper: GlobalScope$1, + variables: Array [ + Variable$3, + Variable$4, + ], + }, + TSModuleScope$3 { + block: TSModuleDeclaration$2, + isStrict: true, + references: Array [], + set: Map { + "B" => Variable$6, + }, + type: "tsModule", + upper: GlobalScope$1, + variables: Array [ + Variable$6, + ], + }, + ClassScope$4 { + block: ClassDeclaration$3, + isStrict: true, + references: Array [], + set: Map { + "B" => Variable$7, + }, + type: "class", + upper: TSModuleScope$3, + variables: Array [ + Variable$7, + ], + }, + ClassScope$5 { + block: ClassDeclaration$4, + isStrict: true, + references: Array [ + Reference$1, + Reference$2, + Reference$3, + ], + set: Map { + "A" => Variable$9, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$9, + ], + }, + ], +} +`;