Skip to content

Commit ca7510e

Browse files
String literal rename support (#39298)
* add basicly support for rename string literal type * fix merge conflict * fix some behavior * Update package-lock.json * Update package-lock.json * do not break old behavior if not type checked * fix cr issue Co-authored-by: TypeScript Bot <[email protected]>
1 parent ad2e2f8 commit ca7510e

File tree

6 files changed

+77
-7
lines changed

6 files changed

+77
-7
lines changed

src/services/findAllReferences.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace ts.FindAllReferences {
1111
| { readonly type: DefinitionKind.Label; readonly node: Identifier }
1212
| { readonly type: DefinitionKind.Keyword; readonly node: Node }
1313
| { readonly type: DefinitionKind.This; readonly node: Node }
14-
| { readonly type: DefinitionKind.String; readonly node: StringLiteral };
14+
| { readonly type: DefinitionKind.String; readonly node: StringLiteralLike };
1515

1616
export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal }
1717
export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal;
@@ -621,7 +621,7 @@ namespace ts.FindAllReferences {
621621
// Could not find a symbol e.g. unknown identifier
622622
if (!symbol) {
623623
// String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial.
624-
return !options.implementations && isStringLiteral(node) ? getReferencesForStringLiteral(node, sourceFiles, cancellationToken) : undefined;
624+
return !options.implementations && isStringLiteralLike(node) ? getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken) : undefined;
625625
}
626626

627627
if (symbol.escapedName === InternalSymbolName.ExportEquals) {
@@ -1889,11 +1889,23 @@ namespace ts.FindAllReferences {
18891889
}];
18901890
}
18911891

1892-
function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] {
1892+
function getReferencesForStringLiteral(node: StringLiteralLike, sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] {
1893+
const type = getContextualTypeOrAncestorTypeNodeType(node, checker);
18931894
const references = flatMap(sourceFiles, sourceFile => {
18941895
cancellationToken.throwIfCancellationRequested();
1895-
return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref =>
1896-
isStringLiteral(ref) && ref.text === node.text ? nodeEntry(ref, EntryKind.StringLiteral) : undefined);
1896+
return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => {
1897+
if (isStringLiteralLike(ref) && ref.text === node.text) {
1898+
if (type) {
1899+
const refType = getContextualTypeOrAncestorTypeNodeType(ref, checker);
1900+
if (type !== checker.getStringType() && type === refType) {
1901+
return nodeEntry(ref, EntryKind.StringLiteral);
1902+
}
1903+
}
1904+
else {
1905+
return nodeEntry(ref, EntryKind.StringLiteral);
1906+
}
1907+
}
1908+
});
18971909
});
18981910

18991911
return [{

src/services/rename.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,15 @@ namespace ts.Rename {
1414
function getRenameInfoForNode(node: Node, typeChecker: TypeChecker, sourceFile: SourceFile, isDefinedInLibraryFile: (declaration: Node) => boolean, options?: RenameInfoOptions): RenameInfo | undefined {
1515
const symbol = typeChecker.getSymbolAtLocation(node);
1616
if (!symbol) {
17-
if (isLabelName(node)) {
17+
if (isStringLiteralLike(node)) {
18+
const type = getContextualTypeOrAncestorTypeNodeType(node, typeChecker);
19+
if (type && ((type.flags & TypeFlags.StringLiteral) || (
20+
(type.flags & TypeFlags.Union) && every((type as UnionType).types, type => !!(type.flags & TypeFlags.StringLiteral))
21+
))) {
22+
return getRenameInfoSuccess(node.text, node.text, ScriptElementKind.string, "", node, sourceFile);
23+
}
24+
}
25+
else if (isLabelName(node)) {
1826
const name = getTextOfNode(node);
1927
return getRenameInfoSuccess(name, name, ScriptElementKind.label, ScriptElementKindModifier.none, node, sourceFile);
2028
}

src/services/utilities.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,27 @@ namespace ts {
774774
}
775775
}
776776

777+
function getAncestorTypeNode(node: Node) {
778+
let lastTypeNode: TypeNode | undefined;
779+
findAncestor(node, a => {
780+
if (isTypeNode(a)) {
781+
lastTypeNode = a;
782+
}
783+
return !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent);
784+
});
785+
return lastTypeNode;
786+
}
787+
788+
export function getContextualTypeOrAncestorTypeNodeType(node: Expression, checker: TypeChecker) {
789+
const contextualType = checker.getContextualType(node);
790+
if (contextualType) {
791+
return contextualType;
792+
}
793+
794+
const ancestorTypeNode = getAncestorTypeNode(node);
795+
return ancestorTypeNode && checker.getTypeAtLocation(ancestorTypeNode);
796+
}
797+
777798
function getAdjustedLocationForDeclaration(node: Node, forRename: boolean) {
778799
if (!forRename) {
779800
switch (node.kind) {
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+
//// interface Foo {
4+
//// f: '[|foo|]' | 'bar'
5+
//// }
6+
//// const d: 'foo' = 'foo'
7+
//// declare const f: Foo
8+
//// f.f = '[|foo|]'
9+
//// f.f = `[|foo|]`
10+
11+
verify.rangesWithSameTextAreRenameLocations("foo");
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
//// declare function f(): '[|foo|]' | 'bar'
4+
//// class Foo {
5+
//// f = f()
6+
//// }
7+
//// const d: 'foo' = 'foo'
8+
//// declare const ff: Foo
9+
//// ff.f = '[|foo|]'
10+
11+
verify.rangesWithSameTextAreRenameLocations("foo");
12+
13+
interface Foo {
14+
f: 'foo' | 'bar'
15+
}
16+
const d: 'foo' = 'foo'
17+
declare const f: Foo
18+
f.f = 'foo'

tests/cases/fourslash/renameStringLiteralTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@
1111
////
1212
////animate({ deltaX: 100, deltaY: 100, easing: "[|ease-in-out|]" });
1313

14-
goTo.eachRange(() => { verify.renameInfoFailed(); });
14+
verify.rangesWithSameTextAreRenameLocations("ease-in-out");

0 commit comments

Comments
 (0)