Fix "Extract to type alias" not available at end of span (#56467)

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
This commit is contained in:
Babak K. Shandiz 2024-01-05 17:52:26 +00:00 committed by GitHub
parent e441420483
commit b2c6a56e38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 82 additions and 7 deletions

View File

@ -23,6 +23,7 @@ import {
getRefactorContextSpan,
getRenameLocation,
getTokenAtPosition,
getTouchingToken,
getUniqueName,
ignoreSourceNewlines,
isArray,
@ -173,14 +174,9 @@ type ExtractInfo = TypeAliasInfo | InterfaceInfo;
function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): ExtractInfo | RefactorErrorInfo | undefined {
const { file, startPosition } = context;
const isJS = isSourceFileJS(file);
const current = getTokenAtPosition(file, startPosition);
const range = createTextRangeFromSpan(getRefactorContextSpan(context));
const cursorRequest = range.pos === range.end && considerEmptySpans;
const overlappingRange = nodeOverlapsWithStartEnd(current, file, range.pos, range.end);
const firstType = findAncestor(current, node =>
node.parent && isTypeNode(node) && !rangeContainsSkipTrivia(range, node.parent, file) &&
(cursorRequest || overlappingRange));
const isCursorRequest = range.pos === range.end && considerEmptySpans;
const firstType = getFirstTypeAt(file, startPosition, range, isCursorRequest);
if (!firstType || !isTypeNode(firstType)) return { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) };
const checker = context.program.getTypeChecker();
@ -209,6 +205,24 @@ function getRangeToExtract(context: RefactorContext, considerEmptySpans = true):
return { isJS, selection, enclosingNode, typeParameters, typeElements };
}
function getFirstTypeAt(file: SourceFile, startPosition: number, range: TextRange, isCursorRequest: boolean): Node | undefined {
const currentNodes = [
() => getTokenAtPosition(file, startPosition),
() => getTouchingToken(file, startPosition, () => true),
];
for (const f of currentNodes) {
const current = f();
const overlappingRange = nodeOverlapsWithStartEnd(current, file, range.pos, range.end);
const firstType = findAncestor(current, node =>
node.parent && isTypeNode(node) && !rangeContainsSkipTrivia(range, node.parent, file) &&
(isCursorRequest || overlappingRange));
if (firstType) {
return firstType;
}
}
return undefined;
}
function flattenTypeLiteralNodeReference(checker: TypeChecker, selection: TypeNode | TypeNode[] | undefined): readonly TypeElement[] | undefined {
if (!selection) return undefined;
if (isArray(selection)) {

View File

@ -0,0 +1,61 @@
/// <reference path='fourslash.ts' />
//// interface Yadda<T> { x: T }
////
//// export let blah: Yadda/*a*/<string>/*b*/;
////
//// interface YaddaWithDefault<T = boolean/*c*/> { x: T/*d*/ }
goTo.marker("a");
verify.refactorAvailableForTriggerReason("invoked", "Extract type", "Extract to type alias")
goTo.marker("b");
edit.applyRefactor({
triggerReason: "invoked",
refactorName: "Extract type",
actionName: "Extract to type alias",
actionDescription: "Extract to type alias",
newContent: `interface Yadda<T> { x: T }
type /*RENAME*/NewType = Yadda<string>;
export let blah: NewType;
interface YaddaWithDefault<T = boolean> { x: T }`,
});
goTo.marker("c");
edit.applyRefactor({
triggerReason: "invoked",
refactorName: "Extract type",
actionName: "Extract to type alias",
actionDescription: "Extract to type alias",
newContent: `interface Yadda<T> { x: T }
type NewType = Yadda<string>;
export let blah: NewType;
type /*RENAME*/NewType_1 = boolean;
interface YaddaWithDefault<T = NewType_1> { x: T }`
});
goTo.marker("d");
edit.applyRefactor({
triggerReason: "invoked",
refactorName: "Extract type",
actionName: "Extract to type alias",
actionDescription: "Extract to type alias",
newContent: `interface Yadda<T> { x: T }
type NewType = Yadda<string>;
export let blah: NewType;
type NewType_1 = boolean;
type /*RENAME*/NewType_2<T> = T;
interface YaddaWithDefault<T = NewType_1> { x: NewType_2<T> }`
});