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": { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy