Skip to content

Commit ccfbb67

Browse files
committed
add basicly support for rename string literal type
1 parent 9f70d49 commit ccfbb67

File tree

5 files changed

+56
-13
lines changed

5 files changed

+56
-13
lines changed

src/services/findAllReferences.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ namespace ts.FindAllReferences {
620620
// Could not find a symbol e.g. unknown identifier
621621
if (!symbol) {
622622
// String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial.
623-
return !options.implementations && isStringLiteral(node) ? getReferencesForStringLiteral(node, sourceFiles, cancellationToken) : undefined;
623+
return !options.implementations && isStringLiteral(node) ? getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken) : undefined;
624624
}
625625

626626
if (symbol.escapedName === InternalSymbolName.ExportEquals) {
@@ -768,7 +768,7 @@ namespace ts.FindAllReferences {
768768
// At `module.exports = ...`, reference node is `module`
769769
const node = isBinaryExpression(decl) && isPropertyAccessExpression(decl.left) ? decl.left.expression :
770770
isExportAssignment(decl) ? Debug.checkDefined(findChildOfKind(decl, SyntaxKind.ExportKeyword, sourceFile)) :
771-
getNameOfDeclaration(decl) || decl;
771+
getNameOfDeclaration(decl) || decl;
772772
references.push(nodeEntry(node));
773773
}
774774
}
@@ -880,7 +880,7 @@ namespace ts.FindAllReferences {
880880
Debug.assert(node.parent.name === node);
881881
return SpecialSearchKind.Class;
882882
}
883-
// falls through
883+
// falls through
884884
default:
885885
return SpecialSearchKind.None;
886886
}
@@ -1833,7 +1833,7 @@ namespace ts.FindAllReferences {
18331833
if (isObjectLiteralMethod(searchSpaceNode)) {
18341834
break;
18351835
}
1836-
// falls through
1836+
// falls through
18371837
case SyntaxKind.PropertyDeclaration:
18381838
case SyntaxKind.PropertySignature:
18391839
case SyntaxKind.Constructor:
@@ -1846,7 +1846,7 @@ namespace ts.FindAllReferences {
18461846
if (isExternalModule(<SourceFile>searchSpaceNode) || isParameterName(thisOrSuperKeyword)) {
18471847
return undefined;
18481848
}
1849-
// falls through
1849+
// falls through
18501850
case SyntaxKind.FunctionDeclaration:
18511851
case SyntaxKind.FunctionExpression:
18521852
break;
@@ -1888,11 +1888,19 @@ namespace ts.FindAllReferences {
18881888
}];
18891889
}
18901890

1891-
function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] {
1891+
function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] {
1892+
const type = getContextualTypeOrAtAncestorTypeNode(node, checker);
18921893
const references = flatMap(sourceFiles, sourceFile => {
18931894
cancellationToken.throwIfCancellationRequested();
1894-
return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref =>
1895-
isStringLiteral(ref) && ref.text === node.text ? nodeEntry(ref, EntryKind.StringLiteral) : undefined);
1895+
return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => {
1896+
if (type && isStringLiteral(ref) && ref.text === node.text) {
1897+
const refType = getContextualTypeOrAtAncestorTypeNode(ref, checker);
1898+
if (type !== checker.getStringType() && type === refType) {
1899+
return nodeEntry(ref, EntryKind.StringLiteral);
1900+
}
1901+
}
1902+
return undefined;
1903+
});
18961904
});
18971905

18981906
return [{
@@ -1907,7 +1915,7 @@ namespace ts.FindAllReferences {
19071915
const result: Symbol[] = [];
19081916
forEachRelatedSymbol<void>(symbol, location, checker, isForRename, !(isForRename && providePrefixAndSuffixText),
19091917
(sym, root, base) => { result.push(base || root || sym); },
1910-
/*allowBaseTypes*/ () => !implementations);
1918+
/*allowBaseTypes*/() => !implementations);
19111919
return result;
19121920
}
19131921

@@ -2081,8 +2089,8 @@ namespace ts.FindAllReferences {
20812089
function isImplementation(node: Node): boolean {
20822090
return !!(node.flags & NodeFlags.Ambient) ? !(isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)) :
20832091
(isVariableLike(node) ? hasInitializer(node) :
2084-
isFunctionLikeDeclaration(node) ? !!node.body :
2085-
isClassLike(node) || isModuleOrEnumDeclaration(node));
2092+
isFunctionLikeDeclaration(node) ? !!node.body :
2093+
isClassLike(node) || isModuleOrEnumDeclaration(node));
20862094
}
20872095

20882096
export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void {

src/services/rename.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,18 @@ namespace ts.Rename {
1313

1414
function getRenameInfoForNode(node: Node, typeChecker: TypeChecker, sourceFile: SourceFile, isDefinedInLibraryFile: (declaration: Node) => boolean, options?: RenameInfoOptions): RenameInfo | undefined {
1515
const symbol = typeChecker.getSymbolAtLocation(node);
16-
if (!symbol) return;
16+
if (!symbol) {
17+
if (isStringLiteralLike(node)) {
18+
const type = getContextualTypeOrAtAncestorTypeNode(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+
return;
26+
};
27+
1728
// Only allow a symbol to be renamed if it actually has at least one declaration.
1829
const { declarations } = symbol;
1930
if (!declarations || declarations.length === 0) return;

src/services/utilities.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,20 @@ namespace ts {
771771
}
772772
}
773773

774+
function getAncestorTypeNode(node: Node) {
775+
return findAncestor(node, a => !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent));
776+
}
777+
778+
export function getContextualTypeOrAtAncestorTypeNode(node: Expression, checker: TypeChecker) {
779+
const contextualType = checker.getContextualType(node);
780+
if (contextualType) {
781+
return contextualType;
782+
}
783+
784+
const ancestorTypeNode = getAncestorTypeNode(node);
785+
return ancestorTypeNode && checker.getTypeAtLocation(ancestorTypeNode);
786+
}
787+
774788
function getAdjustedLocationForDeclaration(node: Node, forRename: boolean) {
775789
if (!forRename) {
776790
switch (node.kind) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
10+
verify.rangesWithSameTextAreRenameLocations("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)