Skip to content

Commit afaffb1

Browse files
committed
Merge pull request #8366 from Microsoft/Fix7611-2
Fix #7611: Add support for String Literal Types in find all refs and occurances
2 parents 10d09a7 + 0c50774 commit afaffb1

7 files changed

+219
-38
lines changed

src/services/services.ts

Lines changed: 132 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4891,8 +4891,8 @@ namespace ts {
48914891
node.kind === SyntaxKind.ThisKeyword ||
48924892
node.kind === SyntaxKind.ThisType ||
48934893
node.kind === SyntaxKind.SuperKeyword ||
4894-
isLiteralNameOfPropertyDeclarationOrIndexAccess(node) ||
4895-
isNameOfExternalModuleImportOrDeclaration(node)) {
4894+
node.kind === SyntaxKind.StringLiteral ||
4895+
isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
48964896

48974897
const referencedSymbols = getReferencedSymbolsForNode(node, sourceFilesToSearch, /*findInStrings*/ false, /*findInComments*/ false);
48984898
return convertReferencedSymbols(referencedSymbols);
@@ -5559,8 +5559,8 @@ namespace ts {
55595559
// TODO (drosen): This should be enabled in a later release - currently breaks rename.
55605560
// node.kind !== SyntaxKind.ThisKeyword &&
55615561
// node.kind !== SyntaxKind.SuperKeyword &&
5562-
!isLiteralNameOfPropertyDeclarationOrIndexAccess(node) &&
5563-
!isNameOfExternalModuleImportOrDeclaration(node)) {
5562+
node.kind !== SyntaxKind.StringLiteral &&
5563+
!isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
55645564
return undefined;
55655565
}
55665566

@@ -5595,6 +5595,10 @@ namespace ts {
55955595

55965596
const symbol = typeChecker.getSymbolAtLocation(node);
55975597

5598+
if (!symbol && node.kind === SyntaxKind.StringLiteral) {
5599+
return getReferencesForStringLiteral(<StringLiteral>node, sourceFiles);
5600+
}
5601+
55985602
// Could not find a symbol e.g. unknown identifier
55995603
if (!symbol) {
56005604
// Can't have references to something that we have no symbol for.
@@ -6151,6 +6155,52 @@ namespace ts {
61516155
}
61526156
}
61536157

6158+
6159+
function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[]): ReferencedSymbol[] {
6160+
const typeChecker = program.getTypeChecker();
6161+
const type = getStringLiteralTypeForNode(node, typeChecker);
6162+
6163+
if (!type) {
6164+
// nothing to do here. moving on
6165+
return undefined;
6166+
}
6167+
6168+
const references: ReferenceEntry[] = [];
6169+
6170+
for (const sourceFile of sourceFiles) {
6171+
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, type.text, sourceFile.getStart(), sourceFile.getEnd());
6172+
getReferencesForStringLiteralInFile(sourceFile, type, possiblePositions, references);
6173+
}
6174+
6175+
return [{
6176+
definition: {
6177+
containerKind: "",
6178+
containerName: "",
6179+
fileName: node.getSourceFile().fileName,
6180+
kind: ScriptElementKind.variableElement,
6181+
name: type.text,
6182+
textSpan: createTextSpanFromBounds(node.getStart(), node.getEnd())
6183+
},
6184+
references: references
6185+
}];
6186+
6187+
function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: ReferenceEntry[]): void {
6188+
for (const position of possiblePositions) {
6189+
cancellationToken.throwIfCancellationRequested();
6190+
6191+
const node = getTouchingWord(sourceFile, position);
6192+
if (!node || node.kind !== SyntaxKind.StringLiteral) {
6193+
return;
6194+
}
6195+
6196+
const type = getStringLiteralTypeForNode(<StringLiteral>node, typeChecker);
6197+
if (type === searchType) {
6198+
references.push(getReferenceEntryFromNode(node));
6199+
}
6200+
}
6201+
}
6202+
}
6203+
61546204
function populateSearchSymbolSet(symbol: Symbol, location: Node): Symbol[] {
61556205
// The search set contains at least the current symbol
61566206
let result = [symbol];
@@ -7671,53 +7721,75 @@ namespace ts {
76717721
}
76727722
}
76737723

