From 6c9595d7943316e97af8072aaee3cd6d02ed89ad Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 28 Jan 2020 15:39:59 -0800 Subject: [PATCH 1/4] Allow 'find references' to work on most declaration keywords --- src/compiler/checker.ts | 75 +++++++++++++++++-- src/compiler/types.ts | 1 + src/harness/fourslashImpl.ts | 26 +++++-- src/services/callHierarchy.ts | 2 +- src/services/findAllReferences.ts | 15 +++- src/services/services.ts | 2 +- src/services/utilities.ts | 18 +++++ .../referencesForDeclarationKeywords.ts | 30 ++++++++ .../referencesForExpressionKeywords.ts | 19 +++++ .../cases/fourslash/referencesForModifiers.ts | 27 +++++++ .../referencesForStatementKeywords.ts | 10 +++ 11 files changed, 209 insertions(+), 16 deletions(-) create mode 100644 tests/cases/fourslash/referencesForDeclarationKeywords.ts create mode 100644 tests/cases/fourslash/referencesForExpressionKeywords.ts create mode 100644 tests/cases/fourslash/referencesForModifiers.ts create mode 100644 tests/cases/fourslash/referencesForStatementKeywords.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 572993e456058..91a3c3c2a94e0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -413,9 +413,13 @@ namespace ts { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; }, - getSymbolAtLocation: node => { + getSymbolAtLocation: (node: Node, includeKeywords?: boolean) => { node = getParseTreeNode(node); - return node ? getSymbolAtLocation(node) : undefined; + if (node) { + return includeKeywords ? + getSymbolAtLocation(node) ?? getSymbolAtKeyword(node) : + getSymbolAtLocation(node); + } }, getShorthandAssignmentValueSymbol: node => { node = getParseTreeNode(node); @@ -34249,7 +34253,7 @@ namespace ts { if (constructorDeclaration && constructorDeclaration.kind === SyntaxKind.Constructor) { return (constructorDeclaration.parent).symbol; } - return undefined; + break; case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: @@ -34287,10 +34291,69 @@ namespace ts { return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal) : undefined; case SyntaxKind.ExportKeyword: - return isExportAssignment(node.parent) ? Debug.assertDefined(node.parent.symbol) : undefined; + if (isExportAssignment(node.parent)) { + return Debug.assertDefined(node.parent.symbol); + } + break; + } + } - default: - return undefined; + /** + * Gets the symbol related to the provided location, if it that location is a keyword. + * These additional keywords are normally only used to resolve references but would + * not be used for document highlights, quickinfo, etc. + */ + function getSymbolAtKeyword(node: Node): Symbol | undefined { + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; + } + + const { parent } = node; + + // If the node is a modifier of its parent, get the symbol for the parent. + if (isModifier(node) && contains(parent.modifiers, node)) { + return getSymbolOfNode(parent); + } + + switch (node.kind) { + case SyntaxKind.InterfaceKeyword: + case SyntaxKind.EnumKeyword: + case SyntaxKind.NamespaceKeyword: + case SyntaxKind.ModuleKeyword: + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + return getSymbolOfNode(parent); + + case SyntaxKind.TypeKeyword: + if (isTypeAliasDeclaration(parent)) { + return getSymbolOfNode(parent); + } + if (isImportClause(parent)) { + return getSymbolAtLocation(parent.parent.moduleSpecifier); + } + if (isLiteralImportTypeNode(parent)) { + return getSymbolAtLocation(parent.argument.literal); + } + break; + + case SyntaxKind.VarKeyword: + case SyntaxKind.ConstKeyword: + case SyntaxKind.LetKeyword: + if (isVariableDeclarationList(parent) && parent.declarations.length === 1) { + return getSymbolOfNode(parent.declarations[0]); + } + break; + } + if (node.kind === SyntaxKind.NewKeyword && isNewExpression(parent) || + node.kind === SyntaxKind.VoidKeyword && isVoidExpression(parent) || + node.kind === SyntaxKind.TypeOfKeyword && isTypeOfExpression(parent) || + node.kind === SyntaxKind.AwaitKeyword && isAwaitExpression(parent) || + node.kind === SyntaxKind.YieldKeyword && isYieldExpression(parent) || + node.kind === SyntaxKind.DeleteKeyword && isDeleteExpression(parent)) { + if (parent.expression) { + return getSymbolAtLocation(skipOuterExpressions(parent.expression)); + } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5565bfa8c2cbb..0c9483e4eaeb8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3405,6 +3405,7 @@ namespace ts { getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol | undefined; + /* @internal*/ getSymbolAtLocation(node: Node, includeKeywords?: boolean): Symbol | undefined; // eslint-disable-line @typescript-eslint/unified-signatures getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): Symbol[]; /** * The function returns the value (local variable) symbol of an identifier in the short-hand property assignment. diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index ea6ccd68fbc35..0c37c4e330e83 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -997,18 +997,34 @@ namespace FourSlash { definition: string | { text: string, range: ts.TextSpan }; references: ts.ReferenceEntry[]; } + interface RangeMarkerData { + isWriteAccess?: boolean, + isDefinition?: boolean, + isInString?: true, + contextRangeIndex?: number, + contextRangeDelta?: number + } const fullExpected = ts.map(parts, ({ definition, ranges }) => ({ definition: typeof definition === "string" ? definition : { ...definition, range: ts.createTextSpanFromRange(definition.range) }, references: ranges.map(r => { - const { isWriteAccess = false, isDefinition = false, isInString, contextRangeIndex } = (r.marker && r.marker.data || {}) as { isWriteAccess?: boolean, isDefinition?: boolean, isInString?: true, contextRangeIndex?: number }; + const { isWriteAccess = false, isDefinition = false, isInString, contextRangeIndex, contextRangeDelta } = (r.marker && r.marker.data || {}) as RangeMarkerData; + let contextSpan: ts.TextSpan | undefined; + if (contextRangeDelta !== undefined) { + const allRanges = this.getRanges(); + const index = allRanges.indexOf(r); + if (index !== -1) { + contextSpan = ts.createTextSpanFromRange(allRanges[index + contextRangeDelta]); + } + } + else if (contextRangeIndex !== undefined) { + contextSpan = ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]); + } return { fileName: r.fileName, textSpan: ts.createTextSpanFromRange(r), isWriteAccess, isDefinition, - ...(contextRangeIndex !== undefined ? - { contextSpan: ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]) } : - undefined), + ...(contextSpan ? { contextSpan } : undefined), ...(isInString ? { isInString: true } : undefined), }; }), @@ -1032,7 +1048,7 @@ namespace FourSlash { } public verifyNoReferences(markerNameOrRange?: string | Range) { - if (markerNameOrRange) this.goToMarkerOrRange(markerNameOrRange); + if (markerNameOrRange !== undefined) this.goToMarkerOrRange(markerNameOrRange); const refs = this.getReferencesAtCaret(); if (refs && refs.length) { this.raiseError(`Expected getReferences to fail, but saw references: ${stringify(refs)}`); diff --git a/src/services/callHierarchy.ts b/src/services/callHierarchy.ts index 838f9355d3dca..41f5c138e6b42 100644 --- a/src/services/callHierarchy.ts +++ b/src/services/callHierarchy.ts @@ -293,7 +293,7 @@ namespace ts.CallHierarchy { return []; } const location = getCallHierarchyDeclarationReferenceNode(declaration); - const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, /*options*/ undefined, convertEntryToCallSite), isDefined); + const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, { keywords: true }, convertEntryToCallSite), isDefined); return calls ? group(calls, getCallSiteGroupKey, entries => convertCallSiteGroupToIncomingCall(program, entries)) : []; } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index b4f1c19837dd1..d3b3aebed3a0f 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -187,11 +187,15 @@ namespace ts.FindAllReferences { * Default is false for backwards compatibility. */ readonly providePrefixAndSuffixTextForRename?: boolean; + /** + * If the source is a modifier or declaration keyword, find references to its parent declaration. + */ + readonly keywords?: boolean; } export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { const node = getTouchingPropertyName(sourceFile, position); - const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken); + const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, { keywords: true }); const checker = program.getTypeChecker(); return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined(referencedSymbols, ({ definition, references }) => // Only include referenced symbols that have a valid definition. @@ -229,7 +233,7 @@ namespace ts.FindAllReferences { } else { // Perform "Find all References" and retrieve only those that are implementations - return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true }); + return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, keywords: true }); } } @@ -553,7 +557,7 @@ namespace ts.FindAllReferences { } const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node); + const symbol = checker.getSymbolAtLocation(node, options.keywords); // Could not find a symbol e.g. unknown identifier if (!symbol) { @@ -723,6 +727,11 @@ namespace ts.FindAllReferences { /** getReferencedSymbols for special node kinds. */ function getReferencedSymbolsSpecial(node: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { if (isTypeKeyword(node.kind)) { + // A void expression (i.e., `void foo()`) is not special, but the `void` type is. + if (node.kind === SyntaxKind.VoidKeyword && isVoidExpression(node.parent)) { + return undefined; + } + // A modifier readonly (like on a property declaration) is not special; // a readonly type keyword (like `readonly string[]`) is. if (node.kind === SyntaxKind.ReadonlyKeyword && !isReadonlyTypeOperator(node)) { diff --git a/src/services/services.ts b/src/services/services.ts index 1a3eae675e62c..aef4c175d1b77 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1628,7 +1628,7 @@ namespace ts { function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined { synchronizeHostData(); - return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, {}, FindAllReferences.toReferenceEntry); + return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { keywords: true }, FindAllReferences.toReferenceEntry); } function getReferencesWorker(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry): T[] | undefined { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 69010b4611fd1..40da86af5c50b 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -114,6 +114,24 @@ namespace ts { // This might be T["name"], which is actually referencing a property and not a type. So allow both meanings. return SemanticMeaning.Type | SemanticMeaning.Value; } + else if (isModifier(node) && contains(node.parent.modifiers, node)) { + // on the modifier of a declaration + return getMeaningFromDeclaration(node.parent); + } + else if (node.kind === SyntaxKind.ClassKeyword && isClassLike(node.parent) || + node.kind === SyntaxKind.InterfaceKeyword && isInterfaceDeclaration(node.parent) || + node.kind === SyntaxKind.TypeKeyword && isTypeAliasDeclaration(node.parent) || + node.kind === SyntaxKind.EnumKeyword && isEnumDeclaration(node.parent) || + node.kind === SyntaxKind.FunctionKeyword && isFunctionLikeDeclaration(node.parent) || + node.kind === SyntaxKind.GetKeyword && isGetAccessorDeclaration(node.parent) || + node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(node.parent) || + (node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword) && isModuleDeclaration(node.parent)) { + // on the keyword of a declaration + return getMeaningFromDeclaration(node.parent); + } + else if (node.kind === SyntaxKind.TypeKeyword && isImportClause(node.parent) && node.parent.isTypeOnly) { + return getMeaningFromDeclaration(node.parent.parent); + } else { return SemanticMeaning.Value; } diff --git a/tests/cases/fourslash/referencesForDeclarationKeywords.ts b/tests/cases/fourslash/referencesForDeclarationKeywords.ts new file mode 100644 index 0000000000000..885626000eaa6 --- /dev/null +++ b/tests/cases/fourslash/referencesForDeclarationKeywords.ts @@ -0,0 +1,30 @@ +/// + +////[|/*classKeyword*/class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}C|] { +//// [|/*getKeyword*/get [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}e|]() { return 1; }|] +//// [|/*setKeyword*/set [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}e|](v) {}|] +////}|] +////[|/*interfaceKeyword*/interface [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}I|] { }|] +////[|/*typeKeyword*/type [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}T|] = { }|] +////[|/*enumKeyword*/enum [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}E|] { }|] +////[|/*namespaceKeyword*/namespace [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}N|] { }|] +////[|/*moduleKeyword*/module [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}M|] { }|] +////[|/*functionKeyword*/function [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}fn|]() {}|] +////[|/*varKeyword*/var [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeDelta": -1 |}x|];|] +////[|/*letKeyword*/let [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeDelta": -1 |}y|];|] +////[|/*constKeyword*/const [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}z|] = 1;|] + +const [, classDef,, getDef,, setDef,, interfaceDef,, typeDef,, enumDef,, namespaceDef,, moduleDef,, functionDef,, varDef,, letDef,, constDef] = test.ranges(); +verify.referenceGroups("classKeyword", [{ definition: "class C", ranges: [classDef] }]); +for (const keyword of ["getKeyword", "setKeyword"]) { + verify.referenceGroups(keyword, [{ definition: "(property) C.e: number", ranges: [getDef, setDef] }]); +} +verify.referenceGroups("interfaceKeyword", [{ definition: "interface I", ranges: [interfaceDef] }]); +verify.referenceGroups("typeKeyword", [{ definition: "type T = {}", ranges: [typeDef] }]); +verify.referenceGroups("enumKeyword", [{ definition: "enum E", ranges: [enumDef] }]); +verify.referenceGroups("namespaceKeyword", [{ definition: "namespace N", ranges: [namespaceDef] }]); +verify.referenceGroups("moduleKeyword", [{ definition: "namespace M", ranges: [moduleDef] }]); +verify.referenceGroups("functionKeyword", [{ definition: "function fn(): void", ranges: [functionDef] }]); +verify.referenceGroups("varKeyword", [{ definition: "var x: any", ranges: [varDef] }]); +verify.referenceGroups("letKeyword", [{ definition: "let y: any", ranges: [letDef] }]); +verify.referenceGroups("constKeyword", [{ definition: "const z: 1", ranges: [constDef] }]); \ No newline at end of file diff --git a/tests/cases/fourslash/referencesForExpressionKeywords.ts b/tests/cases/fourslash/referencesForExpressionKeywords.ts new file mode 100644 index 0000000000000..fb35ba4ab9adc --- /dev/null +++ b/tests/cases/fourslash/referencesForExpressionKeywords.ts @@ -0,0 +1,19 @@ +/// + +////[|class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}C|] { +//// [|static [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}x|] = 1;|] +////}|] +/////*newKeyword*/new [|C|](); +/////*voidKeyword*/void [|C|]; +/////*typeofKeyword*/typeof [|C|]; +/////*deleteKeyword*/delete [|C|].[|x|]; +////async function* f() { +//// /*yieldKeyword*/yield [|C|]; +//// /*awaitKeyword*/await [|C|]; +////} + +const [, classDef,, xDef, newC, voidC, typeofC, deleteC, deleteCx, yieldC, awaitC] = test.ranges(); +for (const keyword of ["newKeyword", "voidKeyword", "typeofKeyword", "yieldKeyword", "awaitKeyword"]) { + verify.referenceGroups(keyword, [{ definition: "class C", ranges: [classDef, newC, voidC, typeofC, deleteC, yieldC, awaitC] }]); +} +verify.referenceGroups("deleteKeyword", [{ definition: "(property) C.x: number", ranges: [xDef, deleteCx] }]); \ No newline at end of file diff --git a/tests/cases/fourslash/referencesForModifiers.ts b/tests/cases/fourslash/referencesForModifiers.ts new file mode 100644 index 0000000000000..2cfa7eebce77b --- /dev/null +++ b/tests/cases/fourslash/referencesForModifiers.ts @@ -0,0 +1,27 @@ +/// + +////[|/*declareModifier*/declare /*abstractModifier*/abstract class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}C1|] { +//// [|/*staticModifier*/static [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}a|];|] +//// [|/*readonlyModifier*/readonly [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}b|];|] +//// [|/*publicModifier*/public [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}c|];|] +//// [|/*protectedModifier*/protected [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}d|];|] +//// [|/*privateModifier*/private [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}e|];|] +////}|] +////[|/*constModifier*/const enum [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}E|] { +////}|] +////[|/*asyncModifier*/async function [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}fn|]() {}|] +////[|/*exportModifier*/export /*defaultModifier*/[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}default|] class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}C2|] {}|] + +const [, classDef1,, aDef,, bDef,, cDef,, dDef,, eDef,, enumDef,, functionDef,, classDef2Default, classDef2Name] = test.ranges(); +for (const modifier of ["declareModifier", "abstractModifier"]) { + verify.referenceGroups(modifier, [{ definition: "class C1", ranges: [classDef1] }]); +} +verify.referenceGroups("staticModifier", [{ definition: "(property) C1.a: any", ranges: [aDef] }]); +verify.referenceGroups("readonlyModifier", [{ definition: "(property) C1.b: any", ranges: [bDef] }]); +verify.referenceGroups("publicModifier", [{ definition: "(property) C1.c: any", ranges: [cDef] }]); +verify.referenceGroups("protectedModifier", [{ definition: "(property) C1.d: any", ranges: [dDef] }]); +verify.referenceGroups("privateModifier", [{ definition: "(property) C1.e: any", ranges: [eDef] }]); +verify.referenceGroups("constModifier", [{ definition: "const enum E", ranges: [enumDef] }]); +verify.referenceGroups("asyncModifier", [{ definition: "function fn(): Promise", ranges: [functionDef] }]); +verify.referenceGroups("exportModifier", [{ definition: "class C2", ranges: [classDef2Name] }]); +verify.referenceGroups("defaultModifier", [{ definition: "class C2", ranges: [classDef2Default] }]); diff --git a/tests/cases/fourslash/referencesForStatementKeywords.ts b/tests/cases/fourslash/referencesForStatementKeywords.ts new file mode 100644 index 0000000000000..15288e94ab146 --- /dev/null +++ b/tests/cases/fourslash/referencesForStatementKeywords.ts @@ -0,0 +1,10 @@ +/// + +// @filename: /a.ts +////[|import /*typeKeyword*/type { T } from "[|{| "contextRangeDelta": -1 |}./b|]";|] + +// @filename: /b.ts +////export type T = number; + +const [, importRef] = test.ranges(); +verify.referenceGroups("typeKeyword", [{ definition: "module \"/b\"", ranges: [importRef] }]); \ No newline at end of file From 8d962aa62c6355d782d6fdcfd6f9f8f3d55710b8 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 28 Jan 2020 18:09:38 -0800 Subject: [PATCH 2/4] Add support for rename --- src/harness/fourslashImpl.ts | 21 +++++++-- src/services/rename.ts | 13 +++--- src/services/services.ts | 2 +- src/services/utilities.ts | 26 +++++++++++ tests/cases/fourslash/getRenameInfoTests2.ts | 2 +- .../fourslash/renameDeclarationKeywords.ts | 43 +++++++++++++++++++ tests/cases/fourslash/renameModifiers.ts | 34 +++++++++++++++ 7 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 tests/cases/fourslash/renameDeclarationKeywords.ts create mode 100644 tests/cases/fourslash/renameModifiers.ts diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 0c37c4e330e83..8445d88e0a623 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -1249,6 +1249,10 @@ namespace FourSlash { } public verifyRenameLocations(startRanges: ArrayOrSingle, options: FourSlashInterface.RenameLocationsOptions) { + interface RangeMarkerData { + contextRangeIndex?: number, + contextRangeDelta?: number + } const { findInStrings = false, findInComments = false, ranges = this.getRanges(), providePrefixAndSuffixTextForRename = true } = ts.isArray(options) ? { findInStrings: false, findInComments: false, ranges: options, providePrefixAndSuffixTextForRename: true } : options; const _startRanges = toArray(startRanges); @@ -1269,13 +1273,22 @@ namespace FourSlash { locations && ts.sort(locations, (r1, r2) => ts.compareStringsCaseSensitive(r1.fileName, r2.fileName) || r1.textSpan.start - r2.textSpan.start); assert.deepEqual(sort(references), sort(ranges.map((rangeOrOptions): ts.RenameLocation => { const { range, ...prefixSuffixText } = "range" in rangeOrOptions ? rangeOrOptions : { range: rangeOrOptions }; // eslint-disable-line no-in-operator - const { contextRangeIndex } = (range.marker && range.marker.data || {}) as { contextRangeIndex?: number; }; + const { contextRangeIndex, contextRangeDelta } = (range.marker && range.marker.data || {}) as RangeMarkerData; + let contextSpan: ts.TextSpan | undefined; + if (contextRangeDelta !== undefined) { + const allRanges = this.getRanges(); + const index = allRanges.indexOf(range); + if (index !== -1) { + contextSpan = ts.createTextSpanFromRange(allRanges[index + contextRangeDelta]); + } + } + else if (contextRangeIndex !== undefined) { + contextSpan = ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]); + } return { fileName: range.fileName, textSpan: ts.createTextSpanFromRange(range), - ...(contextRangeIndex !== undefined ? - { contextSpan: ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]) } : - undefined), + ...(contextSpan ? { contextSpan } : undefined), ...prefixSuffixText }; }))); diff --git a/src/services/rename.ts b/src/services/rename.ts index 775051a8364c3..3251dee6cd4a8 100644 --- a/src/services/rename.ts +++ b/src/services/rename.ts @@ -1,11 +1,14 @@ /* @internal */ namespace ts.Rename { export function getRenameInfo(program: Program, sourceFile: SourceFile, position: number, options?: RenameInfoOptions): RenameInfo { - const node = getTouchingPropertyName(sourceFile, position); - const renameInfo = node && nodeIsEligibleForRename(node) - ? getRenameInfoForNode(node, program.getTypeChecker(), sourceFile, declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile()), options) - : undefined; - return renameInfo || getRenameInfoError(Diagnostics.You_cannot_rename_this_element); + const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); + if (nodeIsEligibleForRename(node)) { + const renameInfo = getRenameInfoForNode(node, program.getTypeChecker(), sourceFile, declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile()), options); + if (renameInfo) { + return renameInfo; + } + } + return getRenameInfoError(Diagnostics.You_cannot_rename_this_element); } function getRenameInfoForNode(node: Node, typeChecker: TypeChecker, sourceFile: SourceFile, isDefinedInLibraryFile: (declaration: Node) => boolean, options?: RenameInfoOptions): RenameInfo | undefined { diff --git a/src/services/services.ts b/src/services/services.ts index aef4c175d1b77..ceb7ec70e2a3f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1608,7 +1608,7 @@ namespace ts { function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] | undefined { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); - const node = getTouchingPropertyName(sourceFile, position); + const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) { const { openingElement, closingElement } = node.parent.parent; return [openingElement, closingElement].map((node): RenameLocation => { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 40da86af5c50b..b387af070feeb 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -738,6 +738,32 @@ namespace ts { return syntaxList; } + export function getAdjustedRenameLocation(node: Node): Node { + const { parent } = node; + if (isModifier(node) ? contains(parent.modifiers, node) : + node.kind === SyntaxKind.ClassKeyword ? isClassDeclaration(parent) : + node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) : + node.kind === SyntaxKind.InterfaceKeyword ? isInterfaceDeclaration(parent) : + node.kind === SyntaxKind.EnumKeyword ? isEnumDeclaration(parent) : + node.kind === SyntaxKind.TypeKeyword ? isTypeAliasDeclaration(parent) : + node.kind === SyntaxKind.NamespaceKeyword ? isModuleDeclaration(parent) : + node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) : + node.kind === SyntaxKind.GetKeyword ? isGetAccessorDeclaration(parent) : + node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(parent)) { + if (isNamedDeclaration(parent)) { + return parent.name; + } + } + if ((node.kind === SyntaxKind.VarKeyword || node.kind === SyntaxKind.ConstKeyword || node.kind === SyntaxKind.LetKeyword) && + isVariableDeclarationList(parent) && parent.declarations.length === 1) { + const decl = parent.declarations[0]; + if (isIdentifier(decl.name)) { + return decl.name; + } + } + return node; + } + /** * Gets the token whose text has range [start, end) and * position >= start and (position < end or (position === end && token is literal or keyword or identifier)) diff --git a/tests/cases/fourslash/getRenameInfoTests2.ts b/tests/cases/fourslash/getRenameInfoTests2.ts index 93e8e451b4a16..880bca7378a62 100644 --- a/tests/cases/fourslash/getRenameInfoTests2.ts +++ b/tests/cases/fourslash/getRenameInfoTests2.ts @@ -1,6 +1,6 @@ /// -/////**/class C { +////class C /**/extends null { //// ////} diff --git a/tests/cases/fourslash/renameDeclarationKeywords.ts b/tests/cases/fourslash/renameDeclarationKeywords.ts new file mode 100644 index 0000000000000..8b5fd27fa5bed --- /dev/null +++ b/tests/cases/fourslash/renameDeclarationKeywords.ts @@ -0,0 +1,43 @@ +/// + +////[|[|class|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}C|] { +//// [|[|get|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}e|]() { return 1; }|] +//// [|[|set|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}e|](v) {}|] +////}|] +////[|[|interface|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}I|] { }|] +////[|[|type|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}T|] = { }|] +////[|[|enum|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}E|] { }|] +////[|[|namespace|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}N|] { }|] +////[|[|module|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}M|] { }|] +////[|[|function|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}fn|]() {}|] +////[|[|var|] [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeDelta": -2 |}x|];|] +////[|[|let|] [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeDelta": -2 |}y|];|] +////[|[|const|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}z|] = 1;|] + +const [ + classDef, classKeyword, className, + getDef, getKeyword, getName, + setDef, setKeyword, setName, + interfaceDef, interfaceKeyword, interfaceName, + typeDef, typeKeyword, typeName, + enumDef, enumKeyword, enumName, + namespaceDef, namespaceKeyword, namespaceName, + moduleDef, moduleKeyword, moduleName, + functionDef, functionKeyword, functionName, + varDef, varKeyword, varName, + letDef, letKeyword, letName, + constDef, constKeyword, constName, +] = test.ranges(); +verify.renameLocations(classKeyword, [{ range: className }]); +for (const keyword of [getKeyword, setKeyword]) { + verify.renameLocations(keyword, [{ range: getName }, { range: setName }]); +} +verify.renameLocations(interfaceKeyword, [{ range: interfaceName }]); +verify.renameLocations(typeKeyword, [{ range: typeName }]); +verify.renameLocations(enumKeyword, [{ range: enumName }]); +verify.renameLocations(namespaceKeyword, [{ range: namespaceName }]); +verify.renameLocations(moduleKeyword, [{ range: moduleName }]); +verify.renameLocations(functionKeyword, [{ range: functionName }]); +verify.renameLocations(varKeyword, [{ range: varName }]); +verify.renameLocations(letKeyword, [{ range: letName }]); +verify.renameLocations(constKeyword, [{ range: constName }]); \ No newline at end of file diff --git a/tests/cases/fourslash/renameModifiers.ts b/tests/cases/fourslash/renameModifiers.ts new file mode 100644 index 0000000000000..ab2258a1d08cf --- /dev/null +++ b/tests/cases/fourslash/renameModifiers.ts @@ -0,0 +1,34 @@ +/// + +////[|[|declare|] [|abstract|] class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -3 |}C1|] { +//// [|[|static|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}a|];|] +//// [|[|readonly|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}b|];|] +//// [|[|public|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}c|];|] +//// [|[|protected|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}d|];|] +//// [|[|private|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}e|];|] +////}|] +////[|[|const|] enum [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}E|] { +////}|] +////[|[|async|] function [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}fn|]() {}|] +////[|[|export|] [|default|] class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -3 |}C2|] {}|] + +const [ + class1Def, declareKeyword, abstractKeyword, class1Name, + aDef, staticKeyword, aName, + bDef, readonlyKeyword, bName, + cDef, publicKeyword, cName, + dDef, protectedKeyword, dName, + eDef, privateKeyword, eName, + enumDef, constKeyword, enumName, + functionDef, asyncKeyword, functionName, + class2Def, exportKeyword, defaultKeyword, class2Name, +] = test.ranges(); +verify.renameLocations([declareKeyword, abstractKeyword], [{ range: class1Name }]); +verify.renameLocations([staticKeyword], [{ range: aName }]); +verify.renameLocations([readonlyKeyword], [{ range: bName }]); +verify.renameLocations([publicKeyword], [{ range: cName }]); +verify.renameLocations([protectedKeyword], [{ range: dName }]); +verify.renameLocations([privateKeyword], [{ range: eName }]); +verify.renameLocations([constKeyword], [{ range: enumName }]); +verify.renameLocations([asyncKeyword], [{ range: functionName }]); +verify.renameLocations([exportKeyword, defaultKeyword], [{ range: class2Name }]); From 6fdba9f42d14deb80322db8716eca03251b0383f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 30 Jan 2020 16:52:57 -0800 Subject: [PATCH 3/4] Add more keywords, move logic out of checker and into services --- src/compiler/checker.ts | 67 +---- src/compiler/types.ts | 1 - src/harness/fourslashImpl.ts | 75 ++++- src/services/callHierarchy.ts | 2 +- src/services/findAllReferences.ts | 58 ++-- src/services/services.ts | 6 +- src/services/utilities.ts | 278 +++++++++++++++++- .../fourslash/findAllRefsExportEquals.ts | 2 +- tests/cases/fourslash/findAllRefsInExport1.ts | 10 - .../findAllRefs_importType_exportEquals.ts | 2 +- .../referencesForDeclarationKeywords.ts | 111 +++++-- .../referencesForStatementKeywords.ts | 265 ++++++++++++++++- .../fourslash/renameDeclarationKeywords.ts | 124 +++++--- 13 files changed, 810 insertions(+), 191 deletions(-) delete mode 100644 tests/cases/fourslash/findAllRefsInExport1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 91a3c3c2a94e0..f7550b1481868 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -413,13 +413,9 @@ namespace ts { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; }, - getSymbolAtLocation: (node: Node, includeKeywords?: boolean) => { + getSymbolAtLocation: (node: Node) => { node = getParseTreeNode(node); - if (node) { - return includeKeywords ? - getSymbolAtLocation(node) ?? getSymbolAtKeyword(node) : - getSymbolAtLocation(node); - } + return node && getSymbolAtLocation(node); }, getShorthandAssignmentValueSymbol: node => { node = getParseTreeNode(node); @@ -34298,65 +34294,6 @@ namespace ts { } } - /** - * Gets the symbol related to the provided location, if it that location is a keyword. - * These additional keywords are normally only used to resolve references but would - * not be used for document highlights, quickinfo, etc. - */ - function getSymbolAtKeyword(node: Node): Symbol | undefined { - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; - } - - const { parent } = node; - - // If the node is a modifier of its parent, get the symbol for the parent. - if (isModifier(node) && contains(parent.modifiers, node)) { - return getSymbolOfNode(parent); - } - - switch (node.kind) { - case SyntaxKind.InterfaceKeyword: - case SyntaxKind.EnumKeyword: - case SyntaxKind.NamespaceKeyword: - case SyntaxKind.ModuleKeyword: - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - return getSymbolOfNode(parent); - - case SyntaxKind.TypeKeyword: - if (isTypeAliasDeclaration(parent)) { - return getSymbolOfNode(parent); - } - if (isImportClause(parent)) { - return getSymbolAtLocation(parent.parent.moduleSpecifier); - } - if (isLiteralImportTypeNode(parent)) { - return getSymbolAtLocation(parent.argument.literal); - } - break; - - case SyntaxKind.VarKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.LetKeyword: - if (isVariableDeclarationList(parent) && parent.declarations.length === 1) { - return getSymbolOfNode(parent.declarations[0]); - } - break; - } - if (node.kind === SyntaxKind.NewKeyword && isNewExpression(parent) || - node.kind === SyntaxKind.VoidKeyword && isVoidExpression(parent) || - node.kind === SyntaxKind.TypeOfKeyword && isTypeOfExpression(parent) || - node.kind === SyntaxKind.AwaitKeyword && isAwaitExpression(parent) || - node.kind === SyntaxKind.YieldKeyword && isYieldExpression(parent) || - node.kind === SyntaxKind.DeleteKeyword && isDeleteExpression(parent)) { - if (parent.expression) { - return getSymbolAtLocation(skipOuterExpressions(parent.expression)); - } - } - } - function getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined { if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) { return resolveEntityName((location).name, SymbolFlags.Value | SymbolFlags.Alias); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0c9483e4eaeb8..5565bfa8c2cbb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3405,7 +3405,6 @@ namespace ts { getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol | undefined; - /* @internal*/ getSymbolAtLocation(node: Node, includeKeywords?: boolean): Symbol | undefined; // eslint-disable-line @typescript-eslint/unified-signatures getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): Symbol[]; /** * The function returns the value (local variable) symbol of an identifier in the short-hand property assignment. diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 8445d88e0a623..062b412ac8621 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -229,7 +229,7 @@ namespace FourSlash { } } - constructor(private originalInputFileName: string, private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) { + constructor(public originalInputFileName: string, private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) { // Create a new Services Adapter this.cancellationToken = new TestCancellationToken(); let compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); @@ -998,16 +998,18 @@ namespace FourSlash { references: ts.ReferenceEntry[]; } interface RangeMarkerData { + id?: string; isWriteAccess?: boolean, isDefinition?: boolean, isInString?: true, contextRangeIndex?: number, - contextRangeDelta?: number + contextRangeDelta?: number, + contextRangeId?: string } const fullExpected = ts.map(parts, ({ definition, ranges }) => ({ definition: typeof definition === "string" ? definition : { ...definition, range: ts.createTextSpanFromRange(definition.range) }, references: ranges.map(r => { - const { isWriteAccess = false, isDefinition = false, isInString, contextRangeIndex, contextRangeDelta } = (r.marker && r.marker.data || {}) as RangeMarkerData; + const { isWriteAccess = false, isDefinition = false, isInString, contextRangeIndex, contextRangeDelta, contextRangeId } = (r.marker && r.marker.data || {}) as RangeMarkerData; let contextSpan: ts.TextSpan | undefined; if (contextRangeDelta !== undefined) { const allRanges = this.getRanges(); @@ -1016,15 +1018,22 @@ namespace FourSlash { contextSpan = ts.createTextSpanFromRange(allRanges[index + contextRangeDelta]); } } + else if (contextRangeId !== undefined) { + const allRanges = this.getRanges(); + const contextRange = ts.find(allRanges, range => (range.marker?.data as RangeMarkerData)?.id === contextRangeId); + if (contextRange) { + contextSpan = ts.createTextSpanFromRange(contextRange); + } + } else if (contextRangeIndex !== undefined) { contextSpan = ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]); } return { - fileName: r.fileName, textSpan: ts.createTextSpanFromRange(r), + fileName: r.fileName, + ...(contextSpan ? { contextSpan } : undefined), isWriteAccess, isDefinition, - ...(contextSpan ? { contextSpan } : undefined), ...(isInString ? { isInString: true } : undefined), }; }), @@ -1250,8 +1259,10 @@ namespace FourSlash { public verifyRenameLocations(startRanges: ArrayOrSingle, options: FourSlashInterface.RenameLocationsOptions) { interface RangeMarkerData { + id?: string; contextRangeIndex?: number, contextRangeDelta?: number + contextRangeId?: string; } const { findInStrings = false, findInComments = false, ranges = this.getRanges(), providePrefixAndSuffixTextForRename = true } = ts.isArray(options) ? { findInStrings: false, findInComments: false, ranges: options, providePrefixAndSuffixTextForRename: true } : options; @@ -1273,7 +1284,7 @@ namespace FourSlash { locations && ts.sort(locations, (r1, r2) => ts.compareStringsCaseSensitive(r1.fileName, r2.fileName) || r1.textSpan.start - r2.textSpan.start); assert.deepEqual(sort(references), sort(ranges.map((rangeOrOptions): ts.RenameLocation => { const { range, ...prefixSuffixText } = "range" in rangeOrOptions ? rangeOrOptions : { range: rangeOrOptions }; // eslint-disable-line no-in-operator - const { contextRangeIndex, contextRangeDelta } = (range.marker && range.marker.data || {}) as RangeMarkerData; + const { contextRangeIndex, contextRangeDelta, contextRangeId } = (range.marker && range.marker.data || {}) as RangeMarkerData; let contextSpan: ts.TextSpan | undefined; if (contextRangeDelta !== undefined) { const allRanges = this.getRanges(); @@ -1282,6 +1293,13 @@ namespace FourSlash { contextSpan = ts.createTextSpanFromRange(allRanges[index + contextRangeDelta]); } } + else if (contextRangeId !== undefined) { + const allRanges = this.getRanges(); + const contextRange = ts.find(allRanges, range => (range.marker?.data as RangeMarkerData)?.id === contextRangeId); + if (contextRange) { + contextSpan = ts.createTextSpanFromRange(contextRange); + } + } else if (contextRangeIndex !== undefined) { contextSpan = ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]); } @@ -3618,19 +3636,43 @@ namespace FourSlash { // Parse out the files and their metadata const testData = parseTestData(absoluteBasePath, content, absoluteFileName); const state = new TestState(absoluteFileName, absoluteBasePath, testType, testData); - const output = ts.transpileModule(content, { reportDiagnostics: true, compilerOptions: { target: ts.ScriptTarget.ES2015 } }); + const actualFileName = Harness.IO.resolvePath(fileName) || absoluteFileName; + const output = ts.transpileModule(content, { reportDiagnostics: true, fileName: actualFileName, compilerOptions: { target: ts.ScriptTarget.ES2015, sourceMap: true } }); if (output.diagnostics!.length > 0) { throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics![0].messageText}`); } - runCode(output.outputText, state); + runCode(output, state, actualFileName); } - function runCode(code: string, state: TestState): void { + function runCode(output: ts.TranspileOutput, state: TestState, fileName: string): void { // Compile and execute the test - const wrappedCode = - `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) { -${code} -})`; + const generatedFile = ts.changeExtension(fileName, ".js"); + const mapFile = generatedFile + ".map"; + const wrappedCode = `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {${output.outputText}\n//# sourceURL=${generatedFile}\n})`; + + type SourceMapSupportModule = typeof import("source-map-support") & { + // TODO(rbuckton): This is missing from the DT definitions and needs to be added. + resetRetrieveHandlers(): void + }; + + // Provide the content of the current test to 'source-map-support' so that it can give us the correct source positions + // for test failures. + let sourceMapSupportModule: SourceMapSupportModule | undefined; + try { + sourceMapSupportModule = require("source-map-support"); + } + catch { + // do nothing + } + + sourceMapSupportModule?.install({ + retrieveFile: path => { + return path === generatedFile ? wrappedCode : + path === mapFile ? output.sourceMapText! : + undefined!; + } + }); + try { const test = new FourSlashInterface.Test(state); const goTo = new FourSlashInterface.GoTo(state); @@ -3645,8 +3687,13 @@ ${code} f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, FourSlashInterface.Completion, verifyOperationIsCancelled); } catch (err) { + // ensure we trigger 'source-map-support' while we still have the handler attached + err.stack?.toString(); throw err; } + finally { + sourceMapSupportModule?.resetRetrieveHandlers(); + } } function chompLeadingSpace(content: string) { @@ -3815,7 +3862,7 @@ ${code} markerValue = JSON.parse("{ " + text + " }"); } catch (e) { - reportError(fileName, location.sourceLine, location.sourceColumn, "Unable to parse marker text " + e.message); + reportError(fileName, location.sourceLine, location.sourceColumn, "Unable to parse marker text " + e.message + "\nSource:\n {| " + text + " |}"); } if (markerValue === undefined) { diff --git a/src/services/callHierarchy.ts b/src/services/callHierarchy.ts index 41f5c138e6b42..a924bc2809108 100644 --- a/src/services/callHierarchy.ts +++ b/src/services/callHierarchy.ts @@ -293,7 +293,7 @@ namespace ts.CallHierarchy { return []; } const location = getCallHierarchyDeclarationReferenceNode(declaration); - const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, { keywords: true }, convertEntryToCallSite), isDefined); + const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, { use: FindAllReferences.FindReferencesUse.References }, convertEntryToCallSite), isDefined); return calls ? group(calls, getCallSiteGroupKey, entries => convertCallSiteGroupToIncomingCall(program, entries)) : []; } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index d3b3aebed3a0f..920d2dcc573ea 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -171,14 +171,27 @@ namespace ts.FindAllReferences { undefined; } + export const enum FindReferencesUse { + /** + * When searching for references to a symbol, the location will not be adjusted (this is the default behavior when not specified). + */ + Other, + /** + * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. + */ + References, + /** + * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. + * Unlike `References`, the location will only be adjusted keyword belonged to a declaration with a valid name. + * If set, we will find fewer references -- if it is referenced by several different names, we still only find references for the original name. + */ + Rename, + } + export interface Options { readonly findInStrings?: boolean; readonly findInComments?: boolean; - /** - * True if we are renaming the symbol. - * If so, we will find fewer references -- if it is referenced by several different names, we still only find references for the original name. - */ - readonly isForRename?: boolean; + readonly use?: FindReferencesUse; /** True if we are searching for implementations. We will have a different method of adding references if so. */ readonly implementations?: boolean; /** @@ -187,15 +200,11 @@ namespace ts.FindAllReferences { * Default is false for backwards compatibility. */ readonly providePrefixAndSuffixTextForRename?: boolean; - /** - * If the source is a modifier or declaration keyword, find references to its parent declaration. - */ - readonly keywords?: boolean; } export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { const node = getTouchingPropertyName(sourceFile, position); - const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, { keywords: true }); + const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, { use: FindReferencesUse.References }); const checker = program.getTypeChecker(); return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined(referencedSymbols, ({ definition, references }) => // Only include referenced symbols that have a valid definition. @@ -233,7 +242,7 @@ namespace ts.FindAllReferences { } else { // Perform "Find all References" and retrieve only those that are implementations - return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, keywords: true }); + return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, use: FindReferencesUse.References }); } } @@ -506,6 +515,7 @@ namespace ts.FindAllReferences { case SyntaxKind.ModuleDeclaration: case SyntaxKind.NamespaceExportDeclaration: case SyntaxKind.NamespaceImport: + case SyntaxKind.NamespaceExport: case SyntaxKind.Parameter: case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.TypeAliasDeclaration: @@ -543,6 +553,12 @@ namespace ts.FindAllReferences { export namespace Core { /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlyMap = arrayToSet(sourceFiles, f => f.fileName)): readonly SymbolAndEntries[] | undefined { + if (options.use === FindReferencesUse.References) { + node = getAdjustedReferenceLocation(node); + } + else if (options.use === FindReferencesUse.Rename) { + node = getAdjustedRenameLocation(node); + } if (isSourceFile(node)) { const reference = GoToDefinition.getReferenceAtPosition(node, position, program); const moduleSymbol = reference && program.getTypeChecker().getMergedSymbol(reference.file.symbol); @@ -557,7 +573,7 @@ namespace ts.FindAllReferences { } const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node, options.keywords); + const symbol = checker.getSymbolAtLocation(node); // Could not find a symbol e.g. unknown identifier if (!symbol) { @@ -788,7 +804,7 @@ namespace ts.FindAllReferences { searchForImportsOfExport(node, symbol, { exportingModuleSymbol: Debug.assertDefined(symbol.parent, "Expected export symbol to have a parent"), exportKind: ExportKind.Default }, state); } else { - const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, !!options.isForRename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] }); + const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, options.use === FindReferencesUse.Rename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] }); getReferencesInContainerOrFiles(symbol, state, search); } @@ -929,7 +945,7 @@ namespace ts.FindAllReferences { /** Gets every place to look for references of an exported symbols. See `ImportsResult` in `importTracker.ts` for more documentation. */ getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult { if (!this.importTracker) this.importTracker = createImportTracker(this.sourceFiles, this.sourceFilesSet, this.checker, this.cancellationToken); - return this.importTracker(exportSymbol, exportInfo, !!this.options.isForRename); + return this.importTracker(exportSymbol, exportInfo, this.options.use === FindReferencesUse.Rename); } /** @param allSearchSymbols set of additional symbols for use by `includes`. */ @@ -1010,7 +1026,7 @@ namespace ts.FindAllReferences { break; case ExportKind.Default: // Search for a property access to '.default'. This can't be renamed. - indirectSearch = state.options.isForRename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); + indirectSearch = state.options.use === FindReferencesUse.Rename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); break; case ExportKind.ExportEquals: break; @@ -1050,7 +1066,7 @@ namespace ts.FindAllReferences { function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean { if (!hasMatchingMeaning(singleRef, state)) return false; - if (!state.options.isForRename) return true; + if (state.options.use !== FindReferencesUse.Rename) return true; // Don't rename an import type `import("./module-name")` when renaming `name` in `export = name;` if (!isIdentifier(singleRef)) return false; // At `default` in `import { default as x }` or `export { default as x }`, do add a reference, but do not rename. @@ -1382,7 +1398,7 @@ namespace ts.FindAllReferences { if (!propertyName) { // Don't rename at `export { default } from "m";`. (but do continue to search for imports of the re-export) - if (!(state.options.isForRename && (name.escapedText === InternalSymbolName.Default))) { + if (!(state.options.use === FindReferencesUse.Rename && (name.escapedText === InternalSymbolName.Default))) { addRef(); } } @@ -1393,7 +1409,7 @@ namespace ts.FindAllReferences { addRef(); } - if (addReferencesHere && !state.options.isForRename && state.markSeenReExportRHS(name)) { + if (addReferencesHere && state.options.use !== FindReferencesUse.Rename && state.markSeenReExportRHS(name)) { addReference(name, Debug.assertDefined(exportSpecifier.symbol), state); } } @@ -1512,7 +1528,7 @@ namespace ts.FindAllReferences { function addClassStaticThisReferences(referenceLocation: Node, search: Search, state: State): void { addReference(referenceLocation, search.symbol, state); const classLike = referenceLocation.parent; - if (state.options.isForRename || !isClassLike(classLike)) return; + if (state.options.use === FindReferencesUse.Rename || !isClassLike(classLike)) return; Debug.assert(classLike.name === referenceLocation); const addRef = state.referenceAdder(search.symbol); for (const member of classLike.members) { @@ -1970,7 +1986,7 @@ namespace ts.FindAllReferences { function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): RelatedSymbol | undefined { const { checker } = state; return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, /*isForRenamePopulateSearchSymbolSet*/ false, - /*onlyIncludeBindingElementAtReferenceLocation*/ !state.options.isForRename || !!state.options.providePrefixAndSuffixTextForRename, + /*onlyIncludeBindingElementAtReferenceLocation*/ state.options.use !== FindReferencesUse.Rename || !!state.options.providePrefixAndSuffixTextForRename, (sym, rootSymbol, baseSymbol, kind): RelatedSymbol | undefined => search.includes(baseSymbol || rootSymbol || sym) // For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol. ? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind } @@ -2063,7 +2079,7 @@ namespace ts.FindAllReferences { } function isForRenameWithPrefixAndSuffixText(options: Options) { - return options.isForRename && options.providePrefixAndSuffixTextForRename; + return options.use === FindReferencesUse.Rename && options.providePrefixAndSuffixTextForRename; } } } diff --git a/src/services/services.ts b/src/services/services.ts index ceb7ec70e2a3f..0c45e3f2d6ba5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1621,21 +1621,21 @@ namespace ts { }); } else { - return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, isForRename: true }, + return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, use: FindAllReferences.FindReferencesUse.Rename }, (entry, originalNode, checker) => FindAllReferences.toRenameLocation(entry, originalNode, checker, providePrefixAndSuffixTextForRename || false)); } } function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined { synchronizeHostData(); - return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { keywords: true }, FindAllReferences.toReferenceEntry); + return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { use: FindAllReferences.FindReferencesUse.References }, FindAllReferences.toReferenceEntry); } function getReferencesWorker(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry): T[] | undefined { synchronizeHostData(); // Exclude default library when renaming as commonly user don't want to change that file. - const sourceFiles = options && options.isForRename + const sourceFiles = options && options.use === FindAllReferences.FindReferencesUse.Rename ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile)) : program.getSourceFiles(); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index b387af070feeb..02843d0a33593 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -738,22 +738,161 @@ namespace ts { return syntaxList; } - export function getAdjustedRenameLocation(node: Node): Node { + function isDefaultModifier(node: Node) { + return node.kind === SyntaxKind.DefaultKeyword; + } + + function isClassKeyword(node: Node) { + return node.kind === SyntaxKind.ClassKeyword; + } + + function isFunctionKeyword(node: Node) { + return node.kind === SyntaxKind.FunctionKeyword; + } + + function getAdjustedLocationForClass(node: ClassDeclaration | ClassExpression) { + if (isNamedDeclaration(node)) { + return node.name; + } + if (isClassDeclaration(node)) { + // for class and function declarations, use the `default` modifier + // when the declaration is unnamed. + const defaultModifier = find(node.modifiers!, isDefaultModifier); + if (defaultModifier) return defaultModifier; + } + if (isClassExpression(node)) { + // for class expressions, use the `class` keyword when the class is unnamed + const classKeyword = find(node.getChildren(), isClassKeyword); + if (classKeyword) return classKeyword; + } + } + + function getAdjustedLocationForFunction(node: FunctionDeclaration | FunctionExpression) { + if (isNamedDeclaration(node)) { + return node.name; + } + if (isFunctionDeclaration(node)) { + // for class and function declarations, use the `default` modifier + // when the declaration is unnamed. + const defaultModifier = find(node.modifiers!, isDefaultModifier); + if (defaultModifier) return defaultModifier; + } + if (isFunctionExpression(node)) { + // for function expressions, use the `function` keyword when the function is unnamed + const functionKeyword = find(node.getChildren(), isFunctionKeyword); + if (functionKeyword) return functionKeyword; + } + } + + function getAdjustedLocationForDeclaration(node: Node, forRename: boolean) { + if (!forRename) { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return getAdjustedLocationForClass(node as ClassDeclaration | ClassExpression); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + return getAdjustedLocationForFunction(node as FunctionDeclaration | FunctionExpression); + } + } + if (isNamedDeclaration(node)) { + return node.name; + } + } + + function getAdjustedLocationForImportDeclaration(node: ImportDeclaration, forRename: boolean) { + if (node.importClause) { + // /**/import [|name|] from ...; + // import /**/type [|name|] from ...; + if (node.importClause.name && !node.importClause.namedBindings) { + return node.importClause.name; + } + // /**/import { [|name|] } from ...; + // /**/import { propertyName as [|name|] } from ...; + // /**/import * as [|name|] from ...; + // import /**/type { [|name|] } from ...; + // import /**/type { propertyName as [|name|] } from ...; + // import /**/type * as [|name|] from ...; + if (!node.importClause.name && node.importClause.namedBindings) { + if (isNamedImports(node.importClause.namedBindings)) { + if (node.importClause.namedBindings.elements.length === 1) { + return node.importClause.namedBindings.elements[0].name; + } + } + else if (isNamespaceImport(node.importClause.namedBindings)) { + return node.importClause.namedBindings.name; + } + } + + } + if (!forRename) { + // /**/import "[|module|]"; + // /**/import ... from "[|module|]"; + // import /**/type ... from "[|module|]"; + return node.moduleSpecifier; + } + } + + function getAdjustedLocationForExportDeclaration(node: ExportDeclaration, forRename: boolean) { + if (node.exportClause) { + // /**/export { [|name|] } ... + // /**/export { propertyName as [|name|] } ... + // /**/export * as [|name|] ... + // export /**/type { [|name|] } from ... + // export /**/type { propertyName as [|name|] } from ... + // export /**/type * as [|name|] ... + if (isNamedExports(node.exportClause)) { + if (node.exportClause.elements.length === 1) { + return node.exportClause.elements[0].name; + } + } + else if (isNamespaceExport(node.exportClause)) { + return node.exportClause.name; + } + } + if (!forRename) { + // /**/export * from "[|module|]"; + // export /**/type * from "[|module|]"; + return node.moduleSpecifier; + } + } + + function getAdjustedLocationForHeritageClause(node: HeritageClause) { + // /**/extends [|name|] + // /**/implements [|name|] + if (node.types.length === 1) { + return node.types[0].expression; + } + + // /**/extends name1, name2 ... + // /**/implements name1, name2 ... + } + + function getAdjustedLocation(node: Node, forRename: boolean): Node { const { parent } = node; - if (isModifier(node) ? contains(parent.modifiers, node) : - node.kind === SyntaxKind.ClassKeyword ? isClassDeclaration(parent) : - node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) : + // /**/ [|name|] ... + // /**/ [|name|] ... + // /**/ [|name|] ... + // /**/import [|name|] = ... + // + // NOTE: If the node is a modifier, we don't adjust its location if it is the `default` modifier as that is handled + // specially by `getSymbolAtLocation`. + if (isModifier(node) && (forRename || node.kind !== SyntaxKind.DefaultKeyword) ? contains(parent.modifiers, node) : + node.kind === SyntaxKind.ClassKeyword ? isClassDeclaration(parent) || isClassExpression(node) : + node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) || isFunctionExpression(node) : node.kind === SyntaxKind.InterfaceKeyword ? isInterfaceDeclaration(parent) : node.kind === SyntaxKind.EnumKeyword ? isEnumDeclaration(parent) : node.kind === SyntaxKind.TypeKeyword ? isTypeAliasDeclaration(parent) : - node.kind === SyntaxKind.NamespaceKeyword ? isModuleDeclaration(parent) : - node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) : + node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) : + node.kind === SyntaxKind.ImportKeyword ? isImportEqualsDeclaration(parent) : node.kind === SyntaxKind.GetKeyword ? isGetAccessorDeclaration(parent) : node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(parent)) { - if (isNamedDeclaration(parent)) { - return parent.name; + const location = getAdjustedLocationForDeclaration(parent, forRename); + if (location) { + return location; } } + // /**/ [|name|] ... if ((node.kind === SyntaxKind.VarKeyword || node.kind === SyntaxKind.ConstKeyword || node.kind === SyntaxKind.LetKeyword) && isVariableDeclarationList(parent) && parent.declarations.length === 1) { const decl = parent.declarations[0]; @@ -761,9 +900,132 @@ namespace ts { return decl.name; } } + if (node.kind === SyntaxKind.TypeKeyword) { + // import /**/type [|name|] from ...; + // import /**/type { [|name|] } from ...; + // import /**/type { propertyName as [|name|] } from ...; + // import /**/type ... from "[|module|]"; + if (isImportClause(parent) && parent.isTypeOnly) { + const location = getAdjustedLocationForImportDeclaration(parent.parent, forRename); + if (location) { + return location; + } + } + // export /**/type { [|name|] } from ...; + // export /**/type { propertyName as [|name|] } from ...; + // export /**/type * from "[|module|]"; + // export /**/type * as ... from "[|module|]"; + if (isExportDeclaration(parent) && parent.isTypeOnly) { + const location = getAdjustedLocationForExportDeclaration(parent, forRename); + if (location) { + return location; + } + } + } + // import { propertyName /**/as [|name|] } ... + // import * /**/as [|name|] ... + // export { propertyName /**/as [|name|] } ... + // export * /**/as [|name|] ... + if (node.kind === SyntaxKind.AsKeyword) { + if (isImportSpecifier(parent) && parent.propertyName || + isExportSpecifier(parent) && parent.propertyName || + isNamespaceImport(parent) || + isNamespaceExport(parent)) { + return parent.name; + } + if (isExportDeclaration(parent) && parent.exportClause && isNamespaceExport(parent.exportClause)) { + return parent.exportClause.name; + } + } + // /**/import [|name|] from ...; + // /**/import { [|name|] } from ...; + // /**/import { propertyName as [|name|] } from ...; + // /**/import ... from "[|module|]"; + // /**/import "[|module|]"; + if (node.kind === SyntaxKind.ImportKeyword && isImportDeclaration(parent)) { + const location = getAdjustedLocationForImportDeclaration(parent, forRename); + if (location) { + return location; + } + } + if (node.kind === SyntaxKind.ExportKeyword) { + // /**/export { [|name|] } ...; + // /**/export { propertyName as [|name|] } ...; + // /**/export * from "[|module|]"; + // /**/export * as ... from "[|module|]"; + if (isExportDeclaration(parent)) { + const location = getAdjustedLocationForExportDeclaration(parent, forRename); + if (location) { + return location; + } + } + // NOTE: We don't adjust the location of the `default` keyword as that is handled specially by `getSymbolAtLocation`. + // /**/export default [|name|]; + // /**/export = [|name|]; + if (isExportAssignment(parent)) { + return skipOuterExpressions(parent.expression); + } + } + // import name = /**/require("[|module|]"); + if (node.kind === SyntaxKind.RequireKeyword && isExternalModuleReference(parent)) { + return parent.expression; + } + // import ... /**/from "[|module|]"; + // export ... /**/from "[|module|]"; + if (node.kind === SyntaxKind.FromKeyword && (isImportDeclaration(parent) || isExportDeclaration(parent)) && parent.moduleSpecifier) { + return parent.moduleSpecifier; + } + // class ... /**/extends [|name|] ... + // class ... /**/implements [|name|] ... + // class ... /**/implements name1, name2 ... + // interface ... /**/extends [|name|] ... + // interface ... /**/extends name1, name2 ... + if ((node.kind === SyntaxKind.ExtendsKeyword || node.kind === SyntaxKind.ImplementsKeyword) && isHeritageClause(parent) && parent.token === node.kind) { + const location = getAdjustedLocationForHeritageClause(parent); + if (location) { + return location; + } + } + if (!forRename) { + // /**/new [|name|](...) + // /**/void [|name|] + // /**/void obj.[|name|] + // /**/typeof [|name|] + // /**/typeof obj.[|name|] + // /**/await [|name|] + // /**/await obj.[|name|] + // /**/yield [|name|] + // /**/yield obj.[|name|] + // /**/delete obj.[|name|] + if (node.kind === SyntaxKind.NewKeyword && isNewExpression(parent) || + node.kind === SyntaxKind.VoidKeyword && isVoidExpression(parent) || + node.kind === SyntaxKind.TypeOfKeyword && isTypeOfExpression(parent) || + node.kind === SyntaxKind.AwaitKeyword && isAwaitExpression(parent) || + node.kind === SyntaxKind.YieldKeyword && isYieldExpression(parent) || + node.kind === SyntaxKind.DeleteKeyword && isDeleteExpression(parent)) { + if (parent.expression) { + return skipOuterExpressions(parent.expression); + } + } + } return node; } + /** + * Adjusts the location used for "find references" and "go to definition" when the cursor was not + * on a property name. + */ + export function getAdjustedReferenceLocation(node: Node): Node { + return getAdjustedLocation(node, /*forRename*/ false); + } + + /** + * Adjusts the location used for "rename" when the cursor was not on a property name. + */ + export function getAdjustedRenameLocation(node: Node): Node { + return getAdjustedLocation(node, /*forRename*/ true); + } + /** * Gets the token whose text has range [start, end) and * position >= start and (position < end or (position === end && token is literal or keyword or identifier)) diff --git a/tests/cases/fourslash/findAllRefsExportEquals.ts b/tests/cases/fourslash/findAllRefsExportEquals.ts index 647023b43ff60..f0fb88e5ab3d6 100644 --- a/tests/cases/fourslash/findAllRefsExportEquals.ts +++ b/tests/cases/fourslash/findAllRefsExportEquals.ts @@ -14,4 +14,4 @@ const b = { definition: '(alias) type T = number\nimport T = require("./a")', ra verify.referenceGroups([r0, r2], [a, b]); verify.referenceGroups(r3, [b, a]); verify.referenceGroups(r4, [mod, a, b]); -verify.referenceGroups(r1, [mod]); +verify.referenceGroups(r1, [a, b]); diff --git a/tests/cases/fourslash/findAllRefsInExport1.ts b/tests/cases/fourslash/findAllRefsInExport1.ts deleted file mode 100644 index 02ec901177f33..0000000000000 --- a/tests/cases/fourslash/findAllRefsInExport1.ts +++ /dev/null @@ -1,10 +0,0 @@ -/// - -//// class C {} -//// /*1*/export { C /*2*/as D }; - -goTo.marker("1"); -verify.noReferences(); - -goTo.marker("2"); -verify.noReferences(); \ No newline at end of file diff --git a/tests/cases/fourslash/findAllRefs_importType_exportEquals.ts b/tests/cases/fourslash/findAllRefs_importType_exportEquals.ts index 61e66de94cac5..59967af0d9ce5 100644 --- a/tests/cases/fourslash/findAllRefs_importType_exportEquals.ts +++ b/tests/cases/fourslash/findAllRefs_importType_exportEquals.ts @@ -19,7 +19,7 @@ verify.referenceGroups(r1, [{ definition: "namespace T", ranges: [r1, r2] }]); const t: FourSlashInterface.ReferenceGroup = { definition: "type T = number\nnamespace T", ranges: [r0, r1, r2, r3] }; verify.referenceGroups(r2, [t]); verify.referenceGroups([r3, r4], [{ definition: 'module "/a"', ranges: [r4, rExport] }, t]); -verify.referenceGroups(rExport, [{ definition: 'module "/a"', ranges: [r3, r4, rExport] }]); +verify.referenceGroups(rExport, [t]); verify.renameLocations(r0, [r0, r2]); verify.renameLocations(r1, [r1, r2]); diff --git a/tests/cases/fourslash/referencesForDeclarationKeywords.ts b/tests/cases/fourslash/referencesForDeclarationKeywords.ts index 885626000eaa6..fa1887680a1ce 100644 --- a/tests/cases/fourslash/referencesForDeclarationKeywords.ts +++ b/tests/cases/fourslash/referencesForDeclarationKeywords.ts @@ -1,30 +1,89 @@ /// - -////[|/*classKeyword*/class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}C|] { -//// [|/*getKeyword*/get [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}e|]() { return 1; }|] -//// [|/*setKeyword*/set [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}e|](v) {}|] +////[|{| "id": "baseDecl" |}class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "baseDecl" |}Base|] {}|] +////[|{| "id": "implemented1Decl" |}interface [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "implemented1Decl" |}Implemented1|] {}|] +////[|{| "id": "classDecl1" |}[|class|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "classDecl1" |}C1|] [|extends|] [|Base|] [|implements|] [|Implemented1|] { +//// [|{| "id": "getDecl" |}[|get|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "getDecl" |}e|]() { return 1; }|] +//// [|{| "id": "setDecl" |}[|set|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "setDecl" |}e|](v) {}|] ////}|] -////[|/*interfaceKeyword*/interface [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}I|] { }|] -////[|/*typeKeyword*/type [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}T|] = { }|] -////[|/*enumKeyword*/enum [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}E|] { }|] -////[|/*namespaceKeyword*/namespace [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}N|] { }|] -////[|/*moduleKeyword*/module [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}M|] { }|] -////[|/*functionKeyword*/function [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}fn|]() {}|] -////[|/*varKeyword*/var [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeDelta": -1 |}x|];|] -////[|/*letKeyword*/let [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeDelta": -1 |}y|];|] -////[|/*constKeyword*/const [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}z|] = 1;|] +////[|{| "id": "interfaceDecl1" |}[|interface|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "interfaceDecl1" |}I1|] [|extends|] [|Base|] { }|] +////[|{| "id": "typeDecl" |}[|type|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "typeDecl" |}T|] = { }|] +////[|{| "id": "enumDecl" |}[|enum|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "enumDecl" |}E|] { }|] +////[|{| "id": "namespaceDecl" |}[|namespace|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "namespaceDecl" |}N|] { }|] +////[|{| "id": "moduleDecl" |}[|module|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "moduleDecl" |}M|] { }|] +////[|{| "id": "functionDecl" |}[|function|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "functionDecl" |}fn|]() {}|] +////[|{| "id": "varDecl" |}[|var|] [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeId": "varDecl" |}x|];|] +////[|{| "id": "letDecl" |}[|let|] [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeId": "letDecl" |}y|];|] +////[|{| "id": "constDecl" |}[|const|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "constDecl" |}z|] = 1;|] +////interface Implemented2 {} +////interface Implemented3 {} +////class C2 [|implements|] Implemented2, Implemented3 {} +////interface I2 [|extends|] Implemented2, Implemented3 {} -const [, classDef,, getDef,, setDef,, interfaceDef,, typeDef,, enumDef,, namespaceDef,, moduleDef,, functionDef,, varDef,, letDef,, constDef] = test.ranges(); -verify.referenceGroups("classKeyword", [{ definition: "class C", ranges: [classDef] }]); -for (const keyword of ["getKeyword", "setKeyword"]) { - verify.referenceGroups(keyword, [{ definition: "(property) C.e: number", ranges: [getDef, setDef] }]); +const [ + baseDecl, + baseDecl_name, + implemented1Decl, + implemented1Decl_name, + classDecl1, + classDecl1_classKeyword, + classDecl1_name, + classDecl1_extendsKeyword, + classDecl1_extendsName, + classDecl1_implementsKeyword, + classDecl1_implementsName, + getDecl, + getDecl_getKeyword, + getDecl_name, + setDecl, + setDecl_setKeyword, + setDecl_name, + interfaceDecl1, + interfaceDecl1_interfaceKeyword, + interfaceDecl1_name, + interfaceDecl1_extendsKeyword, + interfaceDecl1_extendsName, + typeDecl, + typeDecl_typeKeyword, + typeDecl_name, + enumDecl, + enumDecl_enumKeyword, + enumDecl_name, + namespaceDecl, + namespaceDecl_namespaceKeyword, + namespaceDecl_name, + moduleDecl, + moduleDecl_moduleKeyword, + moduleDecl_name, + functionDecl, + functionDecl_functionKeyword, + functionDecl_name, + varDecl, + varDecl_varKeyword, + varDecl_name, + letDecl, + letDecl_letKeyword, + letDecl_name, + constDecl, + constDecl_constKeyword, + constDecl_name, + classDecl2_implementsKeyword, + interfaceDecl2_extendsKeyword, +] = test.ranges(); +verify.referenceGroups(classDecl1_classKeyword, [{ definition: "class C1", ranges: [classDecl1_name] }]); +verify.referenceGroups(classDecl1_extendsKeyword, [{ definition: "class Base", ranges: [baseDecl_name, classDecl1_extendsName, interfaceDecl1_extendsName] }]); +verify.referenceGroups(classDecl1_implementsKeyword, [{ definition: "", ranges: [implemented1Decl_name, classDecl1_implementsName] }]); +for (const keyword of [getDecl_getKeyword, setDecl_setKeyword]) { + verify.referenceGroups(keyword, [{ definition: "(property) C1.e: number", ranges: [getDecl_name, setDecl_name] }]); } -verify.referenceGroups("interfaceKeyword", [{ definition: "interface I", ranges: [interfaceDef] }]); -verify.referenceGroups("typeKeyword", [{ definition: "type T = {}", ranges: [typeDef] }]); -verify.referenceGroups("enumKeyword", [{ definition: "enum E", ranges: [enumDef] }]); -verify.referenceGroups("namespaceKeyword", [{ definition: "namespace N", ranges: [namespaceDef] }]); -verify.referenceGroups("moduleKeyword", [{ definition: "namespace M", ranges: [moduleDef] }]); -verify.referenceGroups("functionKeyword", [{ definition: "function fn(): void", ranges: [functionDef] }]); -verify.referenceGroups("varKeyword", [{ definition: "var x: any", ranges: [varDef] }]); -verify.referenceGroups("letKeyword", [{ definition: "let y: any", ranges: [letDef] }]); -verify.referenceGroups("constKeyword", [{ definition: "const z: 1", ranges: [constDef] }]); \ No newline at end of file +verify.referenceGroups(interfaceDecl1_interfaceKeyword, [{ definition: "interface I1", ranges: [interfaceDecl1_name] }]); +verify.referenceGroups(interfaceDecl1_extendsKeyword, [{ definition: "class Base", ranges: [baseDecl_name, classDecl1_extendsName, interfaceDecl1_extendsName] }]); +verify.referenceGroups(typeDecl_typeKeyword, [{ definition: "type T = {}", ranges: [typeDecl_name] }]); +verify.referenceGroups(enumDecl_enumKeyword, [{ definition: "enum E", ranges: [enumDecl_name] }]); +verify.referenceGroups(namespaceDecl_namespaceKeyword, [{ definition: "namespace N", ranges: [namespaceDecl_name] }]); +verify.referenceGroups(moduleDecl_moduleKeyword, [{ definition: "namespace M", ranges: [moduleDecl_name] }]); +verify.referenceGroups(functionDecl_functionKeyword, [{ definition: "function fn(): void", ranges: [functionDecl_name] }]); +verify.referenceGroups(varDecl_varKeyword, [{ definition: "var x: any", ranges: [varDecl_name] }]); +verify.referenceGroups(letDecl_letKeyword, [{ definition: "let y: any", ranges: [letDecl_name] }]); +verify.referenceGroups(constDecl_constKeyword, [{ definition: "const z: 1", ranges: [constDecl_name] }]); +verify.noReferences(classDecl2_implementsKeyword); +verify.noReferences(interfaceDecl2_extendsKeyword); \ No newline at end of file diff --git a/tests/cases/fourslash/referencesForStatementKeywords.ts b/tests/cases/fourslash/referencesForStatementKeywords.ts index 15288e94ab146..1277b6d32aa6a 100644 --- a/tests/cases/fourslash/referencesForStatementKeywords.ts +++ b/tests/cases/fourslash/referencesForStatementKeywords.ts @@ -1,10 +1,267 @@ /// +// @filename: /main.ts +////// import ... = ... +////[|{| "id": "importEqualsDecl1" |}[|import|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "importEqualsDecl1" |}A|] = [|require|]("[|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "importEqualsDecl1" |}./a|]");|] +////[|{| "id": "namespaceDecl1" |}namespace [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "namespaceDecl1" |}N|] { }|] +////[|{| "id": "importEqualsDecl2" |}[|import|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "importEqualsDecl2" |}N2|] = [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "importEqualsDecl2" |}N|];|] +//// +////// import ... from ... +////[|{| "id": "importDecl1" |}[|import|] [|type|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "importDecl1" |}B|] [|from|] "[|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "importDecl1" |}./b|]";|] +////[|{| "id": "importDecl2" |}[|import|] [|type|] * [|as|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "importDecl2" |}C|] [|from|] "[|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "importDecl2" |}./c|]";|] +////[|{| "id": "importDecl3" |}[|import|] [|type|] { [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "importDecl3" |}D|] } [|from|] "[|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "importDecl3" |}./d|]";|] +////[|{| "id": "importDecl4" |}[|import|] [|type|] { e1, e2 [|as|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "importDecl4" |}e3|] } [|from|] "[|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "importDecl4" |}./e|]";|] +//// +////// import "module" +////[|{| "id": "importDecl5" |}[|import|] "[|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "importDecl5" |}./f|]";|] +//// +////// export ... from ... +////[|{| "id": "exportDecl1" |}[|export|] [|type|] * [|from|] "[|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "exportDecl1" |}./g|]";|] +////[|{| "id": "exportDecl2" |}[|export|] [|type|] * [|as|] [|{| "isWriteAccess": true, "isDefinition": true |}H|] [|from|] "[|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "exportDecl2" |}./h|]";|] +////[|{| "id": "exportDecl3" |}[|export|] [|type|] { [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "exportDecl3" |}I|] } [|from|] "[|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "exportDecl3" |}./i|]";|] +////[|{| "id": "exportDecl4" |}[|export|] [|type|] { j1, j2 [|as|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "exportDecl4" |}j3|] } [|from|] "[|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "exportDecl4" |}./j|]";|] +////[|{| "id": "typeDecl1" |}type [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "typeDecl1" |}Z1|] = 1;|] +////[|{| "id": "exportDecl5" |}[|export|] [|type|] { [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "exportDecl5" |}Z1|] };|] +////type Z2 = 2; +////type Z3 = 3; +////[|{| "id": "exportDecl6" |}[|export|] [|type|] { z2, z3 [|as|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "exportDecl6" |}z4|] };|] + +// @filename: /main2.ts +////[|{| "id": "varDecl1" |}const [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "varDecl1" |}x|] = {};|] +////[|{| "id": "exportAssignment1" |}[|export|] = [|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "exportAssignment1"|}x|];|] + +// @filename: /main3.ts +////[|{| "id": "varDecl3" |}const [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "varDecl3" |}y|] = {};|] +////[|{| "id": "exportAssignment2" |}[|export|] [|default|] [|{| "isWriteAccess": false, "isDefinition": false, "contextRangeId": "exportAssignment2"|}y|];|] + // @filename: /a.ts -////[|import /*typeKeyword*/type { T } from "[|{| "contextRangeDelta": -1 |}./b|]";|] +////export const a = 1; // @filename: /b.ts -////export type T = number; +////[|{| "id": "classDecl1" |}export default class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "classDecl1" |}B|] {}|] + +// @filename: /c.ts +////export const c = 1; + +// @filename: /d.ts +////[|{| "id": "classDecl2" |}export class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "classDecl2" |}D|] {}|] + +// @filename: /e.ts +////export const e1 = 1; +////export const e2 = 2; + +// @filename: /f.ts +////export const f = 1; + +// @filename: /g.ts +////export const g = 1; + +// @filename: /h.ts +////export const h = 1; + +// @filename: /i.ts +////[|{| "id": "classDecl3" |}export class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "classDecl3" |}I|] {}|] + +// @filename: /j.ts +////export const j1 = 1; +////export const j2 = 2; + +const [ + // main.ts + importEqualsDecl1, + importEqualsDecl1_importKeyword, + importEqualsDecl1_name, + importEqualsDecl1_requireKeyword, + importEqualsDecl1_module, + + namespaceDecl1, + namespaceDecl1_name, + + importEqualsDecl2, + importEqualsDecl2_importKeyword, + importEqualsDecl2_name, + importEqualsDecl2_reference, + + importDecl1, + importDecl1_importKeyword, + importDecl1_typeKeyword, + importDecl1_name, + importDecl1_fromKeyword, + importDecl1_module, + + importDecl2, + importDecl2_importKeyword, + importDecl2_typeKeyword, + importDecl2_asKeyword, + importDecl2_name, + importDecl2_fromKeyword, + importDecl2_module, + + importDecl3, + importDecl3_importKeyword, + importDecl3_typeKeyword, + importDecl3_name, + importDecl3_fromKeyword, + importDecl3_module, + + importDecl4, + importDecl4_importKeyword, + importDecl4_typeKeyword, + importDecl4_asKeyword, + importDecl4_name, + importDecl4_fromKeyword, + importDecl4_module, + + importDecl5, + importDecl5_importKeyword, + importDecl5_module, + + exportDecl1, + exportDecl1_exportKeyword, + exportDecl1_typeKeyword, + exportDecl1_fromKeyword, + exportDecl1_module, + + exportDecl2, + exportDecl2_exportKeyword, + exportDecl2_typeKeyword, + exportDecl2_asKeyword, + exportDecl2_name, + exportDecl2_fromKeyword, + exportDecl2_module, + + exportDecl3, + exportDecl3_exportKeyword, + exportDecl3_typeKeyword, + exportDecl3_name, + exportDecl3_fromKeyword, + exportDecl3_module, + + exportDecl4, + exportDecl4_exportKeyword, + exportDecl4_typeKeyword, + exportDecl4_asKeyword, + exportDecl4_name, + exportDecl4_fromKeyword, + exportDecl4_module, + + typeDecl1, + typeDecl1_name, + + exportDecl5, + exportDecl5_exportKeyword, + exportDecl5_typeKeyword, + exportDecl5_name, + + exportDecl6, + exportDecl6_exportKeyword, + exportDecl6_typeKeyword, + exportDecl6_asKeyword, + exportDecl6_name, + + // main2.ts + varDecl1, + varDecl1_name, + + exportAssignment1, + exportAssignment1_exportKeyword, + exportAssignment1_name, + + // main3.ts + varDecl2, + varDecl2_name, + + exportAssignment2, + exportAssignment2_exportKeyword, + exportAssignment2_defaultKeyword, + exportAssignment2_name, + + // a.ts + // b.ts + classDecl1, + classDecl1_name, + + // c.ts + // d.ts + classDecl2, + classDecl2_name, + + // e.ts + // f.ts + // g.ts + // h.ts + // i.ts + classDecl3, + classDecl3_name, + // j.ts + +] = test.ranges(); + + +// importEqualsDecl1: +verify.referenceGroups(importEqualsDecl1_importKeyword, [{ definition: "import A = require(\"./a\")", ranges: [importEqualsDecl1_name] }]); +verify.referenceGroups(importEqualsDecl1_requireKeyword, [{ definition: "module \"/a\"", ranges: [importEqualsDecl1_module] }]); + +// importEqualsDecl2: +verify.referenceGroups(importEqualsDecl2_importKeyword, [{ definition: "(alias) namespace N2\nimport N2 = N", ranges: [importEqualsDecl2_name] }]); + +// importDecl1: +verify.referenceGroups([importDecl1_importKeyword, importDecl1_typeKeyword], [ + { definition: "(alias) class B\nimport B", ranges: [importDecl1_name] }, + { definition: "class B", ranges: [classDecl1_name] } +]); +verify.referenceGroups(importDecl1_fromKeyword, [{ definition: "module \"/b\"", ranges: [importDecl1_module] }]); + +// importDecl2: +verify.referenceGroups([importDecl2_importKeyword, importDecl2_typeKeyword, importDecl2_asKeyword], [{ definition: "import C", ranges: [importDecl2_name] }]); +verify.referenceGroups([importDecl2_fromKeyword], [{ definition: "module \"/c\"", ranges: [importDecl2_module] }]); + +// importDecl3: +verify.referenceGroups([importDecl3_importKeyword, importDecl3_typeKeyword], [ + { definition: "(alias) class D\nimport D", ranges: [importDecl3_name] }, + { definition: "class D", ranges: [classDecl2_name] } +]); +verify.referenceGroups(importDecl3_fromKeyword, [{ definition: "module \"/d\"", ranges: [importDecl3_module] }]); + +// importDecl4: +verify.referenceGroups([importDecl4_importKeyword, importDecl4_typeKeyword, importDecl4_fromKeyword], [{ definition: "module \"/e\"", ranges: [importDecl4_module] }]); +verify.referenceGroups(importDecl4_asKeyword, [{ definition: "(alias) const e3: 2\nimport e3", ranges: [importDecl4_name] }]); + +// importDecl5 +verify.referenceGroups(importDecl5_importKeyword, [{ definition: "module \"/f\"", ranges: [importDecl5_module] }]); + +// exportDecl1: +verify.referenceGroups([exportDecl1_exportKeyword, exportDecl1_typeKeyword, exportDecl1_fromKeyword], [{ definition: "module \"/g\"", ranges: [exportDecl1_module] }]); + +// exportDecl2: +verify.referenceGroups([exportDecl2_exportKeyword, exportDecl2_typeKeyword, exportDecl2_asKeyword], [{ definition: "import H", ranges: [exportDecl2_name] }]); +verify.referenceGroups([exportDecl2_fromKeyword], [{ definition: "module \"/h\"", ranges: [exportDecl2_module] }]); + +// exportDecl3: +verify.referenceGroups([exportDecl3_exportKeyword, exportDecl3_typeKeyword], [ + { definition: "(alias) class I\nexport I", ranges: [exportDecl3_name] }, + { definition: "class I", ranges: [classDecl3_name] } +]); +verify.referenceGroups(exportDecl3_fromKeyword, [{ definition: "module \"/i\"", ranges: [exportDecl3_module] }]); + +// exportDecl4: +verify.referenceGroups([exportDecl4_exportKeyword, exportDecl4_typeKeyword, exportDecl4_fromKeyword], [{ definition: "module \"/j\"", ranges: [exportDecl4_module] }]); +verify.referenceGroups(exportDecl4_asKeyword, [{ definition: "(alias) const j3: 2\nexport j3", ranges: [exportDecl4_name] }]); + +// exportDecl5: +verify.referenceGroups([exportDecl5_exportKeyword, exportDecl5_typeKeyword], [{ definition: "", ranges: [typeDecl1_name, exportDecl5_name] }]); + +// exportDecl6: +verify.noReferences(exportDecl6_exportKeyword); +verify.noReferences(exportDecl6_typeKeyword); +verify.referenceGroups(exportDecl6_asKeyword, [{ definition: "export z4", ranges: [exportDecl6_name] }]); + +// exportAssignment1: +verify.referenceGroups(exportAssignment1_exportKeyword, [ + { definition: "const x: {}", ranges: [varDecl1_name, exportAssignment1_name] } +]); -const [, importRef] = test.ranges(); -verify.referenceGroups("typeKeyword", [{ definition: "module \"/b\"", ranges: [importRef] }]); \ No newline at end of file +// exportAssignment2: +verify.referenceGroups(exportAssignment2_exportKeyword, [ + { definition: "const y: {}", ranges: [varDecl2_name, exportAssignment2_name] } +]); diff --git a/tests/cases/fourslash/renameDeclarationKeywords.ts b/tests/cases/fourslash/renameDeclarationKeywords.ts index 8b5fd27fa5bed..0a762150ff65a 100644 --- a/tests/cases/fourslash/renameDeclarationKeywords.ts +++ b/tests/cases/fourslash/renameDeclarationKeywords.ts @@ -1,43 +1,95 @@ /// -////[|[|class|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}C|] { -//// [|[|get|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}e|]() { return 1; }|] -//// [|[|set|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}e|](v) {}|] +////[|{| "id": "baseDecl" |}class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "baseDecl" |}Base|] {}|] +////[|{| "id": "implemented1Decl" |}interface [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "implemented1Decl" |}Implemented1|] {}|] +////[|{| "id": "classDecl1" |}[|class|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "classDecl1" |}C1|] [|extends|] [|Base|] [|implements|] [|Implemented1|] { +//// [|{| "id": "getDecl" |}[|get|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "getDecl" |}e|]() { return 1; }|] +//// [|{| "id": "setDecl" |}[|set|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "setDecl" |}e|](v) {}|] ////}|] -////[|[|interface|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}I|] { }|] -////[|[|type|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}T|] = { }|] -////[|[|enum|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}E|] { }|] -////[|[|namespace|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}N|] { }|] -////[|[|module|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}M|] { }|] -////[|[|function|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}fn|]() {}|] -////[|[|var|] [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeDelta": -2 |}x|];|] -////[|[|let|] [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeDelta": -2 |}y|];|] -////[|[|const|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -2 |}z|] = 1;|] +////[|{| "id": "interfaceDecl1" |}[|interface|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "interfaceDecl1" |}I1|] [|extends|] [|Base|] { }|] +////[|{| "id": "typeDecl" |}[|type|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "typeDecl" |}T|] = { }|] +////[|{| "id": "enumDecl" |}[|enum|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "enumDecl" |}E|] { }|] +////[|{| "id": "namespaceDecl" |}[|namespace|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "namespaceDecl" |}N|] { }|] +////[|{| "id": "moduleDecl" |}[|module|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "moduleDecl" |}M|] { }|] +////[|{| "id": "functionDecl" |}[|function|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "functionDecl" |}fn|]() {}|] +////[|{| "id": "varDecl" |}[|var|] [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeId": "varDecl" |}x|];|] +////[|{| "id": "letDecl" |}[|let|] [|{| "isWriteAccess": false, "isDefinition": true, "contextRangeId": "letDecl" |}y|];|] +////[|{| "id": "constDecl" |}[|const|] [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "constDecl" |}z|] = 1;|] const [ - classDef, classKeyword, className, - getDef, getKeyword, getName, - setDef, setKeyword, setName, - interfaceDef, interfaceKeyword, interfaceName, - typeDef, typeKeyword, typeName, - enumDef, enumKeyword, enumName, - namespaceDef, namespaceKeyword, namespaceName, - moduleDef, moduleKeyword, moduleName, - functionDef, functionKeyword, functionName, - varDef, varKeyword, varName, - letDef, letKeyword, letName, - constDef, constKeyword, constName, + baseDecl, + baseDecl_name, + + implemented1Decl, + implemented1Decl_name, + + classDecl1, + classDecl1_classKeyword, + classDecl1_name, + classDecl1_extendsKeyword, + classDecl1_extendsName, + classDecl1_implementsKeyword, + classDecl1_implementsName, + + getDecl, + getDecl_getKeyword, + getDecl_name, + + setDecl, + setDecl_setKeyword, + setDecl_name, + + interfaceDecl1, + interfaceDecl1_interfaceKeyword, + interfaceDecl1_name, + interfaceDecl1_extendsKeyword, + interfaceDecl1_extendsName, + + typeDecl, + typeDecl_typeKeyword, + typeDecl_name, + + enumDecl, + enumDecl_enumKeyword, + enumDecl_name, + + namespaceDecl, + namespaceDecl_namespaceKeyword, + namespaceDecl_name, + + moduleDecl, + moduleDecl_moduleKeyword, + moduleDecl_name, + + functionDecl, + functionDecl_functionKeyword, + functionDecl_name, + + varDecl, + varDecl_varKeyword, + varDecl_name, + + letDecl, + letDecl_letKeyword, + letDecl_name, + + constDecl, + constDecl_constKeyword, + constDecl_name, ] = test.ranges(); -verify.renameLocations(classKeyword, [{ range: className }]); -for (const keyword of [getKeyword, setKeyword]) { - verify.renameLocations(keyword, [{ range: getName }, { range: setName }]); +verify.renameLocations(classDecl1_classKeyword, [{ range: classDecl1_name }]); +verify.renameLocations(classDecl1_extendsKeyword, [{ range: baseDecl_name }, { range: classDecl1_extendsName }, { range: interfaceDecl1_extendsName }]); +verify.renameLocations(classDecl1_implementsKeyword, [{ range: implemented1Decl_name }, { range: classDecl1_implementsName }]); +for (const keyword of [getDecl_getKeyword, setDecl_setKeyword]) { + verify.renameLocations(keyword, [{ range: getDecl_name }, { range: setDecl_name }]); } -verify.renameLocations(interfaceKeyword, [{ range: interfaceName }]); -verify.renameLocations(typeKeyword, [{ range: typeName }]); -verify.renameLocations(enumKeyword, [{ range: enumName }]); -verify.renameLocations(namespaceKeyword, [{ range: namespaceName }]); -verify.renameLocations(moduleKeyword, [{ range: moduleName }]); -verify.renameLocations(functionKeyword, [{ range: functionName }]); -verify.renameLocations(varKeyword, [{ range: varName }]); -verify.renameLocations(letKeyword, [{ range: letName }]); -verify.renameLocations(constKeyword, [{ range: constName }]); \ No newline at end of file +verify.renameLocations(interfaceDecl1_interfaceKeyword, [{ range: interfaceDecl1_name }]); +verify.renameLocations(interfaceDecl1_extendsKeyword, [{ range: baseDecl_name }, { range: classDecl1_extendsName }, { range: interfaceDecl1_extendsName }]); +verify.renameLocations(typeDecl_typeKeyword, [{ range: typeDecl_name }]); +verify.renameLocations(enumDecl_enumKeyword, [{ range: enumDecl_name }]); +verify.renameLocations(namespaceDecl_namespaceKeyword, [{ range: namespaceDecl_name }]); +verify.renameLocations(moduleDecl_moduleKeyword, [{ range: moduleDecl_name }]); +verify.renameLocations(functionDecl_functionKeyword, [{ range: functionDecl_name }]); +verify.renameLocations(varDecl_varKeyword, [{ range: varDecl_name }]); +verify.renameLocations(letDecl_letKeyword, [{ range: letDecl_name }]); +verify.renameLocations(constDecl_constKeyword, [{ range: constDecl_name }]); \ No newline at end of file From 46296c04c480e1cae4870d29034fc8e9e834b637 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 30 Jan 2020 18:17:03 -0800 Subject: [PATCH 4/4] Add additional type and expression keywords --- src/compiler/checker.ts | 14 ++-- src/harness/fourslashImpl.ts | 16 ++--- src/services/utilities.ts | 64 +++++++++++++------ .../referencesForDeclarationKeywords.ts | 2 +- .../referencesForExpressionKeywords.ts | 47 ++++++++++---- .../referencesForStatementKeywords.ts | 2 +- .../fourslash/referencesForTypeKeywords.ts | 43 +++++++++++++ 7 files changed, 140 insertions(+), 48 deletions(-) create mode 100644 tests/cases/fourslash/referencesForTypeKeywords.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f7550b1481868..572993e456058 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -413,9 +413,9 @@ namespace ts { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; }, - getSymbolAtLocation: (node: Node) => { + getSymbolAtLocation: node => { node = getParseTreeNode(node); - return node && getSymbolAtLocation(node); + return node ? getSymbolAtLocation(node) : undefined; }, getShorthandAssignmentValueSymbol: node => { node = getParseTreeNode(node); @@ -34249,7 +34249,7 @@ namespace ts { if (constructorDeclaration && constructorDeclaration.kind === SyntaxKind.Constructor) { return (constructorDeclaration.parent).symbol; } - break; + return undefined; case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: @@ -34287,10 +34287,10 @@ namespace ts { return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal) : undefined; case SyntaxKind.ExportKeyword: - if (isExportAssignment(node.parent)) { - return Debug.assertDefined(node.parent.symbol); - } - break; + return isExportAssignment(node.parent) ? Debug.assertDefined(node.parent.symbol) : undefined; + + default: + return undefined; } } diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 062b412ac8621..52ac86263fda3 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -229,7 +229,7 @@ namespace FourSlash { } } - constructor(public originalInputFileName: string, private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) { + constructor(private originalInputFileName: string, private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) { // Create a new Services Adapter this.cancellationToken = new TestCancellationToken(); let compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); @@ -3637,18 +3637,17 @@ namespace FourSlash { const testData = parseTestData(absoluteBasePath, content, absoluteFileName); const state = new TestState(absoluteFileName, absoluteBasePath, testType, testData); const actualFileName = Harness.IO.resolvePath(fileName) || absoluteFileName; - const output = ts.transpileModule(content, { reportDiagnostics: true, fileName: actualFileName, compilerOptions: { target: ts.ScriptTarget.ES2015, sourceMap: true } }); + const output = ts.transpileModule(content, { reportDiagnostics: true, fileName: actualFileName, compilerOptions: { target: ts.ScriptTarget.ES2015, inlineSourceMap: true } }); if (output.diagnostics!.length > 0) { throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics![0].messageText}`); } - runCode(output, state, actualFileName); + runCode(output.outputText, state, actualFileName); } - function runCode(output: ts.TranspileOutput, state: TestState, fileName: string): void { + function runCode(code: string, state: TestState, fileName: string): void { // Compile and execute the test const generatedFile = ts.changeExtension(fileName, ".js"); - const mapFile = generatedFile + ".map"; - const wrappedCode = `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {${output.outputText}\n//# sourceURL=${generatedFile}\n})`; + const wrappedCode = `(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {${code}\n//# sourceURL=${generatedFile}\n})`; type SourceMapSupportModule = typeof import("source-map-support") & { // TODO(rbuckton): This is missing from the DT definitions and needs to be added. @@ -3668,7 +3667,6 @@ namespace FourSlash { sourceMapSupportModule?.install({ retrieveFile: path => { return path === generatedFile ? wrappedCode : - path === mapFile ? output.sourceMapText! : undefined!; } }); @@ -3687,7 +3685,7 @@ namespace FourSlash { f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, FourSlashInterface.Completion, verifyOperationIsCancelled); } catch (err) { - // ensure we trigger 'source-map-support' while we still have the handler attached + // ensure 'source-map-support' is triggered while we still have the handler attached by accessing `error.stack`. err.stack?.toString(); throw err; } @@ -3862,7 +3860,7 @@ namespace FourSlash { markerValue = JSON.parse("{ " + text + " }"); } catch (e) { - reportError(fileName, location.sourceLine, location.sourceColumn, "Unable to parse marker text " + e.message + "\nSource:\n {| " + text + " |}"); + reportError(fileName, location.sourceLine, location.sourceColumn, "Unable to parse marker text " + e.message); } if (markerValue === undefined) { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 02843d0a33593..356ea22dbaa1b 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -88,6 +88,7 @@ namespace ts { } export function getMeaningFromLocation(node: Node): SemanticMeaning { + node = getAdjustedReferenceLocation(node); if (node.kind === SyntaxKind.SourceFile) { return SemanticMeaning.Value; } @@ -114,24 +115,6 @@ namespace ts { // This might be T["name"], which is actually referencing a property and not a type. So allow both meanings. return SemanticMeaning.Type | SemanticMeaning.Value; } - else if (isModifier(node) && contains(node.parent.modifiers, node)) { - // on the modifier of a declaration - return getMeaningFromDeclaration(node.parent); - } - else if (node.kind === SyntaxKind.ClassKeyword && isClassLike(node.parent) || - node.kind === SyntaxKind.InterfaceKeyword && isInterfaceDeclaration(node.parent) || - node.kind === SyntaxKind.TypeKeyword && isTypeAliasDeclaration(node.parent) || - node.kind === SyntaxKind.EnumKeyword && isEnumDeclaration(node.parent) || - node.kind === SyntaxKind.FunctionKeyword && isFunctionLikeDeclaration(node.parent) || - node.kind === SyntaxKind.GetKeyword && isGetAccessorDeclaration(node.parent) || - node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(node.parent) || - (node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword) && isModuleDeclaration(node.parent)) { - // on the keyword of a declaration - return getMeaningFromDeclaration(node.parent); - } - else if (node.kind === SyntaxKind.TypeKeyword && isImportClause(node.parent) && node.parent.isTypeOnly) { - return getMeaningFromDeclaration(node.parent.parent); - } else { return SemanticMeaning.Value; } @@ -986,8 +969,36 @@ namespace ts { return location; } } + if (node.kind === SyntaxKind.ExtendsKeyword) { + // ... ... + if (isTypeParameterDeclaration(parent) && parent.constraint && isTypeReferenceNode(parent.constraint)) { + return parent.constraint.typeName; + } + // ... T /**/extends [|U|] ? ... + if (isConditionalTypeNode(parent) && isTypeReferenceNode(parent.extendsType)) { + return parent.extendsType.typeName; + } + } + // ... T extends /**/infer [|U|] ? ... + if (node.kind === SyntaxKind.InferKeyword && isInferTypeNode(parent)) { + return parent.typeParameter.name; + } + // { [ [|K|] /**/in keyof T]: ... } + if (node.kind === SyntaxKind.InKeyword && isTypeParameterDeclaration(parent) && isMappedTypeNode(parent.parent)) { + return parent.name; + } + // /**/keyof [|T|] + if (node.kind === SyntaxKind.KeyOfKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.KeyOfKeyword && + isTypeReferenceNode(parent.type)) { + return parent.type.typeName; + } + // /**/readonly [|name|][] + if (node.kind === SyntaxKind.ReadonlyKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.ReadonlyKeyword && + isArrayTypeNode(parent.type) && isTypeReferenceNode(parent.type.elementType)) { + return parent.type.elementType.typeName; + } if (!forRename) { - // /**/new [|name|](...) + // /**/new [|name|] // /**/void [|name|] // /**/void obj.[|name|] // /**/typeof [|name|] @@ -1007,6 +1018,21 @@ namespace ts { return skipOuterExpressions(parent.expression); } } + // left /**/in [|name|] + // left /**/instanceof [|name|] + if ((node.kind === SyntaxKind.InKeyword || node.kind === SyntaxKind.InstanceOfKeyword) && isBinaryExpression(parent) && parent.operatorToken === node) { + return skipOuterExpressions(parent.right); + } + // left /**/as [|name|] + if (node.kind === SyntaxKind.AsKeyword && isAsExpression(parent) && isTypeReferenceNode(parent.type)) { + return parent.type.typeName; + } + // for (... /**/in [|name|]) + // for (... /**/of [|name|]) + if (node.kind === SyntaxKind.InKeyword && isForInStatement(parent) || + node.kind === SyntaxKind.OfKeyword && isForOfStatement(parent)) { + return skipOuterExpressions(parent.expression); + } } return node; } diff --git a/tests/cases/fourslash/referencesForDeclarationKeywords.ts b/tests/cases/fourslash/referencesForDeclarationKeywords.ts index fa1887680a1ce..aeb8870ee2388 100644 --- a/tests/cases/fourslash/referencesForDeclarationKeywords.ts +++ b/tests/cases/fourslash/referencesForDeclarationKeywords.ts @@ -71,7 +71,7 @@ const [ ] = test.ranges(); verify.referenceGroups(classDecl1_classKeyword, [{ definition: "class C1", ranges: [classDecl1_name] }]); verify.referenceGroups(classDecl1_extendsKeyword, [{ definition: "class Base", ranges: [baseDecl_name, classDecl1_extendsName, interfaceDecl1_extendsName] }]); -verify.referenceGroups(classDecl1_implementsKeyword, [{ definition: "", ranges: [implemented1Decl_name, classDecl1_implementsName] }]); +verify.referenceGroups(classDecl1_implementsKeyword, [{ definition: "interface Implemented1", ranges: [implemented1Decl_name, classDecl1_implementsName] }]); for (const keyword of [getDecl_getKeyword, setDecl_setKeyword]) { verify.referenceGroups(keyword, [{ definition: "(property) C1.e: number", ranges: [getDecl_name, setDecl_name] }]); } diff --git a/tests/cases/fourslash/referencesForExpressionKeywords.ts b/tests/cases/fourslash/referencesForExpressionKeywords.ts index fb35ba4ab9adc..c64919b765941 100644 --- a/tests/cases/fourslash/referencesForExpressionKeywords.ts +++ b/tests/cases/fourslash/referencesForExpressionKeywords.ts @@ -3,17 +3,42 @@ ////[|class [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}C|] { //// [|static [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeDelta": -1 |}x|] = 1;|] ////}|] -/////*newKeyword*/new [|C|](); -/////*voidKeyword*/void [|C|]; -/////*typeofKeyword*/typeof [|C|]; -/////*deleteKeyword*/delete [|C|].[|x|]; +////[|new|] [|C|](); +////[|void|] [|C|]; +////[|typeof|] [|C|]; +////[|delete|] [|C|].[|x|]; ////async function* f() { -//// /*yieldKeyword*/yield [|C|]; -//// /*awaitKeyword*/await [|C|]; +//// [|yield|] [|C|]; +//// [|await|] [|C|]; ////} +////"x" [|in|] [|C|]; +////undefined [|instanceof|] [|C|]; +////undefined [|as|] [|C|]; -const [, classDef,, xDef, newC, voidC, typeofC, deleteC, deleteCx, yieldC, awaitC] = test.ranges(); -for (const keyword of ["newKeyword", "voidKeyword", "typeofKeyword", "yieldKeyword", "awaitKeyword"]) { - verify.referenceGroups(keyword, [{ definition: "class C", ranges: [classDef, newC, voidC, typeofC, deleteC, yieldC, awaitC] }]); -} -verify.referenceGroups("deleteKeyword", [{ definition: "(property) C.x: number", ranges: [xDef, deleteCx] }]); \ No newline at end of file +const [ + classDecl, + classDecl_name, + fieldDecl, + fieldDecl_name, + newKeyword, + newC, + voidKeyword, + voidC, + typeofKeyword, + typeofC, + deleteKeyword, + deleteC, + deleteCx, + yieldKeyword, + yieldC, + awaitKeyword, + awaitC, + inKeyword, + inC, + instanceofKeyword, + instanceofC, + asKeyword, + asC, +] = test.ranges(); +verify.referenceGroups([newKeyword, voidKeyword, typeofKeyword, yieldKeyword, awaitKeyword, inKeyword, instanceofKeyword, asKeyword], [{ definition: "class C", ranges: [classDecl_name, newC, voidC, typeofC, deleteC, yieldC, awaitC, inC, instanceofC, asC] }]); +verify.referenceGroups(deleteKeyword, [{ definition: "(property) C.x: number", ranges: [fieldDecl_name, deleteCx] }]); \ No newline at end of file diff --git a/tests/cases/fourslash/referencesForStatementKeywords.ts b/tests/cases/fourslash/referencesForStatementKeywords.ts index 1277b6d32aa6a..bcd9aa0f48a3c 100644 --- a/tests/cases/fourslash/referencesForStatementKeywords.ts +++ b/tests/cases/fourslash/referencesForStatementKeywords.ts @@ -249,7 +249,7 @@ verify.referenceGroups([exportDecl4_exportKeyword, exportDecl4_typeKeyword, expo verify.referenceGroups(exportDecl4_asKeyword, [{ definition: "(alias) const j3: 2\nexport j3", ranges: [exportDecl4_name] }]); // exportDecl5: -verify.referenceGroups([exportDecl5_exportKeyword, exportDecl5_typeKeyword], [{ definition: "", ranges: [typeDecl1_name, exportDecl5_name] }]); +verify.referenceGroups([exportDecl5_exportKeyword, exportDecl5_typeKeyword], [{ definition: "type Z1 = 1", ranges: [typeDecl1_name, exportDecl5_name] }]); // exportDecl6: verify.noReferences(exportDecl6_exportKeyword); diff --git a/tests/cases/fourslash/referencesForTypeKeywords.ts b/tests/cases/fourslash/referencesForTypeKeywords.ts new file mode 100644 index 0000000000000..cfd37a43960c0 --- /dev/null +++ b/tests/cases/fourslash/referencesForTypeKeywords.ts @@ -0,0 +1,43 @@ +/// + +////[|{| "id": "interfaceDecl" |}interface [|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "interfaceDecl" |}I|] {}|] +////function f() {} +////type A1 = T [|extends|] [|U|] ? 1 : 0; +////type A2 = T extends [|infer|] [|{| "isWriteAccess": true, "isDefinition": true |}U|] ? 1 : 0; +////type A3 = { [[|{| "id": "mappedType_param" |}[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeId": "mappedType_param" |}P|] [|in|] keyof T|]]: 1 }; +////type A4<[|{| "isWriteAccess": true, "isDefinition": true |}T|]> = [|keyof|] [|T|]; +////type A5<[|{| "isWriteAccess": true, "isDefinition": true |}T|]> = [|readonly|] [|T|][]; + +const [ + interfaceDecl, + interfaceDecl_name, + + typeParam_extendsKeyword, + typeParam_constraint, + + typeParamA1_name, + conditionalType_extendsKeyword, + conditionalType_extendsType, + + inferType_inferKeyword, + inferType_type, + + mappedType_param, + mappedType_name, + mappedType_inOperator, + + typeParamA4_name, + keyofOperator_keyofKeyword, + keyofOperator_type, + + typeParamA5_name, + readonlyOperator_readonlyKeyword, + readonlyOperator_elementType, +] = test.ranges(); + +verify.referenceGroups(typeParam_extendsKeyword, [{ definition: "interface I", ranges: [interfaceDecl_name, typeParam_constraint] }]); +verify.referenceGroups(conditionalType_extendsKeyword, [{ definition: "(type parameter) U in type A1", ranges: [typeParamA1_name, conditionalType_extendsType] }]); +verify.referenceGroups(inferType_inferKeyword, [{ definition: "(type parameter) U", ranges: [inferType_type] }]); +verify.referenceGroups(mappedType_inOperator, [{ definition: "(type parameter) P", ranges: [mappedType_name] }]); +verify.referenceGroups(keyofOperator_keyofKeyword, [{ definition: "(type parameter) T in type A4", ranges: [typeParamA4_name, keyofOperator_type] }]); +verify.referenceGroups(readonlyOperator_readonlyKeyword, [{ definition: "(type parameter) T in type A5", ranges: [typeParamA5_name, readonlyOperator_elementType] }]); \ No newline at end of file