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 pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy