1import { 2 __String, ArrowFunction, CallExpression, createPrinterWithRemoveComments, Debug, EmitHint, EnumMember, equateStringsCaseInsensitive, 3 Expression, findChildOfKind, forEachChild, FunctionDeclaration, FunctionExpression, FunctionLikeDeclaration, 4 GetAccessorDeclaration, getEffectiveReturnTypeNode, getEffectiveTypeAnnotationNode, getLanguageVariant, 5 getLeadingCommentRanges, hasContextSensitiveParameters, Identifier, InlayHint, InlayHintKind, InlayHintsContext, 6 isArrowFunction, isAssertionExpression, isBindingPattern, isCallExpression, isEnumMember, 7 isExpressionWithTypeArguments, isFunctionDeclaration, isFunctionExpression, isFunctionLikeDeclaration, 8 isGetAccessorDeclaration, isIdentifier, isIdentifierText, isInfinityOrNaNString, isLiteralExpression, 9 isMethodDeclaration, isNewExpression, isObjectLiteralExpression, isParameter, isParameterDeclaration, 10 isPropertyAccessExpression, isPropertyDeclaration, isTypeNode, isVarConst, isVariableDeclaration, MethodDeclaration, 11 NewExpression, Node, NodeBuilderFlags, ParameterDeclaration, PrefixUnaryExpression, 12 PropertyDeclaration, Signature, skipParentheses, some, Symbol, SymbolFlags, SyntaxKind, textSpanIntersectsWith, 13 Type, TypeFormatFlags, unescapeLeadingUnderscores, UserPreferences, usingSingleLineStringWriter, 14 VariableDeclaration, 15} from "./_namespaces/ts"; 16 17const maxHintsLength = 30; 18 19const leadingParameterNameCommentRegexFactory = (name: string) => { 20 return new RegExp(`^\\s?/\\*\\*?\\s?${name}\\s?\\*\\/\\s?$`); 21}; 22 23function shouldShowParameterNameHints(preferences: UserPreferences) { 24 return preferences.includeInlayParameterNameHints === "literals" || preferences.includeInlayParameterNameHints === "all"; 25} 26 27function shouldShowLiteralParameterNameHintsOnly(preferences: UserPreferences) { 28 return preferences.includeInlayParameterNameHints === "literals"; 29} 30 31/** @internal */ 32export function provideInlayHints(context: InlayHintsContext): InlayHint[] { 33 const { file, program, span, cancellationToken, preferences } = context; 34 const sourceFileText = file.text; 35 const compilerOptions = program.getCompilerOptions(); 36 37 const checker = program.getTypeChecker(); 38 const result: InlayHint[] = []; 39 40 visitor(file); 41 return result; 42 43 function visitor(node: Node): true | undefined { 44 if (!node || node.getFullWidth() === 0) { 45 return; 46 } 47 48 switch (node.kind) { 49 case SyntaxKind.ModuleDeclaration: 50 case SyntaxKind.ClassDeclaration: 51 case SyntaxKind.InterfaceDeclaration: 52 case SyntaxKind.FunctionDeclaration: 53 case SyntaxKind.ClassExpression: 54 case SyntaxKind.FunctionExpression: 55 case SyntaxKind.MethodDeclaration: 56 case SyntaxKind.ArrowFunction: 57 cancellationToken.throwIfCancellationRequested(); 58 } 59 60 if (!textSpanIntersectsWith(span, node.pos, node.getFullWidth())) { 61 return; 62 } 63 64 if (isTypeNode(node) && !isExpressionWithTypeArguments(node)) { 65 return; 66 } 67 68 if (preferences.includeInlayVariableTypeHints && isVariableDeclaration(node)) { 69 visitVariableLikeDeclaration(node); 70 } 71 else if (preferences.includeInlayPropertyDeclarationTypeHints && isPropertyDeclaration(node)) { 72 visitVariableLikeDeclaration(node); 73 } 74 else if (preferences.includeInlayEnumMemberValueHints && isEnumMember(node)) { 75 visitEnumMember(node); 76 } 77 else if (shouldShowParameterNameHints(preferences) && (isCallExpression(node) || isNewExpression(node))) { 78 visitCallOrNewExpression(node); 79 } 80 else { 81 if (preferences.includeInlayFunctionParameterTypeHints && isFunctionLikeDeclaration(node) && hasContextSensitiveParameters(node)) { 82 visitFunctionLikeForParameterType(node); 83 } 84 if (preferences.includeInlayFunctionLikeReturnTypeHints && isSignatureSupportingReturnAnnotation(node)) { 85 visitFunctionDeclarationLikeForReturnType(node); 86 } 87 } 88 return forEachChild(node, visitor); 89 } 90 91 function isSignatureSupportingReturnAnnotation(node: Node): node is FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration { 92 return isArrowFunction(node) || isFunctionExpression(node) || isFunctionDeclaration(node) || isMethodDeclaration(node) || isGetAccessorDeclaration(node); 93 } 94 95 function addParameterHints(text: string, position: number, isFirstVariadicArgument: boolean) { 96 result.push({ 97 text: `${isFirstVariadicArgument ? "..." : ""}${truncation(text, maxHintsLength)}:`, 98 position, 99 kind: InlayHintKind.Parameter, 100 whitespaceAfter: true, 101 }); 102 } 103 104 function addTypeHints(text: string, position: number) { 105 result.push({ 106 text: `: ${truncation(text, maxHintsLength)}`, 107 position, 108 kind: InlayHintKind.Type, 109 whitespaceBefore: true, 110 }); 111 } 112 113 function addEnumMemberValueHints(text: string, position: number) { 114 result.push({ 115 text: `= ${truncation(text, maxHintsLength)}`, 116 position, 117 kind: InlayHintKind.Enum, 118 whitespaceBefore: true, 119 }); 120 } 121 122 function visitEnumMember(member: EnumMember) { 123 if (member.initializer) { 124 return; 125 } 126 127 const enumValue = checker.getConstantValue(member); 128 if (enumValue !== undefined) { 129 addEnumMemberValueHints(enumValue.toString(), member.end); 130 } 131 } 132 133 function isModuleReferenceType(type: Type) { 134 return type.symbol && (type.symbol.flags & SymbolFlags.Module); 135 } 136 137 function visitVariableLikeDeclaration(decl: VariableDeclaration | PropertyDeclaration) { 138 if (!decl.initializer || isBindingPattern(decl.name) || isVariableDeclaration(decl) && !isHintableDeclaration(decl)) { 139 return; 140 } 141 142 const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(decl); 143 if (effectiveTypeAnnotation) { 144 return; 145 } 146 147 const declarationType = checker.getTypeAtLocation(decl); 148 if (isModuleReferenceType(declarationType)) { 149 return; 150 } 151 152 const typeDisplayString = printTypeInSingleLine(declarationType); 153 if (typeDisplayString) { 154 const isVariableNameMatchesType = preferences.includeInlayVariableTypeHintsWhenTypeMatchesName === false && equateStringsCaseInsensitive(decl.name.getText(), typeDisplayString); 155 if (isVariableNameMatchesType) { 156 return; 157 } 158 addTypeHints(typeDisplayString, decl.name.end); 159 } 160 } 161 162 function visitCallOrNewExpression(expr: CallExpression | NewExpression) { 163 const args = expr.arguments; 164 if (!args || !args.length) { 165 return; 166 } 167 168 const candidates: Signature[] = []; 169 const signature = checker.getResolvedSignatureForSignatureHelp(expr, candidates); 170 if (!signature || !candidates.length) { 171 return; 172 } 173 174 for (let i = 0; i < args.length; ++i) { 175 const originalArg = args[i]; 176 const arg = skipParentheses(originalArg); 177 if (shouldShowLiteralParameterNameHintsOnly(preferences) && !isHintableLiteral(arg)) { 178 continue; 179 } 180 181 const identifierNameInfo = checker.getParameterIdentifierNameAtPosition(signature, i); 182 if (identifierNameInfo) { 183 const [parameterName, isFirstVariadicArgument] = identifierNameInfo; 184 const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName); 185 if (!isParameterNameNotSameAsArgument && !isFirstVariadicArgument) { 186 continue; 187 } 188 189 const name = unescapeLeadingUnderscores(parameterName); 190 if (leadingCommentsContainsParameterName(arg, name)) { 191 continue; 192 } 193 194 addParameterHints(name, originalArg.getStart(), isFirstVariadicArgument); 195 } 196 } 197 } 198 199 function identifierOrAccessExpressionPostfixMatchesParameterName(expr: Expression, parameterName: __String) { 200 if (isIdentifier(expr)) { 201 return expr.text === parameterName; 202 } 203 if (isPropertyAccessExpression(expr)) { 204 return expr.name.text === parameterName; 205 } 206 return false; 207 } 208 209 function leadingCommentsContainsParameterName(node: Node, name: string) { 210 if (!isIdentifierText(name, compilerOptions.target, getLanguageVariant(file.scriptKind))) { 211 return false; 212 } 213 214 const ranges = getLeadingCommentRanges(sourceFileText, node.pos); 215 if (!ranges?.length) { 216 return false; 217 } 218 219 const regex = leadingParameterNameCommentRegexFactory(name); 220 return some(ranges, range => regex.test(sourceFileText.substring(range.pos, range.end))); 221 } 222 223 function isHintableLiteral(node: Node) { 224 switch (node.kind) { 225 case SyntaxKind.PrefixUnaryExpression: { 226 const operand = (node as PrefixUnaryExpression).operand; 227 return isLiteralExpression(operand) || isIdentifier(operand) && isInfinityOrNaNString(operand.escapedText); 228 } 229 case SyntaxKind.TrueKeyword: 230 case SyntaxKind.FalseKeyword: 231 case SyntaxKind.NullKeyword: 232 case SyntaxKind.NoSubstitutionTemplateLiteral: 233 case SyntaxKind.TemplateExpression: 234 return true; 235 case SyntaxKind.Identifier: { 236 const name = (node as Identifier).escapedText; 237 return isUndefined(name) || isInfinityOrNaNString(name); 238 } 239 } 240 return isLiteralExpression(node); 241 } 242 243 function visitFunctionDeclarationLikeForReturnType(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { 244 if (isArrowFunction(decl)) { 245 if (!findChildOfKind(decl, SyntaxKind.OpenParenToken, file)) { 246 return; 247 } 248 } 249 250 const effectiveTypeAnnotation = getEffectiveReturnTypeNode(decl); 251 if (effectiveTypeAnnotation || !decl.body) { 252 return; 253 } 254 255 const signature = checker.getSignatureFromDeclaration(decl); 256 if (!signature) { 257 return; 258 } 259 260 const returnType = checker.getReturnTypeOfSignature(signature); 261 if (isModuleReferenceType(returnType)) { 262 return; 263 } 264 265 const typeDisplayString = printTypeInSingleLine(returnType); 266 if (!typeDisplayString) { 267 return; 268 } 269 270 addTypeHints(typeDisplayString, getTypeAnnotationPosition(decl)); 271 } 272 273 function getTypeAnnotationPosition(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { 274 const closeParenToken = findChildOfKind(decl, SyntaxKind.CloseParenToken, file); 275 if (closeParenToken) { 276 return closeParenToken.end; 277 } 278 return decl.parameters.end; 279 } 280 281 function visitFunctionLikeForParameterType(node: FunctionLikeDeclaration) { 282 const signature = checker.getSignatureFromDeclaration(node); 283 if (!signature) { 284 return; 285 } 286 287 for (let i = 0; i < node.parameters.length && i < signature.parameters.length; ++i) { 288 const param = node.parameters[i]; 289 if (!isHintableDeclaration(param)) { 290 continue; 291 } 292 293 const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(param); 294 if (effectiveTypeAnnotation) { 295 continue; 296 } 297 298 const typeDisplayString = getParameterDeclarationTypeDisplayString(signature.parameters[i]); 299 if (!typeDisplayString) { 300 continue; 301 } 302 303 addTypeHints(typeDisplayString, param.questionToken ? param.questionToken.end : param.name.end); 304 } 305 } 306 307 function getParameterDeclarationTypeDisplayString(symbol: Symbol) { 308 const valueDeclaration = symbol.valueDeclaration; 309 if (!valueDeclaration || !isParameter(valueDeclaration)) { 310 return undefined; 311 } 312 313 const signatureParamType = checker.getTypeOfSymbolAtLocation(symbol, valueDeclaration); 314 if (isModuleReferenceType(signatureParamType)) { 315 return undefined; 316 } 317 318 return printTypeInSingleLine(signatureParamType); 319 } 320 321 function truncation(text: string, maxLength: number) { 322 if (text.length > maxLength) { 323 return text.substr(0, maxLength - "...".length) + "..."; 324 } 325 return text; 326 } 327 328 function printTypeInSingleLine(type: Type) { 329 const flags = NodeBuilderFlags.IgnoreErrors | TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope; 330 const printer = createPrinterWithRemoveComments(); 331 332 return usingSingleLineStringWriter(writer => { 333 const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags, writer); 334 Debug.assertIsDefined(typeNode, "should always get typenode"); 335 printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ file, writer); 336 }); 337 } 338 339 function isUndefined(name: __String) { 340 return name === "undefined"; 341 } 342 343 function isHintableDeclaration(node: VariableDeclaration | ParameterDeclaration) { 344 if ((isParameterDeclaration(node) || isVariableDeclaration(node) && isVarConst(node)) && node.initializer) { 345 const initializer = skipParentheses(node.initializer); 346 return !(isHintableLiteral(initializer) || isNewExpression(initializer) || isObjectLiteralExpression(initializer) || isAssertionExpression(initializer)); 347 } 348 return true; 349 } 350} 351