mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
Include source node inferences in string literal completions (#54121)
Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
This commit is contained in:
parent
55d8bed85c
commit
d7b4cee7df
@ -469,6 +469,7 @@ import {
|
||||
isCallChain,
|
||||
isCallExpression,
|
||||
isCallLikeExpression,
|
||||
isCallLikeOrFunctionLikeExpression,
|
||||
isCallOrNewExpression,
|
||||
isCallSignatureDeclaration,
|
||||
isCatchClause,
|
||||
@ -1653,12 +1654,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
getTypeOfPropertyOfContextualType,
|
||||
getFullyQualifiedName,
|
||||
getResolvedSignature: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal),
|
||||
getResolvedSignatureForStringLiteralCompletions: (call, editingArgument, candidatesOutArray, checkMode = CheckMode.IsForStringLiteralArgumentCompletions) => {
|
||||
if (checkMode & CheckMode.IsForStringLiteralArgumentCompletions) {
|
||||
return runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, checkMode & ~CheckMode.IsForStringLiteralArgumentCompletions));
|
||||
}
|
||||
return runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, checkMode & ~CheckMode.IsForStringLiteralArgumentCompletions));
|
||||
},
|
||||
getCandidateSignaturesForStringLiteralCompletions,
|
||||
getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => runWithoutResolvedSignatureCaching(node, () => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp)),
|
||||
getExpandedParameters,
|
||||
hasEffectiveRestParameter,
|
||||
@ -1838,32 +1834,55 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
typeHasCallOrConstructSignatures,
|
||||
};
|
||||
|
||||
function getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node) {
|
||||
const candidatesSet = new Set<Signature>();
|
||||
const candidates: Signature[] = [];
|
||||
|
||||
// first, get candidates when inference is blocked from the source node.
|
||||
runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions));
|
||||
for (const candidate of candidates) {
|
||||
candidatesSet.add(candidate);
|
||||
}
|
||||
|
||||
// reset candidates for second pass
|
||||
candidates.length = 0;
|
||||
|
||||
// next, get candidates where the source node is considered for inference.
|
||||
runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.Normal));
|
||||
for (const candidate of candidates) {
|
||||
candidatesSet.add(candidate);
|
||||
}
|
||||
|
||||
return arrayFrom(candidatesSet);
|
||||
}
|
||||
|
||||
function runWithoutResolvedSignatureCaching<T>(node: Node | undefined, fn: () => T): T {
|
||||
const cachedResolvedSignatures = [];
|
||||
const cachedTypes = [];
|
||||
while (node) {
|
||||
if (isCallLikeExpression(node) || isFunctionLike(node)) {
|
||||
node = findAncestor(node, isCallLikeOrFunctionLikeExpression);
|
||||
if (node) {
|
||||
const cachedResolvedSignatures = [];
|
||||
const cachedTypes = [];
|
||||
while (node) {
|
||||
const nodeLinks = getNodeLinks(node);
|
||||
const resolvedSignature = nodeLinks.resolvedSignature;
|
||||
cachedResolvedSignatures.push([nodeLinks, resolvedSignature] as const);
|
||||
cachedResolvedSignatures.push([nodeLinks, nodeLinks.resolvedSignature] as const);
|
||||
nodeLinks.resolvedSignature = undefined;
|
||||
if (isFunctionLike(node)) {
|
||||
const symbolLinks = getSymbolLinks(getSymbolOfDeclaration(node));
|
||||
const type = symbolLinks.type;
|
||||
cachedTypes.push([symbolLinks, type] as const);
|
||||
symbolLinks.type = undefined;
|
||||
}
|
||||
node = findAncestor(node.parent, isCallLikeOrFunctionLikeExpression);
|
||||
}
|
||||
if (isFunctionLike(node)) {
|
||||
const symbolLinks = getSymbolLinks(getSymbolOfDeclaration(node));
|
||||
const type = symbolLinks.type;
|
||||
cachedTypes.push([symbolLinks, type] as const);
|
||||
symbolLinks.type = undefined;
|
||||
const result = fn();
|
||||
for (const [nodeLinks, resolvedSignature] of cachedResolvedSignatures) {
|
||||
nodeLinks.resolvedSignature = resolvedSignature;
|
||||
}
|
||||
node = node.parent;
|
||||
for (const [symbolLinks, type] of cachedTypes) {
|
||||
symbolLinks.type = type;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const result = fn();
|
||||
for (const [nodeLinks, resolvedSignature] of cachedResolvedSignatures) {
|
||||
nodeLinks.resolvedSignature = resolvedSignature;
|
||||
}
|
||||
for (const [symbolLinks, type] of cachedTypes) {
|
||||
symbolLinks.type = type;
|
||||
}
|
||||
return result;
|
||||
return fn();
|
||||
}
|
||||
|
||||
function runWithInferenceBlockedFromSourceNode<T>(node: Node | undefined, fn: () => T): T {
|
||||
@ -33207,7 +33226,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
|
||||
for (let i = 0; i < argCount; i++) {
|
||||
const arg = args[i];
|
||||
if (arg.kind !== SyntaxKind.OmittedExpression) {
|
||||
if (arg.kind !== SyntaxKind.OmittedExpression && !(checkMode & CheckMode.IsForStringLiteralArgumentCompletions && hasSkipDirectInferenceFlag(arg))) {
|
||||
const paramType = getTypeAtPosition(signature, i);
|
||||
if (couldContainTypeVariables(paramType)) {
|
||||
const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode);
|
||||
@ -33849,6 +33868,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// decorators are applied to a declaration by the emitter, and not to an expression.
|
||||
const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters;
|
||||
let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal;
|
||||
argCheckMode |= checkMode & CheckMode.IsForStringLiteralArgumentCompletions;
|
||||
|
||||
// The following variables are captured and modified by calls to chooseOverload.
|
||||
// If overload resolution or type argument inference fails, we want to report the
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import {
|
||||
BaseNodeFactory,
|
||||
CheckMode,
|
||||
CreateSourceFileOptions,
|
||||
EmitHelperFactory,
|
||||
GetCanonicalFileName,
|
||||
@ -5010,7 +5009,7 @@ export interface TypeChecker {
|
||||
*/
|
||||
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
|
||||
/** @internal */ getResolvedSignatureForSignatureHelp(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
|
||||
/** @internal */ getResolvedSignatureForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node, candidatesOutArray: Signature[], checkMode?: CheckMode): Signature | undefined;
|
||||
/** @internal */ getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node): Signature[];
|
||||
/** @internal */ getExpandedParameters(sig: Signature): readonly (readonly Symbol[])[];
|
||||
/** @internal */ hasEffectiveRestParameter(sig: Signature): boolean;
|
||||
/** @internal */ containsArgumentsReference(declaration: SignatureDeclaration): boolean;
|
||||
|
||||
@ -1920,6 +1920,11 @@ export function isPropertyAccessOrQualifiedName(node: Node): node is PropertyAcc
|
||||
|| kind === SyntaxKind.QualifiedName;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isCallLikeOrFunctionLikeExpression(node: Node): node is CallLikeExpression | SignatureDeclaration {
|
||||
return isCallLikeExpression(node) || isFunctionLike(node);
|
||||
}
|
||||
|
||||
export function isCallLikeExpression(node: Node): node is CallLikeExpression {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.JsxOpeningElement:
|
||||
|
||||
@ -7,7 +7,6 @@ import {
|
||||
CaseClause,
|
||||
changeExtension,
|
||||
CharacterCodes,
|
||||
CheckMode,
|
||||
combinePaths,
|
||||
comparePaths,
|
||||
comparePatternKeys,
|
||||
@ -119,7 +118,6 @@ import {
|
||||
ScriptElementKind,
|
||||
ScriptElementKindModifier,
|
||||
ScriptTarget,
|
||||
Signature,
|
||||
signatureHasRestParameter,
|
||||
SignatureHelp,
|
||||
singleElementArray,
|
||||
@ -414,7 +412,7 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
|
||||
// Get string literal completions from specialized signatures of the target
|
||||
// i.e. declare function f(a: 'A');
|
||||
// f("/*completion position*/")
|
||||
return argumentInfo && (getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) || getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker, CheckMode.Normal)) || fromContextualType(ContextFlags.None);
|
||||
return argumentInfo && getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) || fromContextualType(ContextFlags.None);
|
||||
}
|
||||
// falls through (is `require("")` or `require(""` or `import("")`)
|
||||
|
||||
@ -504,12 +502,11 @@ function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current:
|
||||
return mapDefined(union.types, type => type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined);
|
||||
}
|
||||
|
||||
function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker, checkMode = CheckMode.IsForStringLiteralArgumentCompletions): StringLiteralCompletionsFromTypes | undefined {
|
||||
function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes | undefined {
|
||||
let isNewIdentifier = false;
|
||||
const uniques = new Map<string, true>();
|
||||
const candidates: Signature[] = [];
|
||||
const editingArgument = isJsxOpeningLikeElement(call) ? Debug.checkDefined(findAncestor(arg.parent, isJsxAttribute)) : arg;
|
||||
checker.getResolvedSignatureForStringLiteralCompletions(call, editingArgument, candidates, checkMode);
|
||||
const candidates = checker.getCandidateSignaturesForStringLiteralCompletions(call, editingArgument);
|
||||
const types = flatMap(candidates, candidate => {
|
||||
if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return;
|
||||
let type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex);
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// repro from https://github.com/microsoft/TypeScript/issues/49680
|
||||
|
||||
//// type PathOf<T, K extends string, P extends string = ""> =
|
||||
//// K extends `${infer U}.${infer V}`
|
||||
//// ? U extends keyof T ? PathOf<T[U], V, `${P}${U}.`> : `${P}${keyof T & (string | number)}`
|
||||
//// : K extends keyof T ? `${P}${K}` : `${P}${keyof T & (string | number)}`;
|
||||
////
|
||||
//// declare function consumer<K extends string>(path: PathOf<{a: string, b: {c: string}}, K>) : number;
|
||||
////
|
||||
//// consumer('b./*ts*/')
|
||||
|
||||
verify.completions({ marker: ["ts"], exact: ["a", "b", "b.c"] });
|
||||
@ -0,0 +1,21 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// repro from https://github.com/microsoft/TypeScript/issues/49680#issuecomment-1249191842
|
||||
|
||||
//// declare function pick<T extends object, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>;
|
||||
//// declare function pick2<T extends object, K extends (keyof T)[]>(obj: T, ...keys: K): Pick<T, K[number]>;
|
||||
////
|
||||
//// const obj = { aaa: 1, bbb: '2', ccc: true };
|
||||
////
|
||||
//// pick(obj, 'aaa', '/*ts1*/');
|
||||
//// pick2(obj, 'aaa', '/*ts2*/');
|
||||
|
||||
// repro from https://github.com/microsoft/TypeScript/issues/49680#issuecomment-1273677941
|
||||
|
||||
//// class Q<T> {
|
||||
//// public select<Keys extends keyof T>(...args: Keys[]) {}
|
||||
//// }
|
||||
//// new Q<{ id: string; name: string }>().select("name", "/*ts3*/");
|
||||
|
||||
verify.completions({ marker: ["ts1", "ts2"], exact: ["aaa", "bbb", "ccc"] });
|
||||
verify.completions({ marker: ["ts3"], exact: ["name", "id"] });
|
||||
Loading…
x
Reference in New Issue
Block a user