mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 04:43:37 -05:00
Provide snippet completions for @param in JSDoc (#53260)
This commit is contained in:
committed by
GitHub
parent
a280cafbf8
commit
e83d61398e
@@ -45703,6 +45703,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType;
|
||||
}
|
||||
|
||||
if (isBindingElement(node)) {
|
||||
return getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ true, CheckMode.Normal) || errorType;
|
||||
}
|
||||
|
||||
if (isDeclaration(node)) {
|
||||
// In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration
|
||||
const symbol = getSymbolOfDeclaration(node);
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
addToSeen,
|
||||
append,
|
||||
BinaryExpression,
|
||||
BindingElement,
|
||||
BindingPattern,
|
||||
BreakOrContinueStatement,
|
||||
CancellationToken,
|
||||
canUsePropertyAccess,
|
||||
@@ -32,6 +34,7 @@ import {
|
||||
concatenate,
|
||||
ConstructorDeclaration,
|
||||
ContextFlags,
|
||||
countWhere,
|
||||
createModuleSpecifierResolutionHost,
|
||||
createPackageJsonImportFilter,
|
||||
createPrinter,
|
||||
@@ -44,6 +47,8 @@ import {
|
||||
Diagnostics,
|
||||
diagnosticToString,
|
||||
displayPart,
|
||||
DotDotDotToken,
|
||||
EmitFlags,
|
||||
EmitHint,
|
||||
EmitTextWriter,
|
||||
EntityName,
|
||||
@@ -79,6 +84,7 @@ import {
|
||||
getEscapedTextOfIdentifierOrLiteral,
|
||||
getExportInfoMap,
|
||||
getFormatCodeSettingsForWriting,
|
||||
getJSDocParameterTags,
|
||||
getLanguageVariant,
|
||||
getLeftmostAccessExpression,
|
||||
getLineAndCharacterOfPosition,
|
||||
@@ -309,6 +315,7 @@ import {
|
||||
ScriptElementKindModifier,
|
||||
ScriptTarget,
|
||||
SemanticMeaning,
|
||||
setEmitFlags,
|
||||
setSnippetElement,
|
||||
shouldUseUriStyleNodeCoreModules,
|
||||
SignatureHelp,
|
||||
@@ -344,6 +351,7 @@ import {
|
||||
tokenToString,
|
||||
tryCast,
|
||||
tryGetImportFromModuleSpecifier,
|
||||
tryGetTextOfPropertyName,
|
||||
Type,
|
||||
TypeChecker,
|
||||
TypeElement,
|
||||
@@ -669,9 +677,10 @@ export function getCompletionsAtPosition(
|
||||
|
||||
}
|
||||
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const checker = program.getTypeChecker();
|
||||
// If the request is a continuation of an earlier `isIncomplete` response,
|
||||
// we can continue it from the cached previous response.
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const incompleteCompletionsCache = preferences.allowIncompleteCompletions ? host.getIncompleteCompletionsCache?.() : undefined;
|
||||
if (incompleteCompletionsCache && completionKind === CompletionTriggerKind.TriggerForIncompleteCompletions && previousToken && isIdentifier(previousToken)) {
|
||||
const incompleteContinuation = continuePreviousIncompleteResponse(incompleteCompletionsCache, sourceFile, previousToken, program, host, preferences, cancellationToken, position);
|
||||
@@ -707,10 +716,26 @@ export function getCompletionsAtPosition(
|
||||
return response;
|
||||
case CompletionDataKind.JsDocTagName:
|
||||
// If the current position is a jsDoc tag name, only tag names should be provided for completion
|
||||
return jsdocCompletionInfo(JsDoc.getJSDocTagNameCompletions());
|
||||
return jsdocCompletionInfo([
|
||||
...JsDoc.getJSDocTagNameCompletions(),
|
||||
...getJSDocParameterCompletions(
|
||||
sourceFile,
|
||||
position,
|
||||
checker,
|
||||
compilerOptions,
|
||||
preferences,
|
||||
/*tagNameOnly*/ true)]);
|
||||
case CompletionDataKind.JsDocTag:
|
||||
// If the current position is a jsDoc tag, only tags should be provided for completion
|
||||
return jsdocCompletionInfo(JsDoc.getJSDocTagCompletions());
|
||||
return jsdocCompletionInfo([
|
||||
...JsDoc.getJSDocTagCompletions(),
|
||||
...getJSDocParameterCompletions(
|
||||
sourceFile,
|
||||
position,
|
||||
checker,
|
||||
compilerOptions,
|
||||
preferences,
|
||||
/*tagNameOnly*/ false)]);
|
||||
case CompletionDataKind.JsDocParameterName:
|
||||
return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag));
|
||||
case CompletionDataKind.Keywords:
|
||||
@@ -827,6 +852,301 @@ function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo {
|
||||
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
|
||||
}
|
||||
|
||||
function getJSDocParameterCompletions(
|
||||
sourceFile: SourceFile,
|
||||
position: number,
|
||||
checker: TypeChecker,
|
||||
options: CompilerOptions,
|
||||
preferences: UserPreferences,
|
||||
tagNameOnly: boolean): CompletionEntry[] {
|
||||
const currentToken = getTokenAtPosition(sourceFile, position);
|
||||
if (!isJSDocTag(currentToken) && !isJSDoc(currentToken)) {
|
||||
return [];
|
||||
}
|
||||
const jsDoc = isJSDoc(currentToken) ? currentToken : currentToken.parent;
|
||||
if (!isJSDoc(jsDoc)) {
|
||||
return [];
|
||||
}
|
||||
const func = jsDoc.parent;
|
||||
if (!isFunctionLike(func)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const isJs = isSourceFileJS(sourceFile);
|
||||
const isSnippet = preferences.includeCompletionsWithSnippetText || undefined;
|
||||
const paramTagCount = countWhere(jsDoc.tags, tag => isJSDocParameterTag(tag) && tag.getEnd() <= position);
|
||||
return mapDefined(func.parameters, param => {
|
||||
if (getJSDocParameterTags(param).length) {
|
||||
return undefined; // Parameter is already annotated.
|
||||
}
|
||||
if (isIdentifier(param.name)) { // Named parameter
|
||||
const tabstopCounter = { tabstop: 1 };
|
||||
const paramName = param.name.text;
|
||||
let displayText =
|
||||
getJSDocParamAnnotation(
|
||||
paramName,
|
||||
param.initializer,
|
||||
param.dotDotDotToken,
|
||||
isJs,
|
||||
/*isObject*/ false,
|
||||
/*isSnippet*/ false,
|
||||
checker,
|
||||
options,
|
||||
preferences);
|
||||
let snippetText = isSnippet
|
||||
? getJSDocParamAnnotation(
|
||||
paramName,
|
||||
param.initializer,
|
||||
param.dotDotDotToken,
|
||||
isJs,
|
||||
/*isObject*/ false,
|
||||
/*isSnippet*/ true,
|
||||
checker,
|
||||
options,
|
||||
preferences,
|
||||
tabstopCounter)
|
||||
: undefined;
|
||||
if (tagNameOnly) { // Remove `@`
|
||||
displayText = displayText.slice(1);
|
||||
if (snippetText) snippetText = snippetText.slice(1);
|
||||
}
|
||||
return {
|
||||
name: displayText,
|
||||
kind: ScriptElementKind.parameterElement,
|
||||
sortText: SortText.LocationPriority,
|
||||
insertText: isSnippet ? snippetText : undefined,
|
||||
isSnippet,
|
||||
};
|
||||
}
|
||||
else if (param.parent.parameters.indexOf(param) === paramTagCount) { // Destructuring parameter; do it positionally
|
||||
const paramPath = `param${paramTagCount}`;
|
||||
const displayTextResult =
|
||||
generateJSDocParamTagsForDestructuring(
|
||||
paramPath,
|
||||
param.name,
|
||||
param.initializer,
|
||||
param.dotDotDotToken,
|
||||
isJs,
|
||||
/*isSnippet*/ false,
|
||||
checker,
|
||||
options,
|
||||
preferences,);
|
||||
const snippetTextResult = isSnippet
|
||||
? generateJSDocParamTagsForDestructuring(
|
||||
paramPath,
|
||||
param.name,
|
||||
param.initializer,
|
||||
param.dotDotDotToken,
|
||||
isJs,
|
||||
/*isSnippet*/ true,
|
||||
checker,
|
||||
options,
|
||||
preferences,)
|
||||
: undefined;
|
||||
let displayText = displayTextResult.join(getNewLineCharacter(options) + "* ");
|
||||
let snippetText = snippetTextResult?.join(getNewLineCharacter(options) + "* ");
|
||||
if (tagNameOnly) { // Remove `@`
|
||||
displayText = displayText.slice(1);
|
||||
if (snippetText) snippetText = snippetText.slice(1);
|
||||
}
|
||||
return {
|
||||
name: displayText,
|
||||
kind: ScriptElementKind.parameterElement,
|
||||
sortText: SortText.LocationPriority,
|
||||
insertText: isSnippet ? snippetText : undefined,
|
||||
isSnippet,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function generateJSDocParamTagsForDestructuring(
|
||||
path: string,
|
||||
pattern: BindingPattern,
|
||||
initializer: Expression | undefined,
|
||||
dotDotDotToken: DotDotDotToken | undefined,
|
||||
isJs: boolean,
|
||||
isSnippet: boolean,
|
||||
checker: TypeChecker,
|
||||
options: CompilerOptions,
|
||||
preferences: UserPreferences): string[] {
|
||||
if (!isJs) {
|
||||
return [
|
||||
getJSDocParamAnnotation(
|
||||
path,
|
||||
initializer,
|
||||
dotDotDotToken,
|
||||
isJs,
|
||||
/*isObject*/ false,
|
||||
isSnippet,
|
||||
checker,
|
||||
options,
|
||||
preferences,
|
||||
{ tabstop: 1 })
|
||||
];
|
||||
}
|
||||
return patternWorker(path, pattern, initializer, dotDotDotToken, { tabstop: 1 });
|
||||
|
||||
function patternWorker(
|
||||
path: string,
|
||||
pattern: BindingPattern,
|
||||
initializer: Expression | undefined,
|
||||
dotDotDotToken: DotDotDotToken | undefined,
|
||||
counter: TabStopCounter): string[] {
|
||||
if (isObjectBindingPattern(pattern) && !dotDotDotToken) {
|
||||
const oldTabstop = counter.tabstop;
|
||||
const childCounter = { tabstop: oldTabstop };
|
||||
const rootParam =
|
||||
getJSDocParamAnnotation(
|
||||
path,
|
||||
initializer,
|
||||
dotDotDotToken,
|
||||
isJs,
|
||||
/*isObject*/ true,
|
||||
isSnippet,
|
||||
checker,
|
||||
options,
|
||||
preferences,
|
||||
childCounter);
|
||||
let childTags: string[] | undefined = [];
|
||||
for (const element of pattern.elements) {
|
||||
const elementTags = elementWorker(path, element, childCounter);
|
||||
if (!elementTags) {
|
||||
childTags = undefined;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
childTags.push(...elementTags);
|
||||
}
|
||||
}
|
||||
if (childTags) {
|
||||
counter.tabstop = childCounter.tabstop;
|
||||
return [rootParam, ...childTags];
|
||||
}
|
||||
}
|
||||
return [
|
||||
getJSDocParamAnnotation(
|
||||
path,
|
||||
initializer,
|
||||
dotDotDotToken,
|
||||
isJs,
|
||||
/*isObject*/ false,
|
||||
isSnippet,
|
||||
checker,
|
||||
options,
|
||||
preferences,
|
||||
counter)
|
||||
];
|
||||
}
|
||||
|
||||
// Assumes binding element is inside object binding pattern.
|
||||
// We can't really deeply annotate an array binding pattern.
|
||||
function elementWorker(path: string, element: BindingElement, counter: TabStopCounter): string[] | undefined {
|
||||
if ((!element.propertyName && isIdentifier(element.name)) || isIdentifier(element.name)) { // `{ b }` or `{ b: newB }`
|
||||
const propertyName = element.propertyName ? tryGetTextOfPropertyName(element.propertyName) : element.name.text;
|
||||
if (!propertyName) {
|
||||
return undefined;
|
||||
}
|
||||
const paramName = `${path}.${propertyName}`;
|
||||
return [
|
||||
getJSDocParamAnnotation(
|
||||
paramName,
|
||||
element.initializer,
|
||||
element.dotDotDotToken,
|
||||
isJs,
|
||||
/*isObject*/ false,
|
||||
isSnippet,
|
||||
checker,
|
||||
options,
|
||||
preferences,
|
||||
counter)];
|
||||
}
|
||||
else if (element.propertyName) { // `{ b: {...} }` or `{ b: [...] }`
|
||||
const propertyName = tryGetTextOfPropertyName(element.propertyName);
|
||||
return propertyName
|
||||
&& patternWorker(`${path}.${propertyName}`, element.name, element.initializer, element.dotDotDotToken, counter);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
interface TabStopCounter {
|
||||
tabstop: number;
|
||||
}
|
||||
|
||||
function getJSDocParamAnnotation(
|
||||
paramName: string,
|
||||
initializer: Expression | undefined,
|
||||
dotDotDotToken: DotDotDotToken | undefined,
|
||||
isJs: boolean,
|
||||
isObject: boolean,
|
||||
isSnippet: boolean,
|
||||
checker: TypeChecker,
|
||||
options: CompilerOptions,
|
||||
preferences: UserPreferences,
|
||||
tabstopCounter?: TabStopCounter) {
|
||||
if (isSnippet) {
|
||||
Debug.assertIsDefined(tabstopCounter);
|
||||
}
|
||||
if (initializer) {
|
||||
paramName = getJSDocParamNameWithInitializer(paramName, initializer);
|
||||
}
|
||||
if (isSnippet) {
|
||||
paramName = escapeSnippetText(paramName);
|
||||
}
|
||||
if (isJs) {
|
||||
let type = "*";
|
||||
if (isObject) {
|
||||
Debug.assert(!dotDotDotToken, `Cannot annotate a rest parameter with type 'Object'.`);
|
||||
type = "Object";
|
||||
}
|
||||
else {
|
||||
if (initializer) {
|
||||
const inferredType = checker.getTypeAtLocation(initializer.parent);
|
||||
if (!(inferredType.flags & (TypeFlags.Any | TypeFlags.Void))) {
|
||||
const sourceFile = initializer.getSourceFile();
|
||||
const quotePreference = getQuotePreference(sourceFile, preferences);
|
||||
const builderFlags = (quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : NodeBuilderFlags.None);
|
||||
const typeNode = checker.typeToTypeNode(inferredType, findAncestor(initializer, isFunctionLike), builderFlags);
|
||||
if (typeNode) {
|
||||
const printer = isSnippet
|
||||
? createSnippetPrinter({
|
||||
removeComments: true,
|
||||
module: options.module,
|
||||
target: options.target,
|
||||
})
|
||||
: createPrinter({
|
||||
removeComments: true,
|
||||
module: options.module,
|
||||
target: options.target
|
||||
});
|
||||
setEmitFlags(typeNode, EmitFlags.SingleLine);
|
||||
type = printer.printNode(EmitHint.Unspecified, typeNode, sourceFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isSnippet && type === "*") {
|
||||
type = `\${${tabstopCounter!.tabstop++}:${type}}`;
|
||||
}
|
||||
}
|
||||
const dotDotDot = !isObject && dotDotDotToken ? "..." : "";
|
||||
const description = isSnippet ? `\${${tabstopCounter!.tabstop++}}` : "";
|
||||
return `@param {${dotDotDot}${type}} ${paramName} ${description}`;
|
||||
}
|
||||
else {
|
||||
const description = isSnippet ? `\${${tabstopCounter!.tabstop++}}` : "";
|
||||
return `@param ${paramName} ${description}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getJSDocParamNameWithInitializer(paramName: string, initializer: Expression): string {
|
||||
const initializerText = initializer.getText().trim();
|
||||
if (initializerText.includes("\n") || initializerText.length > 80) {
|
||||
return `[${paramName}]`;
|
||||
}
|
||||
return `[${paramName}=${initializerText}]`;
|
||||
}
|
||||
|
||||
function keywordToCompletionEntry(keyword: TokenSyntaxKind) {
|
||||
return {
|
||||
name: tokenToString(keyword)!,
|
||||
|
||||
20473
tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline
Normal file
20473
tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
127
tests/cases/fourslash/jsdocParameterTagSnippetCompletion1.ts
Normal file
127
tests/cases/fourslash/jsdocParameterTagSnippetCompletion1.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
///<reference path="fourslash.ts" />
|
||||
|
||||
// @allowJs: true
|
||||
|
||||
// @Filename: a.ts
|
||||
//// /**
|
||||
//// * @para/*0*/
|
||||
//// */
|
||||
//// function printValue(value, maximumFractionDigits) {}
|
||||
////
|
||||
//// /**
|
||||
//// * @p/*a*/
|
||||
//// */
|
||||
//// function aa({ a = 1 }, b: string) {
|
||||
//// a;
|
||||
//// }
|
||||
////
|
||||
//// /**
|
||||
//// * /*b*/
|
||||
//// */
|
||||
//// function bb(b: string) {}
|
||||
////
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*c*/
|
||||
//// */
|
||||
//// function cc({ b: { a, c } = { a: 1, c: 3 } }) {
|
||||
////
|
||||
//// }
|
||||
////
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*d*/
|
||||
//// */
|
||||
//// function dd({ a: { b, c }, d: [e, f] }: { a: { b: number, c: number }, d: [string, string] }) {
|
||||
////
|
||||
//// }
|
||||
|
||||
// @Filename: b.js
|
||||
//// /**
|
||||
//// * @p/*ja*/
|
||||
//// */
|
||||
//// function aa({ a = 1 }, b) {
|
||||
//// a;
|
||||
//// }
|
||||
////
|
||||
//// /**
|
||||
//// * /*jb*/
|
||||
//// */
|
||||
//// function bb(b) {}
|
||||
////
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*jc*/
|
||||
//// */
|
||||
//// function cc({ b: { a, c } = { a: 1, c: 3 } }) {
|
||||
////
|
||||
//// }
|
||||
////
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*jd*/
|
||||
//// */
|
||||
//// function dd({ a: { b, c }, d: [e, f] }) {
|
||||
////
|
||||
//// }
|
||||
////
|
||||
//// const someconst = "aa";
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*je*/
|
||||
//// */
|
||||
//// function ee({ [someconst]: b }) {
|
||||
////
|
||||
//// }
|
||||
////
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*jf*/
|
||||
//// */
|
||||
//// function ff({ "a": b }) {
|
||||
////
|
||||
//// }
|
||||
////
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*jg*/
|
||||
//// */
|
||||
//// function gg(a, { b }) {
|
||||
////
|
||||
//// }
|
||||
////
|
||||
//// /**
|
||||
//// *
|
||||
//// * @param {boolean} a a's description
|
||||
//// * @p/*jh*/
|
||||
//// */
|
||||
//// function hh(a, { b }) {
|
||||
////
|
||||
//// }
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*ji*/
|
||||
//// */
|
||||
//// function ii({ b, ...c }, ...a) {}
|
||||
////
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*jj*/
|
||||
//// */
|
||||
//// function jj(...{ length }) {}
|
||||
////
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*jk*/
|
||||
//// */
|
||||
//// function kk(...a) {}
|
||||
////
|
||||
//// function reallylongfunctionnameabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl(a) {}
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*jl*/
|
||||
//// */
|
||||
//// function ll(a = reallylongfunctionnameabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl("")) {}
|
||||
////}
|
||||
|
||||
verify.baselineCompletions();
|
||||
39
tests/cases/fourslash/jsdocParameterTagSnippetCompletion2.ts
Normal file
39
tests/cases/fourslash/jsdocParameterTagSnippetCompletion2.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
///<reference path="fourslash.ts" />
|
||||
|
||||
// @allowJs: true
|
||||
|
||||
// @Filename: a.ts
|
||||
//// /**
|
||||
//// * /*b*/
|
||||
//// */
|
||||
//// function bb(b: string) {}
|
||||
|
||||
// @Filename: b.js
|
||||
//// /**
|
||||
//// * /*jb*/
|
||||
//// */
|
||||
//// function bb(b) {}
|
||||
////
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*jc*/
|
||||
//// */
|
||||
//// function cc({ b: { a, c } = { a: 1, c: 3 } }) {
|
||||
////
|
||||
//// }
|
||||
////
|
||||
//// /**
|
||||
//// *
|
||||
//// * @p/*jd*/
|
||||
//// */
|
||||
//// function dd(...a) {}
|
||||
////
|
||||
//// /**
|
||||
//// * @p/*z*/
|
||||
//// */
|
||||
//// function zz(a = 3) {}
|
||||
|
||||
|
||||
verify.baselineCompletions({
|
||||
includeCompletionsWithSnippetText: true,
|
||||
});
|
||||
39
tests/cases/fourslash/jsdocParameterTagSnippetCompletion3.ts
Normal file
39
tests/cases/fourslash/jsdocParameterTagSnippetCompletion3.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
///<reference path="fourslash.ts" />
|
||||
|
||||
// Infer types from initializer
|
||||
|
||||
// @allowJs: true
|
||||
|
||||
// @Filename: a.js
|
||||
//// /**
|
||||
//// * @p/*z*/
|
||||
//// */
|
||||
//// function zz(a = 3) {}
|
||||
|
||||
//// /**
|
||||
//// * @p/*y*/
|
||||
//// */
|
||||
//// function yy({ a = 3 }) {}
|
||||
|
||||
//// /**
|
||||
//// * @p/*x*/
|
||||
//// */
|
||||
//// function xx({ a, o: { b, c: [d, e = 1] }}) {}
|
||||
|
||||
//// /**
|
||||
//// * @p/*w*/
|
||||
//// */
|
||||
//// function ww({ a, o: { b, c: [d, e] = [1, true] }}) {}
|
||||
|
||||
//// /**
|
||||
//// * @p/*v*/
|
||||
//// */
|
||||
//// function vv({ a = [1, true] }) {}
|
||||
|
||||
//// function random(a) { return a }
|
||||
//// /**
|
||||
//// * @p/*u*/
|
||||
//// */
|
||||
//// function uu({ a = random() }) {}
|
||||
|
||||
verify.baselineCompletions();
|
||||
Reference in New Issue
Block a user