Support rename in comments and strings.

This commit is contained in:
Cyrus Najmabadi 2014-10-03 14:53:50 -07:00
parent 00e5b15a05
commit 2a8b9ef21f
6 changed files with 121 additions and 12 deletions

View File

@ -2575,7 +2575,7 @@ module ts {
function getStringLiteralType(node: StringLiteralTypeNode): StringLiteralType {
if (hasProperty(stringLiteralTypes, node.text)) return stringLiteralTypes[node.text];
var type = stringLiteralTypes[node.text] = <StringLiteralType>createType(TypeFlags.StringLiteral);
type.text = getSourceTextOfNode(node);
type.text = getTextOfNode(node);
return type;
}
@ -7430,7 +7430,7 @@ module ts {
while (!isUniqueLocalName(escapeIdentifier(prefix + name), container)) {
prefix += "_";
}
links.localModuleName = prefix + getSourceTextOfNode(container.name);
links.localModuleName = prefix + getTextOfNode(container.name);
}
return links.localModuleName;
}

View File

@ -54,11 +54,11 @@ module ts {
return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos);
}
export function getSourceTextOfNodeFromSourceText(sourceText: string, node: Node): string {
export function getTextOfNodeFromSourceText(sourceText: string, node: Node): string {
return sourceText.substring(skipTrivia(sourceText, node.pos), node.end);
}
export function getSourceTextOfNode(node: Node): string {
export function getTextOfNode(node: Node): string {
var text = getSourceFileOfNode(node).text;
return text.substring(skipTrivia(text, node.pos), node.end);
}
@ -75,7 +75,7 @@ module ts {
// Return display name of an identifier
export function identifierToString(identifier: Identifier) {
return identifier.kind === SyntaxKind.Missing ? "(Missing)" : getSourceTextOfNode(identifier);
return identifier.kind === SyntaxKind.Missing ? "(Missing)" : getTextOfNode(identifier);
}
export function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): Diagnostic {
@ -3045,7 +3045,7 @@ module ts {
parseExpected(SyntaxKind.ColonToken);
if (labelledStatementInfo.nodeIsNestedInLabel(node.label, /*requireIterationStatement*/ false, /*stopAtFunctionBoundary*/ true)) {
grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getSourceTextOfNodeFromSourceText(sourceText, node.label));
grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNodeFromSourceText(sourceText, node.label));
}
labelledStatementInfo.addLabel(node.label);

View File

@ -76,7 +76,7 @@ class TypeWriterWalker {
private log(node: ts.Node, type: ts.Type): void {
var actualPos = ts.skipTrivia(this.currentSourceFile.text, node.pos);
var lineAndCharacter = this.currentSourceFile.getLineAndCharacterFromPosition(actualPos);
var sourceText = ts.getSourceTextOfNodeFromSourceText(this.currentSourceFile.text, node);
var sourceText = ts.getTextOfNodeFromSourceText(this.currentSourceFile.text, node);
// If we got an unknown type, we temporarily want to fall back to just pretending the name
// (source text) of the node is the type. This is to align with the old typeWriter to make

View File

@ -666,6 +666,8 @@ module ts {
getSignatureAtPosition(fileName: string, position: number): SignatureInfo;
getRenameInfo(fileName: string, position: number): RenameInfo;
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[];
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[];
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[];
getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[];
@ -757,6 +759,11 @@ module ts {
newText: string;
}
export interface RenameLocation {
textSpan: TypeScript.TextSpan;
fileName: string;
}
export interface ReferenceEntry {
textSpan: TypeScript.TextSpan;
fileName: string;
@ -3062,11 +3069,19 @@ module ts {
}
}
function getReferencesAtPosition(filename: string, position: number): ReferenceEntry[] {
function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] {
return findReferences(fileName, position, findInStrings, findInComments);
}
function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
return findReferences(fileName, position, /*findInStrings:*/ false, /*findInComments:*/ false);
}
function findReferences(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): ReferenceEntry[] {
synchronizeHostData();
filename = TypeScript.switchToForwardSlashes(filename);
var sourceFile = getSourceFile(filename);
fileName = TypeScript.switchToForwardSlashes(fileName);
var sourceFile = getSourceFile(fileName);
var node = getTouchingPropertyName(sourceFile, position);
if (!node) {
@ -3082,7 +3097,86 @@ module ts {
return undefined;
}
return getReferencesForNode(node, program.getSourceFiles());
Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral);
var references = getReferencesForNode(node, program.getSourceFiles());
var name = (<Identifier>node).text;
var tripleSlashDirectivePrefixRegex = /^\/\/\/\s*</
if (findInStrings) {
forEach(program.getSourceFiles(), addStringReferences);
}
if (findInComments) {
forEach(program.getSourceFiles(), addCommentReferences);
}
return references;
function addReferencesInRawText(rawText: string, rawTextPositionInSourceText: number, sourceText: string) {
var matchIndex = 0;
while ((matchIndex = rawText.indexOf(name, matchIndex)) >= 0) {
// Only consider it a match if there isn't a letter/number before or after
// the match.
var indexInSourceText = rawTextPositionInSourceText + matchIndex;
if (indexInSourceText === 0 || !isIdentifierPart(sourceText.charCodeAt(indexInSourceText - 1), ScriptTarget.ES5)) {
var matchEnd = indexInSourceText + name.length;
if (matchEnd >= sourceText.length || !isIdentifierPart(sourceText.charCodeAt(matchEnd), ScriptTarget.ES5)) {
references.push({
fileName: sourceFile.filename,
textSpan: new TypeScript.TextSpan(indexInSourceText, name.length),
isWriteAccess: false
});
}
}
matchIndex++;
}
}
function addCommentReferences(sourceFile: SourceFile) {
var sourceText = sourceFile.text;
forEachChild(sourceFile, addCommentReferencesInNode);
function addCommentReferencesInNode(node: Node) {
if (isToken(node)) {
// Found a token, walk its comments (if it has any) for matches).
forEach(getLeadingCommentRanges(sourceText, node.pos), addReferencesInCommentRange);
}
else {
forEach(node.getChildren(), addCommentReferencesInNode);
}
}
function addReferencesInCommentRange(range: CommentRange) {
var commentText = sourceText.substring(range.pos, range.end);
// Don't add matches in ///<reference comments. We don't want to
// unintentionally update a file name.
if (!tripleSlashDirectivePrefixRegex.test(commentText)) {
addReferencesInRawText(commentText, range.pos, sourceText);
}
}
}
function addStringReferences(sourceFile: SourceFile) {
var sourceText = sourceFile.text;
forEachChild(sourceFile, addStringReferencesInNode);
function addStringReferencesInNode(node: Node) {
if (node.kind === SyntaxKind.StringLiteral) {
// Found a string literal. See if we can find any matches in it.
addReferencesInRawText(getTextOfNodeFromSourceText(sourceText, node), node.getStart(sourceFile), sourceText);
}
else {
// Recurse and keep looking for references in strings.
forEachChild(node, addStringReferencesInNode);
}
}
}
}
function getReferencesForNode(node: Node, sourceFiles: SourceFile[]): ReferenceEntry[] {
@ -4590,6 +4684,7 @@ module ts {
getBreakpointStatementAtPosition: getBreakpointStatementAtPosition,
getNavigateToItems: getNavigateToItems,
getRenameInfo: getRenameInfo,
findRenameLocations: findRenameLocations,
getNavigationBarItems: getNavigationBarItems,
getOutliningSpans: getOutliningSpans,
getTodoComments: getTodoComments,

View File

@ -102,6 +102,12 @@ module ts {
*/
getRenameInfo(fileName: string, position: number): string;
/**
* Returns a JSON-encoded value of the type:
* { fileName: string, textSpan: { start: number, length: number } }[]
*/
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): string;
/**
* Returns a JSON-encoded value of the type:
* { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string }
@ -650,6 +656,14 @@ module ts {
});
}
public findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): string {
return this.forwardJSONCall(
"findRenameLocations('" + fileName + "', " + position + ", " + findInStrings + ", " + findInComments + ")",
() => {
return this.languageService.findRenameLocations(fileName, position, findInStrings, findInComments);
});
}
/// GET BRACE MATCHING
public getBraceMatchingAtPosition(fileName: string, position: number): string {
return this.forwardJSONCall(

View File

@ -222,7 +222,7 @@ module ts {
return n.kind !== SyntaxKind.SyntaxList || n.getChildCount() !== 0;
}
function isToken(n: Node): boolean {
export function isToken(n: Node): boolean {
return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken;
}