7724+
function getStringLiteralTypeForNode(node: StringLiteral | StringLiteralTypeNode, typeChecker: TypeChecker): StringLiteralType {
7725+
const searchNode = node.parent.kind === SyntaxKind.StringLiteralType ? <StringLiteralTypeNode>node.parent : node;
7726+
const type = typeChecker.getTypeAtLocation(searchNode);
7727+
if (type && type.flags & TypeFlags.StringLiteral) {
7728+
return <StringLiteralType>type;
7729+
}
7730+
return undefined;
7731+
}
76747732

76757733
function getRenameInfo(fileName: string, position: number): RenameInfo {
76767734
synchronizeHostData();
76777735

76787736
const sourceFile = getValidSourceFile(fileName);
76797737
const typeChecker = program.getTypeChecker();
76807738

7739+
const defaultLibFileName = host.getDefaultLibFileName(host.getCompilationSettings());
7740+
const canonicalDefaultLibName = getCanonicalFileName(ts.normalizePath(defaultLibFileName));
7741+
76817742
const node = getTouchingWord(sourceFile, position);
76827743

76837744
// Can only rename an identifier.
7684-
if (node && node.kind === SyntaxKind.Identifier) {
7685-
const symbol = typeChecker.getSymbolAtLocation(node);
7686-
7687-
// Only allow a symbol to be renamed if it actually has at least one declaration.
7688-
if (symbol) {
7689-
const declarations = symbol.getDeclarations();
7690-
if (declarations && declarations.length > 0) {
7691-
// Disallow rename for elements that are defined in the standard TypeScript library.
7692-
const defaultLibFileName = host.getDefaultLibFileName(host.getCompilationSettings());
7693-
const canonicalDefaultLibName = getCanonicalFileName(ts.normalizePath(defaultLibFileName));
7694-
if (defaultLibFileName) {
7695-
for (const current of declarations) {
7696-
const sourceFile = current.getSourceFile();
7697-
// TODO (drosen): When is there no source file?
7698-
if (!sourceFile) {
7699-
continue;
7700-
}
7745+
if (node) {
7746+
if (node.kind === SyntaxKind.Identifier ||
7747+
node.kind === SyntaxKind.StringLiteral ||
7748+
isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
7749+
const symbol = typeChecker.getSymbolAtLocation(node);
7750+
7751+
// Only allow a symbol to be renamed if it actually has at least one declaration.
7752+
if (symbol) {
7753+
const declarations = symbol.getDeclarations();
7754+
if (declarations && declarations.length > 0) {
7755+
// Disallow rename for elements that are defined in the standard TypeScript library.
7756+
if (forEach(declarations, isDefinedInLibraryFile)) {
7757+
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library));
7758+
}
77017759

7702-
const canonicalName = getCanonicalFileName(ts.normalizePath(sourceFile.fileName));
7703-
if (canonicalName === canonicalDefaultLibName) {
7704-
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library));
7705-
}
7760+
const displayName = stripQuotes(getDeclaredName(typeChecker, symbol, node));
7761+
const kind = getSymbolKind(symbol, node);
7762+
if (kind) {
7763+
return {
7764+
canRename: true,
7765+
kind,
7766+
displayName,
7767+
localizedErrorMessage: undefined,
7768+
fullDisplayName: typeChecker.getFullyQualifiedName(symbol),
7769+
kindModifiers: getSymbolModifiers(symbol),
7770+
triggerSpan: createTriggerSpanForNode(node, sourceFile)
7771+
};
77067772
}
77077773
}
7708-
7709-
const displayName = stripQuotes(getDeclaredName(typeChecker, symbol, node));
7710-
const kind = getSymbolKind(symbol, node);
7711-
if (kind) {
7712-
return {
7713-
canRename: true,
7714-
kind,
7715-
displayName,
7716-
localizedErrorMessage: undefined,
7717-
fullDisplayName: typeChecker.getFullyQualifiedName(symbol),
7718-
kindModifiers: getSymbolModifiers(symbol),
7719-
triggerSpan: createTextSpan(node.getStart(), node.getWidth())
7720-
};
7774+
}
7775+
else if (node.kind === SyntaxKind.StringLiteral) {
7776+
const type = getStringLiteralTypeForNode(<StringLiteral>node, typeChecker);
7777+
if (type) {
7778+
if (isDefinedInLibraryFile(node)) {
7779+
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library));
7780+
}
7781+
else {
7782+
const displayName = stripQuotes(type.text);
7783+
return {
7784+
canRename: true,
7785+
kind: ScriptElementKind.variableElement,
7786+
displayName,
7787+
localizedErrorMessage: undefined,
7788+
fullDisplayName: displayName,
7789+
kindModifiers: ScriptElementKindModifier.none,
7790+
triggerSpan: createTriggerSpanForNode(node, sourceFile)
7791+
};
7792+
}
77217793
}
77227794
}
77237795
}
@@ -7736,6 +7808,28 @@ namespace ts {
77367808
triggerSpan: undefined
77377809
};
77387810
}
7811+
7812+
function isDefinedInLibraryFile(declaration: Node) {
7813+
if (defaultLibFileName) {
7814+
const sourceFile = declaration.getSourceFile();
7815+
const canonicalName = getCanonicalFileName(ts.normalizePath(sourceFile.fileName));
7816+
if (canonicalName === canonicalDefaultLibName) {
7817+
return true;
7818+
}
7819+
}
7820+
return false;
7821+
}
7822+
7823+
function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) {
7824+
let start = node.getStart(sourceFile);
7825+
let width = node.getWidth(sourceFile);
7826+
if (node.kind === SyntaxKind.StringLiteral) {
7827+
// Exclude the quotes
7828+
start += 1;
7829+
width -= 2;
7830+
}
7831+
return createTextSpan(start, width);
7832+
}
77397833
}
77407834

