Skip to content

Commit 2d81c57

Browse files
committed
add long initializer test case and infer type from initializer
1 parent f912d3e commit 2d81c57

File tree

3 files changed

+1441
-104
lines changed

3 files changed

+1441
-104
lines changed

src/services/completions.ts

+146-18
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
diagnosticToString,
4848
displayPart,
4949
DotDotDotToken,
50+
EmitFlags,
5051
EmitHint,
5152
EmitTextWriter,
5253
EntityName,
@@ -312,6 +313,7 @@ import {
312313
ScriptElementKindModifier,
313314
ScriptTarget,
314315
SemanticMeaning,
316+
setEmitFlags,
315317
setSnippetElement,
316318
shouldUseUriStyleNodeCoreModules,
317319
SignatureHelp,
@@ -673,9 +675,10 @@ export function getCompletionsAtPosition(
673675

674676
}
675677

678+
const compilerOptions = program.getCompilerOptions();
679+
const checker = program.getTypeChecker();
676680
// If the request is a continuation of an earlier `isIncomplete` response,
677681
// we can continue it from the cached previous response.
678-
const compilerOptions = program.getCompilerOptions();
679682
const incompleteCompletionsCache = preferences.allowIncompleteCompletions ? host.getIncompleteCompletionsCache?.() : undefined;
680683
if (incompleteCompletionsCache && completionKind === CompletionTriggerKind.TriggerForIncompleteCompletions && previousToken && isIdentifier(previousToken)) {
681684
const incompleteContinuation = continuePreviousIncompleteResponse(incompleteCompletionsCache, sourceFile, previousToken, program, host, preferences, cancellationToken, position);
@@ -713,12 +716,24 @@ export function getCompletionsAtPosition(
713716
// If the current position is a jsDoc tag name, only tag names should be provided for completion
714717
return jsdocCompletionInfo([
715718
...JsDoc.getJSDocTagNameCompletions(),
716-
...getJSDocParameterCompletions(sourceFile, position, preferences, compilerOptions, /*tagNameOnly*/ true)]);
719+
...getJSDocParameterCompletions(
720+
sourceFile,
721+
position,
722+
checker,
723+
compilerOptions,
724+
preferences,
725+
/*tagNameOnly*/ true)]);
717726
case CompletionDataKind.JsDocTag:
718727
// If the current position is a jsDoc tag, only tags should be provided for completion
719728
return jsdocCompletionInfo([
720729
...JsDoc.getJSDocTagCompletions(),
721-
...getJSDocParameterCompletions(sourceFile, position, preferences, compilerOptions, /*tagNameOnly*/ false)]);
730+
...getJSDocParameterCompletions(
731+
sourceFile,
732+
position,
733+
checker,
734+
compilerOptions,
735+
preferences,
736+
/*tagNameOnly*/ false)]);
722737
case CompletionDataKind.JsDocParameterName:
723738
return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag));
724739
case CompletionDataKind.Keywords:
@@ -838,8 +853,9 @@ function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo {
838853
function getJSDocParameterCompletions(
839854
sourceFile: SourceFile,
840855
position: number,
841-
preferences: UserPreferences,
856+
checker: TypeChecker,
842857
options: CompilerOptions,
858+
preferences: UserPreferences,
843859
tagNameOnly: boolean): CompletionEntry[] {
844860
const currentToken = getTokenAtPosition(sourceFile, position);
845861
if (!isJSDocTag(currentToken) && !isJSDoc(currentToken)) {
@@ -864,8 +880,29 @@ function getJSDocParameterCompletions(
864880
if (isIdentifier(param.name)) { // Named parameter
865881
const tabstopCounter = { tabstop: 1 };
866882
const paramName = param.name.text;
867-
let snippetText = getJSDocParamAnnotation(paramName, param.initializer, param.dotDotDotToken, isJs, /*isObject*/ false, /*isSnippet*/ true, tabstopCounter);
868-
let insertText = getJSDocParamAnnotation(paramName, param.initializer, param.dotDotDotToken, isJs, /*isObject*/ false, /*isSnippet*/ false);
883+
let snippetText =
884+
getJSDocParamAnnotation(
885+
paramName,
886+
param.initializer,
887+
param.dotDotDotToken,
888+
isJs,
889+
/*isObject*/ false,
890+
/*isSnippet*/ true,
891+
checker,
892+
options,
893+
preferences,
894+
tabstopCounter);
895+
let insertText =
896+
getJSDocParamAnnotation(
897+
paramName,
898+
param.initializer,
899+
param.dotDotDotToken,
900+
isJs,
901+
/*isObject*/ false,
902+
/*isSnippet*/ false,
903+
checker,
904+
options,
905+
preferences);
869906
if (tagNameOnly) { // Remove `@`
870907
insertText = insertText.slice(1);
871908
snippetText = snippetText.slice(1);
@@ -881,9 +918,27 @@ function getJSDocParameterCompletions(
881918
else if (param.parent.parameters.indexOf(param) === paramTagCount) { // Destructuring parameter; do it positionally
882919
const paramPath = `param${paramTagCount}`;
883920
const insertTextResult =
884-
generateJSDocParamTagsForDestructuring(paramPath, param.name, param.initializer, param.dotDotDotToken, isJs, /*isSnippet*/ false);
921+
generateJSDocParamTagsForDestructuring(
922+
paramPath,
923+
param.name,
924+
param.initializer,
925+
param.dotDotDotToken,
926+
isJs,
927+
/*isSnippet*/ false,
928+
checker,
929+
options,
930+
preferences,);
885931
const snippetTextResult =
886-
generateJSDocParamTagsForDestructuring(paramPath, param.name, param.initializer, param.dotDotDotToken, isJs, /*isSnippet*/ true);
932+
generateJSDocParamTagsForDestructuring(
933+
paramPath,
934+
param.name,
935+
param.initializer,
936+
param.dotDotDotToken,
937+
isJs,
938+
/*isSnippet*/ true,
939+
checker,
940+
options,
941+
preferences,);
887942
let insertText = insertTextResult.join(getNewLineCharacter(options) + "* ");
888943
let snippetText = snippetTextResult.join(getNewLineCharacter(options) + "* ");
889944
if (tagNameOnly) { // Remove `@`
@@ -907,9 +962,24 @@ function generateJSDocParamTagsForDestructuring(
907962
initializer: Expression | undefined,
908963
dotDotDotToken: DotDotDotToken | undefined,
909964
isJs: boolean,
910-
isSnippet: boolean): string[] {
965+
isSnippet: boolean,
966+
checker: TypeChecker,
967+
options: CompilerOptions,
968+
preferences: UserPreferences): string[] {
911969
if (!isJs) {
912-
return [getJSDocParamAnnotation(path, initializer, dotDotDotToken, isJs, /*isObject*/ false, isSnippet, { tabstop: 1 })];
970+
return [
971+
getJSDocParamAnnotation(
972+
path,
973+
initializer,
974+
dotDotDotToken,
975+
isJs,
976+
/*isObject*/ false,
977+
isSnippet,
978+
checker,
979+
options,
980+
preferences,
981+
{ tabstop: 1 })
982+
];
913983
}
914984
return patternWorker(path, pattern, initializer, dotDotDotToken, { tabstop: 1 });
915985

@@ -922,7 +992,18 @@ function generateJSDocParamTagsForDestructuring(
922992
if (isObjectBindingPattern(pattern) && !dotDotDotToken) {
923993
const oldTabstop = counter.tabstop;
924994
const childCounter = { tabstop: oldTabstop };
925-
const rootParam = getJSDocParamAnnotation(path, initializer, dotDotDotToken, isJs, /*isObject*/ true, isSnippet, childCounter);
995+
const rootParam =
996+
getJSDocParamAnnotation(
997+
path,
998+
initializer,
999+
dotDotDotToken,
1000+
isJs,
1001+
/*isObject*/ true,
1002+
isSnippet,
1003+
checker,
1004+
options,
1005+
preferences,
1006+
childCounter);
9261007
let childTags: string[] | undefined = [];
9271008
for (const element of pattern.elements) {
9281009
const elementTags = elementWorker(path, element, childCounter);
@@ -939,7 +1020,19 @@ function generateJSDocParamTagsForDestructuring(
9391020
return [rootParam, ...childTags];
9401021
}
9411022
}
942-
return [getJSDocParamAnnotation(path, initializer, dotDotDotToken, isJs, /*isObject*/ false, isSnippet, counter)];
1023+
return [
1024+
getJSDocParamAnnotation(
1025+
path,
1026+
initializer,
1027+
dotDotDotToken,
1028+
isJs,
1029+
/*isObject*/ false,
1030+
isSnippet,
1031+
checker,
1032+
options,
1033+
preferences,
1034+
counter)
1035+
];
9431036
}
9441037

9451038
// Assumes binding element is inside object binding pattern.
@@ -951,7 +1044,18 @@ function generateJSDocParamTagsForDestructuring(
9511044
return undefined;
9521045
}
9531046
const paramName = `${path}.${propertyName}`;
954-
return [getJSDocParamAnnotation(paramName, element.initializer, element.dotDotDotToken, isJs, /*isObject*/ false, isSnippet, counter)];
1047+
return [
1048+
getJSDocParamAnnotation(
1049+
paramName,
1050+
element.initializer,
1051+
element.dotDotDotToken,
1052+
isJs,
1053+
/*isObject*/ false,
1054+
isSnippet,
1055+
checker,
1056+
options,
1057+
preferences,
1058+
counter)];
9551059
}
9561060
else if (element.propertyName) { // `{ b: {...} }` or `{ b: [...] }`
9571061
const propertyName = tryGetTextOfPropertyName(element.propertyName);
@@ -973,6 +1077,9 @@ function getJSDocParamAnnotation(
9731077
isJs: boolean,
9741078
isObject: boolean,
9751079
isSnippet: boolean,
1080+
checker: TypeChecker,
1081+
options: CompilerOptions,
1082+
preferences: UserPreferences,
9761083
tabstopCounter?: TabStopCounter) {
9771084
if (isSnippet) {
9781085
Debug.assertIsDefined(tabstopCounter);
@@ -984,17 +1091,38 @@ function getJSDocParamAnnotation(
9841091
paramName = escapeSnippetText(paramName);
9851092
}
9861093
if (isJs) {
987-
let type: string;
1094+
let type = "*";
9881095
if (isObject) {
9891096
Debug.assert(!dotDotDotToken, `Cannot annotate a rest parameter with type 'Object'.`);
9901097
type = "Object";
9911098
}
9921099
else {
993-
if (isSnippet) {
994-
type = `\${${tabstopCounter!.tabstop++}:*}`;
1100+
if (initializer) {
1101+
const inferredType = checker.getTypeAtLocation(initializer.parent);
1102+
if (!(inferredType.flags & TypeFlags.Any)) {
1103+
const sourceFile = initializer.getSourceFile();
1104+
const quotePreference = getQuotePreference(sourceFile, preferences);
1105+
const builderFlags = (quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : NodeBuilderFlags.None);
1106+
const typeNode = checker.typeToTypeNode(inferredType, findAncestor(initializer, isFunctionLike), builderFlags);
1107+
if (typeNode) {
1108+
const printer = isSnippet
1109+
? createSnippetPrinter({
1110+
removeComments: true,
1111+
module: options.module,
1112+
target: options.target,
1113+
})
1114+
: createPrinter({
1115+
removeComments: true,
1116+
module: options.module,
1117+
target: options.target
1118+
});
1119+
setEmitFlags(typeNode, EmitFlags.SingleLine);
1120+
type = printer.printNode(EmitHint.Unspecified, typeNode, sourceFile);
1121+
}
1122+
}
9951123
}
996-
else {
997-
type = "*";
1124+
if (isSnippet) {
1125+
type = `\${${tabstopCounter!.tabstop++}:${type}}`;
9981126
}
9991127
}
10001128
const dotDotDot = !isObject && dotDotDotToken ? "..." : "";

0 commit comments

Comments
 (0)