diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9ea90805fb51e..119dae44fcfcf 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -231,6 +231,7 @@ import { JSDocParameterTag, JSDocPropertyLikeTag, JSDocSignature, + JSDocSpecializeTag, JSDocTypedefTag, JSDocTypeLiteral, JsxAttribute, @@ -3091,6 +3092,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { return bind((node as JSDocOverloadTag).typeExpression); case SyntaxKind.JSDocImportTag: return (jsDocImports || (jsDocImports = [])).push(node as JSDocImportTag); + case SyntaxKind.JSDocSpecializeTag: + return bindEach((node as JSDocSpecializeTag).typeArguments); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8fa3c4fce2a4a..9719407f78aec 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -326,6 +326,7 @@ import { getJSDocParameterTags, getJSDocRoot, getJSDocSatisfiesExpressionType, + getJSDocSpecializeTag, getJSDocTags, getJSDocThisTag, getJSDocType, @@ -35562,15 +35563,34 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node); } - function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { + function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningLikeElement { return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node); } + function getTypeArgumentsForCallLikeExpression(node: CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningLikeElement) { + if (isSuperCall(node)) { + return undefined; + } + if (isInJSFile(node)) { + let { parent } = node; + if (isJsxElement(parent)) { + parent = parent.parent; + } + if (canHaveJSDoc(parent)) { + const specializeTag = getJSDocSpecializeTag(parent); + if (specializeTag) { + return specializeTag.typeArguments; + } + } + } + return node.typeArguments; + } + function resolveUntypedCall(node: CallLikeExpression): Signature { if (callLikeExpressionMayHaveTypeArguments(node)) { // Check type arguments even though we will give an error that untyped calls may not accept type arguments. // This gets us diagnostics for the type arguments and marks them as referenced. - forEach(node.typeArguments, checkSourceElement); + forEach(getTypeArgumentsForCallLikeExpression(node), checkSourceElement); } if (node.kind === SyntaxKind.TaggedTemplateExpression) { @@ -36570,13 +36590,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let candidates: Signature[] = []; let typeArguments: NodeArray | undefined; - if (!isDecorator && !isInstanceof && !isSuperCall(node) && !isJsxOpenFragment) { - typeArguments = (node as CallExpression).typeArguments; - - // We already perform checking on the type arguments on the class declaration itself. - if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node as CallExpression).expression.kind !== SyntaxKind.SuperKeyword) { - forEach(typeArguments, checkSourceElement); - } + if (callLikeExpressionMayHaveTypeArguments(node)) { + typeArguments = getTypeArgumentsForCallLikeExpression(node); + forEach(typeArguments, checkSourceElement); } candidates = candidatesOutArray || []; @@ -36748,7 +36764,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args, headMessage)); } else if (candidateForTypeArgumentError) { - checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, headMessage); + checkTypeArguments(candidateForTypeArgumentError, typeArguments!, /*reportErrors*/ true, headMessage); } else if (!isJsxOpenFragment) { const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); @@ -36953,7 +36969,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return candidate; } - const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined; + const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? getTypeArgumentsForCallLikeExpression(node) : undefined; const instantiated = typeArgumentNodes ? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node))) : inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args, checkMode); @@ -37053,6 +37069,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // that the user will not add any. const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + const typeArguments = getTypeArgumentsForCallLikeExpression(node); // TS 1.0 Spec: 4.12 // In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual @@ -37060,7 +37077,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { // The unknownType indicates that an error already occurred (and was reported). No // need to report another error in this case. - if (!isErrorType(funcType) && node.typeArguments) { + if (!isErrorType(funcType) && typeArguments) { error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); } return resolveUntypedCall(node); @@ -37096,7 +37113,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // use the resolvingSignature singleton to indicate that we deferred processing. This result will be // propagated out and eventually turned into silentNeverType (a type that is assignable to anything and // from which we never make inferences). - if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) { + if (checkMode & CheckMode.SkipGenericFunctions && !typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) { skippedGenericFunction(node, checkMode); return resolvingSignature; } @@ -37141,11 +37158,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return resolveErrorCall(node); } + const typeArguments = getTypeArgumentsForCallLikeExpression(node); // TS 1.0 spec: 4.11 // If expressionType is of type Any, Args can be any argument // list and the result of the operation is of type Any. if (isTypeAny(expressionType)) { - if (node.typeArguments) { + if (typeArguments) { error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); } return resolveUntypedCall(node); @@ -37545,9 +37563,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); const fakeSignature = createSignatureForJSXIntrinsic(node, result); checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*inferenceContext*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes); - if (length(node.typeArguments)) { - forEach(node.typeArguments, checkSourceElement); - diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), node.typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(node.typeArguments))); + const typeArguments = getTypeArgumentsForCallLikeExpression(node); + if (length(typeArguments)) { + forEach(typeArguments, checkSourceElement); + diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(typeArguments))); } return fakeSignature; } @@ -37804,7 +37823,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { * @returns On success, the expression's signature's return type. On failure, anyType. */ function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { - checkGrammarTypeArguments(node, node.typeArguments); + checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node)); const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode); if (signature === resolvingSignature) { @@ -38043,7 +38062,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { - if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); + if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node)); if (languageVersion < LanguageFeatureMinimumTarget.TaggedTemplates) { checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); } @@ -42661,7 +42680,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { - return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node)); + return fillMissingTypeArguments(map(node.typeArguments || [], getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node)); } function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean { @@ -52696,7 +52715,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkGrammarJsxElement(node: JsxOpeningLikeElement) { checkGrammarJsxName(node.tagName); - checkGrammarTypeArguments(node, node.typeArguments); + checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node)); const seen = new Map<__String, boolean>(); for (const attr of node.attributes.properties) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 5a4858b572dc1..e231cf619f0ea 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -257,6 +257,7 @@ import { JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, + JSDocSpecializeTag, JSDocTag, JSDocTemplateTag, JSDocThisTag, @@ -1899,6 +1900,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri return emitJSDocSeeTag(node as JSDocSeeTag); case SyntaxKind.JSDocImportTag: return emitJSDocImportTag(node as JSDocImportTag); + case SyntaxKind.JSDocSpecializeTag: + return emitJSDocSpecializeTag(node as JSDocSpecializeTag); // SyntaxKind.JSDocPropertyTag (see JSDocParameterTag, above) // Transformation nodes @@ -4156,6 +4159,15 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri emitJSDocComment(tag.comment); } + function emitJSDocSpecializeTag(tag: JSDocSpecializeTag) { + emitJSDocTagName(tag.tagName); + writeSpace(); + writePunctuation("<"); + emitList(tag, tag.typeArguments, ListFormat.CommaListElements); + writePunctuation(">"); + emitJSDocComment(tag.comment); + } + function emitJSDocNameReference(node: JSDocNameReference) { writeSpace(); writePunctuation("{"); diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 03e0345c7ea7b..af89ec8455fe6 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -251,6 +251,7 @@ import { JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, + JSDocSpecializeTag, JSDocTag, JSDocTemplateTag, JSDocText, @@ -863,6 +864,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode updateJSDocSeeTag, createJSDocImportTag, updateJSDocImportTag, + createJSDocSpecializeTag, + updateJSDocSpecializeTag, createJSDocNameReference, updateJSDocNameReference, createJSDocMemberName, @@ -5564,6 +5567,22 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode : node; } + // @api + function createJSDocSpecializeTag(tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray | undefined): JSDocSpecializeTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocSpecializeTag, tagName ?? createIdentifier("specialize"), /*comment*/ undefined); + node.typeArguments = asNodeArray(typeArguments); + node.comment = comment; + return node; + } + + function updateJSDocSpecializeTag(node: JSDocSpecializeTag, tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray | undefined): JSDocSpecializeTag { + return node.tagName !== tagName + || node.typeArguments !== typeArguments + || node.comment !== comment + ? update(createJSDocSpecializeTag(tagName, typeArguments, comment), node) + : node; + } + // @api function createJSDocText(text: string): JSDocText { const node = createBaseNode(SyntaxKind.JSDocText); @@ -7227,6 +7246,8 @@ function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string { return "implements"; case SyntaxKind.JSDocImportTag: return "import"; + case SyntaxKind.JSDocSpecializeTag: + return "specialize"; default: return Debug.fail(`Unsupported kind: ${Debug.formatSyntaxKind(kind)}`); } diff --git a/src/compiler/factory/nodeTests.ts b/src/compiler/factory/nodeTests.ts index 8aa4bb02e83d2..ad3f4171880df 100644 --- a/src/compiler/factory/nodeTests.ts +++ b/src/compiler/factory/nodeTests.ts @@ -113,6 +113,7 @@ import { JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, + JSDocSpecializeTag, JSDocTemplateTag, JSDocThisTag, JSDocThrowsTag, @@ -1193,6 +1194,10 @@ export function isJSDocImportTag(node: Node): node is JSDocImportTag { return node.kind === SyntaxKind.JSDocImportTag; } +export function isJSDocSpecializeTag(node: Node): node is JSDocSpecializeTag { + return node.kind === SyntaxKind.JSDocSpecializeTag; +} + // Synthesized list /** @internal */ diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 68133ac5f1e40..eab7783914736 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -149,6 +149,7 @@ import { isJSDocFunctionType, isJSDocNullableType, isJSDocReturnTag, + isJSDocSpecializeTag, isJSDocTypeTag, isJsxNamespacedName, isJsxOpeningElement, @@ -202,6 +203,7 @@ import { JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, + JSDocSpecializeTag, JSDocSyntaxKind, JSDocTag, JSDocTemplateTag, @@ -1132,6 +1134,9 @@ const forEachChildTable: ForEachChildTable = { [SyntaxKind.JSDocDeprecatedTag]: forEachChildInJSDocTag, [SyntaxKind.JSDocOverrideTag]: forEachChildInJSDocTag, [SyntaxKind.JSDocImportTag]: forEachChildInJSDocImportTag, + [SyntaxKind.JSDocSpecializeTag]: function forEachChildInJSDocSpecializeTag(node: JSDocSpecializeTag, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return forEach(node.typeArguments, cbNode); + }, [SyntaxKind.PartiallyEmittedExpression]: forEachChildInPartiallyEmittedExpression, }; @@ -9177,6 +9182,9 @@ namespace Parser { case "import": tag = parseImportTag(start, tagName, margin, indentText); break; + case "specialize": + tag = parseSpecializeTag(start, tagName, margin, indentText); + break; default: tag = parseUnknownTag(start, tagName, margin, indentText); break; @@ -9565,6 +9573,24 @@ namespace Parser { return finishNode(factory.createJSDocImportTag(tagName, importClause, moduleSpecifier, attributes, comments), start); } + function parseSpecializeTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocSpecializeTag { + if (some(tags, isJSDocSpecializeTag)) { + parseErrorAt(tagName.pos, scanner.getTokenStart(), Diagnostics._0_tag_already_specified, unescapeLeadingUnderscores(tagName.escapedText)); + } + const pos = getNodePos(); + scanner.setSkipJsDocLeadingAsterisks(true); + let typeArguments: NodeArray; + if (token() === SyntaxKind.LessThanToken) { + typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseJSDocType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); + } + else { + typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseJSDocType, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken); + } + scanner.setSkipJsDocLeadingAsterisks(false); + const comments = margin !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), margin, indentText) : undefined; + return finishNode(factory.createJSDocSpecializeTag(tagName, typeArguments, comments), pos); + } + function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression; } { const usedBrace = parseOptional(SyntaxKind.OpenBraceToken); const pos = getNodePos(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 08c3b16898307..1c6002563b3c9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -441,6 +441,7 @@ export const enum SyntaxKind { JSDocThrowsTag, JSDocSatisfiesTag, JSDocImportTag, + JSDocSpecializeTag, // Synthesized list SyntaxList, @@ -1055,7 +1056,8 @@ export type ForEachChildNodes = | JSDocOverrideTag | JSDocSatisfiesTag | JSDocOverloadTag - | JSDocImportTag; + | JSDocImportTag + | JSDocSpecializeTag; /** @internal */ export type HasChildren = @@ -4144,6 +4146,11 @@ export interface JSDocImportTag extends JSDocTag { readonly attributes?: ImportAttributes; } +export interface JSDocSpecializeTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocSpecializeTag; + readonly typeArguments: NodeArray; +} + // NOTE: Ensure this is up-to-date with src/debug/debug.ts // dprint-ignore /** @internal */ @@ -9216,6 +9223,8 @@ export interface NodeFactory { updateJSDocSatisfiesTag(node: JSDocSatisfiesTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray | undefined): JSDocSatisfiesTag; createJSDocImportTag(tagName: Identifier | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression, attributes?: ImportAttributes, comment?: string | NodeArray): JSDocImportTag; updateJSDocImportTag(node: JSDocImportTag, tagName: Identifier | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression, attributes: ImportAttributes | undefined, comment: string | NodeArray | undefined): JSDocImportTag; + createJSDocSpecializeTag(tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray | undefined): JSDocSpecializeTag; + updateJSDocSpecializeTag(node: JSDocSpecializeTag, tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray | undefined): JSDocSpecializeTag; createJSDocText(text: string): JSDocText; updateJSDocText(node: JSDocText, text: string): JSDocText; createJSDocComment(comment?: string | NodeArray | undefined, tags?: readonly JSDocTag[] | undefined): JSDoc; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 38425f9ab052e..dc15bbdafe5cd 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -316,6 +316,7 @@ import { isJSDocReturnTag, isJSDocSatisfiesTag, isJSDocSignature, + isJSDocSpecializeTag, isJSDocTag, isJSDocTemplateTag, isJSDocTypeExpression, @@ -4699,7 +4700,7 @@ function filterOwnedJSDocTags(hostNode: Node, comments: JSDocArray) { * a ParenthesizedExpression belongs only to the ParenthesizedExpression. */ function ownsJSDocTag(hostNode: Node, tag: JSDocTag) { - return !(isJSDocTypeTag(tag) || isJSDocSatisfiesTag(tag)) + return !(isJSDocTypeTag(tag) || isJSDocSatisfiesTag(tag) || isJSDocSpecializeTag(tag)) || !tag.parent || !isJSDoc(tag.parent) || !isParenthesizedExpression(tag.parent.parent) diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index 07242439c3409..ab3713d631927 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -151,6 +151,7 @@ import { isJSDocReturnTag, isJSDocSatisfiesTag, isJSDocSignature, + isJSDocSpecializeTag, isJSDocTemplateTag, isJSDocThisTag, isJSDocTypeAlias, @@ -198,6 +199,7 @@ import { JSDocReturnTag, JSDocSatisfiesTag, JSDocSignature, + JSDocSpecializeTag, JSDocTag, JSDocTemplateTag, JSDocThisTag, @@ -1200,6 +1202,10 @@ export function getJSDocSatisfiesTag(node: Node): JSDocSatisfiesTag | undefined return getFirstJSDocTag(node, isJSDocSatisfiesTag); } +export function getJSDocSpecializeTag(node: Node): JSDocSpecializeTag | undefined { + return getFirstJSDocTag(node, isJSDocSpecializeTag); +} + /** Gets the JSDoc type tag for the node if present and valid */ export function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined { // We should have already issued an error if there were multiple type jsdocs, so just use the first one. diff --git a/src/services/classifier.ts b/src/services/classifier.ts index cf2b087aeb85f..027fcedc52dc0 100644 --- a/src/services/classifier.ts +++ b/src/services/classifier.ts @@ -45,6 +45,7 @@ import { JSDocPropertyTag, JSDocReturnTag, JSDocSeeTag, + JSDocSpecializeTag, JSDocTemplateTag, JSDocThisTag, JSDocThrowsTag, @@ -847,6 +848,11 @@ export function getEncodedSyntacticClassifications(cancellationToken: Cancellati processJSDocParameterTag(param); commentStart = param.isNameFirst && param.typeExpression?.end || param.name.end; break; + case SyntaxKind.JSDocSpecializeTag: + const typeArguments = (tag as JSDocSpecializeTag).typeArguments; + typeArguments.forEach(processElement); + commentStart = typeArguments.end; + break; case SyntaxKind.JSDocPropertyTag: const prop = tag as JSDocPropertyTag; commentStart = prop.isNameFirst && prop.typeExpression?.end || prop.name.end; diff --git a/src/services/codefixes/fixAddVoidToPromise.ts b/src/services/codefixes/fixAddVoidToPromise.ts index a9061b9d24ca7..f260a0c412e11 100644 --- a/src/services/codefixes/fixAddVoidToPromise.ts +++ b/src/services/codefixes/fixAddVoidToPromise.ts @@ -7,6 +7,7 @@ import { CodeFixAllContext, Diagnostics, factory, + getJSDocSpecializeTag, getJSDocTypeTag, getTokenAtPosition, idText, @@ -101,6 +102,10 @@ function makeChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, function getEffectiveTypeArguments(node: NewExpression) { if (isInJSFile(node)) { if (isParenthesizedExpression(node.parent)) { + const specializeTag = getJSDocSpecializeTag(node.parent); + if (specializeTag) { + return specializeTag.typeArguments; + } const jsDocType = getJSDocTypeTag(node.parent)?.typeExpression.type; if (jsDocType && isTypeReferenceNode(jsDocType) && isIdentifier(jsDocType.typeName) && idText(jsDocType.typeName) === "Promise") { return jsDocType.typeArguments; diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 0f8cb859d6822..bc336fbe70e07 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -58,6 +58,7 @@ import { JSDocPropertyTag, JSDocSatisfiesTag, JSDocSeeTag, + JSDocSpecializeTag, JSDocTag, JSDocTagInfo, JSDocTemplateTag, @@ -165,6 +166,7 @@ const jsDocTagNames = [ "satisfies", "see", "since", + "specialize", "static", "summary", "template", @@ -297,7 +299,7 @@ function getCommentDisplayParts(tag: JSDocTag, checker?: TypeChecker): SymbolDis return withNode((tag as JSDocImplementsTag).class); case SyntaxKind.JSDocAugmentsTag: return withNode((tag as JSDocAugmentsTag).class); - case SyntaxKind.JSDocTemplateTag: + case SyntaxKind.JSDocTemplateTag: { const templateTag = tag as JSDocTemplateTag; const displayParts: SymbolDisplayPart[] = []; if (templateTag.constraint) { @@ -319,6 +321,21 @@ function getCommentDisplayParts(tag: JSDocTag, checker?: TypeChecker): SymbolDis displayParts.push(...[spacePart(), ...getDisplayPartsFromComment(comment, checker)]); } return displayParts; + } + case SyntaxKind.JSDocSpecializeTag: { + const specializeTag = tag as JSDocSpecializeTag; + const displayParts: SymbolDisplayPart[] = []; + if (specializeTag.typeArguments.length > 0) { + const lastTypeArgument = specializeTag.typeArguments[specializeTag.typeArguments.length - 1]; + forEach(specializeTag.typeArguments, typeArg => { + displayParts.push(textPart(typeArg.getText())); + if (lastTypeArgument !== typeArg) { + displayParts.push(...[punctuationPart(SyntaxKind.CommaToken), spacePart()]); + } + }); + } + return displayParts; + } case SyntaxKind.JSDocTypeTag: case SyntaxKind.JSDocSatisfiesTag: return withNode((tag as JSDocTypeTag | JSDocSatisfiesTag).typeExpression); diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 0f39eae746a04..e8fb4376bc21f 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4037,13 +4037,14 @@ declare namespace ts { JSDocThrowsTag = 350, JSDocSatisfiesTag = 351, JSDocImportTag = 352, - SyntaxList = 353, - NotEmittedStatement = 354, - NotEmittedTypeElement = 355, - PartiallyEmittedExpression = 356, - CommaListExpression = 357, - SyntheticReferenceExpression = 358, - Count = 359, + JSDocSpecializeTag = 353, + SyntaxList = 354, + NotEmittedStatement = 355, + NotEmittedTypeElement = 356, + PartiallyEmittedExpression = 357, + CommaListExpression = 358, + SyntheticReferenceExpression = 359, + Count = 360, FirstAssignment = 64, LastAssignment = 79, FirstCompoundAssignment = 65, @@ -5897,6 +5898,10 @@ declare namespace ts { readonly moduleSpecifier: Expression; readonly attributes?: ImportAttributes; } + interface JSDocSpecializeTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocSpecializeTag; + readonly typeArguments: NodeArray; + } type FlowType = Type | IncompleteType; interface IncompleteType { flags: TypeFlags | 0; @@ -7863,6 +7868,8 @@ declare namespace ts { updateJSDocSatisfiesTag(node: JSDocSatisfiesTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray | undefined): JSDocSatisfiesTag; createJSDocImportTag(tagName: Identifier | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression, attributes?: ImportAttributes, comment?: string | NodeArray): JSDocImportTag; updateJSDocImportTag(node: JSDocImportTag, tagName: Identifier | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression, attributes: ImportAttributes | undefined, comment: string | NodeArray | undefined): JSDocImportTag; + createJSDocSpecializeTag(tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray | undefined): JSDocSpecializeTag; + updateJSDocSpecializeTag(node: JSDocSpecializeTag, tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray | undefined): JSDocSpecializeTag; createJSDocText(text: string): JSDocText; updateJSDocText(node: JSDocText, text: string): JSDocText; createJSDocComment(comment?: string | NodeArray | undefined, tags?: readonly JSDocTag[] | undefined): JSDoc; @@ -8725,6 +8732,7 @@ declare namespace ts { /** Gets the JSDoc template tag for the node if present */ function getJSDocTemplateTag(node: Node): JSDocTemplateTag | undefined; function getJSDocSatisfiesTag(node: Node): JSDocSatisfiesTag | undefined; + function getJSDocSpecializeTag(node: Node): JSDocSpecializeTag | undefined; /** Gets the JSDoc type tag for the node if present and valid */ function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined; /** @@ -9176,6 +9184,7 @@ declare namespace ts { function isJSDocSatisfiesTag(node: Node): node is JSDocSatisfiesTag; function isJSDocThrowsTag(node: Node): node is JSDocThrowsTag; function isJSDocImportTag(node: Node): node is JSDocImportTag; + function isJSDocSpecializeTag(node: Node): node is JSDocSpecializeTag; function isQuestionOrExclamationToken(node: Node): node is QuestionToken | ExclamationToken; function isIdentifierOrThisTypeNode(node: Node): node is Identifier | ThisTypeNode; function isReadonlyKeywordOrPlusOrMinusToken(node: Node): node is ReadonlyKeyword | PlusToken | MinusToken; diff --git a/tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline b/tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline index bee86ad157385..4bc09b9071386 100644 --- a/tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline +++ b/tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline @@ -74,6 +74,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -168,6 +169,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -264,6 +266,7 @@ // | @satisfies // | @see // | @since +// | @specialize // | @static // | @summary // | @template @@ -358,6 +361,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -454,6 +458,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -550,6 +555,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -647,6 +653,7 @@ // | @satisfies // | @see // | @since +// | @specialize // | @static // | @summary // | @template @@ -741,6 +748,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -840,6 +848,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -941,6 +950,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1037,6 +1047,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1134,6 +1145,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1231,6 +1243,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1327,6 +1340,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1424,6 +1438,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1518,6 +1533,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1613,6 +1629,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -2555,6 +2572,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -3692,6 +3722,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -4829,6 +4872,19 @@ ], "documentation": [] }, + { + "name": "@specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "@specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "@static", "kind": "", @@ -5953,6 +6009,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -7077,6 +7146,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -8201,6 +8283,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -9338,6 +9433,19 @@ ], "documentation": [] }, + { + "name": "@specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "@specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "@static", "kind": "", @@ -10462,6 +10570,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -11586,6 +11707,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -12710,6 +12844,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -13834,6 +13981,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -14958,6 +15118,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -16082,6 +16255,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -17206,6 +17392,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -18343,6 +18542,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -19467,6 +19679,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -20591,6 +20816,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", diff --git a/tests/baselines/reference/jsdocParameterTagSnippetCompletion2.baseline b/tests/baselines/reference/jsdocParameterTagSnippetCompletion2.baseline index 973d96fd68aa4..9e22c5e51838c 100644 --- a/tests/baselines/reference/jsdocParameterTagSnippetCompletion2.baseline +++ b/tests/baselines/reference/jsdocParameterTagSnippetCompletion2.baseline @@ -74,6 +74,7 @@ // | @satisfies // | @see // | @since +// | @specialize // | @static // | @summary // | @template @@ -168,6 +169,7 @@ // | @satisfies // | @see // | @since +// | @specialize // | @static // | @summary // | @template @@ -262,6 +264,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -361,6 +364,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -454,6 +458,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1395,6 +1400,19 @@ ], "documentation": [] }, + { + "name": "@specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "@specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "@static", "kind": "", @@ -2521,6 +2539,19 @@ ], "documentation": [] }, + { + "name": "@specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "@specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "@static", "kind": "", @@ -3647,6 +3678,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -4773,6 +4817,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -5899,6 +5956,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", diff --git a/tests/baselines/reference/jsdocParameterTagSnippetCompletion3.baseline b/tests/baselines/reference/jsdocParameterTagSnippetCompletion3.baseline index 1496e3085daab..f0ebe5f3df5c9 100644 --- a/tests/baselines/reference/jsdocParameterTagSnippetCompletion3.baseline +++ b/tests/baselines/reference/jsdocParameterTagSnippetCompletion3.baseline @@ -74,6 +74,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -166,6 +167,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -259,6 +261,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -355,6 +358,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -451,6 +455,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -545,6 +550,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1487,6 +1493,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -2611,6 +2630,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -3735,6 +3767,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -4859,6 +4904,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -5983,6 +6041,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", @@ -7107,6 +7178,19 @@ ], "documentation": [] }, + { + "name": "specialize", + "kind": "", + "kindModifiers": "", + "sortText": "11", + "displayParts": [ + { + "text": "specialize", + "kind": "text" + } + ], + "documentation": [] + }, { "name": "static", "kind": "", diff --git a/tests/baselines/reference/referencesForSpecializeTag.baseline.jsonc b/tests/baselines/reference/referencesForSpecializeTag.baseline.jsonc new file mode 100644 index 0000000000000..723e2127b67ae --- /dev/null +++ b/tests/baselines/reference/referencesForSpecializeTag.baseline.jsonc @@ -0,0 +1,131 @@ +// === findAllReferences === +// === /a.js === +// --- (line: 3) skipped --- +// class Collection {} +// +// /** +// * <|@typedef {object} [|{| isWriteAccess: true, isDefinition: true |}U/*FIND ALL REFS*/serData|] +// * @property {string} id +// * @property {string} name +// |>*/ +// +// /** @specialize <[|UserData|]> */ +// const users = new Collection('users'); + + // === Definitions === + // === /a.js === + // --- (line: 3) skipped --- + // class Collection {} + // + // /** + // * <|@typedef {object} [|U/*FIND ALL REFS*/serData|] + // * @property {string} id + // * @property {string} name + // |>*/ + // + // /** @specialize */ + // const users = new Collection('users'); + + // === Details === + [ + { + "containerKind": "", + "containerName": "", + "kind": "type", + "name": "type UserData = {\n id: string;\n name: string;\n}", + "displayParts": [ + { + "text": "type", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "UserData", + "kind": "aliasName" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "=", + "kind": "operator" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "{", + "kind": "punctuation" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "id", + "kind": "propertyName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "string", + "kind": "keyword" + }, + { + "text": ";", + "kind": "punctuation" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "name", + "kind": "propertyName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "string", + "kind": "keyword" + }, + { + "text": ";", + "kind": "punctuation" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": "}", + "kind": "punctuation" + } + ] + } + ] \ No newline at end of file diff --git a/tests/baselines/reference/specializeTag1.js b/tests/baselines/reference/specializeTag1.js new file mode 100644 index 0000000000000..552200169c683 --- /dev/null +++ b/tests/baselines/reference/specializeTag1.js @@ -0,0 +1,50 @@ +//// [tests/cases/conformance/jsdoc/specializeTag1.ts] //// + +//// [specializeTag1.js] +/** + * @template T + * @param {object} _jsonSchema + * @returns {(x: unknown) => x is T} + */ +function createValidator(_jsonSchema) { + /** + * @param {unknown} _x + * @returns {_x is T} + */ + return (_x) => true; +} + +/** @specialize */ +const isNumber = createValidator({ type: 'number' }); + +const isString = /** @specialize */(createValidator({ type: 'string' })); + + +//// [specializeTag1.js] +/** + * @template T + * @param {object} _jsonSchema + * @returns {(x: unknown) => x is T} + */ +function createValidator(_jsonSchema) { + /** + * @param {unknown} _x + * @returns {_x is T} + */ + return function (_x) { return true; }; +} +/** @specialize */ +var isNumber = createValidator({ type: 'number' }); +var isString = /** @specialize */ (createValidator({ type: 'string' })); + + +//// [specializeTag1.d.ts] +/** + * @template T + * @param {object} _jsonSchema + * @returns {(x: unknown) => x is T} + */ +declare function createValidator(_jsonSchema: object): (x: unknown) => x is T; +/** @specialize */ +declare const isNumber: (x: unknown) => x is number; +declare const isString: (x: unknown) => x is string; diff --git a/tests/baselines/reference/specializeTag1.symbols b/tests/baselines/reference/specializeTag1.symbols new file mode 100644 index 0000000000000..7e95c54fbfb7d --- /dev/null +++ b/tests/baselines/reference/specializeTag1.symbols @@ -0,0 +1,31 @@ +//// [tests/cases/conformance/jsdoc/specializeTag1.ts] //// + +=== specializeTag1.js === +/** + * @template T + * @param {object} _jsonSchema + * @returns {(x: unknown) => x is T} + */ +function createValidator(_jsonSchema) { +>createValidator : Symbol(createValidator, Decl(specializeTag1.js, 0, 0)) +>_jsonSchema : Symbol(_jsonSchema, Decl(specializeTag1.js, 5, 25)) + + /** + * @param {unknown} _x + * @returns {_x is T} + */ + return (_x) => true; +>_x : Symbol(_x, Decl(specializeTag1.js, 10, 12)) +} + +/** @specialize */ +const isNumber = createValidator({ type: 'number' }); +>isNumber : Symbol(isNumber, Decl(specializeTag1.js, 14, 5)) +>createValidator : Symbol(createValidator, Decl(specializeTag1.js, 0, 0)) +>type : Symbol(type, Decl(specializeTag1.js, 14, 34)) + +const isString = /** @specialize */(createValidator({ type: 'string' })); +>isString : Symbol(isString, Decl(specializeTag1.js, 16, 5)) +>createValidator : Symbol(createValidator, Decl(specializeTag1.js, 0, 0)) +>type : Symbol(type, Decl(specializeTag1.js, 16, 62)) + diff --git a/tests/baselines/reference/specializeTag1.types b/tests/baselines/reference/specializeTag1.types new file mode 100644 index 0000000000000..9a12ea335794c --- /dev/null +++ b/tests/baselines/reference/specializeTag1.types @@ -0,0 +1,57 @@ +//// [tests/cases/conformance/jsdoc/specializeTag1.ts] //// + +=== specializeTag1.js === +/** + * @template T + * @param {object} _jsonSchema + * @returns {(x: unknown) => x is T} + */ +function createValidator(_jsonSchema) { +>createValidator : (_jsonSchema: object) => (x: unknown) => x is T +> : ^ ^^ ^^ ^^^^^ +>_jsonSchema : any + + /** + * @param {unknown} _x + * @returns {_x is T} + */ + return (_x) => true; +>(_x) => true : (_x: unknown) => _x is T +> : ^ ^^ ^^^^^ +>_x : unknown +> : ^^^^^^^ +>true : true +> : ^^^^ +} + +/** @specialize */ +const isNumber = createValidator({ type: 'number' }); +>isNumber : (x: unknown) => x is number +> : ^ ^^ ^^^^^ ^^^^^^ +>createValidator({ type: 'number' }) : (x: unknown) => x is number +> : ^ ^^ ^^^^^ ^^^^^^ +>createValidator : (_jsonSchema: object) => (x: unknown) => x is T +> : ^ ^^ ^^ ^^^^^ +>{ type: 'number' } : { type: string; } +> : ^^^^^^^^^^^^^^^^^ +>type : string +> : ^^^^^^ +>'number' : "number" +> : ^^^^^^^^ + +const isString = /** @specialize */(createValidator({ type: 'string' })); +>isString : (x: unknown) => x is string +> : ^ ^^ ^^^^^ ^^^^^^ +>(createValidator({ type: 'string' })) : (x: unknown) => x is string +> : ^ ^^ ^^^^^ ^^^^^^ +>createValidator({ type: 'string' }) : (x: unknown) => x is string +> : ^ ^^ ^^^^^ ^^^^^^ +>createValidator : (_jsonSchema: object) => (x: unknown) => x is T +> : ^ ^^ ^^ ^^^^^ +>{ type: 'string' } : { type: string; } +> : ^^^^^^^^^^^^^^^^^ +>type : string +> : ^^^^^^ +>'string' : "string" +> : ^^^^^^^^ + diff --git a/tests/baselines/reference/specializeTag2.errors.txt b/tests/baselines/reference/specializeTag2.errors.txt new file mode 100644 index 0000000000000..250b1e4ce2267 --- /dev/null +++ b/tests/baselines/reference/specializeTag2.errors.txt @@ -0,0 +1,36 @@ +specializeTag2.js(19,15): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. +specializeTag2.js(23,26): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. + + +==== specializeTag2.js (2 errors) ==== + /** + * @template T + * @param {TemplateStringsArray} strings + * @param {...T} values + * @returns {Record} + */ + function parse(strings, ...values) { + /** @type {Record} */ + const result = {}; + strings.forEach((key, i) => { + if (i < values.length) { + result[key] = values[i]; + } + }) + return result; + } + + const query1 = /** @specialize {string} */( + parse`a=${1}b=${2}` + ~ +!!! error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. + ) + + /** @specialize {string} */ + const query2 = parse`a=${1}b=${2}`; // Type error + ~ +!!! error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. + + /** @specialize <`${number}`> */ + const query3 = parse`a=${"1"}b=${"2"}`; + \ No newline at end of file diff --git a/tests/baselines/reference/specializeTag2.js b/tests/baselines/reference/specializeTag2.js new file mode 100644 index 0000000000000..5be65dab1e0c3 --- /dev/null +++ b/tests/baselines/reference/specializeTag2.js @@ -0,0 +1,76 @@ +//// [tests/cases/conformance/jsdoc/specializeTag2.ts] //// + +//// [specializeTag2.js] +/** + * @template T + * @param {TemplateStringsArray} strings + * @param {...T} values + * @returns {Record} + */ +function parse(strings, ...values) { + /** @type {Record} */ + const result = {}; + strings.forEach((key, i) => { + if (i < values.length) { + result[key] = values[i]; + } + }) + return result; +} + +const query1 = /** @specialize {string} */( + parse`a=${1}b=${2}` +) + +/** @specialize {string} */ +const query2 = parse`a=${1}b=${2}`; // Type error + +/** @specialize <`${number}`> */ +const query3 = parse`a=${"1"}b=${"2"}`; + + +//// [specializeTag2.js] +var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +}; +/** + * @template T + * @param {TemplateStringsArray} strings + * @param {...T} values + * @returns {Record} + */ +function parse(strings) { + var values = []; + for (var _i = 1; _i < arguments.length; _i++) { + values[_i - 1] = arguments[_i]; + } + /** @type {Record} */ + var result = {}; + strings.forEach(function (key, i) { + if (i < values.length) { + result[key] = values[i]; + } + }); + return result; +} +var query1 = /** @specialize {string} */ (parse(__makeTemplateObject(["a=", "b=", ""], ["a=", "b=", ""]), 1, 2)); +/** @specialize {string} */ +var query2 = parse(__makeTemplateObject(["a=", "b=", ""], ["a=", "b=", ""]), 1, 2); // Type error +/** @specialize <`${number}`> */ +var query3 = parse(__makeTemplateObject(["a=", "b=", ""], ["a=", "b=", ""]), "1", "2"); + + +//// [specializeTag2.d.ts] +/** + * @template T + * @param {TemplateStringsArray} strings + * @param {...T} values + * @returns {Record} + */ +declare function parse(strings: TemplateStringsArray, ...values: T[]): Record; +declare const query1: Record; +/** @specialize {string} */ +declare const query2: Record; +/** @specialize <`${number}`> */ +declare const query3: Record; diff --git a/tests/baselines/reference/specializeTag2.symbols b/tests/baselines/reference/specializeTag2.symbols new file mode 100644 index 0000000000000..7ce4b6b5b42e7 --- /dev/null +++ b/tests/baselines/reference/specializeTag2.symbols @@ -0,0 +1,60 @@ +//// [tests/cases/conformance/jsdoc/specializeTag2.ts] //// + +=== specializeTag2.js === +/** + * @template T + * @param {TemplateStringsArray} strings + * @param {...T} values + * @returns {Record} + */ +function parse(strings, ...values) { +>parse : Symbol(parse, Decl(specializeTag2.js, 0, 0)) +>strings : Symbol(strings, Decl(specializeTag2.js, 6, 15)) +>values : Symbol(values, Decl(specializeTag2.js, 6, 23)) + + /** @type {Record} */ + const result = {}; +>result : Symbol(result, Decl(specializeTag2.js, 8, 9)) + + strings.forEach((key, i) => { +>strings.forEach : Symbol(ReadonlyArray.forEach, Decl(lib.es5.d.ts, --, --)) +>strings : Symbol(strings, Decl(specializeTag2.js, 6, 15)) +>forEach : Symbol(ReadonlyArray.forEach, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(specializeTag2.js, 9, 21)) +>i : Symbol(i, Decl(specializeTag2.js, 9, 25)) + + if (i < values.length) { +>i : Symbol(i, Decl(specializeTag2.js, 9, 25)) +>values.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>values : Symbol(values, Decl(specializeTag2.js, 6, 23)) +>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) + + result[key] = values[i]; +>result : Symbol(result, Decl(specializeTag2.js, 8, 9)) +>key : Symbol(key, Decl(specializeTag2.js, 9, 21)) +>values : Symbol(values, Decl(specializeTag2.js, 6, 23)) +>i : Symbol(i, Decl(specializeTag2.js, 9, 25)) + } + }) + return result; +>result : Symbol(result, Decl(specializeTag2.js, 8, 9)) +} + +const query1 = /** @specialize {string} */( +>query1 : Symbol(query1, Decl(specializeTag2.js, 17, 5)) + + parse`a=${1}b=${2}` +>parse : Symbol(parse, Decl(specializeTag2.js, 0, 0)) + +) + +/** @specialize {string} */ +const query2 = parse`a=${1}b=${2}`; // Type error +>query2 : Symbol(query2, Decl(specializeTag2.js, 22, 5)) +>parse : Symbol(parse, Decl(specializeTag2.js, 0, 0)) + +/** @specialize <`${number}`> */ +const query3 = parse`a=${"1"}b=${"2"}`; +>query3 : Symbol(query3, Decl(specializeTag2.js, 25, 5)) +>parse : Symbol(parse, Decl(specializeTag2.js, 0, 0)) + diff --git a/tests/baselines/reference/specializeTag2.types b/tests/baselines/reference/specializeTag2.types new file mode 100644 index 0000000000000..482769786af53 --- /dev/null +++ b/tests/baselines/reference/specializeTag2.types @@ -0,0 +1,124 @@ +//// [tests/cases/conformance/jsdoc/specializeTag2.ts] //// + +=== specializeTag2.js === +/** + * @template T + * @param {TemplateStringsArray} strings + * @param {...T} values + * @returns {Record} + */ +function parse(strings, ...values) { +>parse : (strings: TemplateStringsArray, ...values: T[]) => Record +> : ^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>strings : TemplateStringsArray +> : ^^^^^^^^^^^^^^^^^^^^ +>values : T[] +> : ^^^ + + /** @type {Record} */ + const result = {}; +>result : Record +> : ^^^^^^^^^^^^^^^^^ +>{} : {} +> : ^^ + + strings.forEach((key, i) => { +>strings.forEach((key, i) => { if (i < values.length) { result[key] = values[i]; } }) : void +> : ^^^^ +>strings.forEach : (callbackfn: (value: string, index: number, array: readonly string[]) => void, thisArg?: any) => void +> : ^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ +>strings : TemplateStringsArray +> : ^^^^^^^^^^^^^^^^^^^^ +>forEach : (callbackfn: (value: string, index: number, array: readonly string[]) => void, thisArg?: any) => void +> : ^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ +>(key, i) => { if (i < values.length) { result[key] = values[i]; } } : (key: string, i: number) => void +> : ^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ +>i : number +> : ^^^^^^ + + if (i < values.length) { +>i < values.length : boolean +> : ^^^^^^^ +>i : number +> : ^^^^^^ +>values.length : number +> : ^^^^^^ +>values : T[] +> : ^^^ +>length : number +> : ^^^^^^ + + result[key] = values[i]; +>result[key] = values[i] : T +> : ^ +>result[key] : T +> : ^ +>result : Record +> : ^^^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ +>values[i] : T +> : ^ +>values : T[] +> : ^^^ +>i : number +> : ^^^^^^ + } + }) + return result; +>result : Record +> : ^^^^^^^^^^^^^^^^^ +} + +const query1 = /** @specialize {string} */( +>query1 : Record +> : ^^^^^^^^^^^^^^^^^^^^^^ +>( parse`a=${1}b=${2}`) : Record +> : ^^^^^^^^^^^^^^^^^^^^^^ + + parse`a=${1}b=${2}` +>parse`a=${1}b=${2}` : Record +> : ^^^^^^^^^^^^^^^^^^^^^^ +>parse : (strings: TemplateStringsArray, ...values: T[]) => Record +> : ^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>`a=${1}b=${2}` : string +> : ^^^^^^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ + +) + +/** @specialize {string} */ +const query2 = parse`a=${1}b=${2}`; // Type error +>query2 : Record +> : ^^^^^^^^^^^^^^^^^^^^^^ +>parse`a=${1}b=${2}` : Record +> : ^^^^^^^^^^^^^^^^^^^^^^ +>parse : (strings: TemplateStringsArray, ...values: T[]) => Record +> : ^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>`a=${1}b=${2}` : string +> : ^^^^^^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ + +/** @specialize <`${number}`> */ +const query3 = parse`a=${"1"}b=${"2"}`; +>query3 : Record +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>parse`a=${"1"}b=${"2"}` : Record +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>parse : (strings: TemplateStringsArray, ...values: T[]) => Record +> : ^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>`a=${"1"}b=${"2"}` : string +> : ^^^^^^ +>"1" : "1" +> : ^^^ +>"2" : "2" +> : ^^^ + diff --git a/tests/baselines/reference/specializeTag3.js b/tests/baselines/reference/specializeTag3.js new file mode 100644 index 0000000000000..a4fad934efeee --- /dev/null +++ b/tests/baselines/reference/specializeTag3.js @@ -0,0 +1,84 @@ +//// [tests/cases/conformance/jsdoc/specializeTag3.ts] //// + +//// [specializeTag3.js] +/** + * @template T + */ +class JsonSchemaValidator { + /** + * @param {object} jsonSchema + */ + constructor(jsonSchema) { + /** @type {object} */ + this.jsonSchema = jsonSchema; + } + + /** + * @param {unknown} _value + * @returns {_value is T} + */ + isValid(_value) { + return true; + } +} + +const number = /** @specialize {number} */( + new JsonSchemaValidator({ type: 'number' }) +); + +/** @specialize {number[]} */ +const arrayOfNumbers = new JsonSchemaValidator({ + type: 'array', + items: { type: 'number' }, +}); + + +//// [specializeTag3.js] +/** + * @template T + */ +var JsonSchemaValidator = /** @class */ (function () { + /** + * @param {object} jsonSchema + */ + function JsonSchemaValidator(jsonSchema) { + /** @type {object} */ + this.jsonSchema = jsonSchema; + } + /** + * @param {unknown} _value + * @returns {_value is T} + */ + JsonSchemaValidator.prototype.isValid = function (_value) { + return true; + }; + return JsonSchemaValidator; +}()); +var number = /** @specialize {number} */ (new JsonSchemaValidator({ type: 'number' })); +/** @specialize {number[]} */ +var arrayOfNumbers = new JsonSchemaValidator({ + type: 'array', + items: { type: 'number' }, +}); + + +//// [specializeTag3.d.ts] +/** + * @template T + */ +declare class JsonSchemaValidator { + /** + * @param {object} jsonSchema + */ + constructor(jsonSchema: object); + /** @type {object} */ + jsonSchema: object; + /** + * @param {unknown} _value + * @returns {_value is T} + */ + isValid(_value: unknown): _value is T; +} +declare const number: JsonSchemaValidator; +/** @specialize {number[]} */ +declare const arrayOfNumbers: JsonSchemaValidator; diff --git a/tests/baselines/reference/specializeTag3.symbols b/tests/baselines/reference/specializeTag3.symbols new file mode 100644 index 0000000000000..d5302b50ce87c --- /dev/null +++ b/tests/baselines/reference/specializeTag3.symbols @@ -0,0 +1,58 @@ +//// [tests/cases/conformance/jsdoc/specializeTag3.ts] //// + +=== specializeTag3.js === +/** + * @template T + */ +class JsonSchemaValidator { +>JsonSchemaValidator : Symbol(JsonSchemaValidator, Decl(specializeTag3.js, 0, 0)) + + /** + * @param {object} jsonSchema + */ + constructor(jsonSchema) { +>jsonSchema : Symbol(jsonSchema, Decl(specializeTag3.js, 7, 16)) + + /** @type {object} */ + this.jsonSchema = jsonSchema; +>this.jsonSchema : Symbol(JsonSchemaValidator.jsonSchema, Decl(specializeTag3.js, 7, 29)) +>this : Symbol(JsonSchemaValidator, Decl(specializeTag3.js, 0, 0)) +>jsonSchema : Symbol(JsonSchemaValidator.jsonSchema, Decl(specializeTag3.js, 7, 29)) +>jsonSchema : Symbol(jsonSchema, Decl(specializeTag3.js, 7, 16)) + } + + /** + * @param {unknown} _value + * @returns {_value is T} + */ + isValid(_value) { +>isValid : Symbol(JsonSchemaValidator.isValid, Decl(specializeTag3.js, 10, 5)) +>_value : Symbol(_value, Decl(specializeTag3.js, 16, 12)) + + return true; + } +} + +const number = /** @specialize {number} */( +>number : Symbol(number, Decl(specializeTag3.js, 21, 5)) + + new JsonSchemaValidator({ type: 'number' }) +>JsonSchemaValidator : Symbol(JsonSchemaValidator, Decl(specializeTag3.js, 0, 0)) +>type : Symbol(type, Decl(specializeTag3.js, 22, 29)) + +); + +/** @specialize {number[]} */ +const arrayOfNumbers = new JsonSchemaValidator({ +>arrayOfNumbers : Symbol(arrayOfNumbers, Decl(specializeTag3.js, 26, 5)) +>JsonSchemaValidator : Symbol(JsonSchemaValidator, Decl(specializeTag3.js, 0, 0)) + + type: 'array', +>type : Symbol(type, Decl(specializeTag3.js, 26, 48)) + + items: { type: 'number' }, +>items : Symbol(items, Decl(specializeTag3.js, 27, 18)) +>type : Symbol(type, Decl(specializeTag3.js, 28, 12)) + +}); + diff --git a/tests/baselines/reference/specializeTag3.types b/tests/baselines/reference/specializeTag3.types new file mode 100644 index 0000000000000..6e4ccc3170aa7 --- /dev/null +++ b/tests/baselines/reference/specializeTag3.types @@ -0,0 +1,92 @@ +//// [tests/cases/conformance/jsdoc/specializeTag3.ts] //// + +=== specializeTag3.js === +/** + * @template T + */ +class JsonSchemaValidator { +>JsonSchemaValidator : JsonSchemaValidator +> : ^^^^^^^^^^^^^^^^^^^^^^ + + /** + * @param {object} jsonSchema + */ + constructor(jsonSchema) { +>jsonSchema : any + + /** @type {object} */ + this.jsonSchema = jsonSchema; +>this.jsonSchema = jsonSchema : any +>this.jsonSchema : any +>this : this +> : ^^^^ +>jsonSchema : any +> : ^^^ +>jsonSchema : any + } + + /** + * @param {unknown} _value + * @returns {_value is T} + */ + isValid(_value) { +>isValid : (_value: unknown) => _value is T +> : ^ ^^ ^^^^^ +>_value : unknown +> : ^^^^^^^ + + return true; +>true : true +> : ^^^^ + } +} + +const number = /** @specialize {number} */( +>number : JsonSchemaValidator +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>( new JsonSchemaValidator({ type: 'number' })) : JsonSchemaValidator +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + new JsonSchemaValidator({ type: 'number' }) +>new JsonSchemaValidator({ type: 'number' }) : JsonSchemaValidator +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>JsonSchemaValidator : typeof JsonSchemaValidator +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ type: 'number' } : { type: string; } +> : ^^^^^^^^^^^^^^^^^ +>type : string +> : ^^^^^^ +>'number' : "number" +> : ^^^^^^^^ + +); + +/** @specialize {number[]} */ +const arrayOfNumbers = new JsonSchemaValidator({ +>arrayOfNumbers : JsonSchemaValidator +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>new JsonSchemaValidator({ type: 'array', items: { type: 'number' },}) : JsonSchemaValidator +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>JsonSchemaValidator : typeof JsonSchemaValidator +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ type: 'array', items: { type: 'number' },} : { type: string; items: { type: string; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + type: 'array', +>type : string +> : ^^^^^^ +>'array' : "array" +> : ^^^^^^^ + + items: { type: 'number' }, +>items : { type: string; } +> : ^^^^^^^^^^^^^^^^^ +>{ type: 'number' } : { type: string; } +> : ^^^^^^^^^^^^^^^^^ +>type : string +> : ^^^^^^ +>'number' : "number" +> : ^^^^^^^^ + +}); + diff --git a/tests/baselines/reference/specializeTag4.errors.txt b/tests/baselines/reference/specializeTag4.errors.txt new file mode 100644 index 0000000000000..6139bc0f35023 --- /dev/null +++ b/tests/baselines/reference/specializeTag4.errors.txt @@ -0,0 +1,39 @@ +specializeTag4.js(31,18): error TS2344: Type 'number' does not satisfy the constraint '{ id: string; }'. + + +==== specializeTag4.js (1 errors) ==== + /** + * @template {{ id: string }} T + */ + class Collection { + /** + * @param {string} name + */ + constructor(name) { + /** @type {string} */ + this.name = name; + } + + /** + * @param {string} id + * @returns {T} + */ + getById(id) { + return /** @type {T} */({ id }); + } + } + + /** + * @typedef {object} UserData + * @property {string} id + * @property {string} name + */ + + /** @specialize */ + const users = new Collection('users'); + + /** @specialize */ + ~~~~~~ +!!! error TS2344: Type 'number' does not satisfy the constraint '{ id: string; }'. + const numbers = new Collection('numbers'); + \ No newline at end of file diff --git a/tests/baselines/reference/specializeTag4.js b/tests/baselines/reference/specializeTag4.js new file mode 100644 index 0000000000000..cbc86badcd15e --- /dev/null +++ b/tests/baselines/reference/specializeTag4.js @@ -0,0 +1,101 @@ +//// [tests/cases/conformance/jsdoc/specializeTag4.ts] //// + +//// [specializeTag4.js] +/** + * @template {{ id: string }} T + */ +class Collection { + /** + * @param {string} name + */ + constructor(name) { + /** @type {string} */ + this.name = name; + } + + /** + * @param {string} id + * @returns {T} + */ + getById(id) { + return /** @type {T} */({ id }); + } +} + +/** + * @typedef {object} UserData + * @property {string} id + * @property {string} name + */ + +/** @specialize */ +const users = new Collection('users'); + +/** @specialize */ +const numbers = new Collection('numbers'); + + +//// [specializeTag4.js] +/** + * @template {{ id: string }} T + */ +var Collection = /** @class */ (function () { + /** + * @param {string} name + */ + function Collection(name) { + /** @type {string} */ + this.name = name; + } + /** + * @param {string} id + * @returns {T} + */ + Collection.prototype.getById = function (id) { + return /** @type {T} */ ({ id: id }); + }; + return Collection; +}()); +/** + * @typedef {object} UserData + * @property {string} id + * @property {string} name + */ +/** @specialize */ +var users = new Collection('users'); +/** @specialize */ +var numbers = new Collection('numbers'); + + +//// [specializeTag4.d.ts] +/** + * @template {{ id: string }} T + */ +declare class Collection { + /** + * @param {string} name + */ + constructor(name: string); + /** @type {string} */ + name: string; + /** + * @param {string} id + * @returns {T} + */ + getById(id: string): T; +} +/** + * @typedef {object} UserData + * @property {string} id + * @property {string} name + */ +/** @specialize */ +declare const users: Collection; +/** @specialize */ +declare const numbers: Collection; +type UserData = { + id: string; + name: string; +}; diff --git a/tests/baselines/reference/specializeTag4.symbols b/tests/baselines/reference/specializeTag4.symbols new file mode 100644 index 0000000000000..869a0380ce322 --- /dev/null +++ b/tests/baselines/reference/specializeTag4.symbols @@ -0,0 +1,52 @@ +//// [tests/cases/conformance/jsdoc/specializeTag4.ts] //// + +=== specializeTag4.js === +/** + * @template {{ id: string }} T + */ +class Collection { +>Collection : Symbol(Collection, Decl(specializeTag4.js, 0, 0)) + + /** + * @param {string} name + */ + constructor(name) { +>name : Symbol(name, Decl(specializeTag4.js, 7, 16)) + + /** @type {string} */ + this.name = name; +>this.name : Symbol(Collection.name, Decl(specializeTag4.js, 7, 23)) +>this : Symbol(Collection, Decl(specializeTag4.js, 0, 0)) +>name : Symbol(Collection.name, Decl(specializeTag4.js, 7, 23)) +>name : Symbol(name, Decl(specializeTag4.js, 7, 16)) + } + + /** + * @param {string} id + * @returns {T} + */ + getById(id) { +>getById : Symbol(Collection.getById, Decl(specializeTag4.js, 10, 5)) +>id : Symbol(id, Decl(specializeTag4.js, 16, 12)) + + return /** @type {T} */({ id }); +>id : Symbol(id, Decl(specializeTag4.js, 17, 33)) + } +} + +/** + * @typedef {object} UserData + * @property {string} id + * @property {string} name + */ + +/** @specialize */ +const users = new Collection('users'); +>users : Symbol(users, Decl(specializeTag4.js, 28, 5)) +>Collection : Symbol(Collection, Decl(specializeTag4.js, 0, 0)) + +/** @specialize */ +const numbers = new Collection('numbers'); +>numbers : Symbol(numbers, Decl(specializeTag4.js, 31, 5)) +>Collection : Symbol(Collection, Decl(specializeTag4.js, 0, 0)) + diff --git a/tests/baselines/reference/specializeTag4.types b/tests/baselines/reference/specializeTag4.types new file mode 100644 index 0000000000000..edf6f9ea52407 --- /dev/null +++ b/tests/baselines/reference/specializeTag4.types @@ -0,0 +1,79 @@ +//// [tests/cases/conformance/jsdoc/specializeTag4.ts] //// + +=== specializeTag4.js === +/** + * @template {{ id: string }} T + */ +class Collection { +>Collection : Collection +> : ^^^^^^^^^^^^^ + + /** + * @param {string} name + */ + constructor(name) { +>name : string +> : ^^^^^^ + + /** @type {string} */ + this.name = name; +>this.name = name : string +> : ^^^^^^ +>this.name : string +> : ^^^^^^ +>this : this +> : ^^^^ +>name : string +> : ^^^^^^ +>name : string +> : ^^^^^^ + } + + /** + * @param {string} id + * @returns {T} + */ + getById(id) { +>getById : (id: string) => T +> : ^ ^^ ^^^^^ +>id : string +> : ^^^^^^ + + return /** @type {T} */({ id }); +>({ id }) : T +> : ^ +>{ id } : { id: string; } +> : ^^^^^^^^^^^^^^^ +>id : string +> : ^^^^^^ + } +} + +/** + * @typedef {object} UserData + * @property {string} id + * @property {string} name + */ + +/** @specialize */ +const users = new Collection('users'); +>users : Collection +> : ^^^^^^^^^^^^^^^^^^^^ +>new Collection('users') : Collection +> : ^^^^^^^^^^^^^^^^^^^^ +>Collection : typeof Collection +> : ^^^^^^^^^^^^^^^^^ +>'users' : "users" +> : ^^^^^^^ + +/** @specialize */ +const numbers = new Collection('numbers'); +>numbers : Collection +> : ^^^^^^^^^^^^^^^^^^ +>new Collection('numbers') : Collection +> : ^^^^^^^^^^^^^^^^^^ +>Collection : typeof Collection +> : ^^^^^^^^^^^^^^^^^ +>'numbers' : "numbers" +> : ^^^^^^^^^ + diff --git a/tests/baselines/reference/specializeTag5.js b/tests/baselines/reference/specializeTag5.js new file mode 100644 index 0000000000000..2d07ac784f1b0 --- /dev/null +++ b/tests/baselines/reference/specializeTag5.js @@ -0,0 +1,24 @@ +//// [tests/cases/conformance/jsdoc/specializeTag5.ts] //// + +//// [specializeTag5.js] +const fileInput1 = + /** @specialize {HTMLInputElement} */ + (document.querySelector("input[type=file]")) + +/** @specialize {HTMLInputElement} */ +const fileInput2 = + document.querySelector("input[type=file]") + + +//// [specializeTag5.js] +var fileInput1 = +/** @specialize {HTMLInputElement} */ +(document.querySelector("input[type=file]")); +/** @specialize {HTMLInputElement} */ +var fileInput2 = document.querySelector("input[type=file]"); + + +//// [specializeTag5.d.ts] +declare const fileInput1: HTMLInputElement; +/** @specialize {HTMLInputElement} */ +declare const fileInput2: HTMLInputElement; diff --git a/tests/baselines/reference/specializeTag5.symbols b/tests/baselines/reference/specializeTag5.symbols new file mode 100644 index 0000000000000..be401ace1c238 --- /dev/null +++ b/tests/baselines/reference/specializeTag5.symbols @@ -0,0 +1,21 @@ +//// [tests/cases/conformance/jsdoc/specializeTag5.ts] //// + +=== specializeTag5.js === +const fileInput1 = +>fileInput1 : Symbol(fileInput1, Decl(specializeTag5.js, 0, 5)) + + /** @specialize {HTMLInputElement} */ + (document.querySelector("input[type=file]")) +>document.querySelector : Symbol(ParentNode.querySelector, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --)) +>document : Symbol(document, Decl(lib.dom.d.ts, --, --)) +>querySelector : Symbol(ParentNode.querySelector, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --)) + +/** @specialize {HTMLInputElement} */ +const fileInput2 = +>fileInput2 : Symbol(fileInput2, Decl(specializeTag5.js, 5, 5)) + + document.querySelector("input[type=file]") +>document.querySelector : Symbol(ParentNode.querySelector, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --)) +>document : Symbol(document, Decl(lib.dom.d.ts, --, --)) +>querySelector : Symbol(ParentNode.querySelector, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --)) + diff --git a/tests/baselines/reference/specializeTag5.types b/tests/baselines/reference/specializeTag5.types new file mode 100644 index 0000000000000..76c3e060496ae --- /dev/null +++ b/tests/baselines/reference/specializeTag5.types @@ -0,0 +1,39 @@ +//// [tests/cases/conformance/jsdoc/specializeTag5.ts] //// + +=== specializeTag5.js === +const fileInput1 = +>fileInput1 : HTMLInputElement +> : ^^^^^^^^^^^^^^^^ + + /** @specialize {HTMLInputElement} */ + (document.querySelector("input[type=file]")) +>(document.querySelector("input[type=file]")) : HTMLInputElement +> : ^^^^^^^^^^^^^^^^ +>document.querySelector("input[type=file]") : HTMLInputElement +> : ^^^^^^^^^^^^^^^^ +>document.querySelector : { (selectors: K): HTMLElementTagNameMap[K] | null; (selectors: K): SVGElementTagNameMap[K] | null; (selectors: K): MathMLElementTagNameMap[K] | null; (selectors: K): HTMLElementDeprecatedTagNameMap[K] | null; (selectors: string): E | null; } +> : ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^^^^^^^^^^^ ^^ ^^^ ^^^ +>document : Document +> : ^^^^^^^^ +>querySelector : { (selectors: K): HTMLElementTagNameMap[K] | null; (selectors: K): SVGElementTagNameMap[K] | null; (selectors: K): MathMLElementTagNameMap[K] | null; (selectors: K): HTMLElementDeprecatedTagNameMap[K] | null; (selectors: string): E | null; } +> : ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^^^^^^^^^^^ ^^ ^^^ ^^^ +>"input[type=file]" : "input[type=file]" +> : ^^^^^^^^^^^^^^^^^^ + +/** @specialize {HTMLInputElement} */ +const fileInput2 = +>fileInput2 : HTMLInputElement +> : ^^^^^^^^^^^^^^^^ + + document.querySelector("input[type=file]") +>document.querySelector("input[type=file]") : HTMLInputElement +> : ^^^^^^^^^^^^^^^^ +>document.querySelector : { (selectors: K): HTMLElementTagNameMap[K] | null; (selectors: K): SVGElementTagNameMap[K] | null; (selectors: K): MathMLElementTagNameMap[K] | null; (selectors: K): HTMLElementDeprecatedTagNameMap[K] | null; (selectors: string): E | null; } +> : ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^^^^^^^^^^^ ^^ ^^^ ^^^ +>document : Document +> : ^^^^^^^^ +>querySelector : { (selectors: K): HTMLElementTagNameMap[K] | null; (selectors: K): SVGElementTagNameMap[K] | null; (selectors: K): MathMLElementTagNameMap[K] | null; (selectors: K): HTMLElementDeprecatedTagNameMap[K] | null; (selectors: string): E | null; } +> : ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^^^^^^^^^^^ ^^ ^^^ ^^^ +>"input[type=file]" : "input[type=file]" +> : ^^^^^^^^^^^^^^^^^^ + diff --git a/tests/baselines/reference/specializeTag6.errors.txt b/tests/baselines/reference/specializeTag6.errors.txt new file mode 100644 index 0000000000000..92aebc1f294f5 --- /dev/null +++ b/tests/baselines/reference/specializeTag6.errors.txt @@ -0,0 +1,34 @@ +specializeTag6.jsx(20,20): error TS2322: Type 'string' is not assignable to type 'number'. +specializeTag6.jsx(22,48): error TS2322: Type 'string' is not assignable to type 'number'. + + +==== specializeTag6.jsx (2 errors) ==== + /// + import React from 'react' + + /** + * @template T + * @param {object} props + * @param {T} props.value + * @returns {React.ReactElement} + */ + function Input(props) { + return null; + } + + /** @specialize {number} */ + const el1 = ; + + const el2 = /** @specialize {number} */(); + + /** @specialize {number} */ + const el3 = ; // Type error + ~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'number'. +!!! related TS6500 specializeTag6.jsx:7:4: The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & { value: number; }' + + const el4 = /** @specialize {number} */(); // Type error + ~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'number'. +!!! related TS6500 specializeTag6.jsx:7:4: The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & { value: number; }' + \ No newline at end of file diff --git a/tests/baselines/reference/specializeTag6.js b/tests/baselines/reference/specializeTag6.js new file mode 100644 index 0000000000000..9051f5756ca99 --- /dev/null +++ b/tests/baselines/reference/specializeTag6.js @@ -0,0 +1,54 @@ +//// [tests/cases/conformance/jsdoc/specializeTag6.ts] //// + +//// [specializeTag6.jsx] +/// +import React from 'react' + +/** + * @template T + * @param {object} props + * @param {T} props.value + * @returns {React.ReactElement} + */ +function Input(props) { + return null; +} + +/** @specialize {number} */ +const el1 = ; + +const el2 = /** @specialize {number} */(); + +/** @specialize {number} */ +const el3 = ; // Type error + +const el4 = /** @specialize {number} */(); // Type error + + +//// [specializeTag6.js] +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +/// +var react_1 = __importDefault(require("react")); +/** + * @template T + * @param {object} props + * @param {T} props.value + * @returns {React.ReactElement} + */ +function Input(props) { + return null; +} +/** @specialize {number} */ +var el1 = react_1.default.createElement(Input, { value: 1 }); +var el2 = /** @specialize {number} */ (react_1.default.createElement(Input, { value: 2 })); +/** @specialize {number} */ +var el3 = react_1.default.createElement(Input, { value: "abc" }); // Type error +var el4 = /** @specialize {number} */ (react_1.default.createElement(Input, { value: "abc" })); // Type error + + +//// [specializeTag6.d.ts] +export {}; diff --git a/tests/baselines/reference/specializeTag6.symbols b/tests/baselines/reference/specializeTag6.symbols new file mode 100644 index 0000000000000..fefeabaa8d6f3 --- /dev/null +++ b/tests/baselines/reference/specializeTag6.symbols @@ -0,0 +1,42 @@ +//// [tests/cases/conformance/jsdoc/specializeTag6.ts] //// + +=== specializeTag6.jsx === +/// +import React from 'react' +>React : Symbol(React, Decl(specializeTag6.jsx, 1, 6)) + +/** + * @template T + * @param {object} props + * @param {T} props.value + * @returns {React.ReactElement} + */ +function Input(props) { +>Input : Symbol(Input, Decl(specializeTag6.jsx, 1, 25)) +>props : Symbol(props, Decl(specializeTag6.jsx, 9, 15)) + + return null; +} + +/** @specialize {number} */ +const el1 = ; +>el1 : Symbol(el1, Decl(specializeTag6.jsx, 14, 5)) +>Input : Symbol(Input, Decl(specializeTag6.jsx, 1, 25)) +>value : Symbol(value, Decl(specializeTag6.jsx, 14, 18)) + +const el2 = /** @specialize {number} */(); +>el2 : Symbol(el2, Decl(specializeTag6.jsx, 16, 5)) +>Input : Symbol(Input, Decl(specializeTag6.jsx, 1, 25)) +>value : Symbol(value, Decl(specializeTag6.jsx, 16, 46)) + +/** @specialize {number} */ +const el3 = ; // Type error +>el3 : Symbol(el3, Decl(specializeTag6.jsx, 19, 5)) +>Input : Symbol(Input, Decl(specializeTag6.jsx, 1, 25)) +>value : Symbol(value, Decl(specializeTag6.jsx, 19, 18)) + +const el4 = /** @specialize {number} */(); // Type error +>el4 : Symbol(el4, Decl(specializeTag6.jsx, 21, 5)) +>Input : Symbol(Input, Decl(specializeTag6.jsx, 1, 25)) +>value : Symbol(value, Decl(specializeTag6.jsx, 21, 46)) + diff --git a/tests/baselines/reference/specializeTag6.types b/tests/baselines/reference/specializeTag6.types new file mode 100644 index 0000000000000..21495de7d2daa --- /dev/null +++ b/tests/baselines/reference/specializeTag6.types @@ -0,0 +1,79 @@ +//// [tests/cases/conformance/jsdoc/specializeTag6.ts] //// + +=== Performance Stats === +Assignability cache: 2,500 +Type Count: 5,000 +Instantiation count: 50,000 +Symbol count: 50,000 + +=== specializeTag6.jsx === +/// +import React from 'react' +>React : typeof React +> : ^^^^^^^^^^^^ + +/** + * @template T + * @param {object} props + * @param {T} props.value + * @returns {React.ReactElement} + */ +function Input(props) { +>Input : (props: { value: T;}) => React.ReactElement +> : ^ ^^ ^^ ^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>props : { value: T; } +> : ^^^^^^^^^ ^^^ + + return null; +} + +/** @specialize {number} */ +const el1 = ; +>el1 : JSX.Element +> : ^^^^^^^^^^^ +> : JSX.Element +> : ^^^^^^^^^^^ +>Input : (props: { value: T;}) => React.ReactElement +> : ^ ^^ ^^ ^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>value : number +> : ^^^^^^ +>1 : 1 +> : ^ + +const el2 = /** @specialize {number} */(); +>el2 : JSX.Element +> : ^^^^^^^^^^^ +>() : JSX.Element +> : ^^^^^^^^^^^ +> : JSX.Element +> : ^^^^^^^^^^^ +>Input : (props: { value: T;}) => React.ReactElement +> : ^ ^^ ^^ ^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>value : number +> : ^^^^^^ +>2 : 2 +> : ^ + +/** @specialize {number} */ +const el3 = ; // Type error +>el3 : JSX.Element +> : ^^^^^^^^^^^ +> : JSX.Element +> : ^^^^^^^^^^^ +>Input : (props: { value: T;}) => React.ReactElement +> : ^ ^^ ^^ ^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>value : string +> : ^^^^^^ + +const el4 = /** @specialize {number} */(); // Type error +>el4 : JSX.Element +> : ^^^^^^^^^^^ +>() : JSX.Element +> : ^^^^^^^^^^^ +> : JSX.Element +> : ^^^^^^^^^^^ +>Input : (props: { value: T;}) => React.ReactElement +> : ^ ^^ ^^ ^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>value : string +> : ^^^^^^ + diff --git a/tests/cases/conformance/jsdoc/specializeTag1.ts b/tests/cases/conformance/jsdoc/specializeTag1.ts new file mode 100644 index 0000000000000..c9f49f05552e5 --- /dev/null +++ b/tests/cases/conformance/jsdoc/specializeTag1.ts @@ -0,0 +1,22 @@ +// @checkJs: true +// @outDir: dist/ +// @declaration: true +// @filename: specializeTag1.js + +/** + * @template T + * @param {object} _jsonSchema + * @returns {(x: unknown) => x is T} + */ +function createValidator(_jsonSchema) { + /** + * @param {unknown} _x + * @returns {_x is T} + */ + return (_x) => true; +} + +/** @specialize */ +const isNumber = createValidator({ type: 'number' }); + +const isString = /** @specialize */(createValidator({ type: 'string' })); diff --git a/tests/cases/conformance/jsdoc/specializeTag2.ts b/tests/cases/conformance/jsdoc/specializeTag2.ts new file mode 100644 index 0000000000000..d71c2bad3cad9 --- /dev/null +++ b/tests/cases/conformance/jsdoc/specializeTag2.ts @@ -0,0 +1,31 @@ +// @checkJs: true +// @outDir: dist/ +// @declaration: true +// @filename: specializeTag2.js + +/** + * @template T + * @param {TemplateStringsArray} strings + * @param {...T} values + * @returns {Record} + */ +function parse(strings, ...values) { + /** @type {Record} */ + const result = {}; + strings.forEach((key, i) => { + if (i < values.length) { + result[key] = values[i]; + } + }) + return result; +} + +const query1 = /** @specialize {string} */( + parse`a=${1}b=${2}` +) + +/** @specialize {string} */ +const query2 = parse`a=${1}b=${2}`; // Type error + +/** @specialize <`${number}`> */ +const query3 = parse`a=${"1"}b=${"2"}`; diff --git a/tests/cases/conformance/jsdoc/specializeTag3.ts b/tests/cases/conformance/jsdoc/specializeTag3.ts new file mode 100644 index 0000000000000..c99813aea4a0c --- /dev/null +++ b/tests/cases/conformance/jsdoc/specializeTag3.ts @@ -0,0 +1,35 @@ +// @checkJs: true +// @outDir: dist/ +// @declaration: true +// @filename: specializeTag3.js + +/** + * @template T + */ +class JsonSchemaValidator { + /** + * @param {object} jsonSchema + */ + constructor(jsonSchema) { + /** @type {object} */ + this.jsonSchema = jsonSchema; + } + + /** + * @param {unknown} _value + * @returns {_value is T} + */ + isValid(_value) { + return true; + } +} + +const number = /** @specialize {number} */( + new JsonSchemaValidator({ type: 'number' }) +); + +/** @specialize {number[]} */ +const arrayOfNumbers = new JsonSchemaValidator({ + type: 'array', + items: { type: 'number' }, +}); diff --git a/tests/cases/conformance/jsdoc/specializeTag4.ts b/tests/cases/conformance/jsdoc/specializeTag4.ts new file mode 100644 index 0000000000000..716cc62a2f621 --- /dev/null +++ b/tests/cases/conformance/jsdoc/specializeTag4.ts @@ -0,0 +1,37 @@ +// @checkJs: true +// @outDir: dist/ +// @declaration: true +// @filename: specializeTag4.js + +/** + * @template {{ id: string }} T + */ +class Collection { + /** + * @param {string} name + */ + constructor(name) { + /** @type {string} */ + this.name = name; + } + + /** + * @param {string} id + * @returns {T} + */ + getById(id) { + return /** @type {T} */({ id }); + } +} + +/** + * @typedef {object} UserData + * @property {string} id + * @property {string} name + */ + +/** @specialize */ +const users = new Collection('users'); + +/** @specialize */ +const numbers = new Collection('numbers'); diff --git a/tests/cases/conformance/jsdoc/specializeTag5.ts b/tests/cases/conformance/jsdoc/specializeTag5.ts new file mode 100644 index 0000000000000..6e6616a8a3743 --- /dev/null +++ b/tests/cases/conformance/jsdoc/specializeTag5.ts @@ -0,0 +1,13 @@ +// @checkJs: true +// @lib: dom,esnext +// @outDir: dist/ +// @declaration: true +// @filename: specializeTag5.js + +const fileInput1 = + /** @specialize {HTMLInputElement} */ + (document.querySelector("input[type=file]")) + +/** @specialize {HTMLInputElement} */ +const fileInput2 = + document.querySelector("input[type=file]") diff --git a/tests/cases/conformance/jsdoc/specializeTag6.ts b/tests/cases/conformance/jsdoc/specializeTag6.ts new file mode 100644 index 0000000000000..374ad96fd97cd --- /dev/null +++ b/tests/cases/conformance/jsdoc/specializeTag6.ts @@ -0,0 +1,28 @@ +// @checkJs: true +// @jsx: react +// @outDir: dist/ +// @declaration: true +// @esModuleInterop: true +// @filename: specializeTag6.jsx +/// +import React from 'react' + +/** + * @template T + * @param {object} props + * @param {T} props.value + * @returns {React.ReactElement} + */ +function Input(props) { + return null; +} + +/** @specialize {number} */ +const el1 = ; + +const el2 = /** @specialize {number} */(); + +/** @specialize {number} */ +const el3 = ; // Type error + +const el4 = /** @specialize {number} */(); // Type error diff --git a/tests/cases/fourslash/quickInfoJSDocSpecializeTagInJsx.ts b/tests/cases/fourslash/quickInfoJSDocSpecializeTagInJsx.ts new file mode 100644 index 0000000000000..c454e3cd6512f --- /dev/null +++ b/tests/cases/fourslash/quickInfoJSDocSpecializeTagInJsx.ts @@ -0,0 +1,27 @@ +/// + +// @jsx: preserve +// @allowJs: true +// @checkJs: true + +// @filename: /a.jsx +//// /** +//// * @template T +//// * @param {object} props +//// * @param {T | undefined} props.value +//// * @returns {null} +//// */ +//// function Input(props) { +//// return null; +//// } +//// +//// /** @specialize {number} */ +//// const el1 = ; +//// +//// // Here, the type argument will be inferred +//// const el2 = ; + +verify.quickInfos({ + 1: "function Input(props: {\n value: number;\n}): null", + 2: "function Input(props: {\n value: string;\n}): null", +}); diff --git a/tests/cases/fourslash/referencesForSpecializeTag.ts b/tests/cases/fourslash/referencesForSpecializeTag.ts new file mode 100644 index 0000000000000..1dca63d8f9bd7 --- /dev/null +++ b/tests/cases/fourslash/referencesForSpecializeTag.ts @@ -0,0 +1,21 @@ +/// + +// @allowJs: true +// @checkJs: true + +// @filename: /a.js +//// /** +//// * @template T +//// */ +//// class Collection {} +//// +//// /** +//// * @typedef {object} U/*1*/serData +//// * @property {string} id +//// * @property {string} name +//// */ +//// +//// /** @specialize */ +//// const users = new Collection('users'); + +verify.baselineFindAllReferences('1');