77417835
return {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////type Options = "[|option 1|]" | "option 2";
4+
////let myOption: Options = "[|option 1|]";
5+
6+
let ranges = test.ranges();
7+
for (let range of ranges) {
8+
goTo.position(range.start);
9+
10+
verify.referencesCountIs(ranges.length);
11+
for (let expectedReference of ranges) {
12+
verify.referencesAtPositionContains(expectedReference);
13+
}
14+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function foo(a: "[|option 1|]") { }
4+
////foo("[|option 1|]");
5+
6+
const ranges = test.ranges();
7+
for (let r of ranges) {
8+
goTo.position(r.start);
9+
10+
for (let range of ranges) {
11+
verify.occurrencesAtPositionContains(range, false);
12+
}
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////var x = "[|string|]";
4+
////function f(a = "[|initial value|]") { }
5+
6+
const ranges = test.ranges();
7+
for (let r of ranges) {
8+
goTo.position(r.start);
9+
verify.occurrencesAtPositionCount(0);
10+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////var y: "string" = "string;
4+
////var x = "/*1*/string";
5+
////function f(a = "/*2*/initial value") { }
6+
7+
8+
goTo.marker("1");
9+
verify.renameInfoFailed();
10+
11+
goTo.marker("2");
12+
verify.renameInfoFailed();
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////var o = {
4+
//// [|prop|]: 0
5+
////};
6+
////
7+
////o = {
8+
//// "[|prop|]": 1
9+
////};
10+
////
11+
////o["[|prop|]"];
12+
////o['[|prop|]'];
13+
////o.[|prop|];
14+
15+
16+
let ranges = test.ranges();
17+
for (let range of ranges) {
18+
goTo.position(range.start);
19+
verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false);
20+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
4+
////interface AnimationOptions {
5+
//// deltaX: number;
6+
//// deltaY: number;
7+
//// easing: "ease-in" | "ease-out" | "[|ease-in-out|]";
8+
////}
9+
////
10+
////function animate(o: AnimationOptions) { }
11+
////
12+
////animate({ deltaX: 100, deltaY: 100, easing: "[|ease-in-out|]" });
13+
14+
let ranges = test.ranges();
15+
for (let range of ranges) {
16+
goTo.position(range.start);
17+
verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false);
18+
}

0 commit comments

Comments
 (0)