/* @internal */ namespace ts.InlayHints { const maxHintsLength = 30; const leadingParameterNameCommentRegexFactory = (name: string) => { return new RegExp(`^\\s?/\\*\\*?\\s?${name}\\s?\\*\\/\\s?$`); }; function shouldShowParameterNameHints(preferences: UserPreferences) { return preferences.includeInlayParameterNameHints === "literals" || preferences.includeInlayParameterNameHints === "all"; } function shouldShowLiteralParameterNameHintsOnly(preferences: UserPreferences) { return preferences.includeInlayParameterNameHints === "literals"; } export function provideInlayHints(context: InlayHintsContext): InlayHint[] { const { file, program, span, cancellationToken, preferences } = context; const sourceFileText = file.text; const compilerOptions = program.getCompilerOptions(); const checker = program.getTypeChecker(); const result: InlayHint[] = []; visitor(file); return result; function visitor(node: Node): true | undefined { if (!node || node.getFullWidth() === 0) { return; } switch (node.kind) { case SyntaxKind.ModuleDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.MethodDeclaration: case SyntaxKind.ArrowFunction: cancellationToken.throwIfCancellationRequested(); } if (!textSpanIntersectsWith(span, node.pos, node.getFullWidth())) { return; } if (isTypeNode(node) && !isExpressionWithTypeArguments(node)) { return; } if (preferences.includeInlayVariableTypeHints && isVariableDeclaration(node)) { visitVariableLikeDeclaration(node); } else if (preferences.includeInlayPropertyDeclarationTypeHints && isPropertyDeclaration(node)) { visitVariableLikeDeclaration(node); } else if (preferences.includeInlayEnumMemberValueHints && isEnumMember(node)) { visitEnumMember(node); } else if (shouldShowParameterNameHints(preferences) && (isCallExpression(node) || isNewExpression(node))) { visitCallOrNewExpression(node); } else { if (preferences.includeInlayFunctionParameterTypeHints && isFunctionLikeDeclaration(node) && hasContextSensitiveParameters(node)) { visitFunctionLikeForParameterType(node); } if (preferences.includeInlayFunctionLikeReturnTypeHints && isSignatureSupportingReturnAnnotation(node)) { visitFunctionDeclarationLikeForReturnType(node); } } return forEachChild(node, visitor); } function isSignatureSupportingReturnAnnotation(node: Node): node is FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration { return isArrowFunction(node) || isFunctionExpression(node) || isFunctionDeclaration(node) || isMethodDeclaration(node) || isGetAccessorDeclaration(node); } function addParameterHints(text: string, position: number, isFirstVariadicArgument: boolean) { result.push({ text: `${isFirstVariadicArgument ? "..." : ""}${truncation(text, maxHintsLength)}:`, position, kind: InlayHintKind.Parameter, whitespaceAfter: true, }); } function addTypeHints(text: string, position: number) { result.push({ text: `: ${truncation(text, maxHintsLength)}`, position, kind: InlayHintKind.Type, whitespaceBefore: true, }); } function addEnumMemberValueHints(text: string, position: number) { result.push({ text: `= ${truncation(text, maxHintsLength)}`, position, kind: InlayHintKind.Enum, whitespaceBefore: true, }); } function visitEnumMember(member: EnumMember) { if (member.initializer) { return; } const enumValue = checker.getConstantValue(member); if (enumValue !== undefined) { addEnumMemberValueHints(enumValue.toString(), member.end); } } function isModuleReferenceType(type: Type) { return type.symbol && (type.symbol.flags & SymbolFlags.Module); } function visitVariableLikeDeclaration(decl: VariableDeclaration | PropertyDeclaration) { if (!decl.initializer || isBindingPattern(decl.name) || isVariableDeclaration(decl) && !isHintableDeclaration(decl)) { return; } const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(decl); if (effectiveTypeAnnotation) { return; } const declarationType = checker.getTypeAtLocation(decl); if (isModuleReferenceType(declarationType)) { return; } const typeDisplayString = printTypeInSingleLine(declarationType); if (typeDisplayString) { const isVariableNameMatchesType = preferences.includeInlayVariableTypeHintsWhenTypeMatchesName === false && equateStringsCaseInsensitive(decl.name.getText(), typeDisplayString); if (isVariableNameMatchesType) { return; } addTypeHints(typeDisplayString, decl.name.end); } } function visitCallOrNewExpression(expr: CallExpression | NewExpression) { const args = expr.arguments; if (!args || !args.length) { return; } const candidates: Signature[] = []; const signature = checker.getResolvedSignatureForSignatureHelp(expr, candidates); if (!signature || !candidates.length) { return; } for (let i = 0; i < args.length; ++i) { const originalArg = args[i]; const arg = skipParentheses(originalArg); if (shouldShowLiteralParameterNameHintsOnly(preferences) && !isHintableLiteral(arg)) { continue; } const identifierNameInfo = checker.getParameterIdentifierNameAtPosition(signature, i); if (identifierNameInfo) { const [parameterName, isFirstVariadicArgument] = identifierNameInfo; const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName); if (!isParameterNameNotSameAsArgument && !isFirstVariadicArgument) { continue; } const name = unescapeLeadingUnderscores(parameterName); if (leadingCommentsContainsParameterName(arg, name)) { continue; } addParameterHints(name, originalArg.getStart(), isFirstVariadicArgument); } } } function identifierOrAccessExpressionPostfixMatchesParameterName(expr: Expression, parameterName: __String) { if (isIdentifier(expr)) { return expr.text === parameterName; } if (isPropertyAccessExpression(expr)) { return expr.name.text === parameterName; } return false; } function leadingCommentsContainsParameterName(node: Node, name: string) { if (!isIdentifierText(name, compilerOptions.target, getLanguageVariant(file.scriptKind))) { return false; } const ranges = getLeadingCommentRanges(sourceFileText, node.pos); if (!ranges?.length) { return false; } const regex = leadingParameterNameCommentRegexFactory(name); return some(ranges, range => regex.test(sourceFileText.substring(range.pos, range.end))); } function isHintableLiteral(node: Node) { switch (node.kind) { case SyntaxKind.PrefixUnaryExpression: { const operand = (node as PrefixUnaryExpression).operand; return isLiteralExpression(operand) || isIdentifier(operand) && isInfinityOrNaNString(operand.escapedText); } case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.NullKeyword: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TemplateExpression: return true; case SyntaxKind.Identifier: { const name = (node as Identifier).escapedText; return isUndefined(name) || isInfinityOrNaNString(name); } } return isLiteralExpression(node); } function visitFunctionDeclarationLikeForReturnType(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { if (isArrowFunction(decl)) { if (!findChildOfKind(decl, SyntaxKind.OpenParenToken, file)) { return; } } const effectiveTypeAnnotation = getEffectiveReturnTypeNode(decl); if (effectiveTypeAnnotation || !decl.body) { return; } const signature = checker.getSignatureFromDeclaration(decl); if (!signature) { return; } const returnType = checker.getReturnTypeOfSignature(signature); if (isModuleReferenceType(returnType)) { return; } const typeDisplayString = printTypeInSingleLine(returnType); if (!typeDisplayString) { return; } addTypeHints(typeDisplayString, getTypeAnnotationPosition(decl)); } function getTypeAnnotationPosition(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { const closeParenToken = findChildOfKind(decl, SyntaxKind.CloseParenToken, file); if (closeParenToken) { return closeParenToken.end; } return decl.parameters.end; } function visitFunctionLikeForParameterType(node: FunctionLikeDeclaration) { const signature = checker.getSignatureFromDeclaration(node); if (!signature) { return; } for (let i = 0; i < node.parameters.length && i < signature.parameters.length; ++i) { const param = node.parameters[i]; if (!isHintableDeclaration(param)) { continue; } const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(param); if (effectiveTypeAnnotation) { continue; } const typeDisplayString = getParameterDeclarationTypeDisplayString(signature.parameters[i]); if (!typeDisplayString) { continue; } addTypeHints(typeDisplayString, param.questionToken ? param.questionToken.end : param.name.end); } } function getParameterDeclarationTypeDisplayString(symbol: Symbol) { const valueDeclaration = symbol.valueDeclaration; if (!valueDeclaration || !isParameter(valueDeclaration)) { return undefined; } const signatureParamType = checker.getTypeOfSymbolAtLocation(symbol, valueDeclaration); if (isModuleReferenceType(signatureParamType)) { return undefined; } return printTypeInSingleLine(signatureParamType); } function truncation(text: string, maxLength: number) { if (text.length > maxLength) { return text.substr(0, maxLength - "...".length) + "..."; } return text; } function printTypeInSingleLine(type: Type) { const flags = NodeBuilderFlags.IgnoreErrors | TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope; const options: PrinterOptions = { removeComments: true }; const printer = createPrinter(options); return usingSingleLineStringWriter(writer => { const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags, writer); Debug.assertIsDefined(typeNode, "should always get typenode"); printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ file, writer); }); } function isUndefined(name: __String) { return name === "undefined"; } function isHintableDeclaration(node: VariableDeclaration | ParameterDeclaration) { if ((isParameterDeclaration(node) || isVariableDeclaration(node) && isVarConst(node)) && node.initializer) { const initializer = skipParentheses(node.initializer); return !(isHintableLiteral(initializer) || isNewExpression(initializer) || isObjectLiteralExpression(initializer) || isAssertionExpression(initializer)); } return true; } } }