Skip to content

Commit

Permalink
Merge pull request #8366 from Microsoft/Fix7611-2
Browse files Browse the repository at this point in the history
Fix #7611: Add support for String Literal Types in find all refs and occurances
  • Loading branch information
mhegazy committed May 2, 2016
2 parents 10d09a7 + 0c50774 commit afaffb1
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 38 deletions.
170 changes: 132 additions & 38 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4891,8 +4891,8 @@ namespace ts {
node.kind === SyntaxKind.ThisKeyword ||
node.kind === SyntaxKind.ThisType ||
node.kind === SyntaxKind.SuperKeyword ||
isLiteralNameOfPropertyDeclarationOrIndexAccess(node) ||
isNameOfExternalModuleImportOrDeclaration(node)) {
node.kind === SyntaxKind.StringLiteral ||
isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {

const referencedSymbols = getReferencedSymbolsForNode(node, sourceFilesToSearch, /*findInStrings*/ false, /*findInComments*/ false);
return convertReferencedSymbols(referencedSymbols);
Expand Down Expand Up @@ -5559,8 +5559,8 @@ namespace ts {
// TODO (drosen): This should be enabled in a later release - currently breaks rename.
// node.kind !== SyntaxKind.ThisKeyword &&
// node.kind !== SyntaxKind.SuperKeyword &&
!isLiteralNameOfPropertyDeclarationOrIndexAccess(node) &&
!isNameOfExternalModuleImportOrDeclaration(node)) {
node.kind !== SyntaxKind.StringLiteral &&
!isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
return undefined;
}

Expand Down Expand Up @@ -5595,6 +5595,10 @@ namespace ts {

const symbol = typeChecker.getSymbolAtLocation(node);

if (!symbol && node.kind === SyntaxKind.StringLiteral) {
return getReferencesForStringLiteral(<StringLiteral>node, sourceFiles);
}

// Could not find a symbol e.g. unknown identifier
if (!symbol) {
// Can't have references to something that we have no symbol for.
Expand Down Expand Up @@ -6151,6 +6155,52 @@ namespace ts {
}
}


function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[]): ReferencedSymbol[] {
const typeChecker = program.getTypeChecker();
const type = getStringLiteralTypeForNode(node, typeChecker);

if (!type) {
// nothing to do here. moving on
return undefined;
}

const references: ReferenceEntry[] = [];

for (const sourceFile of sourceFiles) {
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, type.text, sourceFile.getStart(), sourceFile.getEnd());
getReferencesForStringLiteralInFile(sourceFile, type, possiblePositions, references);
}

return [{
definition: {
containerKind: "",
containerName: "",
fileName: node.getSourceFile().fileName,
kind: ScriptElementKind.variableElement,
name: type.text,
textSpan: createTextSpanFromBounds(node.getStart(), node.getEnd())
},
references: references
}];

function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: ReferenceEntry[]): void {
for (const position of possiblePositions) {
cancellationToken.throwIfCancellationRequested();

const node = getTouchingWord(sourceFile, position);
if (!node || node.kind !== SyntaxKind.StringLiteral) {
return;
}

const type = getStringLiteralTypeForNode(<StringLiteral>node, typeChecker);
if (type === searchType) {
references.push(getReferenceEntryFromNode(node));
}
}
}
}

function populateSearchSymbolSet(symbol: Symbol, location: Node): Symbol[] {
// The search set contains at least the current symbol
let result = [symbol];
Expand Down Expand Up @@ -7671,53 +7721,75 @@ namespace ts {
}
}

function getStringLiteralTypeForNode(node: StringLiteral | StringLiteralTypeNode, typeChecker: TypeChecker): StringLiteralType {
const searchNode = node.parent.kind === SyntaxKind.StringLiteralType ? <StringLiteralTypeNode>node.parent : node;
const type = typeChecker.getTypeAtLocation(searchNode);
if (type && type.flags & TypeFlags.StringLiteral) {
return <StringLiteralType>type;
}
return undefined;
}

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

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

const defaultLibFileName = host.getDefaultLibFileName(host.getCompilationSettings());
const canonicalDefaultLibName = getCanonicalFileName(ts.normalizePath(defaultLibFileName));

const node = getTouchingWord(sourceFile, position);

