From ed33caae9b93926c437c08a339b459d91bc5aee5 Mon Sep 17 00:00:00 2001 From: frezc Date: Tue, 10 Nov 2020 01:13:57 +0800 Subject: [PATCH 1/6] fix(eslint-plugin): fix value import in decorator metadata --- .../docs/rules/consistent-type-imports.md | 2 +- .../src/rules/consistent-type-imports.ts | 654 +++++++++++++----- .../tests/fixtures/tsconfig-withmeta.json | 6 + .../rules/consistent-type-imports.test.ts | 231 ++++++- 4 files changed, 726 insertions(+), 167 deletions(-) create mode 100644 packages/eslint-plugin/tests/fixtures/tsconfig-withmeta.json 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 830c7b6669a..0c2f524dd6b 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,51 @@ function isTypeToken( ): token is TSESTree.IdentifierToken & { value: 'type' } { return token.type === AST_TOKEN_TYPES.Identifier && token.value === 'type'; } + +/** + * 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; +} + type MessageIds = | 'typeOverValue' | 'someImportsAreOnlyTypes' | 'aImportIsOnlyTypes' | 'valueOverType' - | 'noImportTypeAnnotations'; + | 'noImportTypeAnnotations' + | 'someImportsInDecoMeta' + | 'aImportInDecoMeta'; export default util.createRule({ name: 'consistent-type-imports', meta: { @@ -59,6 +100,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.', }, @@ -90,6 +135,9 @@ export default util.createRule({ const prefer = option.prefer ?? 'type-imports'; const disallowTypeAnnotations = option.disallowTypeAnnotations !== false; const sourceCode = context.getSourceCode(); + const emitDecoratorMetadata = util + .getParserServices(context, true) + .program.getCompilerOptions().emitDecoratorMetadata; const sourceImportsMap: { [key: string]: SourceImports } = {}; @@ -105,6 +153,7 @@ export default util.createRule({ source, reportValueImports: [], typeOnlyNamedImport: null, + valueOnlyNamedImport: null, }); if (node.importKind === 'type') { if ( @@ -116,9 +165,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 +187,14 @@ export default util.createRule({ } else { const onlyHasTypeReferences = variable.references.every( ref => { + if (ref.isValueReference && ref.isTypeReference) { + /** + * keep origin import kind when export + * export { Type } + * export default Type; + */ + 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 @@ -146,6 +212,139 @@ export default util.createRule({ return false; } + // https://github.com/typescript-eslint/typescript-eslint/issues/2559#issuecomment-692882427 + if (emitDecoratorMetadata && ref.isTypeReference) { + let parent = ref.identifier.parent; + /** + * import * as foo from 'foo'; + * foo.Foo // <--- check this + */ + if (parent?.type === AST_NODE_TYPES.TSQualifiedName) { + parent = parent.parent; + } + if (parent) { + if ( + parent.type === AST_NODE_TYPES.TSTypeReference && + parent.parent?.type === + AST_NODE_TYPES.TSTypeAnnotation + ) { + const annotationParent = parent.parent.parent; + + /** + * class A { + * @meta // <--- check this + * foo: Type; + * } + */ + if ( + annotationParent?.type === + AST_NODE_TYPES.ClassProperty && + annotationParent.decorators + ) { + return false; + } + + let functionNode = annotationParent; + if ( + annotationParent?.type === + AST_NODE_TYPES.Identifier + ) { + /** + * TODO: + * + * I don't think this is valid, but there are no ts errors and no metadata emitted now. + * https://github.com/microsoft/TypeScript/issues/41354 + * + * class A { + * set foo(@meta a: Type) {} + * } + */ + + /** + * class A { + * foo( + * @meta // <--- check this + * a: Type + * ) {} + * } + */ + if (annotationParent.decorators) { + return false; + } + + functionNode = annotationParent.parent; + } + + if ( + functionNode?.type === + AST_NODE_TYPES.FunctionExpression && + functionNode.parent?.type === + AST_NODE_TYPES.MethodDefinition + ) { + const methodNode = functionNode.parent; + /** + * class A { + * @meta // <--- check this + * foo(a: Type) {} + * + * @meta // <--- and this + * foo(): Type {} + * } + */ + if (methodNode.decorators) { + return false; + } + + if (methodNode.kind === 'set') { + const keyName = getLiteralMethodKeyName( + methodNode, + ); + + /** + * class A { + * @meta // <--- check this + * get a() {} + * set ['a'](v: Type) {} + * } + */ + if ( + keyName && + methodNode.parent?.type === + AST_NODE_TYPES.ClassBody && + methodNode.parent.body.find( + (node): node is TSESTree.MethodDefinition => + node.type === + AST_NODE_TYPES.MethodDefinition && + // Node must both be static or not + node.static === methodNode.static && + getLiteralMethodKeyName(node) === keyName, + )?.decorators + ) { + return false; + } + } + + /** + * @meta // <--- check this + * class A { + * constructor(a: Type) {} + * } + */ + if ( + methodNode.kind === 'constructor' && + methodNode.parent?.type === + AST_NODE_TYPES.ClassBody && + methodNode.parent.parent?.type === + AST_NODE_TYPES.ClassDeclaration && + methodNode.parent.parent.decorators + ) { + return false; + } + } + } + } + } + return ref.isTypeReference; }, ); @@ -157,7 +356,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 +387,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 +457,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] @@ -259,6 +476,169 @@ 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[]; + } { + 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) { // e.g. @@ -293,33 +673,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; @@ -365,126 +730,6 @@ export default util.createRule({ yield* fixesNamedSpecifiers.removeTypeNamedSpecifiers; yield* afterFixes; - - /** - * Returns information for fixing named specifiers. - */ - function getFixesNamedSpecifiers( - typeNamedSpecifiers: TSESTree.ImportSpecifier[], - allNamedSpecifiers: TSESTree.ImportSpecifier[], - ): { - typeNamedSpecifiersText: string; - removeTypeNamedSpecifiers: TSESLint.RuleFix[]; - } { - 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( @@ -546,10 +791,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( @@ -564,7 +888,7 @@ export default util.createRule({ ), util.NullThrowsReasons.MissingToken('type', node.type), ); - return fixer.remove(typeToken); + yield fixer.remove(typeToken); } }, }); 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 88d6db374a0..c106c913461 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: [ ` @@ -225,6 +230,99 @@ ruleTester.run('consistent-type-imports', rule, { jsxFragmentName: 'Fragment', }, }, + { + 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 * as foo from 'foo'; + @deco + class A { + constructor(foo: foo.Foo) {} + } + `, + parserOptions: withMetaParserOptions, + }, ], invalid: [ { @@ -1085,5 +1183,136 @@ import { Value4 } from 'default_and_named_import'; }, ], }, + { + 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: 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 type { Foo, Bar, T } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + foo(@deco bar: Bar) {} + } + `, + // eslint-disable-next-line @typescript-eslint/internal/plugin-test-formatting + output: ` + import { 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: 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, + }, ], }); From 5b7de66f8d61b7905f6dee951da2db4385760edb Mon Sep 17 00:00:00 2001 From: frezc Date: Wed, 11 Nov 2020 03:14:01 +0800 Subject: [PATCH 2/6] fix(eslint-plugin): test coverage --- .../rules/consistent-type-imports.test.ts | 71 ++++++++++++++++++- 1 file changed, 68 insertions(+), 3 deletions(-) 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 c106c913461..7c7ab5c6732 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -313,6 +313,19 @@ ruleTester.run('consistent-type-imports', rule, { `, 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'; @@ -1231,6 +1244,31 @@ import { Value4 } from 'default_and_named_import'; ], 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'; @@ -1262,6 +1300,7 @@ import { Value4 } from 'default_and_named_import'; }, { code: ` + import { V } from 'foo'; import type { Foo, Bar, T } from 'foo'; @deco class A { @@ -1269,10 +1308,9 @@ import { Value4 } from 'default_and_named_import'; foo(@deco bar: Bar) {} } `, - // eslint-disable-next-line @typescript-eslint/internal/plugin-test-formatting output: ` - import { Foo, Bar} from 'foo'; -import type { T } from 'foo'; + import { V , Foo, Bar} from 'foo'; + import type { T } from 'foo'; @deco class A { constructor(foo: Foo) {} @@ -1283,6 +1321,33 @@ import type { T } from 'foo'; { messageId: 'someImportsInDecoMeta', data: { typeImports: '"Foo" and "Bar"' }, + line: 3, + column: 9, + }, + ], + parserOptions: withMetaParserOptions, + }, + { + code: ` + import type { Foo, T } from 'foo'; + import { V } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + output: ` + import type { T } from 'foo'; + import { V , Foo} from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + errors: [ + { + messageId: 'aImportInDecoMeta', + data: { typeImports: '"Foo"' }, line: 2, column: 9, }, From e24d78de90bec4be359e45cce83cd4bfd3eff738 Mon Sep 17 00:00:00 2001 From: frezc Date: Sun, 13 Dec 2020 00:53:09 +0800 Subject: [PATCH 3/6] move logic --- .../src/rules/consistent-type-imports.ts | 193 ++---------------- packages/parser/src/parser.ts | 4 + packages/scope-manager/README.md | 6 + packages/scope-manager/jest.config.js | 6 +- packages/scope-manager/src/analyze.ts | 10 + .../src/referencer/Referencer.ts | 77 ++++++- .../src/referencer/TypeVisitor.ts | 172 ++++++++++++++++ .../tests/eslint-scope/references.test.ts | 99 +++++++++ 8 files changed, 379 insertions(+), 188 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index 0c2f524dd6b..d2cc36da602 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -42,42 +42,6 @@ function isTypeToken( return token.type === AST_TOKEN_TYPES.Identifier && token.value === 'type'; } -/** - * 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; -} - type MessageIds = | 'typeOverValue' | 'someImportsAreOnlyTypes' @@ -135,9 +99,6 @@ export default util.createRule({ const prefer = option.prefer ?? 'type-imports'; const disallowTypeAnnotations = option.disallowTypeAnnotations !== false; const sourceCode = context.getSourceCode(); - const emitDecoratorMetadata = util - .getParserServices(context, true) - .program.getCompilerOptions().emitDecoratorMetadata; const sourceImportsMap: { [key: string]: SourceImports } = {}; @@ -187,13 +148,20 @@ export default util.createRule({ } else { const onlyHasTypeReferences = variable.references.every( ref => { - if (ref.isValueReference && ref.isTypeReference) { - /** - * keep origin import kind when export - * export { Type } - * export default Type; - */ - return node.importKind === 'type'; + /** + * 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 @@ -212,139 +180,6 @@ export default util.createRule({ return false; } - // https://github.com/typescript-eslint/typescript-eslint/issues/2559#issuecomment-692882427 - if (emitDecoratorMetadata && ref.isTypeReference) { - let parent = ref.identifier.parent; - /** - * import * as foo from 'foo'; - * foo.Foo // <--- check this - */ - if (parent?.type === AST_NODE_TYPES.TSQualifiedName) { - parent = parent.parent; - } - if (parent) { - if ( - parent.type === AST_NODE_TYPES.TSTypeReference && - parent.parent?.type === - AST_NODE_TYPES.TSTypeAnnotation - ) { - const annotationParent = parent.parent.parent; - - /** - * class A { - * @meta // <--- check this - * foo: Type; - * } - */ - if ( - annotationParent?.type === - AST_NODE_TYPES.ClassProperty && - annotationParent.decorators - ) { - return false; - } - - let functionNode = annotationParent; - if ( - annotationParent?.type === - AST_NODE_TYPES.Identifier - ) { - /** - * TODO: - * - * I don't think this is valid, but there are no ts errors and no metadata emitted now. - * https://github.com/microsoft/TypeScript/issues/41354 - * - * class A { - * set foo(@meta a: Type) {} - * } - */ - - /** - * class A { - * foo( - * @meta // <--- check this - * a: Type - * ) {} - * } - */ - if (annotationParent.decorators) { - return false; - } - - functionNode = annotationParent.parent; - } - - if ( - functionNode?.type === - AST_NODE_TYPES.FunctionExpression && - functionNode.parent?.type === - AST_NODE_TYPES.MethodDefinition - ) { - const methodNode = functionNode.parent; - /** - * class A { - * @meta // <--- check this - * foo(a: Type) {} - * - * @meta // <--- and this - * foo(): Type {} - * } - */ - if (methodNode.decorators) { - return false; - } - - if (methodNode.kind === 'set') { - const keyName = getLiteralMethodKeyName( - methodNode, - ); - - /** - * class A { - * @meta // <--- check this - * get a() {} - * set ['a'](v: Type) {} - * } - */ - if ( - keyName && - methodNode.parent?.type === - AST_NODE_TYPES.ClassBody && - methodNode.parent.body.find( - (node): node is TSESTree.MethodDefinition => - node.type === - AST_NODE_TYPES.MethodDefinition && - // Node must both be static or not - node.static === methodNode.static && - getLiteralMethodKeyName(node) === keyName, - )?.decorators - ) { - return false; - } - } - - /** - * @meta // <--- check this - * class A { - * constructor(a: Type) {} - * } - */ - if ( - methodNode.kind === 'constructor' && - methodNode.parent?.type === - AST_NODE_TYPES.ClassBody && - methodNode.parent.parent?.type === - AST_NODE_TYPES.ClassDeclaration && - methodNode.parent.parent.decorators - ) { - return false; - } - } - } - } - } - return ref.isTypeReference; }, ); 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/jest.config.js b/packages/scope-manager/jest.config.js index 3e3bcb726ed..629629e0cf5 100644 --- a/packages/scope-manager/jest.config.js +++ b/packages/scope-manager/jest.config.js @@ -10,11 +10,11 @@ module.exports = { }, testEnvironment: 'node', transform: { - [/^.+\.tsx?$/.source]: 'ts-jest', + ['^.+\\.tsx?$']: 'ts-jest', }, testRegex: [ - /.\/tests\/.+\.test\.ts$/.source, - /.\/tests\/eslint-scope\/[^/]+\.test\.ts$/.source, + './tests/.+\\.test\\.ts$', + './tests/eslint-scope/[^/]+\\.test\\.ts$', ], collectCoverage: false, collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], 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/Referencer.ts b/packages/scope-manager/src/referencer/Referencer.ts index 56c974055bc..893f8abee13 100644 --- a/packages/scope-manager/src/referencer/Referencer.ts +++ b/packages/scope-manager/src/referencer/Referencer.ts @@ -25,6 +25,7 @@ interface ReferencerOptions extends VisitorOptions { jsxPragma: string; jsxFragmentName: string | null; lib: Lib[]; + emitDecoratorMetadata: boolean; } // Referencing variables and creating bindings. @@ -35,6 +36,8 @@ class Referencer extends Visitor { #hasReferencedJsxFactory = false; #hasReferencedJsxFragmentFactory = false; #lib: Lib[]; + #methodNode: TSESTree.MethodDefinition | null; + public readonly emitDecoratorMetadata: boolean; public readonly scopeManager: ScopeManager; constructor(options: ReferencerOptions, scopeManager: ScopeManager) { @@ -44,6 +47,8 @@ class Referencer extends Visitor { this.#jsxFragmentName = options.jsxFragmentName; this.#lib = options.lib; this.#isInnerMethodDefinition = false; + this.#methodNode = null; + this.emitDecoratorMetadata = options.emitDecoratorMetadata; } public currentScope(): Scope; @@ -63,6 +68,10 @@ class Referencer extends Visitor { } } + public currentMethodNode(): TSESTree.MethodDefinition | null { + return this.#methodNode; + } + protected pushInnerMethodDefinition( isInnerMethodDefinition: boolean, ): boolean { @@ -78,6 +87,21 @@ class Referencer extends Visitor { this.#isInnerMethodDefinition = !!isInnerMethodDefinition; } + protected pushMethodNode( + classPropertyNode: TSESTree.MethodDefinition, + ): TSESTree.MethodDefinition | null { + const previous = this.#methodNode; + + this.#methodNode = classPropertyNode; + return previous; + } + + protected popMethodNode( + classPropertyNode: TSESTree.MethodDefinition | null, + ): void { + this.#methodNode = classPropertyNode; + } + protected referencingDefaultValue( pattern: TSESTree.Identifier, assignments: (TSESTree.AssignmentExpression | TSESTree.AssignmentPattern)[], @@ -199,7 +223,7 @@ class Referencer extends Visitor { node: TSESTree.TSAbstractClassProperty | TSESTree.ClassProperty, ): void { this.visitProperty(node); - this.visitType(node.typeAnnotation); + this.visitMetadataType(node.typeAnnotation, !!node.decorators); } protected visitForIn( @@ -258,13 +282,22 @@ class Referencer extends Visitor { protected visitFunctionParameterTypeAnnotation( node: TSESTree.Parameter, + isMethod: boolean, ): void { if ('typeAnnotation' in node) { - this.visitType(node.typeAnnotation); + if (isMethod) { + this.visitMetadataType(node.typeAnnotation, !!node.decorators); + } else { + this.visitType(node.typeAnnotation); + } } else if (node.type === AST_NODE_TYPES.AssignmentPattern) { - this.visitType(node.left.typeAnnotation); + if (isMethod) { + this.visitMetadataType(node.left.typeAnnotation, !!node.decorators); + } else { + this.visitType(node.left.typeAnnotation); + } } else if (node.type === AST_NODE_TYPES.TSParameterProperty) { - this.visitFunctionParameterTypeAnnotation(node.parameter); + this.visitFunctionParameterTypeAnnotation(node.parameter, isMethod); } } protected visitFunction( @@ -298,6 +331,11 @@ class Referencer extends Visitor { // Consider this function is in the MethodDefinition. this.scopeManager.nestFunctionScope(node, this.#isInnerMethodDefinition); + // check current function node is method of class instead of class property that value is function + const isMethodNode = + this.#methodNode?.type === AST_NODE_TYPES.MethodDefinition && + this.#methodNode.value === node; + // Process parameter declarations. for (const param of node.params) { this.visitPattern( @@ -312,11 +350,15 @@ class Referencer extends Visitor { }, { processRightHandNodes: true }, ); - this.visitFunctionParameterTypeAnnotation(param); + this.visitFunctionParameterTypeAnnotation(param, isMethodNode); param.decorators?.forEach(d => this.visit(d)); } - this.visitType(node.returnType); + if (isMethodNode) { + this.visitMetadataType(node.returnType); + } else { + this.visitType(node.returnType); + } this.visitType(node.typeParameters); // In TypeScript there are a number of function-like constructs which have no body, @@ -342,6 +384,7 @@ class Referencer extends Visitor { | TSESTree.TSAbstractMethodDefinition, ): void { let previous; + let previousClassPropertyNode; if (node.computed) { this.visit(node.key); @@ -351,10 +394,16 @@ class Referencer extends Visitor { if (isMethodDefinition) { previous = this.pushInnerMethodDefinition(true); } + if (node.type === AST_NODE_TYPES.MethodDefinition) { + previousClassPropertyNode = this.pushMethodNode(node); + } this.visit(node.value); if (isMethodDefinition) { this.popInnerMethodDefinition(previous); } + if (previousClassPropertyNode !== undefined) { + this.popMethodNode(previousClassPropertyNode); + } if ('decorators' in node) { node.decorators?.forEach(d => this.visit(d)); @@ -368,6 +417,22 @@ class Referencer extends Visitor { TypeVisitor.visit(this, node); } + /** + * visit type annotation may be emitted by emitDecoratorsMetadata. + * @param node It's may be ClassPropertyType, FunctionParameterType and FunctionReturnType. + * @param withDecorators FunctionParameterType -> parameter decorators; ClassPropertyType -> property decorators; + */ + protected visitMetadataType( + node: TSESTree.TSTypeAnnotation | undefined, + withDecorators?: boolean, + ): void { + if (!node) { + return; + } + + TypeVisitor.visitAndCheckEmit(this, node, withDecorators); + } + protected visitTypeAssertion( node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, ): void { diff --git a/packages/scope-manager/src/referencer/TypeVisitor.ts b/packages/scope-manager/src/referencer/TypeVisitor.ts index 75b5f614e32..b8eacaacb6e 100644 --- a/packages/scope-manager/src/referencer/TypeVisitor.ts +++ b/packages/scope-manager/src/referencer/TypeVisitor.ts @@ -17,6 +17,15 @@ class TypeVisitor extends Visitor { typeReferencer.visit(node); } + static visitAndCheckEmit( + referencer: Referencer, + node: TSESTree.TSTypeAnnotation, + withDecorators?: boolean, + ): void { + const typeReferencer = new TypeVisitor(referencer); + typeReferencer.visitAndCheckDecoratorMetadata(node, withDecorators); + } + /////////////////// // Visit helpers // /////////////////// @@ -70,6 +79,128 @@ class TypeVisitor extends Visitor { this.#referencer.visit(node.key); } + /** + * check type is referenced by decorator metadata + */ + protected visitAndCheckDecoratorMetadata( + node: TSESTree.TSTypeAnnotation, + withDecorators?: boolean, + ): void { + // emit decorators metadata only work for TSTypeReference + if ( + node.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && + this.#referencer.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; + } + const scope = this.#referencer.currentScope(); + /** + * class A { + * @meta // <--- check this + * foo: Type; + * } + */ + if (scope.type === ScopeType.class && withDecorators) { + return this.#referencer + .currentScope() + .referenceDualValueType(identifier); + } + + const methodNode = this.#referencer.currentMethodNode(); + // in method + if (scope.upper?.type === ScopeType.class && methodNode) { + /** + * class A { + * @meta // <--- check this + * foo(a: Type) {} + * + * @meta // <--- check this + * foo(): Type {} + * } + */ + if (methodNode.decorators) { + return this.#referencer + .currentScope() + .referenceDualValueType(identifier); + } + + /** + * class A { + * foo( + * @meta // <--- check this + * a: Type + * ) {} + * + * set foo( + * @meta // <--- EXCEPT this. TS do nothing for this + * a: Type + * ) {} + * } + */ + if (withDecorators && methodNode.kind !== 'set') { + return this.#referencer + .currentScope() + .referenceDualValueType(identifier); + } + + if (methodNode.kind === 'set') { + const keyName = getLiteralMethodKeyName(methodNode); + const classNode = scope.upper.block; + + /** + * class A { + * @meta // <--- check this + * get a() {} + * set ['a'](v: Type) {} + * } + */ + if ( + keyName !== null && + 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 + ) { + return this.#referencer + .currentScope() + .referenceDualValueType(identifier); + } + } + + /** + * @meta // <--- check this + * class A { + * constructor(a: Type) {} + * } + */ + if ( + methodNode.kind === 'constructor' && + scope.upper.block.type === AST_NODE_TYPES.ClassDeclaration && + scope.upper.block.decorators + ) { + return this.#referencer + .currentScope() + .referenceDualValueType(identifier); + } + } + } + this.visit(node); + } + ///////////////////// // Visit selectors // ///////////////////// @@ -260,6 +391,47 @@ class TypeVisitor extends Visitor { this.#referencer.currentScope().referenceValue(expr); } } + + protected TSTypeAnnotation(node: TSESTree.TSTypeAnnotation): void { + // check + this.visitChildren(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 { 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..e01ad8cd1c1 100644 --- a/packages/scope-manager/tests/eslint-scope/references.test.ts +++ b/packages/scope-manager/tests/eslint-scope/references.test.ts @@ -540,4 +540,103 @@ 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) {} + + @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]() {} + } + `, + { + 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 funcParamTypeRef = classAScope.childScopes[1].references[0]; + expect(funcParamTypeRef.identifier.name).toBe('b'); + expect(funcParamTypeRef.isTypeReference).toBe(true); + expect(funcParamTypeRef.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 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 constructorParamTypeRef = classBScope.childScopes[0].references[0]; + expect(constructorParamTypeRef.identifier.name).toBe('c'); + expect(constructorParamTypeRef.isTypeReference).toBe(true); + expect(constructorParamTypeRef.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); + }); + }); }); From 843f072770d2684dbf825cd0d3d6d547492234c4 Mon Sep 17 00:00:00 2001 From: frezc Date: Sun, 13 Dec 2020 02:42:20 +0800 Subject: [PATCH 4/6] fix ci --- .../tests/rules/consistent-type-imports.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 8601903ac33..83d803cf380 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -1358,7 +1358,7 @@ const a: Default = ''; } `, output: noFormat` - import Foo from 'foo'; + import Foo from 'foo'; @deco class A { constructor(foo: Foo) {} @@ -1383,7 +1383,7 @@ const a: Default = ''; } `, output: noFormat` - import { Foo } from 'foo'; + import { Foo } from 'foo'; @deco class A { constructor(foo: Foo) {} @@ -1438,7 +1438,7 @@ const a: Default = ''; foo(@deco bar: Bar) {} } `, - output: ` + output: noFormat` import { V , Foo, Bar} from 'foo'; import type { T } from 'foo'; @deco @@ -1466,7 +1466,7 @@ const a: Default = ''; constructor(foo: Foo) {} } `, - output: ` + output: noFormat` import type { T } from 'foo'; import { V , Foo} from 'foo'; @deco @@ -1493,7 +1493,7 @@ const a: Default = ''; } `, output: noFormat` - import * as Type from 'foo'; + import * as Type from 'foo'; @deco class A { constructor(foo: Type.Foo) {} From 518d21a5a18411bae04087db4234f08bcad80da3 Mon Sep 17 00:00:00 2001 From: frezc Date: Sun, 3 Jan 2021 22:47:26 +0800 Subject: [PATCH 5/6] class visitor --- .../src/referencer/ClassVisitor.ts | 362 +++++++++ .../src/referencer/Referencer.ts | 180 +---- .../src/referencer/TypeVisitor.ts | 167 ---- .../tests/eslint-scope/references.test.ts | 34 +- packages/scope-manager/tests/fixtures.test.ts | 1 + .../class/declaration/emit-metadata.ts | 38 + .../class/declaration/emit-metadata.ts.shot | 759 ++++++++++++++++++ 7 files changed, 1197 insertions(+), 344 deletions(-) create mode 100644 packages/scope-manager/src/referencer/ClassVisitor.ts create mode 100644 packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts create mode 100644 packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts.shot 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 e2c661d8c1c..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, @@ -30,14 +30,12 @@ interface ReferencerOptions extends VisitorOptions { // Referencing variables and creating bindings. class Referencer extends Visitor { - #isInnerMethodDefinition: boolean; #jsxPragma: string; #jsxFragmentName: string | null; #hasReferencedJsxFactory = false; #hasReferencedJsxFragmentFactory = false; #lib: Lib[]; - #methodNode: TSESTree.MethodDefinition | null; - public readonly emitDecoratorMetadata: boolean; + readonly #emitDecoratorMetadata: boolean; public readonly scopeManager: ScopeManager; constructor(options: ReferencerOptions, scopeManager: ScopeManager) { @@ -46,9 +44,7 @@ class Referencer extends Visitor { this.#jsxPragma = options.jsxPragma; this.#jsxFragmentName = options.jsxFragmentName; this.#lib = options.lib; - this.#isInnerMethodDefinition = false; - this.#methodNode = null; - this.emitDecoratorMetadata = options.emitDecoratorMetadata; + this.#emitDecoratorMetadata = options.emitDecoratorMetadata; } public currentScope(): Scope; @@ -68,41 +64,7 @@ class Referencer extends Visitor { } } - public currentMethodNode(): TSESTree.MethodDefinition | null { - return this.#methodNode; - } - - protected pushInnerMethodDefinition( - isInnerMethodDefinition: boolean, - ): boolean { - const previous = this.#isInnerMethodDefinition; - - this.#isInnerMethodDefinition = isInnerMethodDefinition; - return previous; - } - - protected popInnerMethodDefinition( - isInnerMethodDefinition: boolean | undefined, - ): void { - this.#isInnerMethodDefinition = !!isInnerMethodDefinition; - } - - protected pushMethodNode( - classPropertyNode: TSESTree.MethodDefinition, - ): TSESTree.MethodDefinition | null { - const previous = this.#methodNode; - - this.#methodNode = classPropertyNode; - return previous; - } - - protected popMethodNode( - classPropertyNode: TSESTree.MethodDefinition | null, - ): void { - this.#methodNode = classPropertyNode; - } - - protected referencingDefaultValue( + public referencingDefaultValue( pattern: TSESTree.Identifier, assignments: (TSESTree.AssignmentExpression | TSESTree.AssignmentPattern)[], maybeImplicitGlobal: ReferenceImplicitGlobal | null, @@ -186,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.visitMetadataType(node.typeAnnotation, !!node.decorators); + ClassVisitor.visit(this, node, this.#emitDecoratorMetadata); } protected visitForIn( @@ -282,22 +207,13 @@ class Referencer extends Visitor { protected visitFunctionParameterTypeAnnotation( node: TSESTree.Parameter, - isMethod: boolean, ): void { if ('typeAnnotation' in node) { - if (isMethod) { - this.visitMetadataType(node.typeAnnotation, !!node.decorators); - } else { - this.visitType(node.typeAnnotation); - } + this.visitType(node.typeAnnotation); } else if (node.type === AST_NODE_TYPES.AssignmentPattern) { - if (isMethod) { - this.visitMetadataType(node.left.typeAnnotation, !!node.decorators); - } else { - this.visitType(node.left.typeAnnotation); - } + this.visitType(node.left.typeAnnotation); } else if (node.type === AST_NODE_TYPES.TSParameterProperty) { - this.visitFunctionParameterTypeAnnotation(node.parameter, isMethod); + this.visitFunctionParameterTypeAnnotation(node.parameter); } } protected visitFunction( @@ -329,12 +245,7 @@ class Referencer extends Visitor { } // Consider this function is in the MethodDefinition. - this.scopeManager.nestFunctionScope(node, this.#isInnerMethodDefinition); - - // check current function node is method of class instead of class property that value is function - const isMethodNode = - this.#methodNode?.type === AST_NODE_TYPES.MethodDefinition && - this.#methodNode.value === node; + this.scopeManager.nestFunctionScope(node, false); // Process parameter declarations. for (const param of node.params) { @@ -350,15 +261,11 @@ class Referencer extends Visitor { }, { processRightHandNodes: true }, ); - this.visitFunctionParameterTypeAnnotation(param, isMethodNode); + this.visitFunctionParameterTypeAnnotation(param); param.decorators?.forEach(d => this.visit(d)); } - if (isMethodNode) { - this.visitMetadataType(node.returnType); - } else { - this.visitType(node.returnType); - } + this.visitType(node.returnType); this.visitType(node.typeParameters); // In TypeScript there are a number of function-like constructs which have no body, @@ -375,39 +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; - let previousClassPropertyNode; - + 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); - } - if (node.type === AST_NODE_TYPES.MethodDefinition) { - previousClassPropertyNode = this.pushMethodNode(node); - } this.visit(node.value); - if (isMethodDefinition) { - this.popInnerMethodDefinition(previous); - } - if (previousClassPropertyNode !== undefined) { - this.popMethodNode(previousClassPropertyNode); - } - - if ('decorators' in node) { - node.decorators?.forEach(d => this.visit(d)); - } } protected visitType(node: TSESTree.Node | null | undefined): void { @@ -417,22 +297,6 @@ class Referencer extends Visitor { TypeVisitor.visit(this, node); } - /** - * visit type annotation may be emitted by emitDecoratorsMetadata. - * @param node It's may be ClassPropertyType, FunctionParameterType and FunctionReturnType. - * @param withDecorators FunctionParameterType -> parameter decorators; ClassPropertyType -> property decorators; - */ - protected visitMetadataType( - node: TSESTree.TSTypeAnnotation | undefined, - withDecorators?: boolean, - ): void { - if (!node) { - return; - } - - TypeVisitor.visitAndCheckEmit(this, node, withDecorators); - } - protected visitTypeAssertion( node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, ): void { @@ -552,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 } @@ -687,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); @@ -747,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 b8eacaacb6e..155aea5c5d9 100644 --- a/packages/scope-manager/src/referencer/TypeVisitor.ts +++ b/packages/scope-manager/src/referencer/TypeVisitor.ts @@ -17,15 +17,6 @@ class TypeVisitor extends Visitor { typeReferencer.visit(node); } - static visitAndCheckEmit( - referencer: Referencer, - node: TSESTree.TSTypeAnnotation, - withDecorators?: boolean, - ): void { - const typeReferencer = new TypeVisitor(referencer); - typeReferencer.visitAndCheckDecoratorMetadata(node, withDecorators); - } - /////////////////// // Visit helpers // /////////////////// @@ -79,128 +70,6 @@ class TypeVisitor extends Visitor { this.#referencer.visit(node.key); } - /** - * check type is referenced by decorator metadata - */ - protected visitAndCheckDecoratorMetadata( - node: TSESTree.TSTypeAnnotation, - withDecorators?: boolean, - ): void { - // emit decorators metadata only work for TSTypeReference - if ( - node.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && - this.#referencer.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; - } - const scope = this.#referencer.currentScope(); - /** - * class A { - * @meta // <--- check this - * foo: Type; - * } - */ - if (scope.type === ScopeType.class && withDecorators) { - return this.#referencer - .currentScope() - .referenceDualValueType(identifier); - } - - const methodNode = this.#referencer.currentMethodNode(); - // in method - if (scope.upper?.type === ScopeType.class && methodNode) { - /** - * class A { - * @meta // <--- check this - * foo(a: Type) {} - * - * @meta // <--- check this - * foo(): Type {} - * } - */ - if (methodNode.decorators) { - return this.#referencer - .currentScope() - .referenceDualValueType(identifier); - } - - /** - * class A { - * foo( - * @meta // <--- check this - * a: Type - * ) {} - * - * set foo( - * @meta // <--- EXCEPT this. TS do nothing for this - * a: Type - * ) {} - * } - */ - if (withDecorators && methodNode.kind !== 'set') { - return this.#referencer - .currentScope() - .referenceDualValueType(identifier); - } - - if (methodNode.kind === 'set') { - const keyName = getLiteralMethodKeyName(methodNode); - const classNode = scope.upper.block; - - /** - * class A { - * @meta // <--- check this - * get a() {} - * set ['a'](v: Type) {} - * } - */ - if ( - keyName !== null && - 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 - ) { - return this.#referencer - .currentScope() - .referenceDualValueType(identifier); - } - } - - /** - * @meta // <--- check this - * class A { - * constructor(a: Type) {} - * } - */ - if ( - methodNode.kind === 'constructor' && - scope.upper.block.type === AST_NODE_TYPES.ClassDeclaration && - scope.upper.block.decorators - ) { - return this.#referencer - .currentScope() - .referenceDualValueType(identifier); - } - } - } - this.visit(node); - } - ///////////////////// // Visit selectors // ///////////////////// @@ -398,40 +267,4 @@ class TypeVisitor extends Visitor { } } -/** - * 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 { TypeVisitor }; diff --git a/packages/scope-manager/tests/eslint-scope/references.test.ts b/packages/scope-manager/tests/eslint-scope/references.test.ts index e01ad8cd1c1..5f07dd671e9 100644 --- a/packages/scope-manager/tests/eslint-scope/references.test.ts +++ b/packages/scope-manager/tests/eslint-scope/references.test.ts @@ -555,7 +555,7 @@ describe('References:', () => { constructor(foo: b.Foo) {} - foo1(@deco a: Type2) {} + foo1(@deco a: Type2, b: Type0) {} @deco foo2(a: Type3) {} @@ -579,6 +579,11 @@ describe('References:', () => { @deco get [keyName]() {} } + + declare class C { + @deco + foo(): TypeC; + } `, { emitDecoratorMetadata: true, @@ -596,15 +601,19 @@ describe('References:', () => { expect(setterParamTypeRef.isTypeReference).toBe(true); expect(setterParamTypeRef.isValueReference).toBe(false); - const funcParamTypeRef = classAScope.childScopes[1].references[0]; - expect(funcParamTypeRef.identifier.name).toBe('b'); - expect(funcParamTypeRef.isTypeReference).toBe(true); - expect(funcParamTypeRef.isValueReference).toBe(true); + 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'); @@ -628,15 +637,22 @@ describe('References:', () => { const classBScope = scopeManager.globalScope!.childScopes[1]; - const constructorParamTypeRef = classBScope.childScopes[0].references[0]; - expect(constructorParamTypeRef.identifier.name).toBe('c'); - expect(constructorParamTypeRef.isTypeReference).toBe(true); - expect(constructorParamTypeRef.isValueReference).toBe(true); + 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/declaration/emit-metadata.ts b/packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts new file mode 100644 index 00000000000..c3ed8dcf3a1 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts @@ -0,0 +1,38 @@ +//// @emitDecoratorMetadata = true + +function deco(...param: any) {} + +@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]() {} +} diff --git a/packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts.shot b/packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts.shot new file mode 100644 index 00000000000..1d5dafd8250 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts.shot @@ -0,0 +1,759 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`class declaration emit-metadata 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$6 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + Reference$9 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + Reference$12 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + Reference$14 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + Reference$17 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + Reference$18 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + Reference$21 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + Reference$25 { + 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<"A">, + node: ClassDeclaration$2, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$6 { + defs: Array [ + ClassNameDefinition$4 { + name: Identifier<"A">, + node: ClassDeclaration$2, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$8 { + defs: Array [ + ParameterDefinition$5 { + name: Identifier<"a">, + node: FunctionExpression$3, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$9 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$10 { + defs: Array [ + ParameterDefinition$6 { + 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$7 { + name: Identifier<"a">, + node: FunctionExpression$5, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$13 { + defs: Array [ + ParameterDefinition$8 { + name: Identifier<"b">, + node: FunctionExpression$5, + }, + ], + name: "b", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$14 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$15 { + defs: Array [ + ParameterDefinition$9 { + name: Identifier<"a">, + node: FunctionExpression$6, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$16 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$17 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$18 { + defs: Array [ + ParameterDefinition$10 { + name: Identifier<"a">, + node: FunctionExpression$7, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$19 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$20 { + defs: Array [ + ParameterDefinition$11 { + name: Identifier<"a">, + node: FunctionExpression$8, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$21 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$22 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$23 { + defs: Array [ + VariableDefinition$12 { + name: Identifier<"keyName">, + node: VariableDeclarator$9, + }, + ], + name: "keyName", + references: Array [ + Reference$19 { + identifier: Identifier<"keyName">, + init: true, + isRead: false, + isTypeReference: false, + isValueReference: true, + isWrite: true, + resolved: Variable$23, + writeExpr: Literal$10, + }, + Reference$22 { + identifier: Identifier<"keyName">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$23, + }, + Reference$24 { + identifier: Identifier<"keyName">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$23, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$24 { + defs: Array [ + ClassNameDefinition$13 { + name: Identifier<"B">, + node: ClassDeclaration$11, + }, + ], + name: "B", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$25 { + defs: Array [ + ClassNameDefinition$14 { + name: Identifier<"B">, + node: ClassDeclaration$11, + }, + ], + name: "B", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$26 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$27 { + defs: Array [ + ParameterDefinition$15 { + name: Identifier<"foo">, + node: FunctionExpression$12, + }, + ], + name: "foo", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$28 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$29 { + defs: Array [ + ParameterDefinition$16 { + name: Identifier<"a">, + node: FunctionExpression$13, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$30 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$14, + isStrict: false, + references: Array [ + Reference$1, + Reference$19, + ], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "deco" => Variable$2, + "A" => Variable$5, + "keyName" => Variable$23, + "B" => Variable$24, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + Variable$5, + Variable$23, + Variable$24, + ], + }, + 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 [ + Reference$2 { + identifier: Identifier<"Type1">, + isRead: true, + isTypeReference: true, + isValueReference: false, + isWrite: false, + resolved: null, + }, + Reference$3, + Reference$4 { + identifier: Identifier<"a">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: null, + }, + Reference$12, + Reference$14, + Reference$17, + Reference$18, + ], + set: Map { + "A" => Variable$6, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$6, + ], + }, + FunctionScope$4 { + block: FunctionExpression$3, + isStrict: true, + references: Array [ + Reference$5 { + identifier: Identifier<"SetterType">, + isRead: true, + isTypeReference: true, + isValueReference: false, + isWrite: false, + resolved: null, + }, + Reference$6, + ], + set: Map { + "arguments" => Variable$7, + "a" => Variable$8, + }, + type: "function", + upper: ClassScope$3, + variables: Array [ + Variable$7, + Variable$8, + ], + }, + FunctionScope$5 { + block: FunctionExpression$4, + isStrict: true, + references: Array [ + Reference$7 { + identifier: Identifier<"b">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: null, + }, + ], + set: Map { + "arguments" => Variable$9, + "foo" => Variable$10, + }, + type: "function", + upper: ClassScope$3, + variables: Array [ + Variable$9, + Variable$10, + ], + }, + FunctionScope$6 { + block: FunctionExpression$5, + isStrict: true, + references: Array [ + Reference$8 { + identifier: Identifier<"Type2">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: null, + }, + Reference$9, + Reference$10 { + identifier: Identifier<"Type0">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: null, + }, + ], + set: Map { + "arguments" => Variable$11, + "a" => Variable$12, + "b" => Variable$13, + }, + type: "function", + upper: ClassScope$3, + variables: Array [ + Variable$11, + Variable$12, + Variable$13, + ], + }, + FunctionScope$7 { + block: FunctionExpression$6, + isStrict: true, + references: Array [ + Reference$11 { + identifier: Identifier<"Type3">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: null, + }, + ], + set: Map { + "arguments" => Variable$14, + "a" => Variable$15, + }, + type: "function", + upper: ClassScope$3, + variables: Array [ + Variable$14, + Variable$15, + ], + }, + FunctionScope$8 { + block: FunctionExpression$15, + isStrict: true, + references: Array [ + Reference$13 { + identifier: Identifier<"Type4">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: null, + }, + ], + set: Map { + "arguments" => Variable$16, + }, + type: "function", + upper: ClassScope$3, + variables: Array [ + Variable$16, + ], + }, + FunctionScope$9 { + block: FunctionExpression$7, + isStrict: true, + references: Array [ + Reference$15 { + identifier: Identifier<"Type5">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: null, + }, + ], + set: Map { + "arguments" => Variable$17, + "a" => Variable$18, + }, + type: "function", + upper: ClassScope$3, + variables: Array [ + Variable$17, + Variable$18, + ], + }, + FunctionScope$10 { + block: FunctionExpression$8, + isStrict: true, + references: Array [ + Reference$16 { + identifier: Identifier<"Type6">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: null, + }, + ], + set: Map { + "arguments" => Variable$19, + "a" => Variable$20, + }, + type: "function", + upper: ClassScope$3, + variables: Array [ + Variable$19, + Variable$20, + ], + }, + FunctionScope$11 { + block: FunctionExpression$16, + isStrict: true, + references: Array [], + set: Map { + "arguments" => Variable$21, + }, + type: "function", + upper: ClassScope$3, + variables: Array [ + Variable$21, + ], + }, + FunctionScope$12 { + block: FunctionExpression$17, + isStrict: true, + references: Array [], + set: Map { + "arguments" => Variable$22, + }, + type: "function", + upper: ClassScope$3, + variables: Array [ + Variable$22, + ], + }, + ClassScope$13 { + block: ClassDeclaration$11, + isStrict: true, + references: Array [ + Reference$22, + Reference$24, + Reference$25, + ], + set: Map { + "B" => Variable$25, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$25, + ], + }, + FunctionScope$14 { + block: FunctionExpression$12, + isStrict: true, + references: Array [ + Reference$20 { + identifier: Identifier<"c">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: null, + }, + Reference$21, + ], + set: Map { + "arguments" => Variable$26, + "foo" => Variable$27, + }, + type: "function", + upper: ClassScope$13, + variables: Array [ + Variable$26, + Variable$27, + ], + }, + FunctionScope$15 { + block: FunctionExpression$13, + isStrict: true, + references: Array [ + Reference$23 { + identifier: Identifier<"Type">, + isRead: true, + isTypeReference: true, + isValueReference: false, + isWrite: false, + resolved: null, + }, + ], + set: Map { + "arguments" => Variable$28, + "a" => Variable$29, + }, + type: "function", + upper: ClassScope$13, + variables: Array [ + Variable$28, + Variable$29, + ], + }, + FunctionScope$16 { + block: FunctionExpression$18, + isStrict: true, + references: Array [], + set: Map { + "arguments" => Variable$30, + }, + type: "function", + upper: ClassScope$13, + variables: Array [ + Variable$30, + ], + }, + ], +} +`; From b631e7ad33e335d76bb975747f404e8c996a18e2 Mon Sep 17 00:00:00 2001 From: frezc Date: Tue, 5 Jan 2021 02:17:01 +0800 Subject: [PATCH 6/6] split snapshot --- .../class/declaration/emit-metadata.ts | 38 -- .../class/emit-metadata/accessor-deco.ts | 23 + .../accessor-deco.ts.shot} | 599 +++++------------- .../class/emit-metadata/method-deco.ts | 11 + .../class/emit-metadata/method-deco.ts.shot | 291 +++++++++ .../class/emit-metadata/nested-class-both.ts | 15 + .../emit-metadata/nested-class-both.ts.shot | 298 +++++++++ .../class/emit-metadata/nested-class-inner.ts | 14 + .../emit-metadata/nested-class-inner.ts.shot | 288 +++++++++ .../class/emit-metadata/nested-class-outer.ts | 14 + .../emit-metadata/nested-class-outer.ts.shot | 289 +++++++++ .../class/emit-metadata/parameters-deco.ts | 14 + .../emit-metadata/parameters-deco.ts.shot | 344 ++++++++++ .../class/emit-metadata/property-deco.ts | 13 + .../class/emit-metadata/property-deco.ts.shot | 205 ++++++ 15 files changed, 1974 insertions(+), 482 deletions(-) delete mode 100644 packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts rename packages/scope-manager/tests/fixtures/class/{declaration/emit-metadata.ts.shot => emit-metadata/accessor-deco.ts.shot} (50%) create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts.shot create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts.shot create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts.shot create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts.shot create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts.shot create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts.shot diff --git a/packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts b/packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts deleted file mode 100644 index c3ed8dcf3a1..00000000000 --- a/packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts +++ /dev/null @@ -1,38 +0,0 @@ -//// @emitDecoratorMetadata = true - -function deco(...param: any) {} - -@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]() {} -} 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/declaration/emit-metadata.ts.shot b/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot similarity index 50% rename from packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts.shot rename to packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot index 1d5dafd8250..f535f8e61be 100644 --- a/packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts.shot +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`class declaration emit-metadata 1`] = ` +exports[`class emit-metadata accessor-deco 1`] = ` ScopeManager { variables: Array [ ImplicitGlobalConstTypeVariable, @@ -13,14 +13,6 @@ ScopeManager { ], 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, @@ -29,47 +21,7 @@ ScopeManager { isWrite: false, resolved: Variable$2, }, - Reference$6 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$9 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$12 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$14 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$17 { - identifier: Identifier<"deco">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$2, - }, - Reference$18 { + Reference$5 { identifier: Identifier<"deco">, isRead: true, isTypeReference: false, @@ -77,7 +29,7 @@ ScopeManager { isWrite: false, resolved: Variable$2, }, - Reference$21 { + Reference$7 { identifier: Identifier<"deco">, isRead: true, isTypeReference: false, @@ -85,7 +37,7 @@ ScopeManager { isWrite: false, resolved: Variable$2, }, - Reference$25 { + Reference$11 { identifier: Identifier<"deco">, isRead: true, isTypeReference: false, @@ -119,89 +71,135 @@ ScopeManager { Variable$5 { defs: Array [ ClassNameDefinition$3 { - name: Identifier<"A">, + name: Identifier<"T">, node: ClassDeclaration$2, }, ], - name: "A", - references: Array [], + 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<"A">, + name: Identifier<"T">, node: ClassDeclaration$2, }, ], - name: "A", + name: "T", references: Array [], isValueVariable: true, isTypeVariable: true, }, Variable$7 { - defs: Array [], - name: "arguments", - references: Array [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$8 { defs: Array [ - ParameterDefinition$5 { - name: Identifier<"a">, - node: FunctionExpression$3, + 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, }, ], - name: "a", - references: Array [], isValueVariable: true, isTypeVariable: false, }, - Variable$9 { - defs: Array [], - name: "arguments", + Variable$8 { + defs: Array [ + ClassNameDefinition$6 { + name: Identifier<"A">, + node: ClassDeclaration$5, + }, + ], + name: "A", references: Array [], isValueVariable: true, isTypeVariable: true, }, - Variable$10 { + Variable$9 { defs: Array [ - ParameterDefinition$6 { - name: Identifier<"foo">, - node: FunctionExpression$4, + ClassNameDefinition$7 { + name: Identifier<"A">, + node: ClassDeclaration$5, }, ], - name: "foo", + name: "A", references: Array [], isValueVariable: true, - isTypeVariable: false, + isTypeVariable: true, }, - Variable$11 { + Variable$10 { defs: Array [], name: "arguments", references: Array [], isValueVariable: true, isTypeVariable: true, }, - Variable$12 { - defs: Array [ - ParameterDefinition$7 { - name: Identifier<"a">, - node: FunctionExpression$5, - }, - ], - name: "a", - references: Array [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$13 { + Variable$11 { defs: Array [ ParameterDefinition$8 { name: Identifier<"b">, - node: FunctionExpression$5, + node: FunctionExpression$6, }, ], name: "b", @@ -209,18 +207,18 @@ ScopeManager { isValueVariable: true, isTypeVariable: false, }, - Variable$14 { + Variable$12 { defs: Array [], name: "arguments", references: Array [], isValueVariable: true, isTypeVariable: true, }, - Variable$15 { + Variable$13 { defs: Array [ ParameterDefinition$9 { name: Identifier<"a">, - node: FunctionExpression$6, + node: FunctionExpression$7, }, ], name: "a", @@ -228,42 +226,23 @@ ScopeManager { isValueVariable: true, isTypeVariable: false, }, - Variable$16 { + Variable$14 { defs: Array [], name: "arguments", references: Array [], isValueVariable: true, isTypeVariable: true, }, - Variable$17 { + Variable$15 { defs: Array [], name: "arguments", references: Array [], isValueVariable: true, isTypeVariable: true, }, - Variable$18 { + Variable$16 { defs: Array [ ParameterDefinition$10 { - name: Identifier<"a">, - node: FunctionExpression$7, - }, - ], - name: "a", - references: Array [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$19 { - defs: Array [], - name: "arguments", - references: Array [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$20 { - defs: Array [ - ParameterDefinition$11 { name: Identifier<"a">, node: FunctionExpression$8, }, @@ -273,114 +252,25 @@ ScopeManager { isValueVariable: true, isTypeVariable: false, }, - Variable$21 { - defs: Array [], - name: "arguments", - references: Array [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$22 { - defs: Array [], - name: "arguments", - references: Array [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$23 { - defs: Array [ - VariableDefinition$12 { - name: Identifier<"keyName">, - node: VariableDeclarator$9, - }, - ], - name: "keyName", - references: Array [ - Reference$19 { - identifier: Identifier<"keyName">, - init: true, - isRead: false, - isTypeReference: false, - isValueReference: true, - isWrite: true, - resolved: Variable$23, - writeExpr: Literal$10, - }, - Reference$22 { - identifier: Identifier<"keyName">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$23, - }, - Reference$24 { - identifier: Identifier<"keyName">, - isRead: true, - isTypeReference: false, - isValueReference: true, - isWrite: false, - resolved: Variable$23, - }, - ], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$24 { - defs: Array [ - ClassNameDefinition$13 { - name: Identifier<"B">, - node: ClassDeclaration$11, - }, - ], - name: "B", - references: Array [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$25 { - defs: Array [ - ClassNameDefinition$14 { - name: Identifier<"B">, - node: ClassDeclaration$11, - }, - ], - name: "B", - references: Array [], - isValueVariable: true, - isTypeVariable: true, - }, - Variable$26 { + Variable$17 { defs: Array [], name: "arguments", references: Array [], isValueVariable: true, isTypeVariable: true, }, - Variable$27 { - defs: Array [ - ParameterDefinition$15 { - name: Identifier<"foo">, - node: FunctionExpression$12, - }, - ], - name: "foo", - references: Array [], - isValueVariable: true, - isTypeVariable: false, - }, - Variable$28 { + Variable$18 { defs: Array [], name: "arguments", references: Array [], isValueVariable: true, isTypeVariable: true, }, - Variable$29 { + Variable$19 { defs: Array [ - ParameterDefinition$16 { + ParameterDefinition$11 { name: Identifier<"a">, - node: FunctionExpression$13, + node: FunctionExpression$9, }, ], name: "a", @@ -388,7 +278,7 @@ ScopeManager { isValueVariable: true, isTypeVariable: false, }, - Variable$30 { + Variable$20 { defs: Array [], name: "arguments", references: Array [], @@ -398,18 +288,17 @@ ScopeManager { ], scopes: Array [ GlobalScope$1 { - block: Program$14, + block: Program$10, isStrict: false, references: Array [ Reference$1, - Reference$19, ], set: Map { "const" => ImplicitGlobalConstTypeVariable, "deco" => Variable$2, - "A" => Variable$5, - "keyName" => Variable$23, - "B" => Variable$24, + "T" => Variable$5, + "keyName" => Variable$7, + "A" => Variable$8, }, type: "global", upper: null, @@ -417,8 +306,8 @@ ScopeManager { ImplicitGlobalConstTypeVariable, Variable$2, Variable$5, - Variable$23, - Variable$24, + Variable$7, + Variable$8, ], }, FunctionScope$2 { @@ -439,31 +328,9 @@ ScopeManager { ClassScope$3 { block: ClassDeclaration$2, isStrict: true, - references: Array [ - Reference$2 { - identifier: Identifier<"Type1">, - isRead: true, - isTypeReference: true, - isValueReference: false, - isWrite: false, - resolved: null, - }, - Reference$3, - Reference$4 { - identifier: Identifier<"a">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: null, - }, - Reference$12, - Reference$14, - Reference$17, - Reference$18, - ], + references: Array [], set: Map { - "A" => Variable$6, + "T" => Variable$6, }, type: "class", upper: GlobalScope$1, @@ -471,287 +338,131 @@ ScopeManager { Variable$6, ], }, - FunctionScope$4 { - block: FunctionExpression$3, + ClassScope$4 { + block: ClassDeclaration$5, isStrict: true, references: Array [ - Reference$5 { - identifier: Identifier<"SetterType">, - isRead: true, - isTypeReference: true, - isValueReference: false, - isWrite: false, - resolved: null, - }, - Reference$6, + Reference$3, + Reference$5, + Reference$7, + Reference$8, + Reference$10, + Reference$11, ], set: Map { - "arguments" => Variable$7, - "a" => Variable$8, + "A" => Variable$9, }, - type: "function", - upper: ClassScope$3, + type: "class", + upper: GlobalScope$1, variables: Array [ - Variable$7, - Variable$8, + Variable$9, ], }, FunctionScope$5 { - block: FunctionExpression$4, + block: FunctionExpression$6, isStrict: true, references: Array [ - Reference$7 { - identifier: Identifier<"b">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: null, - }, + Reference$2, ], set: Map { - "arguments" => Variable$9, - "foo" => Variable$10, + "arguments" => Variable$10, + "b" => Variable$11, }, type: "function", - upper: ClassScope$3, + upper: ClassScope$4, variables: Array [ - Variable$9, Variable$10, + Variable$11, ], }, FunctionScope$6 { - block: FunctionExpression$5, + block: FunctionExpression$7, isStrict: true, references: Array [ - Reference$8 { - identifier: Identifier<"Type2">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: null, - }, - Reference$9, - Reference$10 { - identifier: Identifier<"Type0">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: null, - }, + Reference$4, ], set: Map { - "arguments" => Variable$11, - "a" => Variable$12, - "b" => Variable$13, + "arguments" => Variable$12, + "a" => Variable$13, }, type: "function", - upper: ClassScope$3, + upper: ClassScope$4, variables: Array [ - Variable$11, Variable$12, Variable$13, ], }, FunctionScope$7 { - block: FunctionExpression$6, + block: FunctionExpression$11, isStrict: true, - references: Array [ - Reference$11 { - identifier: Identifier<"Type3">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: null, - }, - ], + references: Array [], set: Map { "arguments" => Variable$14, - "a" => Variable$15, }, type: "function", - upper: ClassScope$3, + upper: ClassScope$4, variables: Array [ Variable$14, - Variable$15, ], }, FunctionScope$8 { - block: FunctionExpression$15, + block: FunctionExpression$8, isStrict: true, references: Array [ - Reference$13 { - identifier: Identifier<"Type4">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: null, - }, + Reference$6, ], set: Map { - "arguments" => Variable$16, + "arguments" => Variable$15, + "a" => Variable$16, }, type: "function", - upper: ClassScope$3, + upper: ClassScope$4, variables: Array [ + Variable$15, Variable$16, ], }, FunctionScope$9 { - block: FunctionExpression$7, + block: FunctionExpression$12, isStrict: true, - references: Array [ - Reference$15 { - identifier: Identifier<"Type5">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: null, - }, - ], + references: Array [], set: Map { "arguments" => Variable$17, - "a" => Variable$18, }, type: "function", - upper: ClassScope$3, + upper: ClassScope$4, variables: Array [ Variable$17, - Variable$18, ], }, FunctionScope$10 { - block: FunctionExpression$8, + block: FunctionExpression$9, isStrict: true, references: Array [ - Reference$16 { - identifier: Identifier<"Type6">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: null, - }, + Reference$9, ], set: Map { - "arguments" => Variable$19, - "a" => Variable$20, + "arguments" => Variable$18, + "a" => Variable$19, }, type: "function", - upper: ClassScope$3, + upper: ClassScope$4, variables: Array [ + Variable$18, Variable$19, - Variable$20, ], }, FunctionScope$11 { - block: FunctionExpression$16, - isStrict: true, - references: Array [], - set: Map { - "arguments" => Variable$21, - }, - type: "function", - upper: ClassScope$3, - variables: Array [ - Variable$21, - ], - }, - FunctionScope$12 { - block: FunctionExpression$17, - isStrict: true, - references: Array [], - set: Map { - "arguments" => Variable$22, - }, - type: "function", - upper: ClassScope$3, - variables: Array [ - Variable$22, - ], - }, - ClassScope$13 { - block: ClassDeclaration$11, - isStrict: true, - references: Array [ - Reference$22, - Reference$24, - Reference$25, - ], - set: Map { - "B" => Variable$25, - }, - type: "class", - upper: GlobalScope$1, - variables: Array [ - Variable$25, - ], - }, - FunctionScope$14 { - block: FunctionExpression$12, - isStrict: true, - references: Array [ - Reference$20 { - identifier: Identifier<"c">, - isRead: true, - isTypeReference: true, - isValueReference: true, - isWrite: false, - resolved: null, - }, - Reference$21, - ], - set: Map { - "arguments" => Variable$26, - "foo" => Variable$27, - }, - type: "function", - upper: ClassScope$13, - variables: Array [ - Variable$26, - Variable$27, - ], - }, - FunctionScope$15 { block: FunctionExpression$13, isStrict: true, - references: Array [ - Reference$23 { - identifier: Identifier<"Type">, - isRead: true, - isTypeReference: true, - isValueReference: false, - isWrite: false, - resolved: null, - }, - ], - set: Map { - "arguments" => Variable$28, - "a" => Variable$29, - }, - type: "function", - upper: ClassScope$13, - variables: Array [ - Variable$28, - Variable$29, - ], - }, - FunctionScope$16 { - block: FunctionExpression$18, - isStrict: true, references: Array [], set: Map { - "arguments" => Variable$30, + "arguments" => Variable$20, }, type: "function", - upper: ClassScope$13, + upper: ClassScope$4, variables: Array [ - Variable$30, + 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, + ], + }, + ], +} +`;