Skip to content

Commit 6b76a6d

Browse files
Merge pull request #816 from Microsoft/renameCommentsAndStrings
Support rename in comments and strings.
2 parents cc76e0e + a390644 commit 6b76a6d

File tree

12 files changed

+216
-26
lines changed

12 files changed

+216
-26
lines changed

src/compiler/checker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2575,7 +2575,7 @@ module ts {
25752575
function getStringLiteralType(node: StringLiteralTypeNode): StringLiteralType {
25762576
if (hasProperty(stringLiteralTypes, node.text)) return stringLiteralTypes[node.text];
25772577
var type = stringLiteralTypes[node.text] = <StringLiteralType>createType(TypeFlags.StringLiteral);
2578-
type.text = getSourceTextOfNode(node);
2578+
type.text = getTextOfNode(node);
25792579
return type;
25802580
}
25812581

@@ -7430,7 +7430,7 @@ module ts {
74307430
while (!isUniqueLocalName(escapeIdentifier(prefix + name), container)) {
74317431
prefix += "_";
74327432
}
7433-
links.localModuleName = prefix + getSourceTextOfNode(container.name);
7433+
links.localModuleName = prefix + getTextOfNode(container.name);
74347434
}
74357435
return links.localModuleName;
74367436
}

src/compiler/parser.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ module ts {
5454
return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos);
5555
}
5656

