diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a0e55520237..c2df5760e4e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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] = 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; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 5318e679c4e..13fe69cf1ba 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -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); diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index e746bd9e7c9..ebc6ec61d8f 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -758,6 +758,10 @@ module FourSlash { return this.languageService.getImplementorsAtPosition(this.activeFile.fileName, this.currentCaretPosition); } + private assertionMessage(name: string, actualValue: any, expectedValue: any) { + return "\nActual " + name + ":\n\t" + actualValue + "\nExpected value:\n\t" + expectedValue; + } + public verifyQuickInfo(negative: boolean, expectedTypeName?: string, docComment?: string, symbolName?: string, kind?: string) { [expectedTypeName, docComment, symbolName, kind].forEach(str => { if (str) { @@ -772,40 +776,68 @@ module FourSlash { var actualQuickInfoSymbolName = actualQuickInfo ? actualQuickInfo.fullSymbolName : ""; var actualQuickInfoKind = actualQuickInfo ? actualQuickInfo.kind : ""; - function assertionMessage(name: string, actualValue: string, expectedValue: string) { - return "\nActual " + name + ":\n\t" + actualValue + "\nExpected value:\n\t" + expectedValue; - } - if (negative) { if (expectedTypeName !== undefined) { - assert.notEqual(actualQuickInfoMemberName, expectedTypeName, assertionMessage("quick info member name", actualQuickInfoMemberName, expectedTypeName)); + assert.notEqual(actualQuickInfoMemberName, expectedTypeName, this.assertionMessage("quick info member name", actualQuickInfoMemberName, expectedTypeName)); } if (docComment != undefined) { - assert.notEqual(actualQuickInfoDocComment, docComment, assertionMessage("quick info doc comment", actualQuickInfoDocComment, docComment)); + assert.notEqual(actualQuickInfoDocComment, docComment, this.assertionMessage("quick info doc comment", actualQuickInfoDocComment, docComment)); } if (symbolName !== undefined) { - assert.notEqual(actualQuickInfoSymbolName, symbolName, assertionMessage("quick info symbol name", actualQuickInfoSymbolName, symbolName)); + assert.notEqual(actualQuickInfoSymbolName, symbolName, this.assertionMessage("quick info symbol name", actualQuickInfoSymbolName, symbolName)); } if (kind !== undefined) { - assert.notEqual(actualQuickInfoKind, kind, assertionMessage("quick info kind", actualQuickInfoKind, kind)); + assert.notEqual(actualQuickInfoKind, kind, this.assertionMessage("quick info kind", actualQuickInfoKind, kind)); } } else { if (expectedTypeName !== undefined) { - assert.equal(actualQuickInfoMemberName, expectedTypeName, assertionMessage("quick info member", actualQuickInfoMemberName, expectedTypeName)); + assert.equal(actualQuickInfoMemberName, expectedTypeName, this.assertionMessage("quick info member", actualQuickInfoMemberName, expectedTypeName)); } if (docComment != undefined) { - assert.equal(actualQuickInfoDocComment, docComment, assertionMessage("quick info doc", actualQuickInfoDocComment, docComment)); + assert.equal(actualQuickInfoDocComment, docComment, this.assertionMessage("quick info doc", actualQuickInfoDocComment, docComment)); } if (symbolName !== undefined) { - assert.equal(actualQuickInfoSymbolName, symbolName, assertionMessage("quick info symbol name", actualQuickInfoSymbolName, symbolName)); + assert.equal(actualQuickInfoSymbolName, symbolName, this.assertionMessage("quick info symbol name", actualQuickInfoSymbolName, symbolName)); } if (kind !== undefined) { - assert.equal(actualQuickInfoKind, kind, assertionMessage("quick info kind", actualQuickInfoKind, kind)); + assert.equal(actualQuickInfoKind, kind, this.assertionMessage("quick info kind", actualQuickInfoKind, kind)); } } } - public verifyQuickInfoExists(negative: number) { + public verifyRenameLocations(findInStrings: boolean, findInComments: boolean) { + var renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition); + if (renameInfo.canRename) { + var references = this.languageService.findRenameLocations( + this.activeFile.fileName, this.currentCaretPosition, findInStrings, findInComments); + + var ranges = this.getRanges(); + if (ranges.length !== references.length) { + this.raiseError(this.assertionMessage("Rename locations", references.length, ranges.length)); + } + + ranges = ranges.sort((r1, r2) => r1.start - r2.start); + references = references.sort((r1, r2) => r1.textSpan.start() - r2.textSpan.start()); + + for (var i = 0, n = ranges.length; i < n; i++) { + var reference = references[i]; + var range = ranges[i]; + + if (reference.textSpan.start() !== range.start || + reference.textSpan.end() !== range.end) { + + this.raiseError(this.assertionMessage("Rename location", + "[" + reference.textSpan.start() + "," + reference.textSpan.end() + ")", + "[" + range.start + "," + range.end + ")")); + } + } + } + else { + this.raiseError("Expected rename to succeed, but it actually failed."); + } + } + + public verifyQuickInfoExists(negative: boolean) { this.taoInvalidReason = 'verifyQuickInfoExists NYI'; var actualQuickInfo = this.languageService.getTypeAtPosition(this.activeFile.fileName, this.currentCaretPosition); diff --git a/src/harness/typeWriter.ts b/src/harness/typeWriter.ts index 3b5d8cd3fff..8169531c27e 100644 --- a/src/harness/typeWriter.ts +++ b/src/harness/typeWriter.ts @@ -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 diff --git a/src/services/services.ts b/src/services/services.ts index 36ea451497b..bb3fe8e3bf4 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -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,88 @@ 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 = (node).text; + var tripleSlashDirectivePrefixRegex = /^\/\/\/\s*= 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 + }); + } + } + + // Advance the matchIndex forward (if we don't, then we'll simply find the same + // match at the same position again). + 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 /// { + return this.languageService.findRenameLocations(fileName, position, findInStrings, findInComments); + }); + } + /// GET BRACE MATCHING public getBraceMatchingAtPosition(fileName: string, position: number): string { return this.forwardJSONCall( diff --git a/src/services/utilities.ts b/src/services/utilities.ts index caa324fcc7b..af320ea0a0c 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -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; } diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index c204a98c4a4..537ff8527d6 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -240,7 +240,6 @@ module FourSlashInterface { } export class verify extends verifyNegatable { - public caretAtMarker(markerName?: string) { FourSlash.currentTestState.verifyCaretAtMarker(markerName); } @@ -417,6 +416,10 @@ module FourSlashInterface { public renameInfoFailed(message?: string) { FourSlash.currentTestState.verifyRenameInfoFailed(message) } + + public renameLocations(findInStrings: boolean, findInComments: boolean) { + FourSlash.currentTestState.verifyRenameLocations(findInStrings, findInComments); + } } export class edit { diff --git a/tests/cases/fourslash/renameCommentsAndStrings1.ts b/tests/cases/fourslash/renameCommentsAndStrings1.ts new file mode 100644 index 00000000000..b5c8ac6aacc --- /dev/null +++ b/tests/cases/fourslash/renameCommentsAndStrings1.ts @@ -0,0 +1,11 @@ +/// + +/////// + +////function /**/[|Bar|]() { +//// // This is a reference to Bar in a comment. +//// "this is a reference to Bar in a string" +////} + +goTo.marker(); +verify.renameLocations(/*findInStrings:*/ false, /*findInComments:*/ false); \ No newline at end of file diff --git a/tests/cases/fourslash/renameCommentsAndStrings2.ts b/tests/cases/fourslash/renameCommentsAndStrings2.ts new file mode 100644 index 00000000000..590f3a0833f --- /dev/null +++ b/tests/cases/fourslash/renameCommentsAndStrings2.ts @@ -0,0 +1,11 @@ +/// + +/////// + +////function /**/[|Bar|]() { +//// // This is a reference to Bar in a comment. +//// "this is a reference to [|Bar|] in a string" +////} + +goTo.marker(); +verify.renameLocations(/*findInStrings:*/ true, /*findInComments:*/ false); \ No newline at end of file diff --git a/tests/cases/fourslash/renameCommentsAndStrings3.ts b/tests/cases/fourslash/renameCommentsAndStrings3.ts new file mode 100644 index 00000000000..5f1aa09a948 --- /dev/null +++ b/tests/cases/fourslash/renameCommentsAndStrings3.ts @@ -0,0 +1,11 @@ +/// + +/////// + +////function /**/[|Bar|]() { +//// // This is a reference to [|Bar|] in a comment. +//// "this is a reference to Bar in a string" +////} + +goTo.marker(); +verify.renameLocations(/*findInStrings:*/ false, /*findInComments:*/ true); \ No newline at end of file diff --git a/tests/cases/fourslash/renameCommentsAndStrings4.ts b/tests/cases/fourslash/renameCommentsAndStrings4.ts new file mode 100644 index 00000000000..4f8b7b98cd4 --- /dev/null +++ b/tests/cases/fourslash/renameCommentsAndStrings4.ts @@ -0,0 +1,11 @@ +/// + +/////// + +////function /**/[|Bar|]() { +//// // This is a reference to [|Bar|] in a comment. +//// "this is a reference to [|Bar|] in a string" +////} + +goTo.marker(); +verify.renameLocations(/*findInStrings:*/ true, /*findInComments:*/ true); \ No newline at end of file