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 <typescriptbot@microsoft.com>
This commit is contained in:
Wenlu Wang 2020-11-07 01:58:34 +08:00 committed by GitHub
parent ad2e2f80c1
commit ca7510ea05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 77 additions and 7 deletions

View File

@ -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 [{

View File

@ -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);
}

View File

@ -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) {

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts'/>
//// interface Foo {
//// f: '[|foo|]' | 'bar'
//// }
//// const d: 'foo' = 'foo'
//// declare const f: Foo
//// f.f = '[|foo|]'
//// f.f = `[|foo|]`
verify.rangesWithSameTextAreRenameLocations("foo");

View File

@ -0,0 +1,18 @@
/// <reference path='fourslash.ts'/>
//// 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'

View File

@ -11,4 +11,4 @@
////
////animate({ deltaX: 100, deltaY: 100, easing: "[|ease-in-out|]" });
goTo.eachRange(() => { verify.renameInfoFailed(); });
verify.rangesWithSameTextAreRenameLocations("ease-in-out");