From d49eed56c5d30e58e235aad4538d8c48262316b0 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Thu, 8 Aug 2024 09:42:36 +0200 Subject: [PATCH 1/2] Add support for "specialize" tag --- src/compiler/binder.ts | 3 + src/compiler/checker.ts | 68 +++-- src/compiler/emitter.ts | 12 + src/compiler/factory/nodeFactory.ts | 21 ++ src/compiler/factory/nodeTests.ts | 5 + src/compiler/parser.ts | 26 ++ src/compiler/types.ts | 11 +- src/compiler/utilities.ts | 3 +- src/compiler/utilitiesPublic.ts | 6 + src/services/classifier.ts | 6 + src/services/codefixes/fixAddVoidToPromise.ts | 5 + src/services/jsDoc.ts | 19 +- tests/baselines/reference/api/typescript.d.ts | 21 +- ...docParameterTagSnippetCompletion1.baseline | 238 ++++++++++++++++++ ...docParameterTagSnippetCompletion2.baseline | 70 ++++++ ...docParameterTagSnippetCompletion3.baseline | 84 +++++++ 16 files changed, 564 insertions(+), 34 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1666c90883e80..d4f6ed8107984 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -237,6 +237,7 @@ import { JSDocParameterTag, JSDocPropertyLikeTag, JSDocSignature, + JSDocSpecializeTag, JSDocTypedefTag, JSDocTypeLiteral, JsxAttribute, @@ -3078,6 +3079,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 91346429b1774..54361ccc761e2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -320,6 +320,7 @@ import { getJSDocParameterTags, getJSDocRoot, getJSDocSatisfiesExpressionType, + getJSDocSpecializeTag, getJSDocTags, getJSDocThisTag, getJSDocType, @@ -828,7 +829,6 @@ import { JsxFlags, JsxFragment, JsxNamespacedName, - JsxOpeningElement, JsxOpeningFragment, JsxOpeningLikeElement, JsxReferenceKind, @@ -34743,15 +34743,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) { @@ -35702,21 +35721,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, headMessage?: DiagnosticMessage): Signature { - const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isDecorator = node.kind === SyntaxKind.Decorator; - const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); const isInstanceof = node.kind === SyntaxKind.BinaryExpression; const reportErrors = !isInferencePartiallyBlocked && !candidatesOutArray; let typeArguments: NodeArray | undefined; - if (!isDecorator && !isInstanceof && !isSuperCall(node)) { - 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); } const candidates = candidatesOutArray || []; @@ -35888,7 +35901,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 { const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); @@ -36102,7 +36115,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); @@ -36202,6 +36215,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 @@ -36209,7 +36223,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); @@ -36245,7 +36259,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; } @@ -36290,11 +36304,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); @@ -36659,9 +36674,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; } @@ -36917,7 +36933,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) { @@ -37156,7 +37172,9 @@ 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); } @@ -41719,7 +41737,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkDecorators(node); } - function getEffectiveTypeArgumentAtIndex(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[], index: number): Type { + function getEffectiveTypeArgumentAtIndex(node: TypeReferenceNode, typeParameters: readonly TypeParameter[], index: number): Type { if (node.typeArguments && index < node.typeArguments.length) { return getTypeFromTypeNode(node.typeArguments[index]); } @@ -41727,7 +41745,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 { @@ -51499,7 +51517,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 f79fdf2136407..40a9ec3caa670 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -257,6 +257,7 @@ import { JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, + JSDocSpecializeTag, JSDocTag, JSDocTemplateTag, JSDocThisTag, @@ -1876,6 +1877,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 @@ -4059,6 +4062,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 90b9071efe594..1d55476d27909 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -250,6 +250,7 @@ import { JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, + JSDocSpecializeTag, JSDocTag, JSDocTemplateTag, JSDocText, @@ -861,6 +862,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode updateJSDocSeeTag, createJSDocImportTag, updateJSDocImportTag, + createJSDocSpecializeTag, + updateJSDocSpecializeTag, createJSDocNameReference, updateJSDocNameReference, createJSDocMemberName, @@ -5552,6 +5555,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); @@ -7212,6 +7231,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 c95c4759b9c12..61141b9b2bf0f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -148,6 +148,7 @@ import { isJSDocFunctionType, isJSDocNullableType, isJSDocReturnTag, + isJSDocSpecializeTag, isJSDocTypeTag, isJsxNamespacedName, isJsxOpeningElement, @@ -201,6 +202,7 @@ import { JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, + JSDocSpecializeTag, JSDocSyntaxKind, JSDocTag, JSDocTemplateTag, @@ -1131,6 +1133,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, }; @@ -9122,6 +9127,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; @@ -9510,6 +9518,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 dd9dd059a4444..a4be4fa112fb9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -439,6 +439,7 @@ export const enum SyntaxKind { JSDocThrowsTag, JSDocSatisfiesTag, JSDocImportTag, + JSDocSpecializeTag, // Synthesized list SyntaxList, @@ -1050,7 +1051,8 @@ export type ForEachChildNodes = | JSDocOverrideTag | JSDocSatisfiesTag | JSDocOverloadTag - | JSDocImportTag; + | JSDocImportTag + | JSDocSpecializeTag; /** @internal */ export type HasChildren = @@ -4118,6 +4120,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 */ @@ -9059,6 +9066,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 76849b847038c..d03f356dcdef4 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -305,6 +305,7 @@ import { isJSDocReturnTag, isJSDocSatisfiesTag, isJSDocSignature, + isJSDocSpecializeTag, isJSDocTag, isJSDocTemplateTag, isJSDocTypeExpression, @@ -4530,7 +4531,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 325f9739c5641..ec64853f3ad39 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, @@ -1185,6 +1187,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 cbefd89bf8403..1ccceb87536b1 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 e5607bd839993..696cef086c65d 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3981,12 +3981,13 @@ declare namespace ts { JSDocThrowsTag = 349, JSDocSatisfiesTag = 350, JSDocImportTag = 351, - SyntaxList = 352, - NotEmittedStatement = 353, - PartiallyEmittedExpression = 354, - CommaListExpression = 355, - SyntheticReferenceExpression = 356, - Count = 357, + JSDocSpecializeTag = 352, + SyntaxList = 353, + NotEmittedStatement = 354, + PartiallyEmittedExpression = 355, + CommaListExpression = 356, + SyntheticReferenceExpression = 357, + Count = 358, FirstAssignment = 64, LastAssignment = 79, FirstCompoundAssignment = 65, @@ -5826,6 +5827,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; @@ -7745,6 +7750,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; @@ -8600,6 +8607,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; /** @@ -9049,6 +9057,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 644bd98bd242a..3656c48cb423e 100644 --- a/tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline +++ b/tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline @@ -74,6 +74,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -171,6 +172,7 @@ // | @satisfies // | @see // | @since +// | @specialize // | @static // | @summary // | @template @@ -265,6 +267,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -364,6 +367,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -465,6 +469,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -561,6 +566,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -658,6 +664,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -755,6 +762,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -851,6 +859,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -948,6 +957,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1042,6 +1052,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1137,6 +1148,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1232,6 +1244,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1326,6 +1339,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1422,6 +1436,7 @@ // | @satisfies // | @see // | @since +// | @specialize // | @static // | @summary // | @template @@ -1516,6 +1531,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -1612,6 +1628,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 67e028bde039f..c57fff1997ca2 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 @@ -267,6 +269,7 @@ // | satisfies // | see // | since +// | specialize // | static // | summary // | template @@ -360,6 +363,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": "", From b819a75f6b2edcafd440850e28b0d005d9de0890 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Thu, 8 Aug 2024 09:42:36 +0200 Subject: [PATCH 2/2] Add unit tests --- .../referencesForSpecializeTag.baseline.jsonc | 131 ++++++++++++++++++ tests/baselines/reference/specializeTag1.js | 50 +++++++ .../reference/specializeTag1.symbols | 31 +++++ .../baselines/reference/specializeTag1.types | 57 ++++++++ .../reference/specializeTag2.errors.txt | 36 +++++ tests/baselines/reference/specializeTag2.js | 76 ++++++++++ .../reference/specializeTag2.symbols | 60 ++++++++ .../baselines/reference/specializeTag2.types | 124 +++++++++++++++++ tests/baselines/reference/specializeTag3.js | 84 +++++++++++ .../reference/specializeTag3.symbols | 58 ++++++++ .../baselines/reference/specializeTag3.types | 92 ++++++++++++ .../reference/specializeTag4.errors.txt | 39 ++++++ tests/baselines/reference/specializeTag4.js | 101 ++++++++++++++ .../reference/specializeTag4.symbols | 52 +++++++ .../baselines/reference/specializeTag4.types | 79 +++++++++++ tests/baselines/reference/specializeTag5.js | 24 ++++ .../reference/specializeTag5.symbols | 21 +++ .../baselines/reference/specializeTag5.types | 39 ++++++ .../reference/specializeTag6.errors.txt | 34 +++++ tests/baselines/reference/specializeTag6.js | 54 ++++++++ .../reference/specializeTag6.symbols | 42 ++++++ .../baselines/reference/specializeTag6.types | 79 +++++++++++ .../cases/conformance/jsdoc/specializeTag1.ts | 22 +++ .../cases/conformance/jsdoc/specializeTag2.ts | 31 +++++ .../cases/conformance/jsdoc/specializeTag3.ts | 35 +++++ .../cases/conformance/jsdoc/specializeTag4.ts | 37 +++++ .../cases/conformance/jsdoc/specializeTag5.ts | 13 ++ .../cases/conformance/jsdoc/specializeTag6.ts | 28 ++++ .../quickInfoJSDocSpecializeTagInJsx.ts | 27 ++++ .../fourslash/referencesForSpecializeTag.ts | 21 +++ 30 files changed, 1577 insertions(+) create mode 100644 tests/baselines/reference/referencesForSpecializeTag.baseline.jsonc create mode 100644 tests/baselines/reference/specializeTag1.js create mode 100644 tests/baselines/reference/specializeTag1.symbols create mode 100644 tests/baselines/reference/specializeTag1.types create mode 100644 tests/baselines/reference/specializeTag2.errors.txt create mode 100644 tests/baselines/reference/specializeTag2.js create mode 100644 tests/baselines/reference/specializeTag2.symbols create mode 100644 tests/baselines/reference/specializeTag2.types create mode 100644 tests/baselines/reference/specializeTag3.js create mode 100644 tests/baselines/reference/specializeTag3.symbols create mode 100644 tests/baselines/reference/specializeTag3.types create mode 100644 tests/baselines/reference/specializeTag4.errors.txt create mode 100644 tests/baselines/reference/specializeTag4.js create mode 100644 tests/baselines/reference/specializeTag4.symbols create mode 100644 tests/baselines/reference/specializeTag4.types create mode 100644 tests/baselines/reference/specializeTag5.js create mode 100644 tests/baselines/reference/specializeTag5.symbols create mode 100644 tests/baselines/reference/specializeTag5.types create mode 100644 tests/baselines/reference/specializeTag6.errors.txt create mode 100644 tests/baselines/reference/specializeTag6.js create mode 100644 tests/baselines/reference/specializeTag6.symbols create mode 100644 tests/baselines/reference/specializeTag6.types create mode 100644 tests/cases/conformance/jsdoc/specializeTag1.ts create mode 100644 tests/cases/conformance/jsdoc/specializeTag2.ts create mode 100644 tests/cases/conformance/jsdoc/specializeTag3.ts create mode 100644 tests/cases/conformance/jsdoc/specializeTag4.ts create mode 100644 tests/cases/conformance/jsdoc/specializeTag5.ts create mode 100644 tests/cases/conformance/jsdoc/specializeTag6.ts create mode 100644 tests/cases/fourslash/quickInfoJSDocSpecializeTagInJsx.ts create mode 100644 tests/cases/fourslash/referencesForSpecializeTag.ts 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');