// Can only rename an identifier.
if (node && node.kind === SyntaxKind.Identifier) {
const symbol = typeChecker.getSymbolAtLocation(node);

// Only allow a symbol to be renamed if it actually has at least one declaration.
if (symbol) {
const declarations = symbol.getDeclarations();
if (declarations && declarations.length > 0) {
// Disallow rename for elements that are defined in the standard TypeScript library.
const defaultLibFileName = host.getDefaultLibFileName(host.getCompilationSettings());
const canonicalDefaultLibName = getCanonicalFileName(ts.normalizePath(defaultLibFileName));
if (defaultLibFileName) {
for (const current of declarations) {
const sourceFile = current.getSourceFile();
// TODO (drosen): When is there no source file?
if (!sourceFile) {
continue;
}
if (node) {
if (node.kind === SyntaxKind.Identifier ||
node.kind === SyntaxKind.StringLiteral ||
isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
const symbol = typeChecker.getSymbolAtLocation(node);

// Only allow a symbol to be renamed if it actually has at least one declaration.
if (symbol) {
const declarations = symbol.getDeclarations();
if (declarations && declarations.length > 0) {
// Disallow rename for elements that are defined in the standard TypeScript library.
if (forEach(declarations, isDefinedInLibraryFile)) {
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library));
}

const canonicalName = getCanonicalFileName(ts.normalizePath(sourceFile.fileName));
if (canonicalName === canonicalDefaultLibName) {
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library));
}
const displayName = stripQuotes(getDeclaredName(typeChecker, symbol, node));
const kind = getSymbolKind(symbol, node);
if (kind) {
return {
canRename: true,
kind,
displayName,
localizedErrorMessage: undefined,
fullDisplayName: typeChecker.getFullyQualifiedName(symbol),
kindModifiers: getSymbolModifiers(symbol),
triggerSpan: createTriggerSpanForNode(node, sourceFile)
};
}
}

const displayName = stripQuotes(getDeclaredName(typeChecker, symbol, node));
const kind = getSymbolKind(symbol, node);
if (kind) {
return {
canRename: true,
kind,
displayName,
localizedErrorMessage: undefined,
fullDisplayName: typeChecker.getFullyQualifiedName(symbol),
kindModifiers: getSymbolModifiers(symbol),
triggerSpan: createTextSpan(node.getStart(), node.getWidth())
};
}
else if (node.kind === SyntaxKind.StringLiteral) {
const type = getStringLiteralTypeForNode(<StringLiteral>node, typeChecker);
if (type) {
if (isDefinedInLibraryFile(node)) {
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library));
}
else {
const displayName = stripQuotes(type.text);
return {
canRename: true,
kind: ScriptElementKind.variableElement,
displayName,
localizedErrorMessage: undefined,
fullDisplayName: displayName,
kindModifiers: ScriptElementKindModifier.none,
triggerSpan: createTriggerSpanForNode(node, sourceFile)
};
}
}
}
}
Expand All @@ -7736,6 +7808,28 @@ namespace ts {
triggerSpan: undefined
};
}

function isDefinedInLibraryFile(declaration: Node) {
if (defaultLibFileName) {
const sourceFile = declaration.getSourceFile();
const canonicalName = getCanonicalFileName(ts.normalizePath(sourceFile.fileName));
if (canonicalName === canonicalDefaultLibName) {
return true;
}
}
return false;
}

function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) {
let start = node.getStart(sourceFile);
let width = node.getWidth(sourceFile);
if (node.kind === SyntaxKind.StringLiteral) {
// Exclude the quotes
start += 1;
width -= 2;
}
return createTextSpan(start, width);
}
}

return {
Expand Down
14 changes: 14 additions & 0 deletions tests/cases/fourslash/findAllRefsForStringLiteralTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// <reference path='fourslash.ts'/>

////type Options = "[|option 1|]" | "option 2";
////let myOption: Options = "[|option 1|]";

let ranges = test.ranges();
for (let range of ranges) {
goTo.position(range.start);

verify.referencesCountIs(ranges.length);
for (let expectedReference of ranges) {
verify.referencesAtPositionContains(expectedReference);
}
}
13 changes: 13 additions & 0 deletions tests/cases/fourslash/getOccurrencesStringLiteralTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />

////function foo(a: "[|option 1|]") { }
////foo("[|option 1|]");

const ranges = test.ranges();
for (let r of ranges) {
goTo.position(r.start);

for (let range of ranges) {
verify.occurrencesAtPositionContains(range, false);
}
}
10 changes: 10 additions & 0 deletions tests/cases/fourslash/getOccurrencesStringLiterals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

////var x = "[|string|]";
////function f(a = "[|initial value|]") { }

const ranges = test.ranges();
for (let r of ranges) {
goTo.position(r.start);
verify.occurrencesAtPositionCount(0);
}
12 changes: 12 additions & 0 deletions tests/cases/fourslash/renameStingLiterals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// <reference path='fourslash.ts' />

////var y: "string" = "string;
////var x = "/*1*/string";
////function f(a = "/*2*/initial value") { }


goTo.marker("1");
verify.renameInfoFailed();

goTo.marker("2");
verify.renameInfoFailed();
20 changes: 20 additions & 0 deletions tests/cases/fourslash/renameStingPropertyNames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// <reference path='fourslash.ts' />

////var o = {
//// [|prop|]: 0
////};
////
////o = {
//// "[|prop|]": 1
////};
////
////o["[|prop|]"];
////o['[|prop|]'];
////o.[|prop|];


let ranges = test.ranges();
for (let range of ranges) {
goTo.position(range.start);
verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false);
}
18 changes: 18 additions & 0 deletions tests/cases/fourslash/renameStringLiteralTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />


////interface AnimationOptions {
//// deltaX: number;
//// deltaY: number;
//// easing: "ease-in" | "ease-out" | "[|ease-in-out|]";
////}
////
////function animate(o: AnimationOptions) { }
////
////animate({ deltaX: 100, deltaY: 100, easing: "[|ease-in-out|]" });

let ranges = test.ranges();
for (let range of ranges) {
goTo.position(range.start);
verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false);
}

0 comments on commit afaffb1

Please sign in to comment.