From 27805d6585dfb12298d78f62ca064839e30308aa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Sep 2014 14:51:47 -0700 Subject: [PATCH 1/3] Implement the getRenameInfo language service entrypoint. Tests pending. --- src/compiler/checker.ts | 3 +- src/compiler/types.ts | 1 + src/services/services.ts | 89 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index da03c9fb611..be53e7c4da7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -65,7 +65,8 @@ module ts { symbolToString: symbolToString, getAugmentedPropertiesOfApparentType: getAugmentedPropertiesOfApparentType, getRootSymbol: getRootSymbol, - getContextualType: getContextualType + getContextualType: getContextualType, + getFullyQualifiedName: getFullyQualifiedName }; var undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined"); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 69556897979..b652418ffcd 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -623,6 +623,7 @@ module ts { getApparentType(type: Type): ApparentType; typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string; + getFullyQualifiedName(symbol: Symbol): string; getAugmentedPropertiesOfApparentType(type: Type): Symbol[]; getRootSymbol(symbol: Symbol): Symbol; getContextualType(node: Node): Type; diff --git a/src/services/services.ts b/src/services/services.ts index 7447e56df86..75724230471 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3673,6 +3673,93 @@ module ts { return null; } + function getRenameInfo(fileName: string, position: number): RenameInfo { + synchronizeHostData(); + + fileName = TypeScript.switchToForwardSlashes(fileName); + var sourceFile = getSourceFile(fileName); + + var node = getNodeAtPosition(sourceFile, position); + + // Can only rename an identifier. + if (node && node.kind === SyntaxKind.Identifier) { + var symbol = typeInfoResolver.getSymbolInfo(node); + + // Only allow a symbol to be renamed if it actually has at least one declaration. + if (symbol && symbol.getDeclarations() && symbol.getDeclarations().length > 0) { + var kind = getKind(symbol); + if (kind) { + return RenameInfo.Create(symbol.name, typeInfoResolver.getFullyQualifiedName(symbol), kind, getKindModifiers(symbol), + new TypeScript.TextSpan(node.getStart(), node.getWidth())); + } + } + } + + function getKindModifiers(symbol: Symbol): string { + var modifiers: string[] = []; + var decl = symbol.getDeclarations()[0]; + + if (decl.flags & NodeFlags.Ambient) { + modifiers.push(ScriptElementKindModifier.ambientModifier); + } + if (decl.flags & NodeFlags.Export) { + modifiers.push(ScriptElementKindModifier.exportedModifier); + } + if (decl.flags & NodeFlags.Private) { + modifiers.push(ScriptElementKindModifier.privateMemberModifier); + } + if (decl.flags & NodeFlags.Public) { + modifiers.push(ScriptElementKindModifier.publicMemberModifier); + } + if (decl.flags & NodeFlags.Static) { + modifiers.push(ScriptElementKindModifier.staticModifier); + } + + return modifiers.length == 0 ? ScriptElementKindModifier.none : modifiers.join(','); + } + + function getKind(symbol: Symbol): string { + if (symbol.flags & SymbolFlags.Module) { + return ScriptElementKind.moduleElement; + } + else if (symbol.flags & SymbolFlags.Class) { + return ScriptElementKind.classElement; + } + else if (symbol.flags & SymbolFlags.Interface) { + return ScriptElementKind.interfaceElement; + } + else if (symbol.flags & SymbolFlags.Enum) { + return ScriptElementKind.enumElement; + } + else if (symbol.flags & SymbolFlags.Variable) { + return ScriptElementKind.variableElement; + } + else if (symbol.flags & SymbolFlags.Function) { + return ScriptElementKind.functionElement; + } + else if (symbol.flags & SymbolFlags.Method) { + return ScriptElementKind.memberFunctionElement; + } + else if (symbol.flags & SymbolFlags.GetAccessor) { + return ScriptElementKind.memberGetAccessorElement; + } + else if (symbol.flags & SymbolFlags.SetAccessor) { + return ScriptElementKind.memberSetAccessorElement; + } + else if (symbol.flags & SymbolFlags.Property) { + return ScriptElementKind.memberVariableElement; + } + else if (symbol.flags & SymbolFlags.TypeParameter) { + return ScriptElementKind.typeParameterElement; + } + else if (symbol.flags & SymbolFlags.EnumMember) { + return ScriptElementKind.memberVariableElement; + } + } + + return RenameInfo.CreateError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_this_element.key)); + } + return { dispose: dispose, cleanupSemanticCache: cleanupSemanticCache, @@ -3693,7 +3780,7 @@ module ts { getNameOrDottedNameSpan: getNameOrDottedNameSpan, getBreakpointStatementAtPosition: getBreakpointStatementAtPosition, getNavigateToItems: (searchValue) => [], - getRenameInfo: (fileName, position): RenameInfo => RenameInfo.CreateError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_this_element.key)), + getRenameInfo: getRenameInfo, getNavigationBarItems: getNavigationBarItems, getOutliningSpans: getOutliningSpans, getTodoComments: getTodoComments, From a1ca10d3806e486596169074babac224b1dbe873 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Sep 2014 12:33:32 -0700 Subject: [PATCH 2/3] Address CR feedback. --- src/services/services.ts | 67 ++-------------------------------------- 1 file changed, 3 insertions(+), 64 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 75724230471..ce0ccf10486 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3687,76 +3687,15 @@ module ts { // Only allow a symbol to be renamed if it actually has at least one declaration. if (symbol && symbol.getDeclarations() && symbol.getDeclarations().length > 0) { - var kind = getKind(symbol); + var kind = getSymbolKind(symbol); if (kind) { - return RenameInfo.Create(symbol.name, typeInfoResolver.getFullyQualifiedName(symbol), kind, getKindModifiers(symbol), + return RenameInfo.Create(symbol.name, typeInfoResolver.getFullyQualifiedName(symbol), kind, + getNodeModifiers(symbol.getDeclarations()[0]), new TypeScript.TextSpan(node.getStart(), node.getWidth())); } } } - function getKindModifiers(symbol: Symbol): string { - var modifiers: string[] = []; - var decl = symbol.getDeclarations()[0]; - - if (decl.flags & NodeFlags.Ambient) { - modifiers.push(ScriptElementKindModifier.ambientModifier); - } - if (decl.flags & NodeFlags.Export) { - modifiers.push(ScriptElementKindModifier.exportedModifier); - } - if (decl.flags & NodeFlags.Private) { - modifiers.push(ScriptElementKindModifier.privateMemberModifier); - } - if (decl.flags & NodeFlags.Public) { - modifiers.push(ScriptElementKindModifier.publicMemberModifier); - } - if (decl.flags & NodeFlags.Static) { - modifiers.push(ScriptElementKindModifier.staticModifier); - } - - return modifiers.length == 0 ? ScriptElementKindModifier.none : modifiers.join(','); - } - - function getKind(symbol: Symbol): string { - if (symbol.flags & SymbolFlags.Module) { - return ScriptElementKind.moduleElement; - } - else if (symbol.flags & SymbolFlags.Class) { - return ScriptElementKind.classElement; - } - else if (symbol.flags & SymbolFlags.Interface) { - return ScriptElementKind.interfaceElement; - } - else if (symbol.flags & SymbolFlags.Enum) { - return ScriptElementKind.enumElement; - } - else if (symbol.flags & SymbolFlags.Variable) { - return ScriptElementKind.variableElement; - } - else if (symbol.flags & SymbolFlags.Function) { - return ScriptElementKind.functionElement; - } - else if (symbol.flags & SymbolFlags.Method) { - return ScriptElementKind.memberFunctionElement; - } - else if (symbol.flags & SymbolFlags.GetAccessor) { - return ScriptElementKind.memberGetAccessorElement; - } - else if (symbol.flags & SymbolFlags.SetAccessor) { - return ScriptElementKind.memberSetAccessorElement; - } - else if (symbol.flags & SymbolFlags.Property) { - return ScriptElementKind.memberVariableElement; - } - else if (symbol.flags & SymbolFlags.TypeParameter) { - return ScriptElementKind.typeParameterElement; - } - else if (symbol.flags & SymbolFlags.EnumMember) { - return ScriptElementKind.memberVariableElement; - } - } - return RenameInfo.CreateError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_this_element.key)); } From 7320a189f17beedc05f377e7e1491904d052b2bf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Sep 2014 15:19:20 -0700 Subject: [PATCH 3/3] Adding rename tests. --- src/harness/fourslash.ts | 38 ++++++++++++++++++++ tests/cases/fourslash/fourslash.ts | 8 +++++ tests/cases/fourslash/getRenameInfoTests1.ts | 8 +++++ tests/cases/fourslash/getRenameInfoTests2.ts | 8 +++++ 4 files changed, 62 insertions(+) create mode 100644 tests/cases/fourslash/getRenameInfoTests1.ts create mode 100644 tests/cases/fourslash/getRenameInfoTests2.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 0bcea9367e4..26af0a77b31 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -832,6 +832,44 @@ module FourSlash { } } + private validate(name: string, expected: string, actual: string) { + if (expected && expected !== actual) { + throw new Error("Expected " + name + " '" + expected + "'. Got '" + actual + "' instead."); + } + } + + public verifyRenameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string) { + var renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition); + if (!renameInfo.canRename) { + throw new Error("Rename did not succeed"); + } + + this.validate("displayName", displayName, renameInfo.displayName); + this.validate("fullDisplayName", fullDisplayName, renameInfo.fullDisplayName); + this.validate("kind", kind, renameInfo.kind); + this.validate("kindModifiers", kindModifiers, renameInfo.kindModifiers); + + if (this.getRanges().length !== 1) { + throw new Error("Expected a single range to be selected in the test file."); + } + + var expectedRange = this.getRanges()[0]; + if (renameInfo.triggerSpan.start() !== expectedRange.start || + renameInfo.triggerSpan.end() !== expectedRange.end) { + throw new Error("Expected triggerSpan [" + expectedRange.start + "," + expectedRange.end + "). Got [" + + renameInfo.triggerSpan.start() + "," + renameInfo.triggerSpan.end() + ") instead."); + } + } + + public verifyRenameInfoFailed(message?: string) { + var renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition); + if (renameInfo.canRename) { + throw new Error("Rename was expected to fail"); + } + + this.validate("error", message, renameInfo.localizedErrorMessage); + } + //private getFormalParameter() { // var help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition); // return help.formal; diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index e1b1e26c3c2..09ca70134f9 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -390,6 +390,14 @@ module FourSlashInterface { public semanticClassificationsAre(...classifications: { classificationType: string; text: string }[]) { FourSlash.currentTestState.verifySemanticClassifications(classifications); } + + public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string) { + FourSlash.currentTestState.verifyRenameInfoSucceeded(displayName, fullDisplayName, kind, kindModifiers) + } + + public renameInfoFailed(message?: string) { + FourSlash.currentTestState.verifyRenameInfoFailed(message) + } } export class edit { diff --git a/tests/cases/fourslash/getRenameInfoTests1.ts b/tests/cases/fourslash/getRenameInfoTests1.ts new file mode 100644 index 00000000000..6f4300282d6 --- /dev/null +++ b/tests/cases/fourslash/getRenameInfoTests1.ts @@ -0,0 +1,8 @@ +/// + +////class [|/**/C|] { +//// +////} + +goTo.marker(""); +verify.renameInfoSucceeded("C"); \ No newline at end of file diff --git a/tests/cases/fourslash/getRenameInfoTests2.ts b/tests/cases/fourslash/getRenameInfoTests2.ts new file mode 100644 index 00000000000..93e8e451b4a --- /dev/null +++ b/tests/cases/fourslash/getRenameInfoTests2.ts @@ -0,0 +1,8 @@ +/// + +/////**/class C { +//// +////} + +goTo.marker(""); +verify.renameInfoFailed(); \ No newline at end of file