57-
export function getSourceTextOfNodeFromSourceText(sourceText: string, node: Node): string {
57+
export function getTextOfNodeFromSourceText(sourceText: string, node: Node): string {
5858
return sourceText.substring(skipTrivia(sourceText, node.pos), node.end);
5959
}
6060

61-
export function getSourceTextOfNode(node: Node): string {
61+
export function getTextOfNode(node: Node): string {
6262
var text = getSourceFileOfNode(node).text;
6363
return text.substring(skipTrivia(text, node.pos), node.end);
6464
}
@@ -75,7 +75,7 @@ module ts {
7575

7676
// Return display name of an identifier
7777
export function identifierToString(identifier: Identifier) {
78-
return identifier.kind === SyntaxKind.Missing ? "(Missing)" : getSourceTextOfNode(identifier);
78+
return identifier.kind === SyntaxKind.Missing ? "(Missing)" : getTextOfNode(identifier);
7979
}
8080

8181
export function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): Diagnostic {
@@ -3045,7 +3045,7 @@ module ts {
30453045
parseExpected(SyntaxKind.ColonToken);
30463046

30473047
if (labelledStatementInfo.nodeIsNestedInLabel(node.label, /*requireIterationStatement*/ false, /*stopAtFunctionBoundary*/ true)) {
3048-
grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getSourceTextOfNodeFromSourceText(sourceText, node.label));
3048+
grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNodeFromSourceText(sourceText, node.label));
30493049
}
30503050
labelledStatementInfo.addLabel(node.label);
30513051

src/harness/fourslash.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,10 @@ module FourSlash {
758758
return this.languageService.getImplementorsAtPosition(this.activeFile.fileName, this.currentCaretPosition);
759759
}
760760

761+
private assertionMessage(name: string, actualValue: any, expectedValue: any) {
762+
return "\nActual " + name + ":\n\t" + actualValue + "\nExpected value:\n\t" + expectedValue;
763+
}
764+
761765
public verifyQuickInfo(negative: boolean, expectedTypeName?: string, docComment?: string, symbolName?: string, kind?: string) {
762766
[expectedTypeName, docComment, symbolName, kind].forEach(str => {
763767
if (str) {
@@ -772,40 +776,68 @@ module FourSlash {
772776
var actualQuickInfoSymbolName = actualQuickInfo ? actualQuickInfo.fullSymbolName : "";
773777
var actualQuickInfoKind = actualQuickInfo ? actualQuickInfo.kind : "";
774778

775-
function assertionMessage(name: string, actualValue: string, expectedValue: string) {
776-
return "\nActual " + name + ":\n\t" + actualValue + "\nExpected value:\n\t" + expectedValue;
777-
}
778-
779779
if (negative) {
780780
if (expectedTypeName !== undefined) {
781-
assert.notEqual(actualQuickInfoMemberName, expectedTypeName, assertionMessage("quick info member name", actualQuickInfoMemberName, expectedTypeName));
781+
assert.notEqual(actualQuickInfoMemberName, expectedTypeName, this.assertionMessage("quick info member name", actualQuickInfoMemberName, expectedTypeName));
782782
}
783783
if (docComment != undefined) {
784-
assert.notEqual(actualQuickInfoDocComment, docComment, assertionMessage("quick info doc comment", actualQuickInfoDocComment, docComment));
784+
assert.notEqual(actualQuickInfoDocComment, docComment, this.assertionMessage("quick info doc comment", actualQuickInfoDocComment, docComment));
785785
}
786786
if (symbolName !== undefined) {
787-
assert.notEqual(actualQuickInfoSymbolName, symbolName, assertionMessage("quick info symbol name", actualQuickInfoSymbolName, symbolName));
787+
assert.notEqual(actualQuickInfoSymbolName, symbolName, this.assertionMessage("quick info symbol name", actualQuickInfoSymbolName, symbolName));
788788
}
789789
if (kind !== undefined) {
790-
assert.notEqual(actualQuickInfoKind, kind, assertionMessage("quick info kind", actualQuickInfoKind, kind));
790+
assert.notEqual(actualQuickInfoKind, kind, this.assertionMessage("quick info kind", actualQuickInfoKind, kind));
791791
}
792792
} else {
793793
if (expectedTypeName !== undefined) {
794-
assert.equal(actualQuickInfoMemberName, expectedTypeName, assertionMessage("quick info member", actualQuickInfoMemberName, expectedTypeName));
794+
assert.equal(actualQuickInfoMemberName, expectedTypeName, this.assertionMessage("quick info member", actualQuickInfoMemberName, expectedTypeName));
795795
}
796796
if (docComment != undefined) {
797-
assert.equal(actualQuickInfoDocComment, docComment, assertionMessage("quick info doc", actualQuickInfoDocComment, docComment));
797+
assert.equal(actualQuickInfoDocComment, docComment, this.assertionMessage("quick info doc", actualQuickInfoDocComment, docComment));
798798
}
799799
if (symbolName !== undefined) {
800-
assert.equal(actualQuickInfoSymbolName, symbolName, assertionMessage("quick info symbol name", actualQuickInfoSymbolName, symbolName));
800+
assert.equal(actualQuickInfoSymbolName, symbolName, this.assertionMessage("quick info symbol name", actualQuickInfoSymbolName, symbolName));
801801
}
802802
if (kind !== undefined) {
803-
assert.equal(actualQuickInfoKind, kind, assertionMessage("quick info kind", actualQuickInfoKind, kind));
803+
assert.equal(actualQuickInfoKind, kind, this.assertionMessage("quick info kind", actualQuickInfoKind, kind));
804804
}
805805
}
806806
}
807807

808-
public verifyQuickInfoExists(negative: number) {
808+
public verifyRenameLocations(findInStrings: boolean, findInComments: boolean) {
809+
var renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition);
810+
if (renameInfo.canRename) {
811+
var references = this.languageService.findRenameLocations(
812+
this.activeFile.fileName, this.currentCaretPosition, findInStrings, findInComments);
813+
814+
var ranges = this.getRanges();
815+
if (ranges.length !== references.length) {
816+
this.raiseError(this.assertionMessage("Rename locations", references.length, ranges.length));
817+
}
818+
819+
ranges = ranges.sort((r1, r2) => r1.start - r2.start);
820+
references = references.sort((r1, r2) => r1.textSpan.start() - r2.textSpan.start());
821+
822+
for (var i = 0, n = ranges.length; i < n; i++) {
823+
var reference = references[i];
824+
var range = ranges[i];
825+
826+
if (reference.textSpan.start() !== range.start ||
827+
reference.textSpan.end() !== range.end) {
828+
829+
this.raiseError(this.assertionMessage("Rename location",
830+
"[" + reference.textSpan.start() + "," + reference.textSpan.end() + ")",
831+
"[" + range.start + "," + range.end + ")"));
832+
}
833+
}
834+
}
835+
else {
836+
this.raiseError("Expected rename to succeed, but it actually failed.");
837+
}
838+
}
839+
840+
public verifyQuickInfoExists(negative: boolean) {
809841
this.taoInvalidReason = 'verifyQuickInfoExists NYI';
810842

811843
var actualQuickInfo = this.languageService.getTypeAtPosition(this.activeFile.fileName, this.currentCaretPosition);

src/harness/typeWriter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class TypeWriterWalker {
7676
private log(node: ts.Node, type: ts.Type): void {
7777
var actualPos = ts.skipTrivia(this.currentSourceFile.text, node.pos);
7878
var lineAndCharacter = this.currentSourceFile.getLineAndCharacterFromPosition(actualPos);
79-
var sourceText = ts.getSourceTextOfNodeFromSourceText(this.currentSourceFile.text, node);
79+
var sourceText = ts.getTextOfNodeFromSourceText(this.currentSourceFile.text, node);
8080

8181
// If we got an unknown type, we temporarily want to fall back to just pretending the name
8282
// (source text) of the node is the type. This is to align with the old typeWriter to make

src/services/services.ts

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,8 @@ module ts {
666666
getSignatureAtPosition(fileName: string, position: number): SignatureInfo;
667667

668668
getRenameInfo(fileName: string, position: number): RenameInfo;
669+
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[];
670+
669671
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[];
670672
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[];
671673
getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[];
@@ -757,6 +759,11 @@ module ts {
757759
newText: string;
758760
}
759761

762+
export interface RenameLocation {
763+
textSpan: TypeScript.TextSpan;
764+
fileName: string;
765+
}
766+
760767
export interface ReferenceEntry {
761768
textSpan: TypeScript.TextSpan;
762769
fileName: string;
@@ -3062,11 +3069,19 @@ module ts {
30623069
}
30633070
}
30643071

3065-
function getReferencesAtPosition(filename: string, position: number): ReferenceEntry[] {
3072+
function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] {
3073+
return findReferences(fileName, position, findInStrings, findInComments);
3074+
}
3075+
3076+
function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
3077+
return findReferences(fileName, position, /*findInStrings:*/ false, /*findInComments:*/ false);
3078+
}
3079+
3080+
function findReferences(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): ReferenceEntry[] {
30663081
synchronizeHostData();
30673082

3068-
filename = TypeScript.switchToForwardSlashes(filename);
3069-
var sourceFile = getSourceFile(filename);
3083+
fileName = TypeScript.switchToForwardSlashes(fileName);
3084+
var sourceFile = getSourceFile(fileName);
30703085

30713086
var node = getTouchingPropertyName(sourceFile, position);
30723087
if (!node) {
@@ -3082,7 +3097,88 @@ module ts {
30823097
return undefined;
30833098
}
30843099

3085-
return getReferencesForNode(node, program.getSourceFiles());
3100+
Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral);
3101+
var references = getReferencesForNode(node, program.getSourceFiles());
3102+
3103+
var name = (<Identifier>node).text;
3104+
var tripleSlashDirectivePrefixRegex = /^\/\/\/\s*</
3105+
3106+
if (findInStrings) {
3107+
forEach(program.getSourceFiles(), addStringReferences);
3108+
}
3109+
3110+
if (findInComments) {
3111+
forEach(program.getSourceFiles(), addCommentReferences);
3112+
}
3113+
3114+
return references;
3115+
3116+
function addReferencesInRawText(rawText: string, rawTextPositionInSourceText: number, sourceText: string) {
3117+
var matchIndex = 0;
3118+
while ((matchIndex = rawText.indexOf(name, matchIndex)) >= 0) {
3119+
// Only consider it a match if there isn't a letter/number before or after
3120+
// the match.
3121+
var indexInSourceText = rawTextPositionInSourceText + matchIndex;
3122+
3123+
if (indexInSourceText === 0 || !isIdentifierPart(sourceText.charCodeAt(indexInSourceText - 1), ScriptTarget.ES5)) {
3124+
var matchEnd = indexInSourceText + name.length;
3125+
if (matchEnd >= sourceText.length || !isIdentifierPart(sourceText.charCodeAt(matchEnd), ScriptTarget.ES5)) {
3126+
3127+
references.push({
3128+
fileName: sourceFile.filename,
3129+
textSpan: new TypeScript.TextSpan(indexInSourceText, name.length),
3130+
isWriteAccess: false
3131+
});
3132+
}
3133+
}
3134+
3135+
// Advance the matchIndex forward (if we don't, then we'll simply find the same
3136+
// match at the same position again).
3137+
matchIndex++;
3138+
}
3139+
}
3140+
3141+
function addCommentReferences(sourceFile: SourceFile) {
3142+
var sourceText = sourceFile.text;
3143+
forEachChild(sourceFile, addCommentReferencesInNode);
3144+
3145+
function addCommentReferencesInNode(node: Node) {
3146+
if (isToken(node)) {
3147+
// Found a token, walk its comments (if it has any) for matches).
3148+
forEach(getLeadingCommentRanges(sourceText, node.pos), addReferencesInCommentRange);
3149+
}
3150+
else {
3151+
forEach(node.getChildren(), addCommentReferencesInNode);
3152+
}
3153+
}
3154+
3155+
function addReferencesInCommentRange(range: CommentRange) {
3156+
var commentText = sourceText.substring(range.pos, range.end);
3157+
3158+
// Don't add matches in ///<reference comments. We don't want to
3159+
// unintentionally update a file name.
3160+
if (!tripleSlashDirectivePrefixRegex.test(commentText)) {
3161+
addReferencesInRawText(commentText, range.pos, sourceText);
3162+
}
3163+
}
3164+
}
3165+
3166+
function addStringReferences(sourceFile: SourceFile) {
3167+
var sourceText = sourceFile.text;
3168+
3169+
forEachChild(sourceFile, addStringReferencesInNode);
3170+
3171+
function addStringReferencesInNode(node: Node) {
3172+
if (node.kind === SyntaxKind.StringLiteral) {
3173+
// Found a string literal. See if we can find any matches in it.
3174+
addReferencesInRawText(getTextOfNodeFromSourceText(sourceText, node), node.getStart(sourceFile), sourceText);
3175+
}
3176+
else {
3177+
// Recurse and keep looking for references in strings.
3178+
forEachChild(node, addStringReferencesInNode);
3179+
}
3180+
}
3181+
}
30863182
}
30873183

30883184
function getReferencesForNode(node: Node, sourceFiles: SourceFile[]): ReferenceEntry[] {
@@ -4590,6 +4686,7 @@ module ts {
45904686
getBreakpointStatementAtPosition: getBreakpointStatementAtPosition,
45914687
getNavigateToItems: getNavigateToItems,
45924688
getRenameInfo: getRenameInfo,
4689+
findRenameLocations: findRenameLocations,
45934690
getNavigationBarItems: getNavigationBarItems,
45944691
getOutliningSpans: getOutliningSpans,
45954692
getTodoComments: getTodoComments,

src/services/shims.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ module ts {
102102
*/
103103
getRenameInfo(fileName: string, position: number): string;
104104

105+
/**
106+
* Returns a JSON-encoded value of the type:
107+
* { fileName: string, textSpan: { start: number, length: number } }[]
108+
*/
109+
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): string;
110+
105111
/**
106112
* Returns a JSON-encoded value of the type:
107113
* { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string }
@@ -650,6 +656,14 @@ module ts {
650656
});
651657
}
652658

659+
public findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): string {
660+
return this.forwardJSONCall(
661+
"findRenameLocations('" + fileName + "', " + position + ", " + findInStrings + ", " + findInComments + ")",
662+
() => {
663+
return this.languageService.findRenameLocations(fileName, position, findInStrings, findInComments);
664+
});
665+
}
666+
653667
/// GET BRACE MATCHING
654668
public getBraceMatchingAtPosition(fileName: string, position: number): string {
655669
return this.forwardJSONCall(

src/services/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ module ts {
222222
return n.kind !== SyntaxKind.SyntaxList || n.getChildCount() !== 0;
223223
}
224224

225-
function isToken(n: Node): boolean {
225+
export function isToken(n: Node): boolean {
226226
return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken;
227227
}
228228

tests/cases/fourslash/fourslash.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,6 @@ module FourSlashInterface {
240240
}
241241

242242
export class verify extends verifyNegatable {
243-
244243
public caretAtMarker(markerName?: string) {
245244
FourSlash.currentTestState.verifyCaretAtMarker(markerName);
246245
}
@@ -417,6 +416,10 @@ module FourSlashInterface {
417416
public renameInfoFailed(message?: string) {
418417
FourSlash.currentTestState.verifyRenameInfoFailed(message)
419418
}
419+
420+
public renameLocations(findInStrings: boolean, findInComments: boolean) {
421+
FourSlash.currentTestState.verifyRenameLocations(findInStrings, findInComments);
422+
}
420423
}
421424

422425
export class edit {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
///////<reference path="./Bar.ts" />
4+
5+
////function /**/[|Bar|]() {
6+
//// // This is a reference to Bar in a comment.
7+
//// "this is a reference to Bar in a string"
8+
////}
9+
10+
goTo.marker();
11+
verify.renameLocations(/*findInStrings:*/ false, /*findInComments:*/ false);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
///////<reference path="./Bar.ts" />
4+
5+
////function /**/[|Bar|]() {
6+
//// // This is a reference to Bar in a comment.
7+
//// "this is a reference to [|Bar|] in a string"
8+
////}
9+
10+
goTo.marker();
11+
verify.renameLocations(/*findInStrings:*/ true, /*findInComments:*/ false);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
///////<reference path="./Bar.ts" />
4+
5+
////function /**/[|Bar|]() {
6+
//// // This is a reference to [|Bar|] in a comment.
7+
//// "this is a reference to Bar in a string"
8+
////}
9+
10+
goTo.marker();
11+
verify.renameLocations(/*findInStrings:*/ false, /*findInComments:*/ true);

0 commit comments

Comments
 (0)