diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts
index 158b16d00c7f1..953fafe3c91a7 100644
--- a/src/services/findAllReferences.ts
+++ b/src/services/findAllReferences.ts
@@ -11,7 +11,7 @@ namespace ts.FindAllReferences {
| { readonly type: DefinitionKind.Label; readonly node: Identifier }
| { readonly type: DefinitionKind.Keyword; readonly node: Node }
| { readonly type: DefinitionKind.This; readonly node: Node }
- | { readonly type: DefinitionKind.String; readonly node: StringLiteral };
+ | { readonly type: DefinitionKind.String; readonly node: StringLiteralLike };
export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal }
export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal;
@@ -621,7 +621,7 @@ namespace ts.FindAllReferences {
// Could not find a symbol e.g. unknown identifier
if (!symbol) {
// String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial.
- return !options.implementations && isStringLiteral(node) ? getReferencesForStringLiteral(node, sourceFiles, cancellationToken) : undefined;
+ return !options.implementations && isStringLiteralLike(node) ? getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken) : undefined;
}
if (symbol.escapedName === InternalSymbolName.ExportEquals) {
@@ -1889,11 +1889,23 @@ namespace ts.FindAllReferences {
}];
}
- function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] {
+ function getReferencesForStringLiteral(node: StringLiteralLike, sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] {
+ const type = getContextualTypeOrAncestorTypeNodeType(node, checker);
const references = flatMap(sourceFiles, sourceFile => {
cancellationToken.throwIfCancellationRequested();
- return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref =>
- isStringLiteral(ref) && ref.text === node.text ? nodeEntry(ref, EntryKind.StringLiteral) : undefined);
+ return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => {
+ if (isStringLiteralLike(ref) && ref.text === node.text) {
+ if (type) {
+ const refType = getContextualTypeOrAncestorTypeNodeType(ref, checker);
+ if (type !== checker.getStringType() && type === refType) {
+ return nodeEntry(ref, EntryKind.StringLiteral);
+ }
+ }
+ else {
+ return nodeEntry(ref, EntryKind.StringLiteral);
+ }
+ }
+ });
});
return [{
diff --git a/src/services/rename.ts b/src/services/rename.ts
index c3392a0ee6471..59efe2f7a209c 100644
--- a/src/services/rename.ts
+++ b/src/services/rename.ts
@@ -14,7 +14,15 @@ namespace ts.Rename {
function getRenameInfoForNode(node: Node, typeChecker: TypeChecker, sourceFile: SourceFile, isDefinedInLibraryFile: (declaration: Node) => boolean, options?: RenameInfoOptions): RenameInfo | undefined {
const symbol = typeChecker.getSymbolAtLocation(node);
if (!symbol) {
- if (isLabelName(node)) {
+ if (isStringLiteralLike(node)) {
+ const type = getContextualTypeOrAncestorTypeNodeType(node, typeChecker);
+ if (type && ((type.flags & TypeFlags.StringLiteral) || (
+ (type.flags & TypeFlags.Union) && every((type as UnionType).types, type => !!(type.flags & TypeFlags.StringLiteral))
+ ))) {
+ return getRenameInfoSuccess(node.text, node.text, ScriptElementKind.string, "", node, sourceFile);
+ }
+ }
+ else if (isLabelName(node)) {
const name = getTextOfNode(node);
return getRenameInfoSuccess(name, name, ScriptElementKind.label, ScriptElementKindModifier.none, node, sourceFile);
}
diff --git a/src/services/utilities.ts b/src/services/utilities.ts
index d63e21d476f79..bececaaa4f49c 100644
--- a/src/services/utilities.ts
+++ b/src/services/utilities.ts
@@ -774,6 +774,27 @@ namespace ts {
}
}
+ function getAncestorTypeNode(node: Node) {
+ let lastTypeNode: TypeNode | undefined;
+ findAncestor(node, a => {
+ if (isTypeNode(a)) {
+ lastTypeNode = a;
+ }
+ return !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent);
+ });
+ return lastTypeNode;
+ }
+
+ export function getContextualTypeOrAncestorTypeNodeType(node: Expression, checker: TypeChecker) {
+ const contextualType = checker.getContextualType(node);
+ if (contextualType) {
+ return contextualType;
+ }
+
+ const ancestorTypeNode = getAncestorTypeNode(node);
+ return ancestorTypeNode && checker.getTypeAtLocation(ancestorTypeNode);
+ }
+
function getAdjustedLocationForDeclaration(node: Node, forRename: boolean) {
if (!forRename) {
switch (node.kind) {
diff --git a/tests/cases/fourslash/renameStringLiteralOk.ts b/tests/cases/fourslash/renameStringLiteralOk.ts
new file mode 100644
index 0000000000000..7b8bdc5a18f15
--- /dev/null
+++ b/tests/cases/fourslash/renameStringLiteralOk.ts
@@ -0,0 +1,11 @@
+///
+
+//// interface Foo {
+//// f: '[|foo|]' | 'bar'
+//// }
+//// const d: 'foo' = 'foo'
+//// declare const f: Foo
+//// f.f = '[|foo|]'
+//// f.f = `[|foo|]`
+
+verify.rangesWithSameTextAreRenameLocations("foo");
diff --git a/tests/cases/fourslash/renameStringLiteralOk1.ts b/tests/cases/fourslash/renameStringLiteralOk1.ts
new file mode 100644
index 0000000000000..a6b0047d72d1e
--- /dev/null
+++ b/tests/cases/fourslash/renameStringLiteralOk1.ts
@@ -0,0 +1,18 @@
+///
+
+//// declare function f(): '[|foo|]' | 'bar'
+//// class Foo {
+//// f = f()
+//// }
+//// const d: 'foo' = 'foo'
+//// declare const ff: Foo
+//// ff.f = '[|foo|]'
+
+verify.rangesWithSameTextAreRenameLocations("foo");
+
+interface Foo {
+ f: 'foo' | 'bar'
+}
+const d: 'foo' = 'foo'
+declare const f: Foo
+f.f = 'foo'
\ No newline at end of file
diff --git a/tests/cases/fourslash/renameStringLiteralTypes.ts b/tests/cases/fourslash/renameStringLiteralTypes.ts
index ef2b7bb76adc9..294d0fc260893 100644
--- a/tests/cases/fourslash/renameStringLiteralTypes.ts
+++ b/tests/cases/fourslash/renameStringLiteralTypes.ts
@@ -11,4 +11,4 @@
////
////animate({ deltaX: 100, deltaY: 100, easing: "[|ease-in-out|]" });
-goTo.eachRange(() => { verify.renameInfoFailed(); });
+verify.rangesWithSameTextAreRenameLocations("ease-in-out");