diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ff8f8ce454795..3d5ab88fd84d8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -427,6 +427,7 @@ namespace ts { return node ? getTypeFromTypeNode(node) : errorType; }, getParameterType: getTypeAtPosition, + getParameterIdentifierNameAtPosition, getPromisedTypeOfPromise, getAwaitedType: type => getAwaitedType(type), getReturnTypeOfSignature, @@ -30378,6 +30379,39 @@ namespace ts { return restParameter.escapedName; } + function getParameterIdentifierNameAtPosition(signature: Signature, pos: number): [parameterName: __String, isRestParameter: boolean] | undefined { + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + const param = signature.parameters[pos]; + return isParameterDeclarationWithIdentifierName(param) ? [param.escapedName, false] : undefined; + } + + const restParameter = signature.parameters[paramCount] || unknownSymbol; + if (!isParameterDeclarationWithIdentifierName(restParameter)) { + return undefined; + } + + const restType = getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; + const index = pos - paramCount; + const associatedName = associatedNames?.[index]; + const isRestTupleElement = !!associatedName?.dotDotDotToken; + return associatedName ? [ + getTupleElementLabel(associatedName), + isRestTupleElement + ] : undefined; + } + + if (pos === paramCount) { + return [restParameter.escapedName, true]; + } + return undefined; + } + + function isParameterDeclarationWithIdentifierName(symbol: Symbol) { + return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isIdentifier(symbol.valueDeclaration.name); + } function isValidDeclarationForTupleLabel(d: Declaration): d is NamedTupleMember | (ParameterDeclaration & { name: Identifier }) { return d.kind === SyntaxKind.NamedTupleMember || (isParameter(d) && d.name && isIdentifier(d.name)); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5e4a66dc61328..6acfbd2f895f5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4103,6 +4103,7 @@ namespace ts { * Returns `any` if the index is not valid. */ /* @internal */ getParameterType(signature: Signature, parameterIndex: number): Type; + /* @internal */ getParameterIdentifierNameAtPosition(signature: Signature, parameterIndex: number): [parameterName: __String, isRestParameter: boolean] | undefined; getNullableType(type: Type, flags: TypeFlags): Type; getNonNullableType(type: Type): Type; /* @internal */ getNonOptionalType(type: Type): Type; diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index b156908537953..86d3dace28976 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -1232,6 +1232,11 @@ namespace ts { return node && isFunctionLikeDeclarationKind(node.kind); } + /* @internal */ + export function isBooleanLiteral(node: Node): node is BooleanLiteral { + return node.kind === SyntaxKind.TrueKeyword || node.kind === SyntaxKind.FalseKeyword; + } + function isFunctionLikeDeclarationKind(kind: SyntaxKind): boolean { switch (kind) { case SyntaxKind.FunctionDeclaration: diff --git a/src/harness/client.ts b/src/harness/client.ts index 46c90b94b3c81..c72cfe681a725 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -644,6 +644,20 @@ namespace ts.server { applyCodeActionCommand = notImplemented; + provideInlayHints(file: string, span: TextSpan): InlayHint[] { + const { start, length } = span; + const args: protocol.InlayHintsRequestArgs = { file, start, length }; + + const request = this.processRequest(CommandNames.ProvideInlayHints, args); + const response = this.processResponse(request); + + return response.body!.map(item => ({ // TODO: GH#18217 + ...item, + kind: item.kind as InlayHintKind | undefined, + position: this.lineOffsetToPosition(file, item.position), + })); + } + private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs { return typeof positionOrRange === "number" ? this.createFileLocationRequestArgs(fileName, positionOrRange) diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 592fe8817541a..af75c48f0bae0 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -1,3 +1,4 @@ + namespace FourSlash { import ArrayOrSingle = FourSlashInterface.ArrayOrSingle; @@ -836,6 +837,22 @@ namespace FourSlash { }); } + public verifyInlayHints(expected: readonly FourSlashInterface.VerifyInlayHintsOptions[], span: ts.TextSpan = { start: 0, length: this.activeFile.content.length }, preference?: ts.InlayHintsOptions) { + const hints = this.languageService.provideInlayHints(this.activeFile.fileName, span, preference); + assert.equal(hints.length, expected.length, "Number of hints"); + + const sortHints = (a: ts.InlayHint, b: ts.InlayHint) => { + return a.position - b.position; + }; + ts.zipWith(hints.sort(sortHints), [...expected].sort(sortHints), (actual, expected) => { + assert.equal(actual.text, expected.text, "Text"); + assert.equal(actual.position, expected.position, "Position"); + assert.equal(actual.kind, expected.kind, "Kind"); + assert.equal(actual.whitespaceBefore, expected.whitespaceBefore, "whitespaceBefore"); + assert.equal(actual.whitespaceAfter, expected.whitespaceAfter, "whitespaceAfter"); + }); + } + public verifyCompletions(options: FourSlashInterface.VerifyCompletionsOptions) { if (options.marker === undefined) { this.verifyCompletionsWorker(options); diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 08c1c788f63c0..69ef6c9bc2553 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -251,6 +251,10 @@ namespace FourSlashInterface { } } + public getInlayHints(expected: readonly VerifyInlayHintsOptions[], span: ts.TextSpan, preference?: ts.InlayHintsOptions) { + this.state.verifyInlayHints(expected, span, preference); + } + public quickInfoIs(expectedText: string, expectedDocumentation?: string) { this.state.verifyQuickInfoString(expectedText, expectedDocumentation); } @@ -1667,6 +1671,14 @@ namespace FourSlashInterface { readonly containerKind?: ts.ScriptElementKind; } + export interface VerifyInlayHintsOptions { + text: string; + position: number; + kind?: ts.InlayHintKind; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; + } + export type ArrayOrSingle = T | readonly T[]; export interface VerifyCompletionListContainsOptions extends ts.UserPreferences { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 6b4dc207a3e33..d067e1c5addb8 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -599,6 +599,9 @@ namespace Harness.LanguageService { provideCallHierarchyOutgoingCalls(fileName: string, position: number) { return unwrapJSONCallResult(this.shim.provideCallHierarchyOutgoingCalls(fileName, position)); } + provideInlayHints(fileName: string, span: ts.TextSpan, preference: ts.InlayHintsOptions) { + return unwrapJSONCallResult(this.shim.provideInlayHints(fileName, span, preference)); + } getEmitOutput(fileName: string): ts.EmitOutput { return unwrapJSONCallResult(this.shim.getEmitOutput(fileName)); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 6f92ebfe7573e..350e0f5e22ae6 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -154,6 +154,7 @@ namespace ts.server.protocol { PrepareCallHierarchy = "prepareCallHierarchy", ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls", + ProvideInlayHints = "provideInlayHints" // NOTE: If updating this, be sure to also update `allCommandNames` in `testRunner/unittests/tsserver/session.ts`. } @@ -2549,6 +2550,40 @@ namespace ts.server.protocol { body?: SignatureHelpItems; } + export const enum InlayHintKind { + Type = "Type", + Parameter = "Parameter", + Enum = "Enum", + } + + export interface InlayHintsRequestArgs extends FileRequestArgs { + /** + * Start position of the span. + */ + start: number; + /** + * Length of the span. + */ + length: number; + } + + export interface InlayHintsRequest extends Request { + command: CommandTypes.ProvideInlayHints; + arguments: InlayHintsRequestArgs; + } + + export interface InlayHintItem { + text: string; + position: Location; + kind?: InlayHintKind; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; + } + + export interface InlayHintsResponse extends Response { + body?: InlayHintItem[]; + } + /** * Synchronous request for semantic diagnostics of one file. */ diff --git a/src/server/session.ts b/src/server/session.ts index ee2b7a243994c..241ae9647219f 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1449,6 +1449,17 @@ namespace ts.server { }); } + private provideInlayHints(args: protocol.InlayHintsRequestArgs) { + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; + const hints = languageService.provideInlayHints(file, args, this.getPreferences(file)); + + return hints.map(hint => ({ + ...hint, + position: scriptInfo.positionToLineOffset(hint.position), + })); + } + private setCompilerOptionsForInferredProjects(args: protocol.SetCompilerOptionsForInferredProjectsArgs): void { this.projectService.setCompilerOptionsForInferredProjects(args.options, args.projectRootPath); } @@ -2962,6 +2973,9 @@ namespace ts.server { [CommandNames.UncommentSelectionFull]: (request: protocol.UncommentSelectionRequest) => { return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ false)); }, + [CommandNames.ProvideInlayHints]: (request: protocol.InlayHintsRequest) => { + return this.requiredResponse(this.provideInlayHints(request.arguments)); + } })); public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) { diff --git a/src/services/inlayHints.ts b/src/services/inlayHints.ts new file mode 100644 index 0000000000000..d01574941a33a --- /dev/null +++ b/src/services/inlayHints.ts @@ -0,0 +1,305 @@ +/* @internal */ +namespace ts.InlayHints { + + const maxHintsLength = 30; + + const leadingParameterNameCommentRegexFactory = (name: string) => { + return new RegExp(`^\\s?/\\*\\*?\\s?${name}\\s?\\*\\/\\s?$`); + }; + + function shouldShowParameterNameHints(preferences: InlayHintsOptions) { + return preferences.includeInlayParameterNameHints === "literals" || preferences.includeInlayParameterNameHints === "all"; + } + + function shouldShowLiteralParameterNameHintsOnly(preferences: InlayHintsOptions) { + 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)) { + 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 && isFunctionExpressionLike(node)) { + visitFunctionExpressionLikeForParameterType(node); + } + if (preferences.includeInlayFunctionLikeReturnTypeHints && isFunctionDeclarationLike(node)) { + visitFunctionDeclarationLikeForReturnType(node); + } + } + return forEachChild(node, visitor); + } + + function isFunctionExpressionLike(node: Node): node is ArrowFunction | FunctionExpression { + return isArrowFunction(node) || isFunctionExpression(node); + } + + function isFunctionDeclarationLike(node: Node): node is FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration { + return isArrowFunction(node) || isFunctionExpression(node) || isFunctionDeclaration(node) || isMethodDeclaration(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) { + const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(decl); + if (effectiveTypeAnnotation || !decl.initializer) { + return; + } + + const declarationType = checker.getTypeAtLocation(decl); + if (isModuleReferenceType(declarationType)) { + return; + } + + const typeDisplayString = printTypeInSingleLine(declarationType); + if (typeDisplayString) { + 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) && !isHintableExpression(arg)) { + continue; + } + + const identifierNameInfo = checker.getParameterIdentifierNameAtPosition(signature, i); + if (identifierNameInfo) { + const [parameterName, isFirstVariadicArgument] = identifierNameInfo; + const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !isIdentifier(arg) || arg.text !== parameterName; + if (!isParameterNameNotSameAsArgument && !isFirstVariadicArgument) { + continue; + } + + const name = unescapeLeadingUnderscores(parameterName); + if (leadingCommentsContainsParameterName(arg, name)) { + continue; + } + + addParameterHints(name, originalArg.getStart(), isFirstVariadicArgument); + } + } + } + + 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 isHintableExpression(node: Node) { + return isLiteralExpression(node) || isBooleanLiteral(node) || isFunctionExpressionLike(node) || isObjectLiteralExpression(node) || isArrayLiteralExpression(node); + } + + function visitFunctionDeclarationLikeForReturnType(decl: ArrowFunction | FunctionExpression | MethodDeclaration | FunctionDeclaration) { + if (isArrowFunction(decl)) { + if (!findChildOfKind(decl, SyntaxKind.OpenParenToken, file)) { + return; + } + } + + const effectiveTypeAnnotation = getEffectiveReturnTypeNode(decl); + if (effectiveTypeAnnotation || !decl.body) { + return; + } + + const type = checker.getTypeAtLocation(decl); + const signatures = checker.getSignaturesOfType(type, SignatureKind.Call); + const signature = firstOrUndefined(signatures); + 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: ArrowFunction | FunctionExpression | MethodDeclaration | FunctionDeclaration) { + const closeParenToken = findChildOfKind(decl, SyntaxKind.CloseParenToken, file); + if (closeParenToken) { + return closeParenToken.end; + } + return decl.parameters.end; + } + + function visitFunctionExpressionLikeForParameterType(expr: ArrowFunction | FunctionExpression) { + if (!expr.parameters.length || expr.parameters.every(param => !!getEffectiveTypeAnnotationNode(param))) { + return; + } + + const contextualType = checker.getContextualType(expr); + if (!contextualType) { + return; + } + + const signatures = checker.getSignaturesOfType(contextualType, SignatureKind.Call); + const signature = firstOrUndefined(signatures); + if (!signature) { + return; + } + + for (let i = 0; i < expr.parameters.length && i < signature.parameters.length; ++i) { + const param = expr.parameters[i]; + const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(param); + + if (effectiveTypeAnnotation) { + continue; + } + + const typeDisplayString = getParameterDeclarationTypeDisplayString(signature.parameters[i]); + if (!typeDisplayString) { + continue; + } + + addTypeHints(typeDisplayString, param.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); + }); + } + } +} diff --git a/src/services/services.ts b/src/services/services.ts index 0d14cda15daf4..09d59e4cf465c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1188,6 +1188,7 @@ namespace ts { "prepareCallHierarchy", "provideCallHierarchyIncomingCalls", "provideCallHierarchyOutgoingCalls", + "provideInlayHints" ]; const invalidOperationsInSyntacticMode: readonly (keyof LanguageService)[] = [ @@ -2504,6 +2505,17 @@ namespace ts { }; } + function getInlayHintsContext(file: SourceFile, span: TextSpan, preferences: UserPreferences): InlayHintsContext { + return { + file, + program: getProgram()!, + host, + span, + preferences, + cancellationToken, + }; + } + function getSmartSelectionRange(fileName: string, position: number): SelectionRange { return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName)); } @@ -2558,6 +2570,12 @@ namespace ts { return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : []; } + function provideInlayHints(fileName: string, span: TextSpan, preferences: InlayHintsOptions = emptyOptions): InlayHint[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + return InlayHints.provideInlayHints(getInlayHintsContext(sourceFile, span, preferences)); + } + const ls: LanguageService = { dispose, cleanupSemanticCache, @@ -2623,6 +2641,7 @@ namespace ts { toggleMultilineComment, commentSelection, uncommentSelection, + provideInlayHints, }; switch (languageServiceMode) { diff --git a/src/services/shims.ts b/src/services/shims.ts index c6685e15084a9..41501f4995c6b 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -280,7 +280,7 @@ namespace ts { prepareCallHierarchy(fileName: string, position: number): string; provideCallHierarchyIncomingCalls(fileName: string, position: number): string; provideCallHierarchyOutgoingCalls(fileName: string, position: number): string; - + provideInlayHints(fileName: string, span: TextSpan, preference: InlayHintsOptions | undefined): string; getEmitOutput(fileName: string): string; getEmitOutputObject(fileName: string): EmitOutput; @@ -1067,6 +1067,13 @@ namespace ts { ); } + public provideInlayHints(fileName: string, span: TextSpan, preference: InlayHintsOptions | undefined): string { + return this.forwardJSONCall( + `provideInlayHints('${fileName}', '${JSON.stringify(span)}', ${JSON.stringify(preference)})`, + () => this.languageService.provideInlayHints(fileName, span, preference) + ); + } + /// Emit public getEmitOutput(fileName: string): string { return this.forwardJSONCall( diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 1e846b9214d16..ba72007a40e2d 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -33,6 +33,7 @@ "rename.ts", "smartSelection.ts", "signatureHelp.ts", + "inlayHints.ts", "sourcemaps.ts", "suggestionDiagnostics.ts", "symbolDisplay.ts", diff --git a/src/services/types.ts b/src/services/types.ts index be1d09ff69c57..50945159cc37d 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -487,6 +487,8 @@ namespace ts { provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; + provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences | undefined): InlayHint[] + getOutliningSpans(fileName: string): OutliningSpan[]; getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; @@ -570,6 +572,16 @@ namespace ts { includeInsertTextCompletions?: boolean; } + export interface InlayHintsOptions extends UserPreferences { + readonly includeInlayParameterNameHints?: "none" | "literals" | "all"; + readonly includeInlayParameterNameHintsWhenArgumentMatchesName?: boolean; + readonly includeInlayFunctionParameterTypeHints?: boolean, + readonly includeInlayVariableTypeHints?: boolean; + readonly includeInlayPropertyDeclarationTypeHints?: boolean; + readonly includeInlayFunctionLikeReturnTypeHints?: boolean; + readonly includeInlayEnumMemberValueHints?: boolean; + } + export type SignatureHelpTriggerCharacter = "," | "(" | "<"; export type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")"; @@ -693,6 +705,20 @@ namespace ts { fromSpans: TextSpan[]; } + export const enum InlayHintKind { + Type = "Type", + Parameter = "Parameter", + Enum = "Enum", + } + + export interface InlayHint { + text: string; + position: number; + kind?: InlayHintKind; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; + } + export interface TodoCommentDescriptor { text: string; priority: number; @@ -1556,4 +1582,13 @@ namespace ts { triggerReason?: RefactorTriggerReason; kind?: string; } + + export interface InlayHintsContext { + file: SourceFile; + program: Program; + cancellationToken: CancellationToken; + host: LanguageServiceHost; + span: TextSpan; + preferences: InlayHintsOptions; + } } diff --git a/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index 652879227dadc..77daa4c813bf2 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -278,6 +278,7 @@ namespace ts.server { CommandNames.ToggleMultilineComment, CommandNames.CommentSelection, CommandNames.UncommentSelection, + CommandNames.ProvideInlayHints ]; it("should not throw when commands are executed with invalid arguments", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c1621a6358e0a..bf708e8422769 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5675,6 +5675,7 @@ declare namespace ts { prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined; provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; + provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences | undefined): InlayHint[]; getOutliningSpans(fileName: string): OutliningSpan[]; getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; @@ -5736,6 +5737,15 @@ declare namespace ts { /** @deprecated Use includeCompletionsWithInsertText */ includeInsertTextCompletions?: boolean; } + interface InlayHintsOptions extends UserPreferences { + readonly includeInlayParameterNameHints?: "none" | "literals" | "all"; + readonly includeInlayParameterNameHintsWhenArgumentMatchesName?: boolean; + readonly includeInlayFunctionParameterTypeHints?: boolean; + readonly includeInlayVariableTypeHints?: boolean; + readonly includeInlayPropertyDeclarationTypeHints?: boolean; + readonly includeInlayFunctionLikeReturnTypeHints?: boolean; + readonly includeInlayEnumMemberValueHints?: boolean; + } type SignatureHelpTriggerCharacter = "," | "(" | "<"; type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")"; interface SignatureHelpItemsOptions { @@ -5841,6 +5851,18 @@ declare namespace ts { to: CallHierarchyItem; fromSpans: TextSpan[]; } + enum InlayHintKind { + Type = "Type", + Parameter = "Parameter", + Enum = "Enum" + } + interface InlayHint { + text: string; + position: number; + kind?: InlayHintKind; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; + } interface TodoCommentDescriptor { text: string; priority: number; @@ -6506,6 +6528,14 @@ declare namespace ts { jsxAttributeStringLiteralValue = 24, bigintLiteral = 25 } + interface InlayHintsContext { + file: SourceFile; + program: Program; + cancellationToken: CancellationToken; + host: LanguageServiceHost; + span: TextSpan; + preferences: InlayHintsOptions; + } } declare namespace ts { /** The classifier is used for syntactic highlighting in editors via the TSServer */ @@ -6797,7 +6827,8 @@ declare namespace ts.server.protocol { UncommentSelection = "uncommentSelection", PrepareCallHierarchy = "prepareCallHierarchy", ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", - ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls" + ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls", + ProvideInlayHints = "provideInlayHints" } /** * A TypeScript Server message @@ -8669,6 +8700,35 @@ declare namespace ts.server.protocol { interface SignatureHelpResponse extends Response { body?: SignatureHelpItems; } + enum InlayHintKind { + Type = "Type", + Parameter = "Parameter", + Enum = "Enum" + } + interface InlayHintsRequestArgs extends FileRequestArgs { + /** + * Start position of the span. + */ + start: number; + /** + * Length of the span. + */ + length: number; + } + interface InlayHintsRequest extends Request { + command: CommandTypes.ProvideInlayHints; + arguments: InlayHintsRequestArgs; + } + interface InlayHintItem { + text: string; + position: Location; + kind?: InlayHintKind; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; + } + interface InlayHintsResponse extends Response { + body?: InlayHintItem[]; + } /** * Synchronous request for semantic diagnostics of one file. */ @@ -10297,6 +10357,7 @@ declare namespace ts.server { private getSuggestionDiagnosticsSync; private getJsxClosingTag; private getDocumentHighlights; + private provideInlayHints; private setCompilerOptionsForInferredProjects; private getProjectInfo; private getProjectInfoWorker; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index a39955570cdf6..80dadec513b62 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5675,6 +5675,7 @@ declare namespace ts { prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined; provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; + provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences | undefined): InlayHint[]; getOutliningSpans(fileName: string): OutliningSpan[]; getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; @@ -5736,6 +5737,15 @@ declare namespace ts { /** @deprecated Use includeCompletionsWithInsertText */ includeInsertTextCompletions?: boolean; } + interface InlayHintsOptions extends UserPreferences { + readonly includeInlayParameterNameHints?: "none" | "literals" | "all"; + readonly includeInlayParameterNameHintsWhenArgumentMatchesName?: boolean; + readonly includeInlayFunctionParameterTypeHints?: boolean; + readonly includeInlayVariableTypeHints?: boolean; + readonly includeInlayPropertyDeclarationTypeHints?: boolean; + readonly includeInlayFunctionLikeReturnTypeHints?: boolean; + readonly includeInlayEnumMemberValueHints?: boolean; + } type SignatureHelpTriggerCharacter = "," | "(" | "<"; type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")"; interface SignatureHelpItemsOptions { @@ -5841,6 +5851,18 @@ declare namespace ts { to: CallHierarchyItem; fromSpans: TextSpan[]; } + enum InlayHintKind { + Type = "Type", + Parameter = "Parameter", + Enum = "Enum" + } + interface InlayHint { + text: string; + position: number; + kind?: InlayHintKind; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; + } interface TodoCommentDescriptor { text: string; priority: number; @@ -6506,6 +6528,14 @@ declare namespace ts { jsxAttributeStringLiteralValue = 24, bigintLiteral = 25 } + interface InlayHintsContext { + file: SourceFile; + program: Program; + cancellationToken: CancellationToken; + host: LanguageServiceHost; + span: TextSpan; + preferences: InlayHintsOptions; + } } declare namespace ts { /** The classifier is used for syntactic highlighting in editors via the TSServer */ diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 2b4a197a3cbbf..fe874e74d6252 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -66,6 +66,12 @@ declare module ts { Smart = 2, } + const enum InlayHintKind { + Type = "Type", + Parameter = "Parameter", + Enum = "Enum", + } + enum SemicolonPreference { Ignore = "ignore", Insert = "insert", @@ -396,6 +402,10 @@ declare namespace FourSlashInterface { start: number; length: number; }, displayParts: ts.SymbolDisplayPart[], documentation: ts.SymbolDisplayPart[], tags: { name: string, text?: string }[] | undefined): void; + getInlayHints(expected: readonly VerifyInlayHintsOptions[], textSpan?: { + start: number; + length: number; + }, preference?: InlayHintsOptions); getSyntacticDiagnostics(expected: ReadonlyArray): void; getSemanticDiagnostics(expected: ReadonlyArray): void; getSuggestionDiagnostics(expected: ReadonlyArray): void; @@ -633,6 +643,15 @@ declare namespace FourSlashInterface { readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; readonly importModuleSpecifierEnding?: "minimal" | "index" | "js"; } + interface InlayHintsOptions extends UserPreferences { + readonly includeInlayParameterNameHints?: "none" | "literals" | "all"; + readonly includeInlayParameterNameHintsWhenArgumentMatchesName?: boolean; + readonly includeInlayFunctionParameterTypeHints?: boolean; + readonly includeInlayVariableTypeHints?: boolean; + readonly includeInlayPropertyDeclarationTypeHints?: boolean; + readonly includeInlayFunctionLikeReturnTypeHints?: boolean; + readonly includeInlayEnumMemberValueHints?: boolean; + } interface CompletionsOptions { readonly marker?: ArrayOrSingle; readonly isNewIdentifierLocation?: boolean; @@ -735,6 +754,14 @@ declare namespace FourSlashInterface { readonly commands?: ReadonlyArray<{}>; } + export interface VerifyInlayHintsOptions { + text: string; + position: number; + kind?: VerifyInlayHintKind; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; + } + interface VerifyNavigateToOptions { readonly pattern: string; readonly fileName?: string; diff --git a/tests/cases/fourslash/inlayHintsShouldWork1.ts b/tests/cases/fourslash/inlayHintsShouldWork1.ts new file mode 100644 index 0000000000000..418a82e97a42d --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork1.ts @@ -0,0 +1,22 @@ +/// + +//// function foo (a: number, b: number) {} +//// foo(/*a*/1, /*b*/2); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'a:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'b:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork10.ts b/tests/cases/fourslash/inlayHintsShouldWork10.ts new file mode 100644 index 0000000000000..650c3d801bc4b --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork10.ts @@ -0,0 +1,8 @@ +/// + +//// declare const unknownCall: any; +//// unknownCall(); + +verify.getInlayHints([], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork11.ts b/tests/cases/fourslash/inlayHintsShouldWork11.ts new file mode 100644 index 0000000000000..55e08a0ea26f6 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork11.ts @@ -0,0 +1,26 @@ +/// + +//// function foo(a: number) { +//// return (b: number) => { +//// return a + b +//// } +//// } +//// foo(/*a*/1)(/*b*/2); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'a:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'b:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork12.ts b/tests/cases/fourslash/inlayHintsShouldWork12.ts new file mode 100644 index 0000000000000..8414ca08c97e0 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork12.ts @@ -0,0 +1,25 @@ +/// + +//// function foo(a: (b: number) => number) { +//// return a(/*a*/1) + 2 +//// } + +//// foo(/*b*/(c: number) => c + 1); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'b:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'a:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork13.ts b/tests/cases/fourslash/inlayHintsShouldWork13.ts new file mode 100644 index 0000000000000..ffcf223637ee0 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork13.ts @@ -0,0 +1,18 @@ +/// + +//// function foo (a: number, b: number) {} +//// declare const a: 1; +//// foo(a, /*b*/2); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'b:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, +], undefined, { + includeInlayParameterNameHints: "all", + includeInlayParameterNameHintsWhenArgumentMatchesName: false, +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork14.ts b/tests/cases/fourslash/inlayHintsShouldWork14.ts new file mode 100644 index 0000000000000..675d50d1c10f2 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork14.ts @@ -0,0 +1,8 @@ +/// + +//// function foo (a: number, b: number) {} +//// foo(1, 2); + +verify.getInlayHints([], undefined, { + includeInlayParameterNameHints: "none" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork15.ts b/tests/cases/fourslash/inlayHintsShouldWork15.ts new file mode 100644 index 0000000000000..544838b43c73b --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork15.ts @@ -0,0 +1,15 @@ +/// + +//// const a/*a*/ = 123; + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': 123', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayVariableTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork16.ts b/tests/cases/fourslash/inlayHintsShouldWork16.ts new file mode 100644 index 0000000000000..544838b43c73b --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork16.ts @@ -0,0 +1,15 @@ +/// + +//// const a/*a*/ = 123; + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': 123', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayVariableTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork17.ts b/tests/cases/fourslash/inlayHintsShouldWork17.ts new file mode 100644 index 0000000000000..70ea5e0d5eb5d --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork17.ts @@ -0,0 +1,15 @@ +/// + +//// const a/*a*/ = { a: 123 }; + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': { a: number; }', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayVariableTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork18.ts b/tests/cases/fourslash/inlayHintsShouldWork18.ts new file mode 100644 index 0000000000000..e33033549f0dc --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork18.ts @@ -0,0 +1,16 @@ +/// + +//// class Class {} +//// const a/*a*/ = new Class(); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': Class', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayVariableTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork19.ts b/tests/cases/fourslash/inlayHintsShouldWork19.ts new file mode 100644 index 0000000000000..8d21f16cda77e --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork19.ts @@ -0,0 +1,15 @@ +/// + +//// const a/*a*/ = () => 123; + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': () => number', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayVariableTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork2.ts b/tests/cases/fourslash/inlayHintsShouldWork2.ts new file mode 100644 index 0000000000000..ed95b8fc04c71 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork2.ts @@ -0,0 +1,16 @@ +/// + +//// function foo (a: number, { c }: any) {} +//// foo(/*a*/1, { c: 1}); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'a:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork20.ts b/tests/cases/fourslash/inlayHintsShouldWork20.ts new file mode 100644 index 0000000000000..4510a740a79f0 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork20.ts @@ -0,0 +1,7 @@ +/// + +//// const a = 123; + +verify.getInlayHints([], undefined, { + includeInlayVariableTypeHints: false +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork21.ts b/tests/cases/fourslash/inlayHintsShouldWork21.ts new file mode 100644 index 0000000000000..7a63f903f3410 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork21.ts @@ -0,0 +1,7 @@ +/// + +//// const a; + +verify.getInlayHints([], undefined, { + includeInlayVariableTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork22.ts b/tests/cases/fourslash/inlayHintsShouldWork22.ts new file mode 100644 index 0000000000000..91fa9eb02c3a5 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork22.ts @@ -0,0 +1,15 @@ +/// + +//// const a/*a*/ = "I'm very very very very very very very very very long"; + +const markers = test.markers(); +verify.getInlayHints([ + { + text: `: "I'm very very very very ve...`, + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayVariableTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork23.ts b/tests/cases/fourslash/inlayHintsShouldWork23.ts new file mode 100644 index 0000000000000..07de14a89c6e7 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork23.ts @@ -0,0 +1,16 @@ +/// + +//// function foo (Im_very_very_very_very_very_very_very_long: number) {} +//// foo(/*a*/1); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'Im_very_very_very_very_very...:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork24.ts b/tests/cases/fourslash/inlayHintsShouldWork24.ts new file mode 100644 index 0000000000000..9e38e7cf75ffd --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork24.ts @@ -0,0 +1,22 @@ +/// + +//// type F = (a: string, b: number) => void +//// const f: F = (a/*a*/, b/*b*/) => { } + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': string', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, + { + text: ': number', + position: markers[1].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + } +], undefined, { + includeInlayFunctionParameterTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork25.ts b/tests/cases/fourslash/inlayHintsShouldWork25.ts new file mode 100644 index 0000000000000..9bb52811c3412 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork25.ts @@ -0,0 +1,16 @@ +/// + +//// function foo (cb: (a: string) => void) {} +//// foo((a/*a*/) => { }) + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': string', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + } +], undefined, { + includeInlayFunctionParameterTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork26.ts b/tests/cases/fourslash/inlayHintsShouldWork26.ts new file mode 100644 index 0000000000000..e02d1b2792890 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork26.ts @@ -0,0 +1,16 @@ +/// + +//// function foo (cb: (a: Exclude<1 | 2 | 3, 1>) => void) {} +//// foo((a/*a*/) => { }) + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': 2 | 3', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + } +], undefined, { + includeInlayFunctionParameterTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork27.ts b/tests/cases/fourslash/inlayHintsShouldWork27.ts new file mode 100644 index 0000000000000..77d966f549740 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork27.ts @@ -0,0 +1,24 @@ +/// + +//// function foo (a: (b: (c: (d: Exclude<1 | 2 | 3, 1>) => void) => void) => void) {} +//// foo(a/*a*/ => { +//// a(d/*b*/ => {}) +//// }) + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': (c: (d: 2 | 3) => void) => ...', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, + { + text: ': 2 | 3', + position: markers[1].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + } +], undefined, { + includeInlayFunctionParameterTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork28.ts b/tests/cases/fourslash/inlayHintsShouldWork28.ts new file mode 100644 index 0000000000000..9ceca69c2d501 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork28.ts @@ -0,0 +1,16 @@ +/// + +//// type F = (a: string, b: number) => void +//// const f: F = (a/*a*/, b: number) => { } + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': string', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + } +], undefined, { + includeInlayFunctionParameterTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork29.ts b/tests/cases/fourslash/inlayHintsShouldWork29.ts new file mode 100644 index 0000000000000..f371e2c552633 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork29.ts @@ -0,0 +1,37 @@ +/// + +//// function foo (a: (b: (c: (d: Exclude<1 | 2 | 3, 1>) => void) => void) => void) {} +//// foo(/*a*/a/*b*/ => { +//// a(/*c*/d/*d*/ => {}) +//// }) + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'a:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: ': (c: (d: 2 | 3) => void) => ...', + position: markers[1].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, + { + text: 'c:', + position: markers[2].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: ': 2 | 3', + position: markers[3].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + } +], undefined, { + includeInlayParameterNameHints: "literals", + includeInlayFunctionParameterTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork3.ts b/tests/cases/fourslash/inlayHintsShouldWork3.ts new file mode 100644 index 0000000000000..f728d0657946b --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork3.ts @@ -0,0 +1,22 @@ +/// + +//// function foo (a: number, ...b: number[]) {} +//// foo(/*a*/1, /*b*/1, 1, 1); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'a:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: '...b:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork30.ts b/tests/cases/fourslash/inlayHintsShouldWork30.ts new file mode 100644 index 0000000000000..2c44c86cfeb36 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork30.ts @@ -0,0 +1,16 @@ +/// + +//// function f(v: T, a: (v: T) => void) {} +//// f(1, a/*a*/ => { }) + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': number', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + } +], undefined, { + includeInlayFunctionParameterTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork31.ts b/tests/cases/fourslash/inlayHintsShouldWork31.ts new file mode 100644 index 0000000000000..d6c2c41e45385 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork31.ts @@ -0,0 +1,19 @@ +/// + +//// type F = (a: { +//// a: number +//// b: string +//// }) => void +//// const f: F = (a/*a*/) => { } + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': { a: number; b: string; }', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + } +], undefined, { + includeInlayFunctionParameterTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork32.ts b/tests/cases/fourslash/inlayHintsShouldWork32.ts new file mode 100644 index 0000000000000..19e6461a53e16 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork32.ts @@ -0,0 +1,31 @@ +/// + +//// function foo1 (a: number, b: number) {} +//// function foo2 (c: number, d: number) {} +//// function foo3 (e: number, f: number) {} +//// function foo4 (g: number, h: number) {} +//// function foo5 (i: number, j: number) {} +//// function foo6 (k: number, i: number) {} + +//// function c1 () { foo1(/*a*/1, /*b*/2); } +//// function c2 () { foo2(/*c*/1, /*d*/2); } +//// function c3 () { foo3(/*e*/1, /*f*/2); } +//// function c4 () { foo4(/*g*/1, /*h*/2); } +//// function c5 () { foo5(/*i*/1, /*j*/2); } +//// function c6 () { foo6(/*k*/1, /*l*/2); } + +const start = test.markerByName('c'); +const end = test.markerByName('h'); +const span = { start: start.position, length: end.position - start.position }; + +verify.getInlayHints( + ['c', 'd', 'e', 'f', 'g', 'h'].map(mark => { + return { + text: `${mark}:`, + position: test.markerByName(mark).position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } + }), span, { + includeInlayParameterNameHints: "literals" +}) diff --git a/tests/cases/fourslash/inlayHintsShouldWork33.ts b/tests/cases/fourslash/inlayHintsShouldWork33.ts new file mode 100644 index 0000000000000..3a418920d538e --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork33.ts @@ -0,0 +1,31 @@ +/// + +//// function foo1 (a: number, b: number) {} +//// function foo2 (c: number, d: number) {} +//// function foo3 (e: number, f: number) {} +//// function foo4 (g: number, h: number) {} +//// function foo5 (i: number, j: number) {} +//// function foo6 (k: number, l: number) {} + +//// foo1(/*a*/1, /*b*/2); +//// foo2(/*c*/1, /*d*/2); +//// foo3(/*e*/1, /*f*/2); +//// foo4(/*g*/1, /*h*/2); +//// foo5(/*i*/1, /*j*/2); +//// foo6(/*k*/1, /*l*/2); + +const start = test.markerByName('c'); +const end = test.markerByName('h'); +const span = { start: start.position, length: end.position - start.position }; + +verify.getInlayHints( + ['c', 'd', 'e', 'f', 'g', 'h'].map(mark => { + return { + text: `${mark}:`, + position: test.markerByName(mark).position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } + }), span, { + includeInlayParameterNameHints: "literals" +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlayHintsShouldWork34.ts b/tests/cases/fourslash/inlayHintsShouldWork34.ts new file mode 100644 index 0000000000000..6c5f87a78340f --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork34.ts @@ -0,0 +1,29 @@ +/// + +//// function foo (v: any) {} + +//// foo(/*a*/1); +//// foo(/*b*/''); +//// foo(/*c*/true); +//// foo(/*d*/() => 1); +//// foo(/*e*/function () { return 1 }); +//// foo(/*f*/{}); +//// foo(/*g*/{ a: 1 }); +//// foo(/*h*/[]); +//// foo(/*i*/[1]); + +//// foo(foo); +//// foo(/*j*/(1)); +//// foo(foo(/*k*/1)); + +const markers = test.markers(); + +verify.getInlayHints( + markers.map(m => ({ + text: 'v:', + position: m.position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + })) , undefined, { + includeInlayParameterNameHints: "literals" +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlayHintsShouldWork35.ts b/tests/cases/fourslash/inlayHintsShouldWork35.ts new file mode 100644 index 0000000000000..ce33995dd1da2 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork35.ts @@ -0,0 +1,29 @@ +/// + +//// function foo (v: any) {} + +//// foo(/*a*/1); +//// foo(/*b*/''); +//// foo(/*c*/true); +//// foo(/*d*/() => 1); +//// foo(/*e*/function () { return 1 }); +//// foo(/*f*/{}); +//// foo(/*g*/{ a: 1 }); +//// foo(/*h*/[]); +//// foo(/*i*/[1]); + +//// foo(/*j*/foo); +//// foo(/*k*/(1)); +//// foo(/*l*/foo(/*m*/1)); + +const markers = test.markers(); + +verify.getInlayHints( + markers.map(m => ({ + text: 'v:', + position: m.position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + })) , undefined, { + includeInlayParameterNameHints: "all" +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlayHintsShouldWork36.ts b/tests/cases/fourslash/inlayHintsShouldWork36.ts new file mode 100644 index 0000000000000..afb9c14845d24 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork36.ts @@ -0,0 +1,24 @@ +/// + +//// function foo (a: number, b: number) {} +//// declare const a: 1; +//// foo(/*a*/a, /*b*/2); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'a:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'b:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, +], undefined, { + includeInlayParameterNameHints: "all", + includeInlayParameterNameHintsWhenArgumentMatchesName: true, +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork37.ts b/tests/cases/fourslash/inlayHintsShouldWork37.ts new file mode 100644 index 0000000000000..878d92df3bd7b --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork37.ts @@ -0,0 +1,19 @@ +/// + +//// class C { +//// a/*a*/ = 1 +//// b: number = 2 +//// c; +//// } + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': number', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayPropertyDeclarationTypeHints: true, +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork38.ts b/tests/cases/fourslash/inlayHintsShouldWork38.ts new file mode 100644 index 0000000000000..e43d6cedb673a --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork38.ts @@ -0,0 +1,17 @@ +/// + +//// function foo ()/*a*/ { +//// return 1 +//// } + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': number', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayFunctionLikeReturnTypeHints: true, +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork39.ts b/tests/cases/fourslash/inlayHintsShouldWork39.ts new file mode 100644 index 0000000000000..3096ae2aaf983 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork39.ts @@ -0,0 +1,9 @@ +/// + +//// function foo (): number { +//// return 1 +//// } + +verify.getInlayHints([], undefined, { + includeInlayFunctionLikeReturnTypeHints: true, +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork4.ts b/tests/cases/fourslash/inlayHintsShouldWork4.ts new file mode 100644 index 0000000000000..3753c9f19981a --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork4.ts @@ -0,0 +1,32 @@ +/// + +//// declare function foo(w: number): void +//// declare function foo(a: number, b: number): void; +//// declare function foo(a: number | undefined, b: number | undefined): void; + +//// foo(/*a*/1) +//// foo(/*b*/1, /*c*/2) + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'w:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'a:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'b:', + position: markers[2].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork40.ts b/tests/cases/fourslash/inlayHintsShouldWork40.ts new file mode 100644 index 0000000000000..4016b38f4924d --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork40.ts @@ -0,0 +1,19 @@ +/// + +//// class C { +//// foo()/*a*/ { +//// return 1 +//// } +//// } + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': number', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayFunctionLikeReturnTypeHints: true, +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork41.ts b/tests/cases/fourslash/inlayHintsShouldWork41.ts new file mode 100644 index 0000000000000..ddd5ba22d8924 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork41.ts @@ -0,0 +1,15 @@ +/// + +//// const a = ()/*a*/ => 1 + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': number', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayFunctionLikeReturnTypeHints: true, +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork42.ts b/tests/cases/fourslash/inlayHintsShouldWork42.ts new file mode 100644 index 0000000000000..4fbd4b8fd6ff8 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork42.ts @@ -0,0 +1,15 @@ +/// + +//// const a = function ()/*a*/ { return 1} + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': number', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayFunctionLikeReturnTypeHints: true, +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork43.ts b/tests/cases/fourslash/inlayHintsShouldWork43.ts new file mode 100644 index 0000000000000..f89ea44a2646e --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork43.ts @@ -0,0 +1,16 @@ +/// + +//// const a = (b)/*a*/ => 1 +//// const aa = b => 1 + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': number', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayFunctionLikeReturnTypeHints: true, +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork44.ts b/tests/cases/fourslash/inlayHintsShouldWork44.ts new file mode 100644 index 0000000000000..fb890330ddef5 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork44.ts @@ -0,0 +1,33 @@ +/// + +//// enum E { +//// A/*a*/, +//// AA/*b*/, +//// B = 10, +//// BB/*c*/, +//// C = 'C', +//// } + +const markers = test.markers(); +verify.getInlayHints([ + { + text: '= 0', + position: markers[0].position, + kind: ts.InlayHintKind.Enum, + whitespaceBefore: true + }, + { + text: '= 1', + position: markers[1].position, + kind: ts.InlayHintKind.Enum, + whitespaceBefore: true + }, + { + text: '= 11', + position: markers[2].position, + kind: ts.InlayHintKind.Enum, + whitespaceBefore: true + }, +], undefined, { + includeInlayEnumMemberValueHints: true, +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork45.ts b/tests/cases/fourslash/inlayHintsShouldWork45.ts new file mode 100644 index 0000000000000..6c84520fe2f48 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork45.ts @@ -0,0 +1,15 @@ +/// + +// @allowJs: true +// @checkJs: true + +// @Filename: /a.js +//// module.exports.a = 1 + +// @Filename: /b.js +//// const a = require('./a'); + +goTo.file('/b.js') +verify.getInlayHints([], undefined, { + includeInlayVariableTypeHints: true, +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork46.ts b/tests/cases/fourslash/inlayHintsShouldWork46.ts new file mode 100644 index 0000000000000..3fac222fd2467 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork46.ts @@ -0,0 +1,33 @@ +/// + +// @allowJs: true +// @checkJs: true + +// @Filename: /a.js +//// module.exports.a = 1 + +// @Filename: /b.js +//// function foo () { return require('./a'); } +//// function bar ()/*a*/ { return require('./a').a; } +//// const c = foo() +//// const d/*b*/ = bar() + +goTo.file('/b.js') +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': number', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, + { + text: ': number', + position: markers[1].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayVariableTypeHints: true, + includeInlayFunctionLikeReturnTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork47.ts b/tests/cases/fourslash/inlayHintsShouldWork47.ts new file mode 100644 index 0000000000000..e06704d28b20d --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork47.ts @@ -0,0 +1,33 @@ +/// + +// @allowJs: true +// @checkJs: true + +// @Filename: /a.js +//// var x +//// x.foo(1, 2); + +//// /** +//// * @type {{foo: (a: number, b: number) => void}} +//// */ +//// var y +//// y.foo(/*a*/1, /*b*/2) + +goTo.file('/a.js') +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'a:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'b:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork48.ts b/tests/cases/fourslash/inlayHintsShouldWork48.ts new file mode 100644 index 0000000000000..5c7a09fa1f27f --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork48.ts @@ -0,0 +1,16 @@ +/// + +//// declare function foo(t: T): T +//// const x/*a*/ = foo(1) + +const markers = test.markers(); +verify.getInlayHints([ + { + text: `: 1`, + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, +], undefined, { + includeInlayVariableTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork49.ts b/tests/cases/fourslash/inlayHintsShouldWork49.ts new file mode 100644 index 0000000000000..e3b6e1d668dd5 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork49.ts @@ -0,0 +1,16 @@ +/// + +// @allowJs: true +// @checkJs: true + +// @Filename: /a.js +//// /** +//// * @type {string} +//// */ +//// var x = "" + + +goTo.file('/a.js') +verify.getInlayHints([], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork5.ts b/tests/cases/fourslash/inlayHintsShouldWork5.ts new file mode 100644 index 0000000000000..32550023c84d5 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork5.ts @@ -0,0 +1,29 @@ +/// + +//// type Args = [a: number, b: number] +//// declare function foo(c: number, ...args: Args); +//// foo(/*a*/1, /*b*/2, /*c*/3) + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'c:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'a:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'b:', + position: markers[2].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork50.ts b/tests/cases/fourslash/inlayHintsShouldWork50.ts new file mode 100644 index 0000000000000..a722dec40f785 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork50.ts @@ -0,0 +1,49 @@ +/// + +//// type T = [a: string, b: boolean, ...c: number[]] +//// declare function foo(f: number, ...args: T):void +//// declare function foo1(f1: number, ...args: string[]): void +//// foo(/*f*/1, /*a*/'', /*b*/false, /*c*/1, 2) +//// foo1(/*f1*/1, /*args*/"", "") + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'f:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'a:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'b:', + position: markers[2].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: '...c:', + position: markers[3].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'f1:', + position: markers[4].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: '...args:', + position: markers[5].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork51.ts b/tests/cases/fourslash/inlayHintsShouldWork51.ts new file mode 100644 index 0000000000000..2e9f44ccc59d4 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork51.ts @@ -0,0 +1,46 @@ +/// + +// @Filename: /a.ts +//// export interface Foo { a: string } + +// @Filename: /b.ts +//// async function foo ()/*a*/ { +//// return {} as any as import('./a').Foo +//// } +//// function bar ()/*b*/ { return import('./a') } +//// async function main ()/*c*/ { +//// const a/*d*/ = await foo() +//// const b = await bar() +//// } + +goTo.file('/b.ts') +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': Promise', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, + { + text: ': Promise', + position: markers[1].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, + { + text: ': Promise', + position: markers[2].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, + { + text: ': Foo', + position: markers[3].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + } +], undefined, { + includeInlayVariableTypeHints: true, + includeInlayFunctionLikeReturnTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork52.ts b/tests/cases/fourslash/inlayHintsShouldWork52.ts new file mode 100644 index 0000000000000..9c603ceb9b6b0 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork52.ts @@ -0,0 +1,78 @@ +/// + +//// function foo (aParameter: number, bParameter: number, cParameter: number)/*f*/ { } + +//// foo( +//// /** aParameter */ +//// 1, +//// // bParameter +//// /*a*/2, +//// /* cParameter */ +//// 3 +//// ) + +//// foo( +//// /** multiple comments */ +//// /** aParameter */ +//// 1, +//// /** bParameter */ +//// /** multiple comments */ +//// 2, +//// // cParameter +//// /** multiple comments */ +//// /*b*/3 +//// ) + +//// foo( +//// /** wrong name */ +//// /*c*/1, +//// /*d*/2, +//// /** multiple */ +//// /** wrong */ +//// /** name */ +//// /*e*/3 +//// ) + + +const markers = test.markers(); +verify.getInlayHints([ + { + text: ': void', + position: markers[0].position, + kind: ts.InlayHintKind.Type, + whitespaceBefore: true + }, + { + text: 'bParameter:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'cParameter:', + position: markers[2].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'aParameter:', + position: markers[3].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'bParameter:', + position: markers[4].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'cParameter:', + position: markers[5].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals", + includeInlayFunctionLikeReturnTypeHints: true +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork53.ts b/tests/cases/fourslash/inlayHintsShouldWork53.ts new file mode 100644 index 0000000000000..657a8fb7db805 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork53.ts @@ -0,0 +1,16 @@ +/// + +//// const fn = (x: any) => { } +//// fn(/* nobody knows exactly what this param is */ /*a*/42); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'x:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork6.ts b/tests/cases/fourslash/inlayHintsShouldWork6.ts new file mode 100644 index 0000000000000..fe94167b22a12 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork6.ts @@ -0,0 +1,17 @@ +/// + +//// type Args = [number, number] +//// declare function foo(c: number, ...args: Args); +//// foo(/*a*/1, 2, 3) + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'c:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork7.ts b/tests/cases/fourslash/inlayHintsShouldWork7.ts new file mode 100644 index 0000000000000..1505c7b5446e1 --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork7.ts @@ -0,0 +1,33 @@ +/// + +//// interface Call { +//// (a: number): void +//// (b: number, c: number): void +//// } +//// declare const call: Call; +//// call(/*a*/1); +//// call(/*b*/1, /*c*/2); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'a:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'b:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'c:', + position: markers[2].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork8.ts b/tests/cases/fourslash/inlayHintsShouldWork8.ts new file mode 100644 index 0000000000000..a774e6443b13d --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork8.ts @@ -0,0 +1,33 @@ +/// + +//// class Class { +//// constructor(a: number); +//// constructor(b: number, c: number); +//// constructor(b: number, c?: number) { } +//// } +//// new Class(/*a*/1) +//// new Class(/*b*/1, /*c*/2) + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'a:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'b:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'c:', + position: markers[2].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +}); diff --git a/tests/cases/fourslash/inlayHintsShouldWork9.ts b/tests/cases/fourslash/inlayHintsShouldWork9.ts new file mode 100644 index 0000000000000..435da15cbcdcb --- /dev/null +++ b/tests/cases/fourslash/inlayHintsShouldWork9.ts @@ -0,0 +1,41 @@ +/// + +//// interface Call { +//// (a: number): void +//// (b: number, c: number): void +//// new (d: number): Call +//// } +//// declare const call: Call; +//// call(/*a*/1); +//// call(/*b*/1, /*c*/2); +//// new call(/*d*/1); + +const markers = test.markers(); +verify.getInlayHints([ + { + text: 'a:', + position: markers[0].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'b:', + position: markers[1].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'c:', + position: markers[2].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + }, + { + text: 'd:', + position: markers[3].position, + kind: ts.InlayHintKind.Parameter, + whitespaceAfter: true + } +], undefined, { + includeInlayParameterNameHints: "literals" +});