From 445e416878b27a54bf07c2d3b84dabd7b06e51bc Mon Sep 17 00:00:00 2001 From: Frezc <504021398@qq.com> Date: Mon, 18 Jan 2021 11:43:16 +0800 Subject: [PATCH 1/3] feat: add support for decorator metadata in scope analysis and in consistent-type-imports (#2751) Fixes #2559 --- .../docs/rules/consistent-type-imports.md | 2 +- .../src/rules/consistent-type-imports.ts | 501 ++++++++++++------ .../tests/fixtures/tsconfig-withmeta.json | 6 + .../rules/consistent-type-imports.test.ts | 296 ++++++++++- packages/parser/src/parser.ts | 4 + packages/scope-manager/README.md | 6 + packages/scope-manager/src/analyze.ts | 10 + .../src/referencer/ClassVisitor.ts | 362 +++++++++++++ .../src/referencer/Referencer.ts | 107 +--- .../src/referencer/TypeVisitor.ts | 5 + .../tests/eslint-scope/references.test.ts | 115 ++++ packages/scope-manager/tests/fixtures.test.ts | 1 + .../class/emit-metadata/accessor-deco.ts | 23 + .../class/emit-metadata/accessor-deco.ts.shot | 470 ++++++++++++++++ .../class/emit-metadata/method-deco.ts | 11 + .../class/emit-metadata/method-deco.ts.shot | 291 ++++++++++ .../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 +++++++ 26 files changed, 3432 insertions(+), 272 deletions(-) create mode 100644 packages/eslint-plugin/tests/fixtures/tsconfig-withmeta.json create mode 100644 packages/scope-manager/src/referencer/ClassVisitor.ts create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts create mode 100644 packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot 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/eslint-plugin/docs/rules/consistent-type-imports.md b/packages/eslint-plugin/docs/rules/consistent-type-imports.md index a6d06aae8514..c30e59b40b99 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 c4cb6586a00f..5d14576a2e5b 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -20,6 +20,8 @@ interface SourceImports { reportValueImports: ReportValueImport[]; // ImportDeclaration for type-only import only with named imports. typeOnlyNamedImport: TSESTree.ImportDeclaration | null; + // ImportDeclaration for value-only import only with named imports. + valueOnlyNamedImport: TSESTree.ImportDeclaration | null; } interface ReportValueImport { node: TSESTree.ImportDeclaration; @@ -39,12 +41,15 @@ function isTypeToken( ): token is TSESTree.IdentifierToken & { value: 'type' } { return token.type === AST_TOKEN_TYPES.Identifier && token.value === 'type'; } + type MessageIds = | 'typeOverValue' | 'someImportsAreOnlyTypes' | 'aImportIsOnlyTypes' | 'valueOverType' - | 'noImportTypeAnnotations'; + | 'noImportTypeAnnotations' + | 'someImportsInDecoMeta' + | 'aImportInDecoMeta'; export default util.createRule({ name: 'consistent-type-imports', meta: { @@ -59,6 +64,10 @@ export default util.createRule({ 'All imports in the declaration are only used as types. Use `import type`', someImportsAreOnlyTypes: 'Imports {{typeImports}} are only used as types', aImportIsOnlyTypes: 'Import {{typeImports}} is only used as types', + someImportsInDecoMeta: + 'Type imports {{typeImports}} are used by decorator metadata', + aImportInDecoMeta: + 'Type import {{typeImports}} is used by decorator metadata', valueOverType: 'Use an `import` instead of an `import type`.', noImportTypeAnnotations: '`import()` type annotations are forbidden.', }, @@ -105,6 +114,7 @@ export default util.createRule({ source, reportValueImports: [], typeOnlyNamedImport: null, + valueOnlyNamedImport: null, }); if (node.importKind === 'type') { if ( @@ -116,9 +126,18 @@ export default util.createRule({ ) { sourceImports.typeOnlyNamedImport = node; } - return; + } else { + if ( + !sourceImports.valueOnlyNamedImport && + node.specifiers.every( + specifier => + specifier.type === AST_NODE_TYPES.ImportSpecifier, + ) + ) { + sourceImports.valueOnlyNamedImport = node; + } } - // if importKind === 'value' + const typeSpecifiers: TSESTree.ImportClause[] = []; const valueSpecifiers: TSESTree.ImportClause[] = []; const unusedSpecifiers: TSESTree.ImportClause[] = []; @@ -129,6 +148,21 @@ export default util.createRule({ } else { const onlyHasTypeReferences = variable.references.every( ref => { + /** + * keep origin import kind when export + * export { Type } + * export default Type; + */ + if ( + ref.identifier.parent?.type === + AST_NODE_TYPES.ExportSpecifier || + ref.identifier.parent?.type === + AST_NODE_TYPES.ExportDefaultDeclaration + ) { + if (ref.isValueReference && ref.isTypeReference) { + return node.importKind === 'type'; + } + } if (ref.isValueReference) { // `type T = typeof foo` will create a value reference because "foo" must be a value type // however this value reference is safe to use with type-only imports @@ -157,7 +191,10 @@ export default util.createRule({ } } - if (typeSpecifiers.length) { + if ( + (node.importKind === 'value' && typeSpecifiers.length) || + (node.importKind === 'type' && valueSpecifiers.length) + ) { sourceImports.reportValueImports.push({ node, typeSpecifiers, @@ -185,27 +222,42 @@ export default util.createRule({ }, }); } else { + const isTypeImport = report.node.importKind === 'type'; + // we have a mixed type/value import, so we need to split them out into multiple exports - const typeImportNames: string[] = report.typeSpecifiers.map( - specifier => `"${specifier.local.name}"`, - ); + const importNames = (isTypeImport + ? report.valueSpecifiers + : report.typeSpecifiers + ).map(specifier => `"${specifier.local.name}"`); context.report({ node: report.node, messageId: - typeImportNames.length === 1 - ? 'aImportIsOnlyTypes' + importNames.length === 1 + ? isTypeImport + ? 'aImportInDecoMeta' + : 'aImportIsOnlyTypes' + : isTypeImport + ? 'someImportsInDecoMeta' : 'someImportsAreOnlyTypes', data: { typeImports: - typeImportNames.length === 1 - ? typeImportNames[0] + importNames.length === 1 + ? importNames[0] : [ - typeImportNames.slice(0, -1).join(', '), - typeImportNames.slice(-1)[0], + importNames.slice(0, -1).join(', '), + importNames.slice(-1)[0], ].join(' and '), }, *fix(fixer) { - yield* fixToTypeImport(fixer, report, sourceImports); + if (isTypeImport) { + yield* fixToValueImportInDecoMeta( + fixer, + report, + sourceImports, + ); + } else { + yield* fixToTypeImport(fixer, report, sourceImports); + } }, }); } @@ -240,13 +292,13 @@ export default util.createRule({ : {}), }; - function* fixToTypeImport( - fixer: TSESLint.RuleFixer, - report: ReportValueImport, - sourceImports: SourceImports, - ): IterableIterator { - const { node } = report; - + function classifySpecifier( + node: TSESTree.ImportDeclaration, + ): { + defaultSpecifier: TSESTree.ImportDefaultSpecifier | null; + namespaceSpecifier: TSESTree.ImportNamespaceSpecifier | null; + namedSpecifiers: TSESTree.ImportSpecifier[]; + } { const defaultSpecifier: TSESTree.ImportDefaultSpecifier | null = node.specifiers[0].type === AST_NODE_TYPES.ImportDefaultSpecifier ? node.specifiers[0] @@ -260,6 +312,175 @@ export default util.createRule({ (specifier): specifier is TSESTree.ImportSpecifier => specifier.type === AST_NODE_TYPES.ImportSpecifier, ); + return { + defaultSpecifier, + namespaceSpecifier, + namedSpecifiers, + }; + } + + /** + * Returns information for fixing named specifiers. + */ + function getFixesNamedSpecifiers( + fixer: TSESLint.RuleFixer, + node: TSESTree.ImportDeclaration, + typeNamedSpecifiers: TSESTree.ImportSpecifier[], + allNamedSpecifiers: TSESTree.ImportSpecifier[], + ): { + typeNamedSpecifiersText: string; + removeTypeNamedSpecifiers: TSESLint.RuleFix[]; + } { + if (allNamedSpecifiers.length === 0) { + return { + typeNamedSpecifiersText: '', + removeTypeNamedSpecifiers: [], + }; + } + const typeNamedSpecifiersTexts: string[] = []; + const removeTypeNamedSpecifiers: TSESLint.RuleFix[] = []; + if (typeNamedSpecifiers.length === allNamedSpecifiers.length) { + // e.g. + // import Foo, {Type1, Type2} from 'foo' + // import DefType, {Type1, Type2} from 'foo' + const openingBraceToken = util.nullThrows( + sourceCode.getTokenBefore( + typeNamedSpecifiers[0], + util.isOpeningBraceToken, + ), + util.NullThrowsReasons.MissingToken('{', node.type), + ); + const commaToken = util.nullThrows( + sourceCode.getTokenBefore(openingBraceToken, util.isCommaToken), + util.NullThrowsReasons.MissingToken(',', node.type), + ); + const closingBraceToken = util.nullThrows( + sourceCode.getFirstTokenBetween( + openingBraceToken, + node.source, + util.isClosingBraceToken, + ), + util.NullThrowsReasons.MissingToken('}', node.type), + ); + + // import DefType, {...} from 'foo' + // ^^^^^^^ remove + removeTypeNamedSpecifiers.push( + fixer.removeRange([commaToken.range[0], closingBraceToken.range[1]]), + ); + + typeNamedSpecifiersTexts.push( + sourceCode.text.slice( + openingBraceToken.range[1], + closingBraceToken.range[0], + ), + ); + } else { + const typeNamedSpecifierGroups: TSESTree.ImportSpecifier[][] = []; + let group: TSESTree.ImportSpecifier[] = []; + for (const namedSpecifier of allNamedSpecifiers) { + if (typeNamedSpecifiers.includes(namedSpecifier)) { + group.push(namedSpecifier); + } else if (group.length) { + typeNamedSpecifierGroups.push(group); + group = []; + } + } + if (group.length) { + typeNamedSpecifierGroups.push(group); + } + for (const namedSpecifiers of typeNamedSpecifierGroups) { + const { removeRange, textRange } = getNamedSpecifierRanges( + namedSpecifiers, + allNamedSpecifiers, + ); + removeTypeNamedSpecifiers.push(fixer.removeRange(removeRange)); + + typeNamedSpecifiersTexts.push(sourceCode.text.slice(...textRange)); + } + } + return { + typeNamedSpecifiersText: typeNamedSpecifiersTexts.join(','), + removeTypeNamedSpecifiers, + }; + } + + /** + * Returns ranges for fixing named specifier. + */ + function getNamedSpecifierRanges( + namedSpecifierGroup: TSESTree.ImportSpecifier[], + allNamedSpecifiers: TSESTree.ImportSpecifier[], + ): { + textRange: TSESTree.Range; + removeRange: TSESTree.Range; + } { + const first = namedSpecifierGroup[0]; + const last = namedSpecifierGroup[namedSpecifierGroup.length - 1]; + const removeRange: TSESTree.Range = [first.range[0], last.range[1]]; + const textRange: TSESTree.Range = [...removeRange]; + const before = sourceCode.getTokenBefore(first)!; + textRange[0] = before.range[1]; + if (util.isCommaToken(before)) { + removeRange[0] = before.range[0]; + } else { + removeRange[0] = before.range[1]; + } + + const isFirst = allNamedSpecifiers[0] === first; + const isLast = allNamedSpecifiers[allNamedSpecifiers.length - 1] === last; + const after = sourceCode.getTokenAfter(last)!; + textRange[1] = after.range[0]; + if (isFirst || isLast) { + if (util.isCommaToken(after)) { + removeRange[1] = after.range[1]; + } + } + + return { + textRange, + removeRange, + }; + } + + /** + * insert specifiers to named import node. + * e.g. + * import type { Already, Type1, Type2 } from 'foo' + * ^^^^^^^^^^^^^ insert + */ + function insertToNamedImport( + fixer: TSESLint.RuleFixer, + target: TSESTree.ImportDeclaration, + insertText: string, + ): TSESLint.RuleFix { + const closingBraceToken = util.nullThrows( + sourceCode.getFirstTokenBetween( + sourceCode.getFirstToken(target)!, + target.source, + util.isClosingBraceToken, + ), + util.NullThrowsReasons.MissingToken('}', target.type), + ); + const before = sourceCode.getTokenBefore(closingBraceToken)!; + if (!util.isCommaToken(before) && !util.isOpeningBraceToken(before)) { + insertText = ',' + insertText; + } + return fixer.insertTextBefore(closingBraceToken, insertText); + } + + function* fixToTypeImport( + fixer: TSESLint.RuleFixer, + report: ReportValueImport, + sourceImports: SourceImports, + ): IterableIterator { + const { node } = report; + + const { + defaultSpecifier, + namespaceSpecifier, + namedSpecifiers, + } = classifySpecifier(node); if (namespaceSpecifier && !defaultSpecifier) { // e.g. @@ -296,33 +517,18 @@ export default util.createRule({ ); const fixesNamedSpecifiers = getFixesNamedSpecifiers( + fixer, + node, typeNamedSpecifiers, namedSpecifiers, ); const afterFixes: TSESLint.RuleFix[] = []; if (typeNamedSpecifiers.length) { if (sourceImports.typeOnlyNamedImport) { - const closingBraceToken = util.nullThrows( - sourceCode.getFirstTokenBetween( - sourceCode.getFirstToken(sourceImports.typeOnlyNamedImport)!, - sourceImports.typeOnlyNamedImport.source, - util.isClosingBraceToken, - ), - util.NullThrowsReasons.MissingToken( - '}', - sourceImports.typeOnlyNamedImport.type, - ), - ); - let insertText = fixesNamedSpecifiers.typeNamedSpecifiersText; - const before = sourceCode.getTokenBefore(closingBraceToken)!; - if (!util.isCommaToken(before) && !util.isOpeningBraceToken(before)) { - insertText = ',' + insertText; - } - // import type { Already, Type1, Type2 } from 'foo' - // ^^^^^^^^^^^^^ insert - const insertTypeNamedSpecifiers = fixer.insertTextBefore( - closingBraceToken, - insertText, + const insertTypeNamedSpecifiers = insertToNamedImport( + fixer, + sourceImports.typeOnlyNamedImport, + fixesNamedSpecifiers.typeNamedSpecifiersText, ); if (sourceImports.typeOnlyNamedImport.range[1] <= node.range[0]) { yield insertTypeNamedSpecifiers; @@ -413,132 +619,6 @@ export default util.createRule({ yield* fixesRemoveTypeNamespaceSpecifier; yield* afterFixes; - - /** - * Returns information for fixing named specifiers. - */ - function getFixesNamedSpecifiers( - typeNamedSpecifiers: TSESTree.ImportSpecifier[], - allNamedSpecifiers: TSESTree.ImportSpecifier[], - ): { - typeNamedSpecifiersText: string; - removeTypeNamedSpecifiers: TSESLint.RuleFix[]; - } { - if (allNamedSpecifiers.length === 0) { - return { - typeNamedSpecifiersText: '', - removeTypeNamedSpecifiers: [], - }; - } - const typeNamedSpecifiersTexts: string[] = []; - const removeTypeNamedSpecifiers: TSESLint.RuleFix[] = []; - if (typeNamedSpecifiers.length === allNamedSpecifiers.length) { - // e.g. - // import Foo, {Type1, Type2} from 'foo' - // import DefType, {Type1, Type2} from 'foo' - const openingBraceToken = util.nullThrows( - sourceCode.getTokenBefore( - typeNamedSpecifiers[0], - util.isOpeningBraceToken, - ), - util.NullThrowsReasons.MissingToken('{', node.type), - ); - const commaToken = util.nullThrows( - sourceCode.getTokenBefore(openingBraceToken, util.isCommaToken), - util.NullThrowsReasons.MissingToken(',', node.type), - ); - const closingBraceToken = util.nullThrows( - sourceCode.getFirstTokenBetween( - openingBraceToken, - node.source, - util.isClosingBraceToken, - ), - util.NullThrowsReasons.MissingToken('}', node.type), - ); - - // import DefType, {...} from 'foo' - // ^^^^^^^ remove - removeTypeNamedSpecifiers.push( - fixer.removeRange([ - commaToken.range[0], - closingBraceToken.range[1], - ]), - ); - - typeNamedSpecifiersTexts.push( - sourceCode.text.slice( - openingBraceToken.range[1], - closingBraceToken.range[0], - ), - ); - } else { - const typeNamedSpecifierGroups: TSESTree.ImportSpecifier[][] = []; - let group: TSESTree.ImportSpecifier[] = []; - for (const namedSpecifier of allNamedSpecifiers) { - if (typeNamedSpecifiers.includes(namedSpecifier)) { - group.push(namedSpecifier); - } else if (group.length) { - typeNamedSpecifierGroups.push(group); - group = []; - } - } - if (group.length) { - typeNamedSpecifierGroups.push(group); - } - for (const namedSpecifiers of typeNamedSpecifierGroups) { - const { removeRange, textRange } = getNamedSpecifierRanges( - namedSpecifiers, - allNamedSpecifiers, - ); - removeTypeNamedSpecifiers.push(fixer.removeRange(removeRange)); - - typeNamedSpecifiersTexts.push(sourceCode.text.slice(...textRange)); - } - } - return { - typeNamedSpecifiersText: typeNamedSpecifiersTexts.join(','), - removeTypeNamedSpecifiers, - }; - } - - /** - * Returns ranges for fixing named specifier. - */ - function getNamedSpecifierRanges( - namedSpecifierGroup: TSESTree.ImportSpecifier[], - allNamedSpecifiers: TSESTree.ImportSpecifier[], - ): { - textRange: TSESTree.Range; - removeRange: TSESTree.Range; - } { - const first = namedSpecifierGroup[0]; - const last = namedSpecifierGroup[namedSpecifierGroup.length - 1]; - const removeRange: TSESTree.Range = [first.range[0], last.range[1]]; - const textRange: TSESTree.Range = [...removeRange]; - const before = sourceCode.getTokenBefore(first)!; - textRange[0] = before.range[1]; - if (util.isCommaToken(before)) { - removeRange[0] = before.range[0]; - } else { - removeRange[0] = before.range[1]; - } - - const isFirst = allNamedSpecifiers[0] === first; - const isLast = - allNamedSpecifiers[allNamedSpecifiers.length - 1] === last; - const after = sourceCode.getTokenAfter(last)!; - textRange[1] = after.range[0]; - if (isFirst || isLast) { - if (util.isCommaToken(after)) { - removeRange[1] = after.range[1]; - } - } - - return { - textRange, - removeRange, - }; - } } function* fixToTypeImportByInsertType( @@ -600,10 +680,89 @@ export default util.createRule({ } } - function fixToValueImport( + function* fixToValueImportInDecoMeta( + fixer: TSESLint.RuleFixer, + report: ReportValueImport, + sourceImports: SourceImports, + ): IterableIterator { + const { node } = report; + + const { + defaultSpecifier, + namespaceSpecifier, + namedSpecifiers, + } = classifySpecifier(node); + + if (namespaceSpecifier) { + // e.g. + // import type * as types from 'foo' + yield* fixToValueImport(fixer, node); + return; + } else if (defaultSpecifier) { + if ( + report.valueSpecifiers.includes(defaultSpecifier) && + namedSpecifiers.length === 0 + ) { + // e.g. + // import type Type from 'foo' + yield* fixToValueImport(fixer, node); + return; + } + } else { + if ( + namedSpecifiers.every(specifier => + report.valueSpecifiers.includes(specifier), + ) + ) { + // e.g. + // import type {Type1, Type2} from 'foo' + yield* fixToValueImport(fixer, node); + return; + } + } + + const valueNamedSpecifiers = namedSpecifiers.filter(specifier => + report.valueSpecifiers.includes(specifier), + ); + + const fixesNamedSpecifiers = getFixesNamedSpecifiers( + fixer, + node, + valueNamedSpecifiers, + namedSpecifiers, + ); + const afterFixes: TSESLint.RuleFix[] = []; + if (valueNamedSpecifiers.length) { + if (sourceImports.valueOnlyNamedImport) { + const insertTypeNamedSpecifiers = insertToNamedImport( + fixer, + sourceImports.valueOnlyNamedImport, + fixesNamedSpecifiers.typeNamedSpecifiersText, + ); + if (sourceImports.valueOnlyNamedImport.range[1] <= node.range[0]) { + yield insertTypeNamedSpecifiers; + } else { + afterFixes.push(insertTypeNamedSpecifiers); + } + } else { + yield fixer.insertTextBefore( + node, + `import {${ + fixesNamedSpecifiers.typeNamedSpecifiersText + }} from ${sourceCode.getText(node.source)};\n`, + ); + } + } + + yield* fixesNamedSpecifiers.removeTypeNamedSpecifiers; + + yield* afterFixes; + } + + function* fixToValueImport( fixer: TSESLint.RuleFixer, node: TSESTree.ImportDeclaration, - ): TSESLint.RuleFix { + ): IterableIterator { // import type Foo from 'foo' // ^^^^ remove const importToken = util.nullThrows( @@ -622,7 +781,7 @@ export default util.createRule({ sourceCode.getTokenAfter(typeToken, { includeComments: true }), util.NullThrowsReasons.MissingToken('any token', node.type), ); - return fixer.removeRange([typeToken.range[0], afterToken.range[0]]); + yield fixer.removeRange([typeToken.range[0], afterToken.range[0]]); } }, }); diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig-withmeta.json b/packages/eslint-plugin/tests/fixtures/tsconfig-withmeta.json new file mode 100644 index 000000000000..4987fc7e1745 --- /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 8818225a024a..83d803cf3809 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/consistent-type-imports'; -import { RuleTester, noFormat } from '../RuleTester'; +import { RuleTester, noFormat, getFixturesRootDir } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -9,6 +9,11 @@ const ruleTester = new RuleTester({ }, }); +const withMetaParserOptions = { + tsconfigRootDir: getFixturesRootDir(), + project: './tsconfig-withmeta.json', +}; + ruleTester.run('consistent-type-imports', rule, { valid: [ ` @@ -230,6 +235,112 @@ ruleTester.run('consistent-type-imports', rule, { const a: typeof Default = Default; const b: typeof Rest = Rest; `, + { + code: ` + import Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + parserOptions: withMetaParserOptions, + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + foo: Foo; + } + `, + parserOptions: withMetaParserOptions, + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + foo(foo: Foo) {} + } + `, + parserOptions: withMetaParserOptions, + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + foo(): Foo {} + } + `, + parserOptions: withMetaParserOptions, + }, + { + code: ` + import Foo from 'foo'; + class A { + foo(@deco foo: Foo) {} + } + `, + parserOptions: withMetaParserOptions, + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + set foo(value: Foo) {} + } + `, + parserOptions: withMetaParserOptions, + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + get foo() {} + + set foo(value: Foo) {} + } + `, + parserOptions: withMetaParserOptions, + }, + { + code: ` + import Foo from 'foo'; + class A { + @deco + get foo() {} + + set ['foo'](value: Foo) {} + } + `, + parserOptions: withMetaParserOptions, + }, + { + code: ` + import type { Foo } from 'foo'; + const key = 'k'; + class A { + @deco + get [key]() {} + + set [key](value: Foo) {} + } + `, + parserOptions: withMetaParserOptions, + }, + { + code: ` + import * as foo from 'foo'; + @deco + class A { + constructor(foo: foo.Foo) {} + } + `, + parserOptions: withMetaParserOptions, + }, ], invalid: [ { @@ -1215,5 +1326,188 @@ const a: Default = ''; }, ], }, + { + code: ` + import Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + output: ` + import type Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + errors: [ + { + messageId: 'typeOverValue', + line: 2, + column: 9, + }, + ], + }, + { + code: ` + import type Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + output: noFormat` + import Foo from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + errors: [ + { + messageId: 'aImportInDecoMeta', + data: { typeImports: '"Foo"' }, + line: 2, + column: 9, + }, + ], + parserOptions: withMetaParserOptions, + }, + { + code: ` + import type { Foo } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + output: noFormat` + import { Foo } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + errors: [ + { + messageId: 'aImportInDecoMeta', + data: { typeImports: '"Foo"' }, + line: 2, + column: 9, + }, + ], + parserOptions: withMetaParserOptions, + }, + { + code: noFormat` + import type { Type } from 'foo'; + import { Foo, Bar } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + type T = Bar; + `, + output: noFormat` + import type { Type , Bar } from 'foo'; + import { Foo } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + type T = Bar; + `, + errors: [ + { + messageId: 'aImportIsOnlyTypes', + data: { typeImports: '"Bar"' }, + line: 3, + column: 9, + }, + ], + parserOptions: withMetaParserOptions, + }, + { + code: ` + import { V } from 'foo'; + import type { Foo, Bar, T } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + foo(@deco bar: Bar) {} + } + `, + output: noFormat` + import { V , Foo, Bar} from 'foo'; + import type { T } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + foo(@deco bar: Bar) {} + } + `, + errors: [ + { + messageId: 'someImportsInDecoMeta', + data: { typeImports: '"Foo" and "Bar"' }, + line: 3, + column: 9, + }, + ], + parserOptions: withMetaParserOptions, + }, + { + code: ` + import type { Foo, T } from 'foo'; + import { V } from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + output: noFormat` + import type { T } from 'foo'; + import { V , Foo} from 'foo'; + @deco + class A { + constructor(foo: Foo) {} + } + `, + errors: [ + { + messageId: 'aImportInDecoMeta', + data: { typeImports: '"Foo"' }, + line: 2, + column: 9, + }, + ], + parserOptions: withMetaParserOptions, + }, + { + code: ` + import type * as Type from 'foo'; + @deco + class A { + constructor(foo: Type.Foo) {} + } + `, + output: noFormat` + import * as Type from 'foo'; + @deco + class A { + constructor(foo: Type.Foo) {} + } + `, + errors: [ + { + messageId: 'aImportInDecoMeta', + data: { typeImports: '"Type"' }, + line: 2, + column: 9, + }, + ], + parserOptions: withMetaParserOptions, + }, ], }); diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index c1422dc261b1..f304a837d8b6 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 24ccd839dff0..7671214e8d7c 100644 --- a/packages/scope-manager/README.md +++ b/packages/scope-manager/README.md @@ -83,6 +83,12 @@ interface AnalyzeOptions { * The source type of the script. */ sourceType?: 'script' | 'module'; + + /** + * Emit design-type metadata for decorated declarations in source. + * Defaults to `false`. + */ + emitDecoratorMetadata?: boolean; } ``` diff --git a/packages/scope-manager/src/analyze.ts b/packages/scope-manager/src/analyze.ts index 9e734925da19..1aaf874049fc 100644 --- a/packages/scope-manager/src/analyze.ts +++ b/packages/scope-manager/src/analyze.ts @@ -61,6 +61,12 @@ interface AnalyzeOptions { * The source type of the script. */ sourceType?: 'script' | 'module'; + + /** + * Emit design-type metadata for decorated declarations in source. + * Defaults to `false`. + */ + emitDecoratorMetadata?: boolean; } const DEFAULT_OPTIONS: Required = { @@ -72,6 +78,7 @@ const DEFAULT_OPTIONS: Required = { jsxFragmentName: null, lib: ['es2018'], sourceType: 'script', + emitDecoratorMetadata: false, }; function mapEcmaVersion(version: EcmaVersion | undefined): Lib { @@ -106,6 +113,9 @@ function analyze( providedOptions?.jsxFragmentName ?? DEFAULT_OPTIONS.jsxFragmentName, sourceType: providedOptions?.sourceType ?? DEFAULT_OPTIONS.sourceType, lib: providedOptions?.lib ?? [mapEcmaVersion(ecmaVersion)], + emitDecoratorMetadata: + providedOptions?.emitDecoratorMetadata ?? + DEFAULT_OPTIONS.emitDecoratorMetadata, }; // ensure the option is lower cased diff --git a/packages/scope-manager/src/referencer/ClassVisitor.ts b/packages/scope-manager/src/referencer/ClassVisitor.ts new file mode 100644 index 000000000000..ff758a3ae8f8 --- /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 a36dcfc9c1a0..b0e7557ee7eb 100644 --- a/packages/scope-manager/src/referencer/Referencer.ts +++ b/packages/scope-manager/src/referencer/Referencer.ts @@ -1,4 +1,5 @@ import { AST_NODE_TYPES, Lib, TSESTree } from '@typescript-eslint/types'; +import { ClassVisitor } from './ClassVisitor'; import { ExportVisitor } from './ExportVisitor'; import { ImportVisitor } from './ImportVisitor'; import { PatternVisitor } from './PatternVisitor'; @@ -9,7 +10,6 @@ import { Visitor, VisitorOptions } from './Visitor'; import { assert } from '../assert'; import { CatchClauseDefinition, - ClassNameDefinition, FunctionNameDefinition, ImportBindingDefinition, ParameterDefinition, @@ -25,16 +25,17 @@ interface ReferencerOptions extends VisitorOptions { jsxPragma: string; jsxFragmentName: string | null; lib: Lib[]; + emitDecoratorMetadata: boolean; } // Referencing variables and creating bindings. class Referencer extends Visitor { - #isInnerMethodDefinition: boolean; #jsxPragma: string; #jsxFragmentName: string | null; #hasReferencedJsxFactory = false; #hasReferencedJsxFragmentFactory = false; #lib: Lib[]; + readonly #emitDecoratorMetadata: boolean; public readonly scopeManager: ScopeManager; constructor(options: ReferencerOptions, scopeManager: ScopeManager) { @@ -43,7 +44,7 @@ class Referencer extends Visitor { this.#jsxPragma = options.jsxPragma; this.#jsxFragmentName = options.jsxFragmentName; this.#lib = options.lib; - this.#isInnerMethodDefinition = false; + this.#emitDecoratorMetadata = options.emitDecoratorMetadata; } public currentScope(): Scope; @@ -63,22 +64,7 @@ class Referencer extends Visitor { } } - protected pushInnerMethodDefinition( - isInnerMethodDefinition: boolean, - ): boolean { - const previous = this.#isInnerMethodDefinition; - - this.#isInnerMethodDefinition = isInnerMethodDefinition; - return previous; - } - - protected popInnerMethodDefinition( - isInnerMethodDefinition: boolean | undefined, - ): void { - this.#isInnerMethodDefinition = !!isInnerMethodDefinition; - } - - protected referencingDefaultValue( + public referencingDefaultValue( pattern: TSESTree.Identifier, assignments: (TSESTree.AssignmentExpression | TSESTree.AssignmentPattern)[], maybeImplicitGlobal: ReferenceImplicitGlobal | null, @@ -162,44 +148,7 @@ class Referencer extends Visitor { protected visitClass( node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, ): void { - if (node.type === AST_NODE_TYPES.ClassDeclaration && node.id) { - this.currentScope().defineIdentifier( - node.id, - new ClassNameDefinition(node.id, node), - ); - } - - node.decorators?.forEach(d => this.visit(d)); - - this.scopeManager.nestClassScope(node); - - if (node.id) { - // define the class name again inside the new scope - // references to the class should not resolve directly to the parent class - this.currentScope().defineIdentifier( - node.id, - new ClassNameDefinition(node.id, node), - ); - } - - this.visit(node.superClass); - - // visit the type param declarations - this.visitType(node.typeParameters); - // then the usages - this.visitType(node.superTypeParameters); - node.implements?.forEach(imp => this.visitType(imp)); - - this.visit(node.body); - - this.close(node); - } - - protected visitClassProperty( - node: TSESTree.TSAbstractClassProperty | TSESTree.ClassProperty, - ): void { - this.visitProperty(node); - this.visitType(node.typeAnnotation); + ClassVisitor.visit(this, node, this.#emitDecoratorMetadata); } protected visitForIn( @@ -296,7 +245,7 @@ class Referencer extends Visitor { } // Consider this function is in the MethodDefinition. - this.scopeManager.nestFunctionScope(node, this.#isInnerMethodDefinition); + this.scopeManager.nestFunctionScope(node, false); // Process parameter declarations. for (const param of node.params) { @@ -333,32 +282,12 @@ class Referencer extends Visitor { this.close(node); } - protected visitProperty( - node: - | TSESTree.ClassProperty - | TSESTree.MethodDefinition - | TSESTree.Property - | TSESTree.TSAbstractClassProperty - | TSESTree.TSAbstractMethodDefinition, - ): void { - let previous; - + protected visitProperty(node: TSESTree.Property): void { if (node.computed) { this.visit(node.key); } - const isMethodDefinition = node.type === AST_NODE_TYPES.MethodDefinition; - if (isMethodDefinition) { - previous = this.pushInnerMethodDefinition(true); - } this.visit(node.value); - if (isMethodDefinition) { - this.popInnerMethodDefinition(previous); - } - - if ('decorators' in node) { - node.decorators?.forEach(d => this.visit(d)); - } } protected visitType(node: TSESTree.Node | null | undefined): void { @@ -487,10 +416,6 @@ class Referencer extends Visitor { this.visitClass(node); } - protected ClassProperty(node: TSESTree.ClassProperty): void { - this.visitClassProperty(node); - } - protected ContinueStatement(): void { // don't reference the continue statement's label } @@ -622,10 +547,6 @@ class Referencer extends Visitor { // meta properties all builtin globals } - protected MethodDefinition(node: TSESTree.MethodDefinition): void { - this.visitProperty(node); - } - protected NewExpression(node: TSESTree.NewExpression): void { this.visitChildren(node, ['typeParameters']); this.visitType(node.typeParameters); @@ -682,18 +603,6 @@ class Referencer extends Visitor { this.visitType(node.typeParameters); } - protected TSAbstractClassProperty( - node: TSESTree.TSAbstractClassProperty, - ): void { - this.visitClassProperty(node); - } - - protected TSAbstractMethodDefinition( - node: TSESTree.TSAbstractMethodDefinition, - ): void { - this.visitProperty(node); - } - protected TSAsExpression(node: TSESTree.TSAsExpression): void { this.visitTypeAssertion(node); } diff --git a/packages/scope-manager/src/referencer/TypeVisitor.ts b/packages/scope-manager/src/referencer/TypeVisitor.ts index 75b5f614e32f..155aea5c5d93 100644 --- a/packages/scope-manager/src/referencer/TypeVisitor.ts +++ b/packages/scope-manager/src/referencer/TypeVisitor.ts @@ -260,6 +260,11 @@ class TypeVisitor extends Visitor { this.#referencer.currentScope().referenceValue(expr); } } + + protected TSTypeAnnotation(node: TSESTree.TSTypeAnnotation): void { + // check + this.visitChildren(node); + } } export { TypeVisitor }; diff --git a/packages/scope-manager/tests/eslint-scope/references.test.ts b/packages/scope-manager/tests/eslint-scope/references.test.ts index 573d0ce84b30..5f07dd671e93 100644 --- a/packages/scope-manager/tests/eslint-scope/references.test.ts +++ b/packages/scope-manager/tests/eslint-scope/references.test.ts @@ -540,4 +540,119 @@ describe('References:', () => { }), ); }); + + describe('When emitDecoratorMetadata is true', () => { + it('check type referenced by decorator metadata', () => { + const { scopeManager } = parseAndAnalyze( + ` + @deco + class A { + property: Type1; + @deco + propertyWithDeco: a.Foo; + + set foo(@deco a: SetterType) {} + + constructor(foo: b.Foo) {} + + foo1(@deco a: Type2, b: Type0) {} + + @deco + foo2(a: Type3) {} + + @deco + foo3(): Type4 {} + + set ['a'](a: Type5) {} + set [0](a: Type6) {} + @deco + get a() {} + @deco + get [0]() {} + } + + const keyName = 'foo'; + class B { + constructor(@deco foo: c.Foo) {} + + set [keyName](a: Type) {} + @deco + get [keyName]() {} + } + + declare class C { + @deco + foo(): TypeC; + } + `, + { + emitDecoratorMetadata: true, + }, + ); + + const classAScope = scopeManager.globalScope!.childScopes[0]; + const propertyTypeRef = classAScope.references[2]; + expect(propertyTypeRef.identifier.name).toBe('a'); + expect(propertyTypeRef.isTypeReference).toBe(true); + expect(propertyTypeRef.isValueReference).toBe(true); + + const setterParamTypeRef = classAScope.childScopes[0].references[0]; + expect(setterParamTypeRef.identifier.name).toBe('SetterType'); + expect(setterParamTypeRef.isTypeReference).toBe(true); + expect(setterParamTypeRef.isValueReference).toBe(false); + + const constructorParamTypeRef = classAScope.childScopes[1].references[0]; + expect(constructorParamTypeRef.identifier.name).toBe('b'); + expect(constructorParamTypeRef.isTypeReference).toBe(true); + expect(constructorParamTypeRef.isValueReference).toBe(true); + + const methodParamTypeRef = classAScope.childScopes[2].references[0]; + expect(methodParamTypeRef.identifier.name).toBe('Type2'); + expect(methodParamTypeRef.isTypeReference).toBe(true); + expect(methodParamTypeRef.isValueReference).toBe(true); + const methodParamTypeRef0 = classAScope.childScopes[2].references[2]; + expect(methodParamTypeRef0.identifier.name).toBe('Type0'); + expect(methodParamTypeRef0.isTypeReference).toBe(true); + expect(methodParamTypeRef0.isValueReference).toBe(true); + + const methodParamTypeRef1 = classAScope.childScopes[3].references[0]; + expect(methodParamTypeRef1.identifier.name).toBe('Type3'); + expect(methodParamTypeRef1.isTypeReference).toBe(true); + expect(methodParamTypeRef1.isValueReference).toBe(true); + + const methodReturnTypeRef = classAScope.childScopes[4].references[0]; + expect(methodReturnTypeRef.identifier.name).toBe('Type4'); + expect(methodReturnTypeRef.isTypeReference).toBe(true); + expect(methodReturnTypeRef.isValueReference).toBe(true); + + const setterParamTypeRef1 = classAScope.childScopes[5].references[0]; + expect(setterParamTypeRef1.identifier.name).toBe('Type5'); + expect(setterParamTypeRef1.isTypeReference).toBe(true); + expect(setterParamTypeRef1.isValueReference).toBe(true); + + const setterParamTypeRef2 = classAScope.childScopes[6].references[0]; + expect(setterParamTypeRef2.identifier.name).toBe('Type6'); + expect(setterParamTypeRef2.isTypeReference).toBe(true); + expect(setterParamTypeRef2.isValueReference).toBe(true); + + const classBScope = scopeManager.globalScope!.childScopes[1]; + + const constructorParamTypeRef1 = classBScope.childScopes[0].references[0]; + expect(constructorParamTypeRef1.identifier.name).toBe('c'); + expect(constructorParamTypeRef1.isTypeReference).toBe(true); + expect(constructorParamTypeRef1.isValueReference).toBe(true); + + const setterParamTypeRef3 = classBScope.childScopes[1].references[0]; + expect(setterParamTypeRef3.identifier.name).toBe('Type'); + expect(setterParamTypeRef3.isTypeReference).toBe(true); + expect(setterParamTypeRef3.isValueReference).toBe(false); + + const classCScope = scopeManager.globalScope!.childScopes[2]; + + const methodReturnTypeRef1 = classCScope.childScopes[0].references[0]; + expect(methodReturnTypeRef1.identifier.name).toBe('TypeC'); + expect(methodReturnTypeRef1.isTypeReference).toBe(true); + expect(methodReturnTypeRef1.isValueReference).toBe(false); + }); + }); }); diff --git a/packages/scope-manager/tests/fixtures.test.ts b/packages/scope-manager/tests/fixtures.test.ts index 08eea4f4995e..69833caa7faa 100644 --- a/packages/scope-manager/tests/fixtures.test.ts +++ b/packages/scope-manager/tests/fixtures.test.ts @@ -44,6 +44,7 @@ const ALLOWED_OPTIONS: Map = new Map< ['jsxPragma', ['string']], ['jsxFragmentName', ['string']], ['sourceType', ['string', new Set(['module', 'script'])]], + ['emitDecoratorMetadata', ['boolean']], ]); function nestDescribe( diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts b/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts new file mode 100644 index 000000000000..41c40b287703 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts @@ -0,0 +1,23 @@ +//// @emitDecoratorMetadata = true + +function deco(...param: any) {} + +class T {} +const keyName = 'foo'; + +class A { + @deco + set b(b: T) {} + + set ['a'](a: T) {} + @deco + get a() {} + + set [0](a: T) {} + @deco + get [0]() {} + + set [keyName](a: T) {} + @deco + get [keyName]() {} +} diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot b/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot new file mode 100644 index 000000000000..f535f8e61be2 --- /dev/null +++ b/packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot @@ -0,0 +1,470 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`class emit-metadata accessor-deco 1`] = ` +ScopeManager { + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2 { + defs: Array [ + FunctionNameDefinition$1 { + name: Identifier<"deco">, + node: FunctionDeclaration$1, + }, + ], + name: "deco", + references: Array [ + Reference$3 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + Reference$5 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + Reference$7 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + Reference$11 { + identifier: Identifier<"deco">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$2, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$3 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$4 { + defs: Array [ + ParameterDefinition$2 { + name: Identifier<"param">, + node: FunctionDeclaration$1, + }, + ], + name: "param", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$5 { + defs: Array [ + ClassNameDefinition$3 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [ + Reference$2 { + identifier: Identifier<"T">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: Variable$5, + }, + Reference$4 { + identifier: Identifier<"T">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: Variable$5, + }, + Reference$6 { + identifier: Identifier<"T">, + isRead: true, + isTypeReference: true, + isValueReference: true, + isWrite: false, + resolved: Variable$5, + }, + Reference$9 { + identifier: Identifier<"T">, + isRead: true, + isTypeReference: true, + isValueReference: false, + isWrite: false, + resolved: Variable$5, + }, + ], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$6 { + defs: Array [ + ClassNameDefinition$4 { + name: Identifier<"T">, + node: ClassDeclaration$2, + }, + ], + name: "T", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$7 { + defs: Array [ + VariableDefinition$5 { + name: Identifier<"keyName">, + node: VariableDeclarator$3, + }, + ], + name: "keyName", + references: Array [ + Reference$1 { + identifier: Identifier<"keyName">, + init: true, + isRead: false, + isTypeReference: false, + isValueReference: true, + isWrite: true, + resolved: Variable$7, + writeExpr: Literal$4, + }, + Reference$8 { + identifier: Identifier<"keyName">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$7, + }, + Reference$10 { + identifier: Identifier<"keyName">, + isRead: true, + isTypeReference: false, + isValueReference: true, + isWrite: false, + resolved: Variable$7, + }, + ], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$8 { + defs: Array [ + ClassNameDefinition$6 { + name: Identifier<"A">, + node: ClassDeclaration$5, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$9 { + defs: Array [ + ClassNameDefinition$7 { + name: Identifier<"A">, + node: ClassDeclaration$5, + }, + ], + name: "A", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$10 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$11 { + defs: Array [ + ParameterDefinition$8 { + name: Identifier<"b">, + node: FunctionExpression$6, + }, + ], + name: "b", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$12 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$13 { + defs: Array [ + ParameterDefinition$9 { + name: Identifier<"a">, + node: FunctionExpression$7, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$14 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$15 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$16 { + defs: Array [ + ParameterDefinition$10 { + name: Identifier<"a">, + node: FunctionExpression$8, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$17 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$18 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + Variable$19 { + defs: Array [ + ParameterDefinition$11 { + name: Identifier<"a">, + node: FunctionExpression$9, + }, + ], + name: "a", + references: Array [], + isValueVariable: true, + isTypeVariable: false, + }, + Variable$20 { + defs: Array [], + name: "arguments", + references: Array [], + isValueVariable: true, + isTypeVariable: true, + }, + ], + scopes: Array [ + GlobalScope$1 { + block: Program$10, + isStrict: false, + references: Array [ + Reference$1, + ], + set: Map { + "const" => ImplicitGlobalConstTypeVariable, + "deco" => Variable$2, + "T" => Variable$5, + "keyName" => Variable$7, + "A" => Variable$8, + }, + type: "global", + upper: null, + variables: Array [ + ImplicitGlobalConstTypeVariable, + Variable$2, + Variable$5, + Variable$7, + Variable$8, + ], + }, + FunctionScope$2 { + block: FunctionDeclaration$1, + isStrict: false, + references: Array [], + set: Map { + "arguments" => Variable$3, + "param" => Variable$4, + }, + type: "function", + upper: GlobalScope$1, + variables: Array [ + Variable$3, + Variable$4, + ], + }, + ClassScope$3 { + block: ClassDeclaration$2, + isStrict: true, + references: Array [], + set: Map { + "T" => Variable$6, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$6, + ], + }, + ClassScope$4 { + block: ClassDeclaration$5, + isStrict: true, + references: Array [ + Reference$3, + Reference$5, + Reference$7, + Reference$8, + Reference$10, + Reference$11, + ], + set: Map { + "A" => Variable$9, + }, + type: "class", + upper: GlobalScope$1, + variables: Array [ + Variable$9, + ], + }, + FunctionScope$5 { + block: FunctionExpression$6, + isStrict: true, + references: Array [ + Reference$2, + ], + set: Map { + "arguments" => Variable$10, + "b" => Variable$11, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$10, + Variable$11, + ], + }, + FunctionScope$6 { + block: FunctionExpression$7, + isStrict: true, + references: Array [ + Reference$4, + ], + set: Map { + "arguments" => Variable$12, + "a" => Variable$13, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$12, + Variable$13, + ], + }, + FunctionScope$7 { + block: FunctionExpression$11, + isStrict: true, + references: Array [], + set: Map { + "arguments" => Variable$14, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$14, + ], + }, + FunctionScope$8 { + block: FunctionExpression$8, + isStrict: true, + references: Array [ + Reference$6, + ], + set: Map { + "arguments" => Variable$15, + "a" => Variable$16, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$15, + Variable$16, + ], + }, + FunctionScope$9 { + block: FunctionExpression$12, + isStrict: true, + references: Array [], + set: Map { + "arguments" => Variable$17, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$17, + ], + }, + FunctionScope$10 { + block: FunctionExpression$9, + isStrict: true, + references: Array [ + Reference$9, + ], + set: Map { + "arguments" => Variable$18, + "a" => Variable$19, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$18, + Variable$19, + ], + }, + FunctionScope$11 { + block: FunctionExpression$13, + isStrict: true, + references: Array [], + set: Map { + "arguments" => Variable$20, + }, + type: "function", + upper: ClassScope$4, + variables: Array [ + Variable$20, + ], + }, + ], +} +`; diff --git a/packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts b/packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts new file mode 100644 index 000000000000..ab030a5b0b9a --- /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 000000000000..74707c85f354 --- /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 000000000000..39fa3c402325 --- /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 000000000000..01686cb28848 --- /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 000000000000..d2fd5be90214 --- /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 000000000000..963402a90367 --- /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 000000000000..2f5247067bf1 --- /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 000000000000..5db83db9116b --- /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 000000000000..fc0eb3c0525f --- /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 000000000000..ac1f8b41de5e --- /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 000000000000..a4c17dcaa5d1 --- /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 000000000000..348c6009163d --- /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, + ], + }, + ], +} +`; From 32bd18de80f4f8388717d0f0c16d493234362aa5 Mon Sep 17 00:00:00 2001 From: mpsijm Date: Mon, 18 Jan 2021 04:52:20 +0100 Subject: [PATCH 2/3] feat(eslint-plugin): add `object-curly-spacing` rule (#2892) --- packages/eslint-plugin/README.md | 1 + .../docs/rules/object-curly-spacing.md | 22 + packages/eslint-plugin/src/configs/all.ts | 2 + packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/object-curly-spacing.ts | 267 +++ .../tests/rules/object-curly-spacing.test.ts | 1955 +++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 25 + 7 files changed, 2274 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/object-curly-spacing.md create mode 100644 packages/eslint-plugin/src/rules/object-curly-spacing.ts create mode 100644 packages/eslint-plugin/tests/rules/object-curly-spacing.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 7c55b54aca6f..3a504fe7d83a 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -218,6 +218,7 @@ In these cases, we create what we call an extension rule; a rule within our plug | [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | | | [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | | | | | [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | | +| [`@typescript-eslint/object-curly-spacing`](./docs/rules/object-curly-spacing.md) | Enforce consistent spacing inside braces | | :wrench: | | | [`@typescript-eslint/quotes`](./docs/rules/quotes.md) | Enforce the consistent use of either backticks, double, or single quotes | | :wrench: | | | [`@typescript-eslint/require-await`](./docs/rules/require-await.md) | Disallow async functions which have no `await` expression | :heavy_check_mark: | | :thought_balloon: | | [`@typescript-eslint/return-await`](./docs/rules/return-await.md) | Enforces consistent returning of awaited values | | :wrench: | :thought_balloon: | diff --git a/packages/eslint-plugin/docs/rules/object-curly-spacing.md b/packages/eslint-plugin/docs/rules/object-curly-spacing.md new file mode 100644 index 000000000000..44bd35afb592 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/object-curly-spacing.md @@ -0,0 +1,22 @@ +# Enforce consistent spacing inside braces (`object-curly-spacing`) + +## Rule Details + +This rule extends the base [`eslint/object-curly-spacing`](https://eslint.org/docs/rules/object-curly-spacing) rule. +It adds support for TypeScript's object types. + +## How to use + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "object-curly-spacing": "off", + "@typescript-eslint/object-curly-spacing": ["error"] +} +``` + +## Options + +See [`eslint/object-curly-spacing` options](https://eslint.org/docs/rules/object-curly-spacing#options). + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/object-curly-spacing.md) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 36cd690afa05..98afc4ae3a45 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -113,6 +113,8 @@ export = { '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', + 'object-curly-spacing': 'off', + '@typescript-eslint/object-curly-spacing': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-enum-initializers': 'error', '@typescript-eslint/prefer-for-of': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 1be014e46c55..ffa70e57a290 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -79,6 +79,7 @@ import noUseBeforeDefine from './no-use-before-define'; import noUselessConstructor from './no-useless-constructor'; import noVarRequires from './no-var-requires'; import nonNullableTypeAssertionStyle from './non-nullable-type-assertion-style'; +import objectCurlySpacing from './object-curly-spacing'; import preferAsConst from './prefer-as-const'; import preferEnumInitializers from './prefer-enum-initializers'; import preferForOf from './prefer-for-of'; @@ -195,6 +196,7 @@ export default { 'no-useless-constructor': noUselessConstructor, 'no-var-requires': noVarRequires, 'non-nullable-type-assertion-style': nonNullableTypeAssertionStyle, + 'object-curly-spacing': objectCurlySpacing, 'prefer-as-const': preferAsConst, 'prefer-enum-initializers': preferEnumInitializers, 'prefer-for-of': preferForOf, diff --git a/packages/eslint-plugin/src/rules/object-curly-spacing.ts b/packages/eslint-plugin/src/rules/object-curly-spacing.ts new file mode 100644 index 000000000000..581e7ac15557 --- /dev/null +++ b/packages/eslint-plugin/src/rules/object-curly-spacing.ts @@ -0,0 +1,267 @@ +import { + AST_NODE_TYPES, + AST_TOKEN_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import baseRule from 'eslint/lib/rules/object-curly-spacing'; +import { + createRule, + InferMessageIdsTypeFromRule, + InferOptionsTypeFromRule, + isClosingBraceToken, + isClosingBracketToken, + isTokenOnSameLine, +} from '../util'; + +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; + +export default createRule({ + name: 'object-curly-spacing', + meta: { + ...baseRule.meta, + docs: { + description: 'Enforce consistent spacing inside braces', + category: 'Stylistic Issues', + recommended: false, + extendsBaseRule: true, + }, + }, + defaultOptions: ['never'], + create(context) { + const spaced = context.options[0] === 'always'; + const sourceCode = context.getSourceCode(); + + /** + * Determines whether an option is set, relative to the spacing option. + * If spaced is "always", then check whether option is set to false. + * If spaced is "never", then check whether option is set to true. + * @param option The option to exclude. + * @returns Whether or not the property is excluded. + */ + function isOptionSet( + option: 'arraysInObjects' | 'objectsInObjects', + ): boolean { + return context.options[1] + ? context.options[1][option] === !spaced + : false; + } + + const options = { + spaced, + arraysInObjectsException: isOptionSet('arraysInObjects'), + objectsInObjectsException: isOptionSet('objectsInObjects'), + }; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports that there shouldn't be a space after the first token + * @param node The node to report in the event of an error. + * @param token The token to use for the report. + */ + function reportNoBeginningSpace( + node: TSESTree.TSTypeLiteral, + token: TSESTree.Token, + ): void { + const nextToken = context + .getSourceCode() + .getTokenAfter(token, { includeComments: true })!; + + context.report({ + node, + loc: { start: token.loc.end, end: nextToken.loc.start }, + messageId: 'unexpectedSpaceAfter', + data: { + token: token.value, + }, + fix(fixer) { + return fixer.removeRange([token.range[1], nextToken.range[0]]); + }, + }); + } + + /** + * Reports that there shouldn't be a space before the last token + * @param node The node to report in the event of an error. + * @param token The token to use for the report. + */ + function reportNoEndingSpace( + node: TSESTree.TSTypeLiteral, + token: TSESTree.Token, + ): void { + const previousToken = context + .getSourceCode() + .getTokenBefore(token, { includeComments: true })!; + + context.report({ + node, + loc: { start: previousToken.loc.end, end: token.loc.start }, + messageId: 'unexpectedSpaceBefore', + data: { + token: token.value, + }, + fix(fixer) { + return fixer.removeRange([previousToken.range[1], token.range[0]]); + }, + }); + } + + /** + * Reports that there should be a space after the first token + * @param node The node to report in the event of an error. + * @param token The token to use for the report. + */ + function reportRequiredBeginningSpace( + node: TSESTree.TSTypeLiteral, + token: TSESTree.Token, + ): void { + context.report({ + node, + loc: token.loc, + messageId: 'requireSpaceAfter', + data: { + token: token.value, + }, + fix(fixer) { + return fixer.insertTextAfter(token, ' '); + }, + }); + } + + /** + * Reports that there should be a space before the last token + * @param node The node to report in the event of an error. + * @param token The token to use for the report. + */ + function reportRequiredEndingSpace( + node: TSESTree.TSTypeLiteral, + token: TSESTree.Token, + ): void { + context.report({ + node, + loc: token.loc, + messageId: 'requireSpaceBefore', + data: { + token: token.value, + }, + fix(fixer) { + return fixer.insertTextBefore(token, ' '); + }, + }); + } + + /** + * Determines if spacing in curly braces is valid. + * @param node The AST node to check. + * @param first The first token to check (should be the opening brace) + * @param second The second token to check (should be first after the opening brace) + * @param penultimate The penultimate token to check (should be last before closing brace) + * @param last The last token to check (should be closing brace) + */ + function validateBraceSpacing( + node: TSESTree.TSTypeLiteral, + first: TSESTree.Token, + second: TSESTree.Token | TSESTree.Comment, + penultimate: TSESTree.Token | TSESTree.Comment, + last: TSESTree.Token, + ): void { + if (isTokenOnSameLine(first, second)) { + const firstSpaced = sourceCode.isSpaceBetween!(first, second); + const secondType = sourceCode.getNodeByRangeIndex(second.range[0])! + .type; + + const openingCurlyBraceMustBeSpaced = + options.arraysInObjectsException && + secondType === AST_NODE_TYPES.TSIndexSignature + ? !options.spaced + : options.spaced; + + if (openingCurlyBraceMustBeSpaced && !firstSpaced) { + reportRequiredBeginningSpace(node, first); + } + if ( + !openingCurlyBraceMustBeSpaced && + firstSpaced && + second.type !== AST_TOKEN_TYPES.Line + ) { + reportNoBeginningSpace(node, first); + } + } + + if (isTokenOnSameLine(penultimate, last)) { + const shouldCheckPenultimate = + (options.arraysInObjectsException && + isClosingBracketToken(penultimate)) || + (options.objectsInObjectsException && + isClosingBraceToken(penultimate)); + const penultimateType = + shouldCheckPenultimate && + sourceCode.getNodeByRangeIndex(penultimate.range[0])!.type; + + const closingCurlyBraceMustBeSpaced = + (options.arraysInObjectsException && + penultimateType === AST_NODE_TYPES.TSTupleType) || + (options.objectsInObjectsException && + penultimateType === AST_NODE_TYPES.TSTypeLiteral) + ? !options.spaced + : options.spaced; + + const lastSpaced = sourceCode.isSpaceBetween!(penultimate, last); + + if (closingCurlyBraceMustBeSpaced && !lastSpaced) { + reportRequiredEndingSpace(node, last); + } + if (!closingCurlyBraceMustBeSpaced && lastSpaced) { + reportNoEndingSpace(node, last); + } + } + } + + /** + * Gets '}' token of an object node. + * + * Because the last token of object patterns might be a type annotation, + * this traverses tokens preceded by the last property, then returns the + * first '}' token. + * @param node The node to get. This node is an + * ObjectExpression or an ObjectPattern. And this node has one or + * more properties. + * @returns '}' token. + */ + function getClosingBraceOfObject( + node: TSESTree.TSTypeLiteral, + ): TSESTree.Token | null { + const lastProperty = node.members[node.members.length - 1]; + + return sourceCode.getTokenAfter(lastProperty, isClosingBraceToken); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + const rules = baseRule.create(context); + return { + ...rules, + TSTypeLiteral(node: TSESTree.TSTypeLiteral): void { + if (node.members.length === 0) { + return; + } + + const first = sourceCode.getFirstToken(node)!; + const last = getClosingBraceOfObject(node)!; + const second = sourceCode.getTokenAfter(first, { + includeComments: true, + })!; + const penultimate = sourceCode.getTokenBefore(last, { + includeComments: true, + })!; + + validateBraceSpacing(node, first, second, penultimate, last); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/object-curly-spacing.test.ts b/packages/eslint-plugin/tests/rules/object-curly-spacing.test.ts new file mode 100644 index 000000000000..5c28cbef7ed7 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/object-curly-spacing.test.ts @@ -0,0 +1,1955 @@ +/* eslint-disable eslint-comments/no-use */ +// this rule tests the position of braces, which prettier will want to fix and break the tests +/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ +/* eslint-enable eslint-comments/no-use */ + +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; +import rule from '../../src/rules/object-curly-spacing'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('object-curly-spacing', rule, { + valid: [ + // always - object literals + { code: 'var obj = { foo: bar, baz: qux };', options: ['always'] }, + { + code: 'var obj = { foo: { bar: quxx }, baz: qux };', + options: ['always'], + }, + { code: 'var obj = {\nfoo: bar,\nbaz: qux\n};', options: ['always'] }, + { code: 'var obj = { /**/foo:bar/**/ };', options: ['always'] }, + { code: 'var obj = { //\nfoo:bar };', options: ['always'] }, + + // always - destructuring + { + code: 'var { x } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { x, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { x,y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\nx,y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\nx,y\n} = z', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { /**/x/**/ } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { //\nx } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { x = 10, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { x: { z }, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\ny,\n} = x', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { y, } = x', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { y: x } = x', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + + // always - import / export + { + code: "import door from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import * as door from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { door } from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {\ndoor } from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { /**/door/**/ } from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { //\ndoor } from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export { door } from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { house, mouse } from 'caravan'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import house, { mouse } from 'caravan'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import door, { house, mouse } from 'caravan'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var door = 0;export { door }', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { bar as x } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { x, } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {\nx,\n} from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export { x, } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {\nx,\n} from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export { /**/x/**/ } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export { //\nx } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var x = 1;\nexport { /**/x/**/ };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var x = 1;\nexport { //\nx };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + + // always - empty object + { code: 'var foo = {};', options: ['always'] }, + + // always - objectsInObjects + { + code: "var obj = { 'foo': { 'bar': 1, 'baz': 2 }};", + options: ['always', { objectsInObjects: false }], + }, + { + code: 'var a = { noop: function () {} };', + options: ['always', { objectsInObjects: false }], + }, + { + code: 'var { y: { z }} = x', + options: ['always', { objectsInObjects: false }], + parserOptions: { ecmaVersion: 6 }, + }, + + // always - arraysInObjects + { + code: "var obj = { 'foo': [ 1, 2 ]};", + options: ['always', { arraysInObjects: false }], + }, + { + code: 'var a = { thingInList: list[0] };', + options: ['always', { arraysInObjects: false }], + }, + + // always - arraysInObjects, objectsInObjects + { + code: "var obj = { 'qux': [ 1, 2 ], 'foo': { 'bar': 1, 'baz': 2 }};", + options: ['always', { arraysInObjects: false, objectsInObjects: false }], + }, + + // always - arraysInObjects, objectsInObjects (reverse) + { + code: "var obj = { 'foo': { 'bar': 1, 'baz': 2 }, 'qux': [ 1, 2 ]};", + options: ['always', { arraysInObjects: false, objectsInObjects: false }], + }, + + // never + { code: 'var obj = {foo: bar,\nbaz: qux\n};', options: ['never'] }, + { code: 'var obj = {\nfoo: bar,\nbaz: qux};', options: ['never'] }, + + // never - object literals + { code: 'var obj = {foo: bar, baz: qux};', options: ['never'] }, + { code: 'var obj = {foo: {bar: quxx}, baz: qux};', options: ['never'] }, + { code: 'var obj = {foo: {\nbar: quxx}, baz: qux\n};', options: ['never'] }, + { code: 'var obj = {foo: {\nbar: quxx\n}, baz: qux};', options: ['never'] }, + { code: 'var obj = {\nfoo: bar,\nbaz: qux\n};', options: ['never'] }, + { code: 'var obj = {foo: bar, baz: qux /* */};', options: ['never'] }, + { code: 'var obj = {/* */ foo: bar, baz: qux};', options: ['never'] }, + { code: 'var obj = {//\n foo: bar};', options: ['never'] }, + { + code: 'var obj = { // line comment exception\n foo: bar};', + options: ['never'], + }, + + // never - destructuring + { + code: 'var {x} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {x, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {x,y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\nx,y\n} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {x = 10} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {x = 10, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {x: {z}, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\nx: {z\n}, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\ny,\n} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {y,} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {y:x} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {/* */ y} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {y /* */} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {//\n y} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { // line comment exception\n y} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + + // never - import / export + { + code: "import door from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import * as door from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {/* */ door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {/* */ door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {door /* */} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {door /* */} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {//\n door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {//\n door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var door = foo;\nexport {//\n door}', + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { // line comment exception\n door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export { // line comment exception\n door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var door = foo; export { // line comment exception\n door}', + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {\ndoor} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {\ndoor\n} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {house,mouse} from 'caravan'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {house, mouse} from 'caravan'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var door = 0;export {door}', + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import x, {bar} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import x, {bar, baz} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {bar as y} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {x,} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {\nx,\n} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {x,} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {\nx,\n} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + + // never - empty object + { code: 'var foo = {};', options: ['never'] }, + + // never - objectsInObjects + { + code: "var obj = {'foo': {'bar': 1, 'baz': 2} };", + options: ['never', { objectsInObjects: true }], + }, + + /* + * https://github.com/eslint/eslint/issues/3658 + * Empty cases. + */ + { code: 'var {} = foo;', parserOptions: { ecmaVersion: 6 } }, + { code: 'var [] = foo;', parserOptions: { ecmaVersion: 6 } }, + { code: 'var {a: {}} = foo;', parserOptions: { ecmaVersion: 6 } }, + { code: 'var {a: []} = foo;', parserOptions: { ecmaVersion: 6 } }, + { + code: "import {} from 'foo';", + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {} from 'foo';", + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'export {};', + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var {} = foo;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var [] = foo;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {a: {}} = foo;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {a: []} = foo;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: "import {} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'export {};', + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + + // https://github.com/eslint/eslint/issues/6940 + { + code: 'function foo ({a, b}: Props) {\n}', + options: ['never'], + }, + + // default - object literal types + { + code: 'const x:{}', + }, + { + code: 'const x:{ }', + }, + { + code: 'const x:{f: number}', + }, + { + code: 'const x:{ // line-comment\nf: number\n}', + }, + { + code: 'const x:{// line-comment\nf: number\n}', + }, + { + code: 'const x:{/* inline-comment */f: number/* inline-comment */}', + }, + { + code: 'const x:{\nf: number\n}', + }, + { + code: 'const x:{f: {g: number}}', + }, + { + code: 'const x:{f: [number]}', + }, + { + code: 'const x:{[key: string]: value}', + }, + { + code: 'const x:{[key: string]: [number]}', + }, + + // never - object literal types + { + code: 'const x:{f: {g: number} }', + options: ['never', { objectsInObjects: true }], + }, + { + code: 'const x:{f: {g: number}}', + options: ['never', { objectsInObjects: false }], + }, + { + code: 'const x:{f: () => {g: number} }', + options: ['never', { objectsInObjects: true }], + }, + { + code: 'const x:{f: () => {g: number}}', + options: ['never', { objectsInObjects: false }], + }, + { + code: 'const x:{f: [number] }', + options: ['never', { arraysInObjects: true }], + }, + { + code: 'const x:{f: [ number ]}', + options: ['never', { arraysInObjects: false }], + }, + { + code: 'const x:{ [key: string]: value}', + options: ['never', { arraysInObjects: true }], + }, + { + code: 'const x:{[key: string]: value}', + options: ['never', { arraysInObjects: false }], + }, + { + code: 'const x:{ [key: string]: [number] }', + options: ['never', { arraysInObjects: true }], + }, + { + code: 'const x:{[key: string]: [number]}', + options: ['never', { arraysInObjects: false }], + }, + + // always - object literal types + { + code: 'const x:{}', + options: ['always'], + }, + { + code: 'const x:{ }', + options: ['always'], + }, + { + code: 'const x:{ f: number }', + options: ['always'], + }, + { + code: 'const x:{ // line-comment\nf: number\n}', + options: ['always'], + }, + { + code: 'const x:{ /* inline-comment */ f: number /* inline-comment */ }', + options: ['always'], + }, + { + code: 'const x:{\nf: number\n}', + options: ['always'], + }, + { + code: 'const x:{ f: [number] }', + options: ['always'], + }, + + // always - objectsInObjects + { + code: 'const x:{ f: { g: number } }', + options: ['always', { objectsInObjects: true }], + }, + { + code: 'const x:{ f: { g: number }}', + options: ['always', { objectsInObjects: false }], + }, + { + code: 'const x:{ f: () => { g: number } }', + options: ['always', { objectsInObjects: true }], + }, + { + code: 'const x:{ f: () => { g: number }}', + options: ['always', { objectsInObjects: false }], + }, + + // always - arraysInObjects + { + code: 'const x:{ f: [number] }', + options: ['always', { arraysInObjects: true }], + }, + { + code: 'const x:{ f: [ number ]}', + options: ['always', { arraysInObjects: false }], + }, + { + code: 'const x:{ [key: string]: value }', + options: ['always', { arraysInObjects: true }], + }, + { + code: 'const x:{[key: string]: value }', + options: ['always', { arraysInObjects: false }], + }, + { + code: 'const x:{ [key: string]: [number] }', + options: ['always', { arraysInObjects: true }], + }, + { + code: 'const x:{[key: string]: [number]}', + options: ['always', { arraysInObjects: false }], + }, + ], + + invalid: [ + { + code: "import {bar} from 'foo.js';", + output: "import { bar } from 'foo.js';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "import { bar as y} from 'foo.js';", + output: "import { bar as y } from 'foo.js';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 18, + endLine: 1, + endColumn: 19, + }, + ], + }, + { + code: "import {bar as y} from 'foo.js';", + output: "import { bar as y } from 'foo.js';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 17, + endLine: 1, + endColumn: 18, + }, + ], + }, + { + code: "import { bar} from 'foo.js';", + output: "import { bar } from 'foo.js';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + ], + }, + { + code: "import x, { bar} from 'foo';", + output: "import x, { bar } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: "import x, { bar/* */} from 'foo';", + output: "import x, { bar/* */ } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 21, + endLine: 1, + endColumn: 22, + }, + ], + }, + { + code: "import x, {/* */bar } from 'foo';", + output: "import x, { /* */bar } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: "import x, {//\n bar } from 'foo';", + output: "import x, { //\n bar } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: "import x, { bar, baz} from 'foo';", + output: "import x, { bar, baz } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 21, + endLine: 1, + endColumn: 22, + }, + ], + }, + { + code: "import x, {bar} from 'foo';", + output: "import x, { bar } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 15, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: "import x, {bar, baz} from 'foo';", + output: "import x, { bar, baz } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 20, + endLine: 1, + endColumn: 21, + }, + ], + }, + { + code: "import {bar,} from 'foo';", + output: "import { bar, } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + ], + }, + { + code: "import { bar, } from 'foo';", + output: "import {bar,} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "import { /* */ bar, /* */ } from 'foo';", + output: "import {/* */ bar, /* */} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 26, + endLine: 1, + endColumn: 27, + }, + ], + }, + { + code: 'var bar = 0;\nexport {bar};', + output: 'var bar = 0;\nexport { bar };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 8, + endLine: 2, + endColumn: 9, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 12, + }, + ], + }, + { + code: 'var bar = 0;\nexport {/* */ bar /* */};', + output: 'var bar = 0;\nexport { /* */ bar /* */ };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 8, + endLine: 2, + endColumn: 9, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 24, + endLine: 2, + endColumn: 25, + }, + ], + }, + { + code: 'var bar = 0;\nexport {//\n bar };', + output: 'var bar = 0;\nexport { //\n bar };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 8, + endLine: 2, + endColumn: 9, + }, + ], + }, + { + code: 'var bar = 0;\nexport { /* */ bar /* */ };', + output: 'var bar = 0;\nexport {/* */ bar /* */};', + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 9, + endLine: 2, + endColumn: 10, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 25, + endLine: 2, + endColumn: 26, + }, + ], + }, + + // always - arraysInObjects + { + code: "var obj = { 'foo': [ 1, 2 ] };", + output: "var obj = { 'foo': [ 1, 2 ]};", + options: ['always', { arraysInObjects: false }], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 28, + endLine: 1, + endColumn: 29, + }, + ], + }, + { + code: "var obj = { 'foo': [ 1, 2 ] , 'bar': [ 'baz', 'qux' ] };", + output: "var obj = { 'foo': [ 1, 2 ] , 'bar': [ 'baz', 'qux' ]};", + options: ['always', { arraysInObjects: false }], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 54, + endLine: 1, + endColumn: 55, + }, + ], + }, + + // always-objectsInObjects + { + code: "var obj = { 'foo': { 'bar': 1, 'baz': 2 } };", + output: "var obj = { 'foo': { 'bar': 1, 'baz': 2 }};", + options: ['always', { objectsInObjects: false }], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 42, + endLine: 1, + endColumn: 43, + }, + ], + }, + { + code: "var obj = { 'foo': [ 1, 2 ] , 'bar': { 'baz': 1, 'qux': 2 } };", + output: "var obj = { 'foo': [ 1, 2 ] , 'bar': { 'baz': 1, 'qux': 2 }};", + options: ['always', { objectsInObjects: false }], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 60, + endLine: 1, + endColumn: 61, + }, + ], + }, + + // always-destructuring trailing comma + { + code: 'var { a,} = x;', + output: 'var { a, } = x;', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: 'var {a, } = x;', + output: 'var {a,} = x;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: 'var {a:b } = x;', + output: 'var {a:b} = x;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: 'var { a:b } = x;', + output: 'var {a:b} = x;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 10, + endLine: 1, + endColumn: 11, + }, + ], + }, + { + code: 'var { a:b } = x;', + output: 'var {a:b} = x;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 6, + endLine: 1, + endColumn: 8, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 11, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: 'var { a:b } = x;', + output: 'var {a:b} = x;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 6, + endLine: 1, + endColumn: 9, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 12, + endLine: 1, + endColumn: 16, + }, + ], + }, + + // never-objectsInObjects + { + code: "var obj = {'foo': {'bar': 1, 'baz': 2}};", + output: "var obj = {'foo': {'bar': 1, 'baz': 2} };", + options: ['never', { objectsInObjects: true }], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 39, + endLine: 1, + endColumn: 40, + }, + ], + }, + { + code: "var obj = {'foo': [1, 2] , 'bar': {'baz': 1, 'qux': 2}};", + output: "var obj = {'foo': [1, 2] , 'bar': {'baz': 1, 'qux': 2} };", + options: ['never', { objectsInObjects: true }], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 55, + endLine: 1, + endColumn: 56, + }, + ], + }, + + // always & never + { + code: 'var obj = {foo: bar, baz: qux};', + output: 'var obj = { foo: bar, baz: qux };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 30, + endLine: 1, + endColumn: 31, + }, + ], + }, + { + code: 'var obj = {foo: bar, baz: qux };', + output: 'var obj = { foo: bar, baz: qux };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var obj = {/* */foo: bar, baz: qux };', + output: 'var obj = { /* */foo: bar, baz: qux };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var obj = {//\n foo: bar };', + output: 'var obj = { //\n foo: bar };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux};', + output: 'var obj = { foo: bar, baz: qux };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 31, + endLine: 1, + endColumn: 32, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux/* */};', + output: 'var obj = { foo: bar, baz: qux/* */ };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 36, + endLine: 1, + endColumn: 37, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux };', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 31, + endLine: 1, + endColumn: 32, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux };', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 14, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 32, + endLine: 1, + endColumn: 33, + }, + ], + }, + { + code: 'var obj = {foo: bar, baz: qux };', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 30, + endLine: 1, + endColumn: 31, + }, + ], + }, + { + code: 'var obj = {foo: bar, baz: qux };', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 30, + endLine: 1, + endColumn: 32, + }, + ], + }, + { + code: 'var obj = {foo: bar, baz: qux /* */ };', + output: 'var obj = {foo: bar, baz: qux /* */};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 36, + endLine: 1, + endColumn: 37, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux};', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux};', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 14, + }, + ], + }, + { + code: 'var obj = { /* */ foo: bar, baz: qux};', + output: 'var obj = {/* */ foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: 'var obj = { // line comment exception\n foo: bar };', + output: 'var obj = { // line comment exception\n foo: bar};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 2, + column: 10, + endLine: 2, + endColumn: 11, + }, + ], + }, + { + code: 'var obj = { foo: { bar: quxx}, baz: qux};', + output: 'var obj = {foo: {bar: quxx}, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 19, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: 'var obj = {foo: {bar: quxx }, baz: qux };', + output: 'var obj = {foo: {bar: quxx}, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 27, + endLine: 1, + endColumn: 28, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 39, + endLine: 1, + endColumn: 40, + }, + ], + }, + { + code: 'export const thing = {value: 1 };', + output: 'export const thing = { value: 1 };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 22, + endLine: 1, + endColumn: 23, + }, + ], + }, + + // destructuring + { + code: 'var {x, y} = y', + output: 'var { x, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 10, + endLine: 1, + endColumn: 11, + }, + ], + }, + { + code: 'var { x, y} = y', + output: 'var { x, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var { x, y/* */} = y', + output: 'var { x, y/* */ } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: 'var {/* */x, y } = y', + output: 'var { /* */x, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + ], + }, + { + code: 'var {//\n x } = y', + output: 'var { //\n x } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + ], + }, + { + code: 'var { x, y } = y', + output: 'var {x, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var {x, y } = y', + output: 'var {x, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 10, + endLine: 1, + endColumn: 11, + }, + ], + }, + { + code: 'var {x, y/* */ } = y', + output: 'var {x, y/* */} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 15, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: 'var { /* */x, y} = y', + output: 'var {/* */x, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + ], + }, + { + code: 'var { x=10} = y', + output: 'var { x=10 } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var {x=10 } = y', + output: 'var { x=10 } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + ], + }, + + // never - arraysInObjects + { + code: "var obj = {'foo': [1, 2]};", + output: "var obj = {'foo': [1, 2] };", + options: ['never', { arraysInObjects: true }], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 25, + endLine: 1, + endColumn: 26, + }, + ], + }, + { + code: "var obj = {'foo': [1, 2] , 'bar': ['baz', 'qux']};", + output: "var obj = {'foo': [1, 2] , 'bar': ['baz', 'qux'] };", + options: ['never', { arraysInObjects: true }], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 49, + endLine: 1, + endColumn: 50, + }, + ], + }, + + // https://github.com/eslint/eslint/issues/6940 + { + code: 'function foo ({a, b }: Props) {\n}', + output: 'function foo ({a, b}: Props) {\n}', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 20, + endLine: 1, + endColumn: 21, + }, + ], + }, + + // object literal types + { + code: 'type x = { f: number }', + output: 'type x = {f: number}', + errors: [ + { messageId: 'unexpectedSpaceAfter' }, + { messageId: 'unexpectedSpaceBefore' }, + ], + }, + { + code: 'type x = { f: number}', + output: 'type x = {f: number}', + errors: [{ messageId: 'unexpectedSpaceAfter' }], + }, + { + code: 'type x = {f: number }', + output: 'type x = {f: number}', + errors: [{ messageId: 'unexpectedSpaceBefore' }], + }, + { + code: 'type x = {f: number}', + output: 'type x = { f: number }', + options: ['always'], + errors: [ + { messageId: 'requireSpaceAfter' }, + { messageId: 'requireSpaceBefore' }, + ], + }, + { + code: 'type x = {f: number }', + output: 'type x = { f: number }', + options: ['always'], + errors: [{ messageId: 'requireSpaceAfter' }], + }, + { + code: 'type x = { f: number}', + output: 'type x = { f: number }', + options: ['always'], + errors: [{ messageId: 'requireSpaceBefore' }], + }, + ], +}); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index ca726d6fb837..49e48541f873 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -830,3 +830,28 @@ declare module 'eslint/lib/rules/prefer-const' { >; export = rule; } + +declare module 'eslint/lib/rules/object-curly-spacing' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + + const rule: TSESLint.RuleModule< + | 'requireSpaceBefore' + | 'requireSpaceAfter' + | 'unexpectedSpaceBefore' + | 'unexpectedSpaceAfter', + [ + 'always' | 'never', + { + arraysInObjects?: boolean; + objectsInObjects?: boolean; + }?, + ], + { + ObjectPattern(node: TSESTree.ObjectPattern): void; + ObjectExpression(node: TSESTree.ObjectExpression): void; + ImportDeclaration(node: TSESTree.ImportDeclaration): void; + ExportNamedDeclaration(node: TSESTree.ExportNamedDeclaration): void; + } + >; + export = rule; +} From 60c5dcd6868bc08f5c8cd47b89e0ed327f17c576 Mon Sep 17 00:00:00 2001 From: James Henry Date: Mon, 18 Jan 2021 18:02:18 +0000 Subject: [PATCH 3/3] chore: publish v4.14.0 --- CHANGELOG.md | 12 ++++++++++++ lerna.json | 2 +- packages/eslint-plugin-internal/CHANGELOG.md | 8 ++++++++ packages/eslint-plugin-internal/package.json | 4 ++-- packages/eslint-plugin-tslint/CHANGELOG.md | 8 ++++++++ packages/eslint-plugin-tslint/package.json | 6 +++--- packages/eslint-plugin/CHANGELOG.md | 12 ++++++++++++ packages/eslint-plugin/package.json | 6 +++--- packages/experimental-utils/CHANGELOG.md | 8 ++++++++ packages/experimental-utils/package.json | 8 ++++---- packages/parser/CHANGELOG.md | 11 +++++++++++ packages/parser/package.json | 12 ++++++------ packages/scope-manager/CHANGELOG.md | 11 +++++++++++ packages/scope-manager/package.json | 8 ++++---- packages/shared-fixtures/CHANGELOG.md | 8 ++++++++ packages/shared-fixtures/package.json | 2 +- packages/types/CHANGELOG.md | 8 ++++++++ packages/types/package.json | 2 +- packages/typescript-estree/CHANGELOG.md | 8 ++++++++ packages/typescript-estree/package.json | 8 ++++---- packages/visitor-keys/CHANGELOG.md | 8 ++++++++ packages/visitor-keys/package.json | 4 ++-- 22 files changed, 133 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b89c408ec7..f2cf9b51d1d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.14.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.13.0...v4.14.0) (2021-01-18) + + +### Features + +* add support for decorator metadata in scope analysis and in consistent-type-imports ([#2751](https://github.com/typescript-eslint/typescript-eslint/issues/2751)) ([445e416](https://github.com/typescript-eslint/typescript-eslint/commit/445e416878b27a54bf07c2d3b84dabd7b06e51bc)), closes [#2559](https://github.com/typescript-eslint/typescript-eslint/issues/2559) +* **eslint-plugin:** add `object-curly-spacing` rule ([#2892](https://github.com/typescript-eslint/typescript-eslint/issues/2892)) ([32bd18d](https://github.com/typescript-eslint/typescript-eslint/commit/32bd18de80f4f8388717d0f0c16d493234362aa5)) + + + + + # [4.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.12.0...v4.13.0) (2021-01-11) diff --git a/lerna.json b/lerna.json index 480c0ab5f98c..d55cb70f0537 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "4.13.0", + "version": "4.14.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index 6ac7f11060e0..d57138c4ad58 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.14.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.13.0...v4.14.0) (2021-01-18) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal + + + + + # [4.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.12.0...v4.13.0) (2021-01-11) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index c050e1a20992..546896073089 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-internal", - "version": "4.13.0", + "version": "4.14.0", "private": true, "main": "dist/index.js", "scripts": { @@ -14,7 +14,7 @@ }, "dependencies": { "@types/prettier": "*", - "@typescript-eslint/experimental-utils": "4.13.0", + "@typescript-eslint/experimental-utils": "4.14.0", "prettier": "*" } } diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index a643bbfe6364..ad6a3fafdde1 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.14.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.13.0...v4.14.0) (2021-01-18) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + + + + + # [4.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.12.0...v4.13.0) (2021-01-11) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index 2008ec82198c..7d02cd87ff07 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-tslint", - "version": "4.13.0", + "version": "4.14.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -38,7 +38,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "4.13.0", + "@typescript-eslint/experimental-utils": "4.14.0", "lodash": "^4.17.15" }, "peerDependencies": { @@ -48,6 +48,6 @@ }, "devDependencies": { "@types/lodash": "*", - "@typescript-eslint/parser": "4.13.0" + "@typescript-eslint/parser": "4.14.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index bcb5f1b3b8d6..af2174cba993 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.14.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.13.0...v4.14.0) (2021-01-18) + + +### Features + +* add support for decorator metadata in scope analysis and in consistent-type-imports ([#2751](https://github.com/typescript-eslint/typescript-eslint/issues/2751)) ([445e416](https://github.com/typescript-eslint/typescript-eslint/commit/445e416878b27a54bf07c2d3b84dabd7b06e51bc)), closes [#2559](https://github.com/typescript-eslint/typescript-eslint/issues/2559) +* **eslint-plugin:** add `object-curly-spacing` rule ([#2892](https://github.com/typescript-eslint/typescript-eslint/issues/2892)) ([32bd18d](https://github.com/typescript-eslint/typescript-eslint/commit/32bd18de80f4f8388717d0f0c16d493234362aa5)) + + + + + # [4.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.12.0...v4.13.0) (2021-01-11) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 87b1178bf425..d8edd5bce698 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "4.13.0", + "version": "4.14.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -42,8 +42,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "4.13.0", - "@typescript-eslint/scope-manager": "4.13.0", + "@typescript-eslint/experimental-utils": "4.14.0", + "@typescript-eslint/scope-manager": "4.14.0", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.15", diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index e7056ae1cc49..0f77d4718bd4 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.14.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.13.0...v4.14.0) (2021-01-18) + +**Note:** Version bump only for package @typescript-eslint/experimental-utils + + + + + # [4.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.12.0...v4.13.0) (2021-01-11) **Note:** Version bump only for package @typescript-eslint/experimental-utils diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 446f71254a7f..c084e2430103 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "4.13.0", + "version": "4.14.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -40,9 +40,9 @@ }, "dependencies": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.13.0", - "@typescript-eslint/types": "4.13.0", - "@typescript-eslint/typescript-estree": "4.13.0", + "@typescript-eslint/scope-manager": "4.14.0", + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/typescript-estree": "4.14.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" }, diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 58e16f75421a..e0443ad166f3 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.14.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.13.0...v4.14.0) (2021-01-18) + + +### Features + +* add support for decorator metadata in scope analysis and in consistent-type-imports ([#2751](https://github.com/typescript-eslint/typescript-eslint/issues/2751)) ([445e416](https://github.com/typescript-eslint/typescript-eslint/commit/445e416878b27a54bf07c2d3b84dabd7b06e51bc)), closes [#2559](https://github.com/typescript-eslint/typescript-eslint/issues/2559) + + + + + # [4.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.12.0...v4.13.0) (2021-01-11) **Note:** Version bump only for package @typescript-eslint/parser diff --git a/packages/parser/package.json b/packages/parser/package.json index 2fa108132913..0e9f82c64cdc 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "4.13.0", + "version": "4.14.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -44,15 +44,15 @@ "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "4.13.0", - "@typescript-eslint/types": "4.13.0", - "@typescript-eslint/typescript-estree": "4.13.0", + "@typescript-eslint/scope-manager": "4.14.0", + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/typescript-estree": "4.14.0", "debug": "^4.1.1" }, "devDependencies": { "@types/glob": "*", - "@typescript-eslint/experimental-utils": "4.13.0", - "@typescript-eslint/shared-fixtures": "4.13.0", + "@typescript-eslint/experimental-utils": "4.14.0", + "@typescript-eslint/shared-fixtures": "4.14.0", "glob": "*", "typescript": "*" }, diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index 9323d04481a2..2cf11abdb397 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.14.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.13.0...v4.14.0) (2021-01-18) + + +### Features + +* add support for decorator metadata in scope analysis and in consistent-type-imports ([#2751](https://github.com/typescript-eslint/typescript-eslint/issues/2751)) ([445e416](https://github.com/typescript-eslint/typescript-eslint/commit/445e416878b27a54bf07c2d3b84dabd7b06e51bc)), closes [#2559](https://github.com/typescript-eslint/typescript-eslint/issues/2559) + + + + + # [4.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.12.0...v4.13.0) (2021-01-11) **Note:** Version bump only for package @typescript-eslint/scope-manager diff --git a/packages/scope-manager/package.json b/packages/scope-manager/package.json index cea3d7616577..3e602a08dbff 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "4.13.0", + "version": "4.14.0", "description": "TypeScript scope analyser for ESLint", "keywords": [ "eslint", @@ -39,12 +39,12 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "4.13.0", - "@typescript-eslint/visitor-keys": "4.13.0" + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/visitor-keys": "4.14.0" }, "devDependencies": { "@types/glob": "*", - "@typescript-eslint/typescript-estree": "4.13.0", + "@typescript-eslint/typescript-estree": "4.14.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 1d199cd813b1..a5372be7f103 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.14.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.13.0...v4.14.0) (2021-01-18) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [4.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.12.0...v4.13.0) (2021-01-11) **Note:** Version bump only for package @typescript-eslint/shared-fixtures diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index ca4814529850..726da455b4c3 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "4.13.0", + "version": "4.14.0", "private": true, "scripts": { "build": "tsc -b tsconfig.build.json", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index b79e2c221f29..e40692d8614f 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.14.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.13.0...v4.14.0) (2021-01-18) + +**Note:** Version bump only for package @typescript-eslint/types + + + + + # [4.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.12.0...v4.13.0) (2021-01-11) **Note:** Version bump only for package @typescript-eslint/types diff --git a/packages/types/package.json b/packages/types/package.json index 326f50624515..dc575b3be566 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "4.13.0", + "version": "4.14.0", "description": "Types for the TypeScript-ESTree AST spec", "keywords": [ "eslint", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index d57dd5006ea7..87064eac93b7 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.14.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.13.0...v4.14.0) (2021-01-18) + +**Note:** Version bump only for package @typescript-eslint/typescript-estree + + + + + # [4.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.12.0...v4.13.0) (2021-01-11) **Note:** Version bump only for package @typescript-eslint/typescript-estree diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index e773e01c2400..cbfc388b5f42 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "4.13.0", + "version": "4.14.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -41,8 +41,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "4.13.0", - "@typescript-eslint/visitor-keys": "4.13.0", + "@typescript-eslint/types": "4.14.0", + "@typescript-eslint/visitor-keys": "4.14.0", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -61,7 +61,7 @@ "@types/lodash": "*", "@types/semver": "^7.1.0", "@types/tmp": "^0.2.0", - "@typescript-eslint/shared-fixtures": "4.13.0", + "@typescript-eslint/shared-fixtures": "4.14.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index b5fc6e7eaa44..c0cbad34c318 100644 --- a/packages/visitor-keys/CHANGELOG.md +++ b/packages/visitor-keys/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.14.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.13.0...v4.14.0) (2021-01-18) + +**Note:** Version bump only for package @typescript-eslint/visitor-keys + + + + + # [4.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v4.12.0...v4.13.0) (2021-01-11) **Note:** Version bump only for package @typescript-eslint/visitor-keys diff --git a/packages/visitor-keys/package.json b/packages/visitor-keys/package.json index a317676556fb..4774308be6a3 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "4.13.0", + "version": "4.14.0", "description": "Visitor keys used to help traverse the TypeScript-ESTree AST", "keywords": [ "eslint", @@ -38,7 +38,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "4.13.0", + "@typescript-eslint/types": "4.14.0", "eslint-visitor-keys": "^2.0.0" }, "devDependencies": {