diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a39f1a9a6883d..798bce5bf64c6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -222,6 +222,7 @@ import { GetAccessorDeclaration, getAliasDeclarationFromName, getAllAccessorDeclarations, + getAllJSDocTags, getAllowSyntheticDefaultImports, getAncestor, getAssignedExpandoInitializer, @@ -284,6 +285,7 @@ import { getJSDocHost, getJSDocParameterTags, getJSDocRoot, + getJSDocSatisfiesExpressionType, getJSDocTags, getJSDocThisTag, getJSDocType, @@ -557,6 +559,8 @@ import { isJSDocPropertyLikeTag, isJSDocPropertyTag, isJSDocReturnTag, + isJSDocSatisfiesExpression, + isJSDocSatisfiesTag, isJSDocSignature, isJSDocTemplateTag, isJSDocTypeAlias, @@ -732,6 +736,7 @@ import { JSDocPropertyTag, JSDocProtectedTag, JSDocPublicTag, + JSDocSatisfiesTag, JSDocSignature, JSDocTemplateTag, JSDocTypedefTag, @@ -972,6 +977,7 @@ import { tryExtractTSExtension, tryGetClassImplementingOrExtendingExpressionWithTypeArguments, tryGetExtensionFromPath, + tryGetJSDocSatisfiesTypeNode, tryGetModuleSpecifierFromDeclaration, tryGetPropertyAccessOrIdentifierToString, TryStatement, @@ -28201,7 +28207,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { - const typeNode = getEffectiveTypeAnnotationNode(declaration); + const typeNode = getEffectiveTypeAnnotationNode(declaration) || (isInJSFile(declaration) ? tryGetJSDocSatisfiesTypeNode(declaration) : undefined); if (typeNode) { return getTypeFromTypeNode(typeNode); } @@ -28883,11 +28889,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node); case SyntaxKind.ParenthesizedExpression: { - // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. - const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined; - return !tag ? getContextualType(parent as ParenthesizedExpression, contextFlags) : - isJSDocTypeTag(tag) && isConstTypeReference(tag.typeExpression.type) ? getContextualType(parent as ParenthesizedExpression, contextFlags) : - getTypeFromTypeNode(tag.typeExpression.type); + if (isInJSFile(parent)) { + if (isJSDocSatisfiesExpression(parent)) { + return getTypeFromTypeNode(getJSDocSatisfiesExpressionType(parent)); + } + // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. + const typeTag = getJSDocTypeTag(parent); + if (typeTag && !isConstTypeReference(typeTag.typeExpression.type)) { + return getTypeFromTypeNode(typeTag.typeExpression.type); + } + } + return getContextualType(parent as ParenthesizedExpression, contextFlags); } case SyntaxKind.NonNullExpression: return getContextualType(parent as NonNullExpression, contextFlags); @@ -33907,15 +33919,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkSatisfiesExpression(node: SatisfiesExpression) { checkSourceElement(node.type); - const exprType = checkExpression(node.expression); + return checkSatisfiesExpressionWorker(node.expression, node.type); + } - const targetType = getTypeFromTypeNode(node.type); + function checkSatisfiesExpressionWorker(expression: Expression, target: TypeNode, checkMode?: CheckMode) { + const exprType = checkExpression(expression, checkMode); + const targetType = getTypeFromTypeNode(target); if (isErrorType(targetType)) { return targetType; } - - checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, node.type, node.expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1); - + checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, target, expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1); return exprType; } @@ -36253,6 +36266,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { contextualType?: Type | undefined ) { const initializer = getEffectiveInitializer(declaration)!; + if (isInJSFile(declaration)) { + const typeNode = tryGetJSDocSatisfiesTypeNode(declaration); + if (typeNode) { + return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode); + } + } const type = getQuickTypeOfExpression(initializer) || (contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal) @@ -36635,9 +36654,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { - if (hasJSDocNodes(node) && isJSDocTypeAssertion(node)) { - const type = getJSDocTypeAssertionType(node); - return checkAssertionWorker(type, type, node.expression, checkMode); + if (hasJSDocNodes(node)) { + if (isJSDocSatisfiesExpression(node)) { + return checkSatisfiesExpressionWorker(node.expression, getJSDocSatisfiesExpressionType(node), checkMode); + } + if (isJSDocTypeAssertion(node)) { + const type = getJSDocTypeAssertionType(node); + return checkAssertionWorker(type, type, node.expression, checkMode); + } } return checkExpression(node.expression, checkMode); } @@ -38872,6 +38896,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkSourceElement(node.typeExpression); } + function checkJSDocSatisfiesTag(node: JSDocSatisfiesTag) { + checkSourceElement(node.typeExpression); + const host = getEffectiveJSDocHost(node); + if (host) { + const tags = getAllJSDocTags(host, isJSDocSatisfiesTag); + if (length(tags) > 1) { + for (let i = 1; i < length(tags); i++) { + const tagName = tags[i].tagName; + error(tagName, Diagnostics._0_tag_already_specified, idText(tagName)); + } + } + } + } + function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) { if (node.name) { resolveJSDocMemberName(node.name, /*ignoreErrors*/ true); @@ -43385,6 +43423,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.JSDocProtectedTag: case SyntaxKind.JSDocPrivateTag: return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag); + case SyntaxKind.JSDocSatisfiesTag: + return checkJSDocSatisfiesTag(node as JSDocSatisfiesTag); case SyntaxKind.IndexedAccessType: return checkIndexedAccessType(node as IndexedAccessTypeNode); case SyntaxKind.MappedType: diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 4e8bbe723efaa..e765fec95e644 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -265,11 +265,13 @@ import { JSDocOverloadTag, JSDocPropertyLikeTag, JSDocReturnTag, + JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, JSDocTag, JSDocTemplateTag, JSDocThisTag, + JSDocThrowsTag, JSDocTypedefTag, JSDocTypeExpression, JSDocTypeLiteral, @@ -2135,7 +2137,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri case SyntaxKind.JSDocThisTag: case SyntaxKind.JSDocTypeTag: case SyntaxKind.JSDocThrowsTag: - return emitJSDocSimpleTypedTag(node as JSDocTypeTag); + case SyntaxKind.JSDocSatisfiesTag: + return emitJSDocSimpleTypedTag(node as JSDocTypeTag | JSDocReturnTag | JSDocThisTag | JSDocTypeTag | JSDocThrowsTag | JSDocSatisfiesTag); case SyntaxKind.JSDocTemplateTag: return emitJSDocTemplateTag(node as JSDocTemplateTag); case SyntaxKind.JSDocTypedefTag: @@ -4336,7 +4339,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri write("*/"); } - function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) { + function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag | JSDocThrowsTag | JSDocSatisfiesTag) { emitJSDocTagName(tag.tagName); emitJSDocTypeExpression(tag.typeExpression); emitJSDocComment(tag.comment); diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 739d47e707a91..aef623811d658 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -250,6 +250,7 @@ import { JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, + JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, JSDocTag, @@ -876,6 +877,9 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocDeprecatedTag); }, get createJSDocThrowsTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocThrowsTag); }, get updateJSDocThrowsTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocThrowsTag); }, + get createJSDocSatisfiesTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocSatisfiesTag); }, + get updateJSDocSatisfiesTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocSatisfiesTag); }, + createJSDocEnumTag, updateJSDocEnumTag, createJSDocUnknownTag, @@ -5417,6 +5421,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode // createJSDocReturnTag // createJSDocThisTag // createJSDocEnumTag + // createJSDocSatisfiesTag function createJSDocTypeLikeTagWorker(kind: T["kind"], tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: string | NodeArray) { const node = createBaseJSDocTag(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment); node.typeExpression = typeExpression; @@ -5428,6 +5433,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode // updateJSDocReturnTag // updateJSDocThisTag // updateJSDocEnumTag + // updateJSDocSatisfiesTag function updateJSDocTypeLikeTagWorker(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, comment: string | NodeArray | undefined) { return node.tagName !== tagName || node.typeExpression !== typeExpression diff --git a/src/compiler/factory/nodeTests.ts b/src/compiler/factory/nodeTests.ts index 874b443d9bdc5..0d98989d39700 100644 --- a/src/compiler/factory/nodeTests.ts +++ b/src/compiler/factory/nodeTests.ts @@ -107,6 +107,7 @@ import { JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, + JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, JSDocTemplateTag, @@ -1188,6 +1189,10 @@ export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag { return node.kind === SyntaxKind.JSDocImplementsTag; } +export function isJSDocSatisfiesTag(node: Node): node is JSDocSatisfiesTag { + return node.kind === SyntaxKind.JSDocSatisfiesTag; +} + export function isJSDocThrowsTag(node: Node): node is JSDocThrowsTag { return node.kind === SyntaxKind.JSDocThrowsTag; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 3ad88b528a58c..bfdd8a82677b9 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -190,6 +190,7 @@ import { JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, + JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, JSDocSyntaxKind, @@ -1108,6 +1109,7 @@ const forEachChildTable: ForEachChildTable = { [SyntaxKind.JSDocTypeTag]: forEachChildInJSDocTypeLikeTag, [SyntaxKind.JSDocThisTag]: forEachChildInJSDocTypeLikeTag, [SyntaxKind.JSDocEnumTag]: forEachChildInJSDocTypeLikeTag, + [SyntaxKind.JSDocSatisfiesTag]: forEachChildInJSDocTypeLikeTag, [SyntaxKind.JSDocThrowsTag]: forEachChildInJSDocTypeLikeTag, [SyntaxKind.JSDocOverloadTag]: forEachChildInJSDocTypeLikeTag, [SyntaxKind.JSDocSignature]: function forEachChildInJSDocSignature(node: JSDocSignature, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { @@ -1203,7 +1205,7 @@ function forEachChildInJSDocParameterOrPropertyTag(node: JSDocParameterTag | (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); } -function forEachChildInJSDocTypeLikeTag(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocThrowsTag | JSDocOverloadTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { +function forEachChildInJSDocTypeLikeTag(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocThrowsTag | JSDocOverloadTag | JSDocSatisfiesTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || visitNode(cbNode, node.typeExpression) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); @@ -8779,6 +8781,9 @@ namespace Parser { case "overload": tag = parseOverloadTag(start, tagName, margin, indentText); break; + case "satisfies": + tag = parseSatisfiesTag(start, tagName, margin, indentText); + break; case "see": tag = parseSeeTag(start, tagName, margin, indentText); break; @@ -9144,6 +9149,12 @@ namespace Parser { return finishNode(factory.createJSDocAugmentsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); } + function parseSatisfiesTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocSatisfiesTag { + const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ false); + const comments = margin !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), margin, indentText) : undefined; + return finishNode(factory.createJSDocSatisfiesTag(tagName, typeExpression, comments), start); + } + 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 80f0c539aef82..52e33ada64d4a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -434,6 +434,7 @@ export const enum SyntaxKind { JSDocSeeTag, JSDocPropertyTag, JSDocThrowsTag, + JSDocSatisfiesTag, // Synthesized list SyntaxList, @@ -478,9 +479,9 @@ export const enum SyntaxKind { LastStatement = DebuggerStatement, FirstNode = QualifiedName, FirstJSDocNode = JSDocTypeExpression, - LastJSDocNode = JSDocThrowsTag, + LastJSDocNode = JSDocSatisfiesTag, FirstJSDocTagNode = JSDocTag, - LastJSDocTagNode = JSDocThrowsTag, + LastJSDocTagNode = JSDocSatisfiesTag, /** @internal */ FirstContextualKeyword = AbstractKeyword, /** @internal */ LastContextualKeyword = OfKeyword, } @@ -1010,6 +1011,7 @@ export type ForEachChildNodes = | JSDocDeprecatedTag | JSDocThrowsTag | JSDocOverrideTag + | JSDocSatisfiesTag | JSDocOverloadTag ; @@ -4105,6 +4107,16 @@ export interface JSDocTypeLiteral extends JSDocType, Declaration { readonly isArrayType: boolean; } +export interface JSDocSatisfiesTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocSatisfiesTag; + readonly typeExpression: JSDocTypeExpression; +} + +/** @internal */ +export interface JSDocSatisfiesExpression extends ParenthesizedExpression { + readonly _jsDocSatisfiesExpressionBrand: never; +} + // NOTE: Ensure this is up-to-date with src/debug/debug.ts export const enum FlowFlags { Unreachable = 1 << 0, // Unreachable code @@ -8580,6 +8592,8 @@ export interface NodeFactory { updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray): JSDocOverrideTag; createJSDocThrowsTag(tagName: Identifier, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray): JSDocThrowsTag; updateJSDocThrowsTag(node: JSDocThrowsTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray | undefined): JSDocThrowsTag; + createJSDocSatisfiesTag(tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment?: string | NodeArray): JSDocSatisfiesTag; + updateJSDocSatisfiesTag(node: JSDocSatisfiesTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray | undefined): JSDocSatisfiesTag; 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 cc95054826c47..c1e02d1939ebf 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -169,6 +169,7 @@ import { getJSDocPublicTagNoCache, getJSDocReadonlyTagNoCache, getJSDocReturnType, + getJSDocSatisfiesTag, getJSDocTags, getJSDocType, getJSDocTypeParameterTags, @@ -275,6 +276,7 @@ import { isJSDocOverloadTag, isJSDocParameterTag, isJSDocPropertyLikeTag, + isJSDocSatisfiesTag, isJSDocSignature, isJSDocTag, isJSDocTemplateTag, @@ -336,6 +338,7 @@ import { JSDocMemberName, JSDocParameterTag, JSDocPropertyLikeTag, + JSDocSatisfiesExpression, JSDocSignature, JSDocTag, JSDocTemplateTag, @@ -3761,11 +3764,11 @@ function filterOwnedJSDocTags(hostNode: Node, jsDoc: JSDoc | JSDocTag) { } /** - * Determines whether a host node owns a jsDoc tag. A `@type` tag attached to a + * Determines whether a host node owns a jsDoc tag. A `@type`/`@satisfies` tag attached to a * a ParenthesizedExpression belongs only to the ParenthesizedExpression. */ function ownsJSDocTag(hostNode: Node, tag: JSDocTag) { - return !isJSDocTypeTag(tag) + return !(isJSDocTypeTag(tag) || isJSDocSatisfiesTag(tag)) || !tag.parent || !isJSDoc(tag.parent) || !isParenthesizedExpression(tag.parent.parent) @@ -9509,3 +9512,19 @@ export function isNonNullAccess(node: Node): node is AccessExpression { return (kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.ElementAccessExpression) && isNonNullExpression((node as AccessExpression).expression); } + +/** @internal */ +export function isJSDocSatisfiesExpression(node: Node): node is JSDocSatisfiesExpression { + return isInJSFile(node) && isParenthesizedExpression(node) && hasJSDocNodes(node) && !!getJSDocSatisfiesTag(node); +} + +/** @internal */ +export function getJSDocSatisfiesExpressionType(node: JSDocSatisfiesExpression) { + return Debug.checkDefined(tryGetJSDocSatisfiesTypeNode(node)); +} + +/** @internal */ +export function tryGetJSDocSatisfiesTypeNode(node: Node) { + const tag = getJSDocSatisfiesTag(node); + return tag && tag.typeExpression && tag.typeExpression.type; +} diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index 2e78151cda548..8a13b258be14a 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -78,8 +78,8 @@ import { getJSDocRoot, getJSDocTypeParameterDeclarations, hasAccessorModifier, - hasDecorators, HasDecorators, + hasDecorators, HasExpressionInitializer, HasInitializer, HasJSDoc, @@ -132,6 +132,7 @@ import { isJSDocPublicTag, isJSDocReadonlyTag, isJSDocReturnTag, + isJSDocSatisfiesTag, isJSDocSignature, isJSDocTemplateTag, isJSDocThisTag, @@ -179,6 +180,7 @@ import { JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, + JSDocSatisfiesTag, JSDocSignature, JSDocTag, JSDocTemplateTag, @@ -1105,6 +1107,10 @@ export function getJSDocTemplateTag(node: Node): JSDocTemplateTag | undefined { return getFirstJSDocTag(node, isJSDocTemplateTag); } +export function getJSDocSatisfiesTag(node: Node): JSDocSatisfiesTag | undefined { + return getFirstJSDocTag(node, isJSDocSatisfiesTag); +} + /** 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/completions.ts b/src/services/completions.ts index 343a323f34544..434bf29663dbc 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -237,6 +237,7 @@ import { JSDocParameterTag, JSDocPropertyTag, JSDocReturnTag, + JSDocSatisfiesTag, JSDocTag, JSDocTagInfo, JSDocTemplateTag, @@ -2999,7 +3000,8 @@ function getCompletionData( | JSDocTypeTag | JSDocTypedefTag | JSDocTemplateTag - | JSDocThrowsTag; + | JSDocThrowsTag + | JSDocSatisfiesTag; function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { switch (tag.kind) { @@ -3009,6 +3011,7 @@ function getCompletionData( case SyntaxKind.JSDocTypeTag: case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocThrowsTag: + case SyntaxKind.JSDocSatisfiesTag: return true; case SyntaxKind.JSDocTemplateTag: return !!(tag as JSDocTemplateTag).constraint; diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 8e26719167ba2..866ef2d9df2a0 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -52,6 +52,7 @@ import { JSDocImplementsTag, JSDocParameterTag, JSDocPropertyTag, + JSDocSatisfiesTag, JSDocSeeTag, JSDocTag, JSDocTagInfo, @@ -152,6 +153,7 @@ const jsDocTagNames = [ "readonly", "requires", "returns", + "satisfies", "see", "since", "static", @@ -290,7 +292,8 @@ function getCommentDisplayParts(tag: JSDocTag, checker?: TypeChecker): SymbolDis } return displayParts; case SyntaxKind.JSDocTypeTag: - return withNode((tag as JSDocTypeTag).typeExpression); + case SyntaxKind.JSDocSatisfiesTag: + return withNode((tag as JSDocTypeTag | JSDocSatisfiesTag).typeExpression); case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocPropertyTag: diff --git a/src/testRunner/unittests/jsDocParsing.ts b/src/testRunner/unittests/jsDocParsing.ts index 63701cf0c16ba..d318dc4fffee7 100644 --- a/src/testRunner/unittests/jsDocParsing.ts +++ b/src/testRunner/unittests/jsDocParsing.ts @@ -177,6 +177,10 @@ describe("unittests:: JSDocParsing", () => { * @type {number} */`); + parsesCorrectly("satisfiesTag", + `/** + * @satisfies {number} + */`); parsesCorrectly("returnTag1", `/** diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.satisfiesTag.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.satisfiesTag.json new file mode 100644 index 0000000000000..f1588491c48cb --- /dev/null +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.satisfiesTag.json @@ -0,0 +1,44 @@ +{ + "kind": "JSDoc", + "pos": 0, + "end": 32, + "flags": "JSDoc", + "modifierFlagsCache": 0, + "transformFlags": 0, + "tags": { + "0": { + "kind": "JSDocSatisfiesTag", + "pos": 8, + "end": 30, + "modifierFlagsCache": 0, + "transformFlags": 0, + "tagName": { + "kind": "Identifier", + "pos": 9, + "end": 18, + "modifierFlagsCache": 0, + "transformFlags": 0, + "escapedText": "satisfies" + }, + "typeExpression": { + "kind": "JSDocTypeExpression", + "pos": 19, + "end": 27, + "modifierFlagsCache": 0, + "transformFlags": 0, + "type": { + "kind": "NumberKeyword", + "pos": 20, + "end": 26, + "modifierFlagsCache": 0, + "transformFlags": 1 + } + } + }, + "length": 1, + "pos": 8, + "end": 30, + "hasTrailingComma": false, + "transformFlags": 0 + } +} \ No newline at end of file diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 63c9dfc79ae68..695ea34540129 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4335,14 +4335,15 @@ declare namespace ts { JSDocSeeTag = 350, JSDocPropertyTag = 351, JSDocThrowsTag = 352, - SyntaxList = 353, - NotEmittedStatement = 354, - PartiallyEmittedExpression = 355, - CommaListExpression = 356, - MergeDeclarationMarker = 357, - EndOfDeclarationMarker = 358, - SyntheticReferenceExpression = 359, - Count = 360, + JSDocSatisfiesTag = 353, + SyntaxList = 354, + NotEmittedStatement = 355, + PartiallyEmittedExpression = 356, + CommaListExpression = 357, + MergeDeclarationMarker = 358, + EndOfDeclarationMarker = 359, + SyntheticReferenceExpression = 360, + Count = 361, FirstAssignment = 63, LastAssignment = 78, FirstCompoundAssignment = 64, @@ -4371,9 +4372,9 @@ declare namespace ts { LastStatement = 256, FirstNode = 163, FirstJSDocNode = 312, - LastJSDocNode = 352, + LastJSDocNode = 353, FirstJSDocTagNode = 330, - LastJSDocTagNode = 352 + LastJSDocTagNode = 353 } type TriviaSyntaxKind = SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia | SyntaxKind.NewLineTrivia | SyntaxKind.WhitespaceTrivia | SyntaxKind.ShebangTrivia | SyntaxKind.ConflictMarkerTrivia; type LiteralSyntaxKind = SyntaxKind.NumericLiteral | SyntaxKind.BigIntLiteral | SyntaxKind.StringLiteral | SyntaxKind.JsxText | SyntaxKind.JsxTextAllWhiteSpaces | SyntaxKind.RegularExpressionLiteral | SyntaxKind.NoSubstitutionTemplateLiteral; @@ -5971,6 +5972,10 @@ declare namespace ts { /** If true, then this type literal represents an *array* of its type. */ readonly isArrayType: boolean; } + interface JSDocSatisfiesTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocSatisfiesTag; + readonly typeExpression: JSDocTypeExpression; + } enum FlowFlags { Unreachable = 1, Start = 2, @@ -7823,6 +7828,8 @@ declare namespace ts { updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray): JSDocOverrideTag; createJSDocThrowsTag(tagName: Identifier, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray): JSDocThrowsTag; updateJSDocThrowsTag(node: JSDocThrowsTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray | undefined): JSDocThrowsTag; + createJSDocSatisfiesTag(tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment?: string | NodeArray): JSDocSatisfiesTag; + updateJSDocSatisfiesTag(node: JSDocSatisfiesTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray | undefined): JSDocSatisfiesTag; createJSDocText(text: string): JSDocText; updateJSDocText(node: JSDocText, text: string): JSDocText; createJSDocComment(comment?: string | NodeArray | undefined, tags?: readonly JSDocTag[] | undefined): JSDoc; @@ -8669,6 +8676,7 @@ declare namespace ts { function getJSDocReturnTag(node: Node): JSDocReturnTag | undefined; /** Gets the JSDoc template tag for the node if present */ function getJSDocTemplateTag(node: Node): JSDocTemplateTag | undefined; + function getJSDocSatisfiesTag(node: Node): JSDocSatisfiesTag | undefined; /** Gets the JSDoc type tag for the node if present and valid */ function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined; /** @@ -9071,6 +9079,7 @@ declare namespace ts { function isJSDocUnknownTag(node: Node): node is JSDocUnknownTag; function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag; function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag; + function isJSDocSatisfiesTag(node: Node): node is JSDocSatisfiesTag; function isJSDocThrowsTag(node: Node): node is JSDocThrowsTag; function setTextRange(range: T, location: TextRange | undefined): T; function canHaveModifiers(node: Node): node is HasModifiers; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7d4bb8e1d0985..32b2585c29f7e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -400,14 +400,15 @@ declare namespace ts { JSDocSeeTag = 350, JSDocPropertyTag = 351, JSDocThrowsTag = 352, - SyntaxList = 353, - NotEmittedStatement = 354, - PartiallyEmittedExpression = 355, - CommaListExpression = 356, - MergeDeclarationMarker = 357, - EndOfDeclarationMarker = 358, - SyntheticReferenceExpression = 359, - Count = 360, + JSDocSatisfiesTag = 353, + SyntaxList = 354, + NotEmittedStatement = 355, + PartiallyEmittedExpression = 356, + CommaListExpression = 357, + MergeDeclarationMarker = 358, + EndOfDeclarationMarker = 359, + SyntheticReferenceExpression = 360, + Count = 361, FirstAssignment = 63, LastAssignment = 78, FirstCompoundAssignment = 64, @@ -436,9 +437,9 @@ declare namespace ts { LastStatement = 256, FirstNode = 163, FirstJSDocNode = 312, - LastJSDocNode = 352, + LastJSDocNode = 353, FirstJSDocTagNode = 330, - LastJSDocTagNode = 352 + LastJSDocTagNode = 353 } type TriviaSyntaxKind = SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia | SyntaxKind.NewLineTrivia | SyntaxKind.WhitespaceTrivia | SyntaxKind.ShebangTrivia | SyntaxKind.ConflictMarkerTrivia; type LiteralSyntaxKind = SyntaxKind.NumericLiteral | SyntaxKind.BigIntLiteral | SyntaxKind.StringLiteral | SyntaxKind.JsxText | SyntaxKind.JsxTextAllWhiteSpaces | SyntaxKind.RegularExpressionLiteral | SyntaxKind.NoSubstitutionTemplateLiteral; @@ -2036,6 +2037,10 @@ declare namespace ts { /** If true, then this type literal represents an *array* of its type. */ readonly isArrayType: boolean; } + interface JSDocSatisfiesTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocSatisfiesTag; + readonly typeExpression: JSDocTypeExpression; + } enum FlowFlags { Unreachable = 1, Start = 2, @@ -3888,6 +3893,8 @@ declare namespace ts { updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray): JSDocOverrideTag; createJSDocThrowsTag(tagName: Identifier, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray): JSDocThrowsTag; updateJSDocThrowsTag(node: JSDocThrowsTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray | undefined): JSDocThrowsTag; + createJSDocSatisfiesTag(tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment?: string | NodeArray): JSDocSatisfiesTag; + updateJSDocSatisfiesTag(node: JSDocSatisfiesTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray | undefined): JSDocSatisfiesTag; createJSDocText(text: string): JSDocText; updateJSDocText(node: JSDocText, text: string): JSDocText; createJSDocComment(comment?: string | NodeArray | undefined, tags?: readonly JSDocTag[] | undefined): JSDoc; @@ -4734,6 +4741,7 @@ declare namespace ts { function getJSDocReturnTag(node: Node): JSDocReturnTag | undefined; /** Gets the JSDoc template tag for the node if present */ function getJSDocTemplateTag(node: Node): JSDocTemplateTag | undefined; + function getJSDocSatisfiesTag(node: Node): JSDocSatisfiesTag | undefined; /** Gets the JSDoc type tag for the node if present and valid */ function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined; /** @@ -5136,6 +5144,7 @@ declare namespace ts { function isJSDocUnknownTag(node: Node): node is JSDocUnknownTag; function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag; function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag; + function isJSDocSatisfiesTag(node: Node): node is JSDocSatisfiesTag; function isJSDocThrowsTag(node: Node): node is JSDocThrowsTag; function setTextRange(range: T, location: TextRange | undefined): T; function canHaveModifiers(node: Node): node is HasModifiers; diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag1.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag1.errors.txt new file mode 100644 index 0000000000000..9ba25ad3d60d4 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag1.errors.txt @@ -0,0 +1,51 @@ +/a.js(21,44): error TS1360: Type '{ a: number; b: number; }' does not satisfy the expected type 'T1'. + Object literal may only specify known properties, and 'b' does not exist in type 'T1'. +/a.js(22,28): error TS1360: Type '{}' does not satisfy the expected type 'T1'. + Property 'a' is missing in type '{}' but required in type 'T1'. +/a.js(31,49): error TS1360: Type '{ a: string; b: string; }' does not satisfy the expected type 'T4'. + Object literal may only specify known properties, and 'b' does not exist in type 'T4'. + + +==== /a.js (3 errors) ==== + /** + * @typedef {Object} T1 + * @property {number} a + */ + + /** + * @typedef {Object} T2 + * @property {"a" | "b"} a + */ + + /** + * @typedef {(x: string) => string} T3 + */ + + /** + * @typedef {Object} T4 + * @property {string} a + */ + + const t1 = /** @satisfies {T1} */ ({ a: 1 }); + const t2 = /** @satisfies {T1} */ ({ a: 1, b: 1 }); + ~~~~ +!!! error TS1360: Type '{ a: number; b: number; }' does not satisfy the expected type 'T1'. +!!! error TS1360: Object literal may only specify known properties, and 'b' does not exist in type 'T1'. + const t3 = /** @satisfies {T1} */ ({}); + ~~ +!!! error TS1360: Type '{}' does not satisfy the expected type 'T1'. +!!! error TS1360: Property 'a' is missing in type '{}' but required in type 'T1'. +!!! related TS2728 /a.js:3:4: 'a' is declared here. + + /** @type {T2} */ + const t4 = /** @satisfies {T2} */ ({ a: "a" }); + + /** @type {(m: string) => string} */ + const t5 = /** @satisfies {T3} */((m) => m.substring(0)); + const t6 = /** @satisfies {[number, number]} */ ([1, 2]); + const t7 = /** @satisfies {T4} */ ({ a: 'test' }); + const t8 = /** @satisfies {T4} */ ({ a: 'test', b: 'test' }); + ~~~~~~~~~ +!!! error TS1360: Type '{ a: string; b: string; }' does not satisfy the expected type 'T4'. +!!! error TS1360: Object literal may only specify known properties, and 'b' does not exist in type 'T4'. + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag1.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag1.symbols new file mode 100644 index 0000000000000..251532e5c77d7 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag1.symbols @@ -0,0 +1,57 @@ +=== /a.js === +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {"a" | "b"} a + */ + +/** + * @typedef {(x: string) => string} T3 + */ + +/** + * @typedef {Object} T4 + * @property {string} a + */ + +const t1 = /** @satisfies {T1} */ ({ a: 1 }); +>t1 : Symbol(t1, Decl(a.js, 19, 5)) +>a : Symbol(a, Decl(a.js, 19, 36)) + +const t2 = /** @satisfies {T1} */ ({ a: 1, b: 1 }); +>t2 : Symbol(t2, Decl(a.js, 20, 5)) +>a : Symbol(a, Decl(a.js, 20, 36)) +>b : Symbol(b, Decl(a.js, 20, 42)) + +const t3 = /** @satisfies {T1} */ ({}); +>t3 : Symbol(t3, Decl(a.js, 21, 5)) + +/** @type {T2} */ +const t4 = /** @satisfies {T2} */ ({ a: "a" }); +>t4 : Symbol(t4, Decl(a.js, 24, 5)) +>a : Symbol(a, Decl(a.js, 24, 36)) + +/** @type {(m: string) => string} */ +const t5 = /** @satisfies {T3} */((m) => m.substring(0)); +>t5 : Symbol(t5, Decl(a.js, 27, 5)) +>m : Symbol(m, Decl(a.js, 27, 35)) +>m.substring : Symbol(String.substring, Decl(lib.es5.d.ts, --, --)) +>m : Symbol(m, Decl(a.js, 27, 35)) +>substring : Symbol(String.substring, Decl(lib.es5.d.ts, --, --)) + +const t6 = /** @satisfies {[number, number]} */ ([1, 2]); +>t6 : Symbol(t6, Decl(a.js, 28, 5)) + +const t7 = /** @satisfies {T4} */ ({ a: 'test' }); +>t7 : Symbol(t7, Decl(a.js, 29, 5)) +>a : Symbol(a, Decl(a.js, 29, 36)) + +const t8 = /** @satisfies {T4} */ ({ a: 'test', b: 'test' }); +>t8 : Symbol(t8, Decl(a.js, 30, 5)) +>a : Symbol(a, Decl(a.js, 30, 36)) +>b : Symbol(b, Decl(a.js, 30, 47)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag1.types b/tests/baselines/reference/checkJsdocSatisfiesTag1.types new file mode 100644 index 0000000000000..9a7f071e8fef6 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag1.types @@ -0,0 +1,84 @@ +=== /a.js === +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {"a" | "b"} a + */ + +/** + * @typedef {(x: string) => string} T3 + */ + +/** + * @typedef {Object} T4 + * @property {string} a + */ + +const t1 = /** @satisfies {T1} */ ({ a: 1 }); +>t1 : { a: number; } +>({ a: 1 }) : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +const t2 = /** @satisfies {T1} */ ({ a: 1, b: 1 }); +>t2 : { a: number; b: number; } +>({ a: 1, b: 1 }) : { a: number; b: number; } +>{ a: 1, b: 1 } : { a: number; b: number; } +>a : number +>1 : 1 +>b : number +>1 : 1 + +const t3 = /** @satisfies {T1} */ ({}); +>t3 : {} +>({}) : {} +>{} : {} + +/** @type {T2} */ +const t4 = /** @satisfies {T2} */ ({ a: "a" }); +>t4 : T2 +>({ a: "a" }) : T2 +>{ a: "a" } : { a: "a"; } +>a : "a" +>"a" : "a" + +/** @type {(m: string) => string} */ +const t5 = /** @satisfies {T3} */((m) => m.substring(0)); +>t5 : (m: string) => string +>((m) => m.substring(0)) : (m: string) => string +>(m) => m.substring(0) : (m: string) => string +>m : string +>m.substring(0) : string +>m.substring : (start: number, end?: number) => string +>m : string +>substring : (start: number, end?: number) => string +>0 : 0 + +const t6 = /** @satisfies {[number, number]} */ ([1, 2]); +>t6 : [number, number] +>([1, 2]) : [number, number] +>[1, 2] : [number, number] +>1 : 1 +>2 : 2 + +const t7 = /** @satisfies {T4} */ ({ a: 'test' }); +>t7 : { a: string; } +>({ a: 'test' }) : { a: string; } +>{ a: 'test' } : { a: string; } +>a : string +>'test' : "test" + +const t8 = /** @satisfies {T4} */ ({ a: 'test', b: 'test' }); +>t8 : { a: string; b: string; } +>({ a: 'test', b: 'test' }) : { a: string; b: string; } +>{ a: 'test', b: 'test' } : { a: string; b: string; } +>a : string +>'test' : "test" +>b : string +>'test' : "test" + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag10.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag10.errors.txt new file mode 100644 index 0000000000000..140508fb29743 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag10.errors.txt @@ -0,0 +1,26 @@ +/a.js(6,5): error TS1360: Type '{ a: number; b: string; x: number; }' does not satisfy the expected type 'Partial>'. + Object literal may only specify known properties, and 'x' does not exist in type 'Partial>'. +/a.js(14,11): error TS2339: Property 'd' does not exist on type '{ a: number; b: string; x: number; }'. + + +==== /a.js (2 errors) ==== + /** @typedef {"a" | "b" | "c" | "d"} Keys */ + + const p = /** @satisfies {Partial>} */ ({ + a: 0, + b: "hello", + x: 8 // Should error, 'x' isn't in 'Keys' + ~~~~ +!!! error TS1360: Type '{ a: number; b: string; x: number; }' does not satisfy the expected type 'Partial>'. +!!! error TS1360: Object literal may only specify known properties, and 'x' does not exist in type 'Partial>'. + }); + + // Should be OK -- retain info that a is number and b is string + let a = p.a.toFixed(); + let b = p.b.substring(1); + + // Should error even though 'd' is in 'Keys' + let d = p.d; + ~ +!!! error TS2339: Property 'd' does not exist on type '{ a: number; b: string; x: number; }'. + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag10.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag10.symbols new file mode 100644 index 0000000000000..47ea5801f763b --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag10.symbols @@ -0,0 +1,39 @@ +=== /a.js === +/** @typedef {"a" | "b" | "c" | "d"} Keys */ + +const p = /** @satisfies {Partial>} */ ({ +>p : Symbol(p, Decl(a.js, 2, 5)) + + a: 0, +>a : Symbol(a, Decl(a.js, 2, 63)) + + b: "hello", +>b : Symbol(b, Decl(a.js, 3, 9)) + + x: 8 // Should error, 'x' isn't in 'Keys' +>x : Symbol(x, Decl(a.js, 4, 15)) + +}); + +// Should be OK -- retain info that a is number and b is string +let a = p.a.toFixed(); +>a : Symbol(a, Decl(a.js, 9, 3)) +>p.a.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>p.a : Symbol(a, Decl(a.js, 2, 63)) +>p : Symbol(p, Decl(a.js, 2, 5)) +>a : Symbol(a, Decl(a.js, 2, 63)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + +let b = p.b.substring(1); +>b : Symbol(b, Decl(a.js, 10, 3)) +>p.b.substring : Symbol(String.substring, Decl(lib.es5.d.ts, --, --)) +>p.b : Symbol(b, Decl(a.js, 3, 9)) +>p : Symbol(p, Decl(a.js, 2, 5)) +>b : Symbol(b, Decl(a.js, 3, 9)) +>substring : Symbol(String.substring, Decl(lib.es5.d.ts, --, --)) + +// Should error even though 'd' is in 'Keys' +let d = p.d; +>d : Symbol(d, Decl(a.js, 13, 3)) +>p : Symbol(p, Decl(a.js, 2, 5)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag10.types b/tests/baselines/reference/checkJsdocSatisfiesTag10.types new file mode 100644 index 0000000000000..7fb3c450641d7 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag10.types @@ -0,0 +1,49 @@ +=== /a.js === +/** @typedef {"a" | "b" | "c" | "d"} Keys */ + +const p = /** @satisfies {Partial>} */ ({ +>p : { a: number; b: string; x: number; } +>({ a: 0, b: "hello", x: 8 // Should error, 'x' isn't in 'Keys'}) : { a: number; b: string; x: number; } +>{ a: 0, b: "hello", x: 8 // Should error, 'x' isn't in 'Keys'} : { a: number; b: string; x: number; } + + a: 0, +>a : number +>0 : 0 + + b: "hello", +>b : string +>"hello" : "hello" + + x: 8 // Should error, 'x' isn't in 'Keys' +>x : number +>8 : 8 + +}); + +// Should be OK -- retain info that a is number and b is string +let a = p.a.toFixed(); +>a : string +>p.a.toFixed() : string +>p.a.toFixed : (fractionDigits?: number) => string +>p.a : number +>p : { a: number; b: string; x: number; } +>a : number +>toFixed : (fractionDigits?: number) => string + +let b = p.b.substring(1); +>b : string +>p.b.substring(1) : string +>p.b.substring : (start: number, end?: number) => string +>p.b : string +>p : { a: number; b: string; x: number; } +>b : string +>substring : (start: number, end?: number) => string +>1 : 1 + +// Should error even though 'd' is in 'Keys' +let d = p.d; +>d : any +>p.d : any +>p : { a: number; b: string; x: number; } +>d : any + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt new file mode 100644 index 0000000000000..62e873fdcf1c7 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt @@ -0,0 +1,30 @@ +/a.js(13,5): error TS1223: 'satisfies' tag already specified. +/a.js(18,5): error TS1223: 'satisfies' tag already specified. + + +==== /a.js (2 errors) ==== + /** + * @typedef {Object} T1 + * @property {number} a + */ + + /** + * @typedef {Object} T2 + * @property {number} a + */ + + /** + * @satisfies {T1} + * @satisfies {T2} + ~~~~~~~~~ +!!! error TS1223: 'satisfies' tag already specified. + */ + const t1 = { a: 1 }; + + /** + * @satisfies {number} + ~~~~~~~~~ +!!! error TS1223: 'satisfies' tag already specified. + */ + const t2 = /** @satisfies {number} */ (1); + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag11.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag11.symbols new file mode 100644 index 0000000000000..2cf68fe55027d --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag11.symbols @@ -0,0 +1,25 @@ +=== /a.js === +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {number} a + */ + +/** + * @satisfies {T1} + * @satisfies {T2} + */ +const t1 = { a: 1 }; +>t1 : Symbol(t1, Decl(a.js, 14, 5)) +>a : Symbol(a, Decl(a.js, 14, 12)) + +/** + * @satisfies {number} + */ +const t2 = /** @satisfies {number} */ (1); +>t2 : Symbol(t2, Decl(a.js, 19, 5)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag11.types b/tests/baselines/reference/checkJsdocSatisfiesTag11.types new file mode 100644 index 0000000000000..c02af90fc2f84 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag11.types @@ -0,0 +1,29 @@ +=== /a.js === +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {number} a + */ + +/** + * @satisfies {T1} + * @satisfies {T2} + */ +const t1 = { a: 1 }; +>t1 : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +/** + * @satisfies {number} + */ +const t2 = /** @satisfies {number} */ (1); +>t2 : 1 +>(1) : 1 +>1 : 1 + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt new file mode 100644 index 0000000000000..5ac43d867ac36 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt @@ -0,0 +1,68 @@ +/a.js(24,20): error TS1360: Type '{ a: number; b: number; }' does not satisfy the expected type 'T1'. + Object literal may only specify known properties, and 'b' does not exist in type 'T1'. +/a.js(44,25): error TS1360: Type '{ a: string; b: string; }' does not satisfy the expected type 'T2'. + Object literal may only specify known properties, and 'b' does not exist in type 'T2'. +/a.js(51,17): error TS1360: Type 'number' does not satisfy the expected type 'string'. + + +==== /a.js (3 errors) ==== + /** + * @typedef {Object} T1 + * @property {number} a + */ + + /** + * @typedef {Object} T2 + * @property {string} a + */ + + /** + * @typedef {Object} T3 + * @property {"a" | "b"} a + */ + + /** + * @satisfies {T1} + */ + const t1 = { a: 1 }; + + /** + * @satisfies {T1} + */ + const t2 = { a: 1, b: 1 }; + ~~~~ +!!! error TS1360: Type '{ a: number; b: number; }' does not satisfy the expected type 'T1'. +!!! error TS1360: Object literal may only specify known properties, and 'b' does not exist in type 'T1'. + + /** + * @satisfies {T1} + */ + const t3 = {}; + + /** + * @satisfies {Array.} + */ + const t4 = [1, 2]; + + /** + * @satisfies {T2} + */ + const t5 = { a: 'test' }; + + /** + * @satisfies {T2} + */ + const t6 = { a: 'test', b: 'test' }; + ~~~~~~~~~ +!!! error TS1360: Type '{ a: string; b: string; }' does not satisfy the expected type 'T2'. +!!! error TS1360: Object literal may only specify known properties, and 'b' does not exist in type 'T2'. + + /** + * @satisfies {T3} + */ + const t7 = { a: "a" }; + + /** @satisfies {string} */ const t8 = (1); + ~~~~~~ +!!! error TS1360: Type 'number' does not satisfy the expected type 'string'. + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols new file mode 100644 index 0000000000000..62526d8a278f4 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols @@ -0,0 +1,68 @@ +=== /a.js === +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {string} a + */ + +/** + * @typedef {Object} T3 + * @property {"a" | "b"} a + */ + +/** + * @satisfies {T1} + */ +const t1 = { a: 1 }; +>t1 : Symbol(t1, Decl(a.js, 18, 5)) +>a : Symbol(a, Decl(a.js, 18, 12)) + +/** + * @satisfies {T1} + */ +const t2 = { a: 1, b: 1 }; +>t2 : Symbol(t2, Decl(a.js, 23, 5)) +>a : Symbol(a, Decl(a.js, 23, 12)) +>b : Symbol(b, Decl(a.js, 23, 18)) + +/** + * @satisfies {T1} + */ +const t3 = {}; +>t3 : Symbol(t3, Decl(a.js, 28, 5)) + +/** + * @satisfies {Array.} + */ +const t4 = [1, 2]; +>t4 : Symbol(t4, Decl(a.js, 33, 5)) + +/** + * @satisfies {T2} + */ +const t5 = { a: 'test' }; +>t5 : Symbol(t5, Decl(a.js, 38, 5)) +>a : Symbol(a, Decl(a.js, 38, 12)) + +/** + * @satisfies {T2} + */ +const t6 = { a: 'test', b: 'test' }; +>t6 : Symbol(t6, Decl(a.js, 43, 5)) +>a : Symbol(a, Decl(a.js, 43, 12)) +>b : Symbol(b, Decl(a.js, 43, 23)) + +/** + * @satisfies {T3} + */ +const t7 = { a: "a" }; +>t7 : Symbol(t7, Decl(a.js, 48, 5)) +>a : Symbol(a, Decl(a.js, 48, 12)) + +/** @satisfies {string} */ const t8 = (1); +>t8 : Symbol(t8, Decl(a.js, 50, 32)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag12.types b/tests/baselines/reference/checkJsdocSatisfiesTag12.types new file mode 100644 index 0000000000000..4c767ef749d66 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.types @@ -0,0 +1,86 @@ +=== /a.js === +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {string} a + */ + +/** + * @typedef {Object} T3 + * @property {"a" | "b"} a + */ + +/** + * @satisfies {T1} + */ +const t1 = { a: 1 }; +>t1 : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +/** + * @satisfies {T1} + */ +const t2 = { a: 1, b: 1 }; +>t2 : { a: number; b: number; } +>{ a: 1, b: 1 } : { a: number; b: number; } +>a : number +>1 : 1 +>b : number +>1 : 1 + +/** + * @satisfies {T1} + */ +const t3 = {}; +>t3 : {} +>{} : {} + +/** + * @satisfies {Array.} + */ +const t4 = [1, 2]; +>t4 : number[] +>[1, 2] : number[] +>1 : 1 +>2 : 2 + +/** + * @satisfies {T2} + */ +const t5 = { a: 'test' }; +>t5 : { a: string; } +>{ a: 'test' } : { a: string; } +>a : string +>'test' : "test" + +/** + * @satisfies {T2} + */ +const t6 = { a: 'test', b: 'test' }; +>t6 : { a: string; b: string; } +>{ a: 'test', b: 'test' } : { a: string; b: string; } +>a : string +>'test' : "test" +>b : string +>'test' : "test" + +/** + * @satisfies {T3} + */ +const t7 = { a: "a" }; +>t7 : { a: "a"; } +>{ a: "a" } : { a: "a"; } +>a : "a" +>"a" : "a" + +/** @satisfies {string} */ const t8 = (1); +>t8 : 1 +>(1) : 1 +>1 : 1 + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag13.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag13.errors.txt new file mode 100644 index 0000000000000..b4fbfe2e75e98 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag13.errors.txt @@ -0,0 +1,14 @@ +/a.js(5,14): error TS1360: Type '{ g: string; }' does not satisfy the expected type '{ f: (x: string) => string; }'. + Object literal may only specify known properties, and 'g' does not exist in type '{ f: (x: string) => string; }'. + + +==== /a.js (1 errors) ==== + /** @satisfies {{ f: (x: string) => string }} */ + const t1 = { f: s => s.toLowerCase() }; // should work + + /** @satisfies {{ f: (x: string) => string }} */ + const t2 = { g: "oops" }; // should error + ~~~~~~~~~ +!!! error TS1360: Type '{ g: string; }' does not satisfy the expected type '{ f: (x: string) => string; }'. +!!! error TS1360: Object literal may only specify known properties, and 'g' does not exist in type '{ f: (x: string) => string; }'. + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag13.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag13.symbols new file mode 100644 index 0000000000000..6d34d3b11dd36 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag13.symbols @@ -0,0 +1,15 @@ +=== /a.js === +/** @satisfies {{ f: (x: string) => string }} */ +const t1 = { f: s => s.toLowerCase() }; // should work +>t1 : Symbol(t1, Decl(a.js, 1, 5)) +>f : Symbol(f, Decl(a.js, 1, 12)) +>s : Symbol(s, Decl(a.js, 1, 15)) +>s.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +>s : Symbol(s, Decl(a.js, 1, 15)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) + +/** @satisfies {{ f: (x: string) => string }} */ +const t2 = { g: "oops" }; // should error +>t2 : Symbol(t2, Decl(a.js, 4, 5)) +>g : Symbol(g, Decl(a.js, 4, 12)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag13.types b/tests/baselines/reference/checkJsdocSatisfiesTag13.types new file mode 100644 index 0000000000000..ba3a4423aac3c --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag13.types @@ -0,0 +1,20 @@ +=== /a.js === +/** @satisfies {{ f: (x: string) => string }} */ +const t1 = { f: s => s.toLowerCase() }; // should work +>t1 : { f: (s: string) => string; } +>{ f: s => s.toLowerCase() } : { f: (s: string) => string; } +>f : (s: string) => string +>s => s.toLowerCase() : (s: string) => string +>s : string +>s.toLowerCase() : string +>s.toLowerCase : () => string +>s : string +>toLowerCase : () => string + +/** @satisfies {{ f: (x: string) => string }} */ +const t2 = { g: "oops" }; // should error +>t2 : { g: string; } +>{ g: "oops" } : { g: string; } +>g : string +>"oops" : "oops" + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag14.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag14.errors.txt new file mode 100644 index 0000000000000..eb0441c842d85 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag14.errors.txt @@ -0,0 +1,26 @@ +/a.js(7,15): error TS1005: '{' expected. +/a.js(8,2): error TS1005: '}' expected. +/a.js(10,27): error TS1005: '{' expected. +/a.js(10,30): error TS1005: '}' expected. + + +==== /a.js (4 errors) ==== + /** + * @typedef {Object} T1 + * @property {number} a + */ + + /** + * @satisfies T1 + ~~ +!!! error TS1005: '{' expected. + */ + +!!! error TS1005: '}' expected. + const t1 = { a: 1 }; + const t2 = /** @satisfies T1 */ ({ a: 1 }); + ~~ +!!! error TS1005: '{' expected. + +!!! error TS1005: '}' expected. + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag14.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag14.symbols new file mode 100644 index 0000000000000..b3d346e73b635 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag14.symbols @@ -0,0 +1,17 @@ +=== /a.js === +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @satisfies T1 + */ +const t1 = { a: 1 }; +>t1 : Symbol(t1, Decl(a.js, 8, 5)) +>a : Symbol(a, Decl(a.js, 8, 12)) + +const t2 = /** @satisfies T1 */ ({ a: 1 }); +>t2 : Symbol(t2, Decl(a.js, 9, 5)) +>a : Symbol(a, Decl(a.js, 9, 34)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag14.types b/tests/baselines/reference/checkJsdocSatisfiesTag14.types new file mode 100644 index 0000000000000..5f22412f586be --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag14.types @@ -0,0 +1,22 @@ +=== /a.js === +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @satisfies T1 + */ +const t1 = { a: 1 }; +>t1 : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +const t2 = /** @satisfies T1 */ ({ a: 1 }); +>t2 : { a: number; } +>({ a: 1 }) : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag2.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag2.symbols new file mode 100644 index 0000000000000..242f5c2f922a7 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag2.symbols @@ -0,0 +1,18 @@ +=== /a.js === +/** @typedef {Object. boolean>} Predicates */ + +const p = /** @satisfies {Predicates} */ ({ +>p : Symbol(p, Decl(a.js, 2, 5)) + + isEven: n => n % 2 === 0, +>isEven : Symbol(isEven, Decl(a.js, 2, 43)) +>n : Symbol(n, Decl(a.js, 3, 11)) +>n : Symbol(n, Decl(a.js, 3, 11)) + + isOdd: n => n % 2 === 1 +>isOdd : Symbol(isOdd, Decl(a.js, 3, 29)) +>n : Symbol(n, Decl(a.js, 4, 10)) +>n : Symbol(n, Decl(a.js, 4, 10)) + +}); + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag2.types b/tests/baselines/reference/checkJsdocSatisfiesTag2.types new file mode 100644 index 0000000000000..3040c6258c795 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag2.types @@ -0,0 +1,30 @@ +=== /a.js === +/** @typedef {Object. boolean>} Predicates */ + +const p = /** @satisfies {Predicates} */ ({ +>p : { isEven: (n: number) => boolean; isOdd: (n: number) => boolean; } +>({ isEven: n => n % 2 === 0, isOdd: n => n % 2 === 1}) : { isEven: (n: number) => boolean; isOdd: (n: number) => boolean; } +>{ isEven: n => n % 2 === 0, isOdd: n => n % 2 === 1} : { isEven: (n: number) => boolean; isOdd: (n: number) => boolean; } + + isEven: n => n % 2 === 0, +>isEven : (n: number) => boolean +>n => n % 2 === 0 : (n: number) => boolean +>n : number +>n % 2 === 0 : boolean +>n % 2 : number +>n : number +>2 : 2 +>0 : 0 + + isOdd: n => n % 2 === 1 +>isOdd : (n: number) => boolean +>n => n % 2 === 1 : (n: number) => boolean +>n : number +>n % 2 === 1 : boolean +>n % 2 : number +>n : number +>2 : 2 +>1 : 1 + +}); + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag3.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag3.errors.txt new file mode 100644 index 0000000000000..bb8cbbe3192db --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag3.errors.txt @@ -0,0 +1,15 @@ +/a.js(3,7): error TS7006: Parameter 's' implicitly has an 'any' type. + + +==== /a.js (1 errors) ==== + /** @type {{ f(s: string): void } & Record }} */ + let obj = /** @satisfies {{ g(s: string): void } & Record} */ ({ + f(s) { }, // "incorrect" implicit any on 's' + ~ +!!! error TS7006: Parameter 's' implicitly has an 'any' type. + g(s) { } + }); + + // This needs to not crash (outer node is not expression) + /** @satisfies {{ f(s: string): void }} */ ({ f(x) { } }) + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag3.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag3.symbols new file mode 100644 index 0000000000000..d468ebb534842 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag3.symbols @@ -0,0 +1,20 @@ +=== /a.js === +/** @type {{ f(s: string): void } & Record }} */ +let obj = /** @satisfies {{ g(s: string): void } & Record} */ ({ +>obj : Symbol(obj, Decl(a.js, 1, 3)) + + f(s) { }, // "incorrect" implicit any on 's' +>f : Symbol(f, Decl(a.js, 1, 81)) +>s : Symbol(s, Decl(a.js, 2, 6)) + + g(s) { } +>g : Symbol(g, Decl(a.js, 2, 13)) +>s : Symbol(s, Decl(a.js, 3, 6)) + +}); + +// This needs to not crash (outer node is not expression) +/** @satisfies {{ f(s: string): void }} */ ({ f(x) { } }) +>f : Symbol(f, Decl(a.js, 7, 45)) +>x : Symbol(x, Decl(a.js, 7, 48)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag3.types b/tests/baselines/reference/checkJsdocSatisfiesTag3.types new file mode 100644 index 0000000000000..5714bec139a41 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag3.types @@ -0,0 +1,24 @@ +=== /a.js === +/** @type {{ f(s: string): void } & Record }} */ +let obj = /** @satisfies {{ g(s: string): void } & Record} */ ({ +>obj : { f(s: string): void; } & Record +>({ f(s) { }, // "incorrect" implicit any on 's' g(s) { }}) : { f(s: string): void; } & Record +>{ f(s) { }, // "incorrect" implicit any on 's' g(s) { }} : { f(s: any): void; g(s: string): void; } + + f(s) { }, // "incorrect" implicit any on 's' +>f : (s: any) => void +>s : any + + g(s) { } +>g : (s: string) => void +>s : string + +}); + +// This needs to not crash (outer node is not expression) +/** @satisfies {{ f(s: string): void }} */ ({ f(x) { } }) +>({ f(x) { } }) : { f(x: string): void; } +>{ f(x) { } } : { f(x: string): void; } +>f : (x: string) => void +>x : string + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag4.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag4.errors.txt new file mode 100644 index 0000000000000..916671a213854 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag4.errors.txt @@ -0,0 +1,22 @@ +/a.js(5,32): error TS1360: Type '{}' does not satisfy the expected type 'Foo'. + Property 'a' is missing in type '{}' but required in type 'Foo'. + + +==== /a.js (1 errors) ==== + /** + * @typedef {Object} Foo + * @property {number} a + */ + export default /** @satisfies {Foo} */ ({}); + ~~~ +!!! error TS1360: Type '{}' does not satisfy the expected type 'Foo'. +!!! error TS1360: Property 'a' is missing in type '{}' but required in type 'Foo'. +!!! related TS2728 /a.js:3:4: 'a' is declared here. + +==== /b.js (0 errors) ==== + /** + * @typedef {Object} Foo + * @property {number} a + */ + + export default /** @satisfies {Foo} */ ({ a: 1 }); \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag4.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag4.symbols new file mode 100644 index 0000000000000..1ffaa36ba0993 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag4.symbols @@ -0,0 +1,17 @@ +=== /a.js === + +/** + * @typedef {Object} Foo + * @property {number} a + */ +export default /** @satisfies {Foo} */ ({}); + +=== /b.js === +/** + * @typedef {Object} Foo + * @property {number} a + */ + +export default /** @satisfies {Foo} */ ({ a: 1 }); +>a : Symbol(a, Decl(b.js, 5, 41)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag4.types b/tests/baselines/reference/checkJsdocSatisfiesTag4.types new file mode 100644 index 0000000000000..fa03b4f474065 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag4.types @@ -0,0 +1,21 @@ +=== /a.js === +/** + * @typedef {Object} Foo + * @property {number} a + */ +export default /** @satisfies {Foo} */ ({}); +>({}) : {} +>{} : {} + +=== /b.js === +/** + * @typedef {Object} Foo + * @property {number} a + */ + +export default /** @satisfies {Foo} */ ({ a: 1 }); +>({ a: 1 }) : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag5.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag5.symbols new file mode 100644 index 0000000000000..0e36d7d335dbd --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag5.symbols @@ -0,0 +1,20 @@ +=== /a.js === +/** @typedef {{ move(distance: number): void }} Movable */ + +const car = /** @satisfies {Movable & Record} */ ({ +>car : Symbol(car, Decl(a.js, 2, 5)) + + start() { }, +>start : Symbol(start, Decl(a.js, 2, 68)) + + move(d) { +>move : Symbol(move, Decl(a.js, 3, 16)) +>d : Symbol(d, Decl(a.js, 4, 9)) + + // d should be number + }, + stop() { } +>stop : Symbol(stop, Decl(a.js, 6, 6)) + +}) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag5.types b/tests/baselines/reference/checkJsdocSatisfiesTag5.types new file mode 100644 index 0000000000000..5bcd9c0ce9337 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag5.types @@ -0,0 +1,22 @@ +=== /a.js === +/** @typedef {{ move(distance: number): void }} Movable */ + +const car = /** @satisfies {Movable & Record} */ ({ +>car : { start(): void; move(d: number): void; stop(): void; } +>({ start() { }, move(d) { // d should be number }, stop() { }}) : { start(): void; move(d: number): void; stop(): void; } +>{ start() { }, move(d) { // d should be number }, stop() { }} : { start(): void; move(d: number): void; stop(): void; } + + start() { }, +>start : () => void + + move(d) { +>move : (d: number) => void +>d : number + + // d should be number + }, + stop() { } +>stop : () => void + +}) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag6.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag6.errors.txt new file mode 100644 index 0000000000000..81d15c60388a2 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag6.errors.txt @@ -0,0 +1,21 @@ +/a.js(14,11): error TS2339: Property 'y' does not exist on type '{ x: number; }'. + + +==== /a.js (1 errors) ==== + /** + * @typedef {Object} Point2d + * @property {number} x + * @property {number} y + */ + + // Undesirable behavior today with type annotation + const a = /** @satisfies {Partial} */ ({ x: 10 }); + + // Should OK + console.log(a.x.toFixed()); + + // Should error + let p = a.y; + ~ +!!! error TS2339: Property 'y' does not exist on type '{ x: number; }'. + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag6.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag6.symbols new file mode 100644 index 0000000000000..76a4c6ebcf7eb --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag6.symbols @@ -0,0 +1,28 @@ +=== /a.js === +/** + * @typedef {Object} Point2d + * @property {number} x + * @property {number} y + */ + +// Undesirable behavior today with type annotation +const a = /** @satisfies {Partial} */ ({ x: 10 }); +>a : Symbol(a, Decl(a.js, 7, 5)) +>x : Symbol(x, Decl(a.js, 7, 49)) + +// Should OK +console.log(a.x.toFixed()); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>a.x.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>a.x : Symbol(x, Decl(a.js, 7, 49)) +>a : Symbol(a, Decl(a.js, 7, 5)) +>x : Symbol(x, Decl(a.js, 7, 49)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + +// Should error +let p = a.y; +>p : Symbol(p, Decl(a.js, 13, 3)) +>a : Symbol(a, Decl(a.js, 7, 5)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag6.types b/tests/baselines/reference/checkJsdocSatisfiesTag6.types new file mode 100644 index 0000000000000..a7117dbc5a134 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag6.types @@ -0,0 +1,35 @@ +=== /a.js === +/** + * @typedef {Object} Point2d + * @property {number} x + * @property {number} y + */ + +// Undesirable behavior today with type annotation +const a = /** @satisfies {Partial} */ ({ x: 10 }); +>a : { x: number; } +>({ x: 10 }) : { x: number; } +>{ x: 10 } : { x: number; } +>x : number +>10 : 10 + +// Should OK +console.log(a.x.toFixed()); +>console.log(a.x.toFixed()) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>a.x.toFixed() : string +>a.x.toFixed : (fractionDigits?: number) => string +>a.x : number +>a : { x: number; } +>x : number +>toFixed : (fractionDigits?: number) => string + +// Should error +let p = a.y; +>p : any +>a.y : any +>a : { x: number; } +>y : any + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag7.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag7.errors.txt new file mode 100644 index 0000000000000..a815d1cd7e73c --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag7.errors.txt @@ -0,0 +1,26 @@ +/a.js(6,5): error TS1360: Type '{ a: number; b: string; x: number; }' does not satisfy the expected type 'Record'. + Object literal may only specify known properties, and 'x' does not exist in type 'Record'. +/a.js(14,11): error TS2339: Property 'd' does not exist on type '{ a: number; b: string; x: number; }'. + + +==== /a.js (2 errors) ==== + /** @typedef {"a" | "b" | "c" | "d"} Keys */ + + const p = /** @satisfies {Record} */ ({ + a: 0, + b: "hello", + x: 8 // Should error, 'x' isn't in 'Keys' + ~~~~ +!!! error TS1360: Type '{ a: number; b: string; x: number; }' does not satisfy the expected type 'Record'. +!!! error TS1360: Object literal may only specify known properties, and 'x' does not exist in type 'Record'. + }) + + // Should be OK -- retain info that a is number and b is string + let a = p.a.toFixed(); + let b = p.b.substring(1); + + // Should error even though 'd' is in 'Keys' + let d = p.d; + ~ +!!! error TS2339: Property 'd' does not exist on type '{ a: number; b: string; x: number; }'. + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag7.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag7.symbols new file mode 100644 index 0000000000000..c1c87516212c2 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag7.symbols @@ -0,0 +1,39 @@ +=== /a.js === +/** @typedef {"a" | "b" | "c" | "d"} Keys */ + +const p = /** @satisfies {Record} */ ({ +>p : Symbol(p, Decl(a.js, 2, 5)) + + a: 0, +>a : Symbol(a, Decl(a.js, 2, 54)) + + b: "hello", +>b : Symbol(b, Decl(a.js, 3, 9)) + + x: 8 // Should error, 'x' isn't in 'Keys' +>x : Symbol(x, Decl(a.js, 4, 15)) + +}) + +// Should be OK -- retain info that a is number and b is string +let a = p.a.toFixed(); +>a : Symbol(a, Decl(a.js, 9, 3)) +>p.a.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>p.a : Symbol(a, Decl(a.js, 2, 54)) +>p : Symbol(p, Decl(a.js, 2, 5)) +>a : Symbol(a, Decl(a.js, 2, 54)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + +let b = p.b.substring(1); +>b : Symbol(b, Decl(a.js, 10, 3)) +>p.b.substring : Symbol(String.substring, Decl(lib.es5.d.ts, --, --)) +>p.b : Symbol(b, Decl(a.js, 3, 9)) +>p : Symbol(p, Decl(a.js, 2, 5)) +>b : Symbol(b, Decl(a.js, 3, 9)) +>substring : Symbol(String.substring, Decl(lib.es5.d.ts, --, --)) + +// Should error even though 'd' is in 'Keys' +let d = p.d; +>d : Symbol(d, Decl(a.js, 13, 3)) +>p : Symbol(p, Decl(a.js, 2, 5)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag7.types b/tests/baselines/reference/checkJsdocSatisfiesTag7.types new file mode 100644 index 0000000000000..2b0987ca00bc9 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag7.types @@ -0,0 +1,49 @@ +=== /a.js === +/** @typedef {"a" | "b" | "c" | "d"} Keys */ + +const p = /** @satisfies {Record} */ ({ +>p : { a: number; b: string; x: number; } +>({ a: 0, b: "hello", x: 8 // Should error, 'x' isn't in 'Keys'}) : { a: number; b: string; x: number; } +>{ a: 0, b: "hello", x: 8 // Should error, 'x' isn't in 'Keys'} : { a: number; b: string; x: number; } + + a: 0, +>a : number +>0 : 0 + + b: "hello", +>b : string +>"hello" : "hello" + + x: 8 // Should error, 'x' isn't in 'Keys' +>x : number +>8 : 8 + +}) + +// Should be OK -- retain info that a is number and b is string +let a = p.a.toFixed(); +>a : string +>p.a.toFixed() : string +>p.a.toFixed : (fractionDigits?: number) => string +>p.a : number +>p : { a: number; b: string; x: number; } +>a : number +>toFixed : (fractionDigits?: number) => string + +let b = p.b.substring(1); +>b : string +>p.b.substring(1) : string +>p.b.substring : (start: number, end?: number) => string +>p.b : string +>p : { a: number; b: string; x: number; } +>b : string +>substring : (start: number, end?: number) => string +>1 : 1 + +// Should error even though 'd' is in 'Keys' +let d = p.d; +>d : any +>p.d : any +>p : { a: number; b: string; x: number; } +>d : any + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag8.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag8.errors.txt new file mode 100644 index 0000000000000..249f07b4e567e --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag8.errors.txt @@ -0,0 +1,14 @@ +/a.js(6,5): error TS2322: Type 'string' is not assignable to type 'boolean'. + + +==== /a.js (1 errors) ==== + /** @typedef {Object.} Facts */ + + // Should be able to detect a failure here + const x = /** @satisfies {Facts} */ ({ + m: true, + s: "false" + ~ +!!! error TS2322: Type 'string' is not assignable to type 'boolean'. + }) + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag8.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag8.symbols new file mode 100644 index 0000000000000..8997ccecede79 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag8.symbols @@ -0,0 +1,15 @@ +=== /a.js === +/** @typedef {Object.} Facts */ + +// Should be able to detect a failure here +const x = /** @satisfies {Facts} */ ({ +>x : Symbol(x, Decl(a.js, 3, 5)) + + m: true, +>m : Symbol(m, Decl(a.js, 3, 38)) + + s: "false" +>s : Symbol(s, Decl(a.js, 4, 12)) + +}) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag8.types b/tests/baselines/reference/checkJsdocSatisfiesTag8.types new file mode 100644 index 0000000000000..59a3342382ffb --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag8.types @@ -0,0 +1,19 @@ +=== /a.js === +/** @typedef {Object.} Facts */ + +// Should be able to detect a failure here +const x = /** @satisfies {Facts} */ ({ +>x : { m: true; s: string; } +>({ m: true, s: "false"}) : { m: true; s: string; } +>{ m: true, s: "false"} : { m: true; s: string; } + + m: true, +>m : true +>true : true + + s: "false" +>s : string +>"false" : "false" + +}) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag9.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag9.errors.txt new file mode 100644 index 0000000000000..4f002d1cff1f7 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag9.errors.txt @@ -0,0 +1,22 @@ +/a.js(11,26): error TS2322: Type '{ r: number; g: number; d: number; }' is not assignable to type 'Color'. + Object literal may only specify known properties, and 'd' does not exist in type 'Color'. + + +==== /a.js (1 errors) ==== + /** + * @typedef {Object} Color + * @property {number} r + * @property {number} g + * @property {number} b + */ + + // All of these should be Colors, but I only use some of them here. + export const Palette = /** @satisfies {Record} */ ({ + white: { r: 255, g: 255, b: 255 }, + black: { r: 0, g: 0, d: 0 }, // <- oops! 'd' in place of 'b' + ~~~~ +!!! error TS2322: Type '{ r: number; g: number; d: number; }' is not assignable to type 'Color'. +!!! error TS2322: Object literal may only specify known properties, and 'd' does not exist in type 'Color'. + blue: { r: 0, g: 0, b: 255 }, + }); + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag9.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag9.symbols new file mode 100644 index 0000000000000..46966d216afcf --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag9.symbols @@ -0,0 +1,32 @@ +=== /a.js === +/** + * @typedef {Object} Color + * @property {number} r + * @property {number} g + * @property {number} b + */ + +// All of these should be Colors, but I only use some of them here. +export const Palette = /** @satisfies {Record} */ ({ +>Palette : Symbol(Palette, Decl(a.js, 8, 12)) + + white: { r: 255, g: 255, b: 255 }, +>white : Symbol(white, Decl(a.js, 8, 67)) +>r : Symbol(r, Decl(a.js, 9, 12)) +>g : Symbol(g, Decl(a.js, 9, 20)) +>b : Symbol(b, Decl(a.js, 9, 28)) + + black: { r: 0, g: 0, d: 0 }, // <- oops! 'd' in place of 'b' +>black : Symbol(black, Decl(a.js, 9, 38)) +>r : Symbol(r, Decl(a.js, 10, 12)) +>g : Symbol(g, Decl(a.js, 10, 18)) +>d : Symbol(d, Decl(a.js, 10, 24)) + + blue: { r: 0, g: 0, b: 255 }, +>blue : Symbol(blue, Decl(a.js, 10, 32)) +>r : Symbol(r, Decl(a.js, 11, 11)) +>g : Symbol(g, Decl(a.js, 11, 17)) +>b : Symbol(b, Decl(a.js, 11, 23)) + +}); + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag9.types b/tests/baselines/reference/checkJsdocSatisfiesTag9.types new file mode 100644 index 0000000000000..3aaee7a16270f --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag9.types @@ -0,0 +1,46 @@ +=== /a.js === +/** + * @typedef {Object} Color + * @property {number} r + * @property {number} g + * @property {number} b + */ + +// All of these should be Colors, but I only use some of them here. +export const Palette = /** @satisfies {Record} */ ({ +>Palette : { white: { r: number; g: number; b: number; }; black: { r: number; g: number; d: number; }; blue: { r: number; g: number; b: number; }; } +>({ white: { r: 255, g: 255, b: 255 }, black: { r: 0, g: 0, d: 0 }, // <- oops! 'd' in place of 'b' blue: { r: 0, g: 0, b: 255 },}) : { white: { r: number; g: number; b: number; }; black: { r: number; g: number; d: number; }; blue: { r: number; g: number; b: number; }; } +>{ white: { r: 255, g: 255, b: 255 }, black: { r: 0, g: 0, d: 0 }, // <- oops! 'd' in place of 'b' blue: { r: 0, g: 0, b: 255 },} : { white: { r: number; g: number; b: number; }; black: { r: number; g: number; d: number; }; blue: { r: number; g: number; b: number; }; } + + white: { r: 255, g: 255, b: 255 }, +>white : { r: number; g: number; b: number; } +>{ r: 255, g: 255, b: 255 } : { r: number; g: number; b: number; } +>r : number +>255 : 255 +>g : number +>255 : 255 +>b : number +>255 : 255 + + black: { r: 0, g: 0, d: 0 }, // <- oops! 'd' in place of 'b' +>black : { r: number; g: number; d: number; } +>{ r: 0, g: 0, d: 0 } : { r: number; g: number; d: number; } +>r : number +>0 : 0 +>g : number +>0 : 0 +>d : number +>0 : 0 + + blue: { r: 0, g: 0, b: 255 }, +>blue : { r: number; g: number; b: number; } +>{ r: 0, g: 0, b: 255 } : { r: number; g: number; b: number; } +>r : number +>0 : 0 +>g : number +>0 : 0 +>b : number +>255 : 255 + +}); + diff --git a/tests/baselines/reference/jsdocSatisfiesTagFindAllReferences.baseline.jsonc b/tests/baselines/reference/jsdocSatisfiesTagFindAllReferences.baseline.jsonc new file mode 100644 index 0000000000000..c0188df554db4 --- /dev/null +++ b/tests/baselines/reference/jsdocSatisfiesTagFindAllReferences.baseline.jsonc @@ -0,0 +1,116 @@ +// === /a.js === +// /** +// * @typedef {Object} [|T|] +// * @property {number} a +// */ +// +// /** @satisfies {/*FIND ALL REFS*/[|T|]} comment */ +// const foo = { a: 1 }; + +[ + { + "definition": { + "containerKind": "", + "containerName": "", + "fileName": "/a.js", + "kind": "type", + "name": "type T = {\n a: number;\n}", + "textSpan": { + "start": 25, + "length": 1 + }, + "displayParts": [ + { + "text": "type", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "T", + "kind": "aliasName" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "=", + "kind": "operator" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "{", + "kind": "punctuation" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "a", + "kind": "propertyName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "number", + "kind": "keyword" + }, + { + "text": ";", + "kind": "punctuation" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": "}", + "kind": "punctuation" + } + ], + "contextSpan": { + "start": 7, + "length": 45 + } + }, + "references": [ + { + "textSpan": { + "start": 25, + "length": 1 + }, + "fileName": "/a.js", + "contextSpan": { + "start": 7, + "length": 45 + }, + "isWriteAccess": true + }, + { + "textSpan": { + "start": 72, + "length": 1 + }, + "fileName": "/a.js", + "isWriteAccess": false + } + ] + } +] \ No newline at end of file diff --git a/tests/baselines/reference/jsdocSatisfiesTagRename.baseline b/tests/baselines/reference/jsdocSatisfiesTagRename.baseline new file mode 100644 index 0000000000000..7e6ebcc931818 --- /dev/null +++ b/tests/baselines/reference/jsdocSatisfiesTagRename.baseline @@ -0,0 +1,9 @@ +/*====== /a.js ======*/ + +/** + * @typedef {Object} RENAME + * @property {number} a + */ + +/** @satisfies {[|RENAME|]} comment */ +const foo = { a: 1 }; diff --git a/tests/baselines/reference/quickInfoSatisfiesTag.baseline b/tests/baselines/reference/quickInfoSatisfiesTag.baseline new file mode 100644 index 0000000000000..8f91bd95ca89d --- /dev/null +++ b/tests/baselines/reference/quickInfoSatisfiesTag.baseline @@ -0,0 +1,63 @@ +[ + { + "marker": { + "fileName": "/a.js", + "position": 41, + "name": "1" + }, + "quickInfo": { + "kind": "const", + "kindModifiers": "", + "textSpan": { + "start": 41, + "length": 1 + }, + "displayParts": [ + { + "text": "const", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "a", + "kind": "localName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "1", + "kind": "stringLiteral" + } + ], + "documentation": [], + "tags": [ + { + "name": "satisfies", + "text": [ + { + "text": "{number}", + "kind": "text" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "comment", + "kind": "text" + } + ] + } + ] + } + } +] \ No newline at end of file diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag1.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag1.ts new file mode 100644 index 0000000000000..804f1e7d0294d --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag1.ts @@ -0,0 +1,37 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js + +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {"a" | "b"} a + */ + +/** + * @typedef {(x: string) => string} T3 + */ + +/** + * @typedef {Object} T4 + * @property {string} a + */ + +const t1 = /** @satisfies {T1} */ ({ a: 1 }); +const t2 = /** @satisfies {T1} */ ({ a: 1, b: 1 }); +const t3 = /** @satisfies {T1} */ ({}); + +/** @type {T2} */ +const t4 = /** @satisfies {T2} */ ({ a: "a" }); + +/** @type {(m: string) => string} */ +const t5 = /** @satisfies {T3} */((m) => m.substring(0)); +const t6 = /** @satisfies {[number, number]} */ ([1, 2]); +const t7 = /** @satisfies {T4} */ ({ a: 'test' }); +const t8 = /** @satisfies {T4} */ ({ a: 'test', b: 'test' }); diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag10.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag10.ts new file mode 100644 index 0000000000000..991888b6c57bb --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag10.ts @@ -0,0 +1,19 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/** @typedef {"a" | "b" | "c" | "d"} Keys */ + +const p = /** @satisfies {Partial>} */ ({ + a: 0, + b: "hello", + x: 8 // Should error, 'x' isn't in 'Keys' +}); + +// Should be OK -- retain info that a is number and b is string +let a = p.a.toFixed(); +let b = p.b.substring(1); + +// Should error even though 'd' is in 'Keys' +let d = p.d; diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts new file mode 100644 index 0000000000000..694bc1601e408 --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts @@ -0,0 +1,25 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {number} a + */ + +/** + * @satisfies {T1} + * @satisfies {T2} + */ +const t1 = { a: 1 }; + +/** + * @satisfies {number} + */ +const t2 = /** @satisfies {number} */ (1); diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts new file mode 100644 index 0000000000000..e8e6ad971fb3c --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts @@ -0,0 +1,56 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {string} a + */ + +/** + * @typedef {Object} T3 + * @property {"a" | "b"} a + */ + +/** + * @satisfies {T1} + */ +const t1 = { a: 1 }; + +/** + * @satisfies {T1} + */ +const t2 = { a: 1, b: 1 }; + +/** + * @satisfies {T1} + */ +const t3 = {}; + +/** + * @satisfies {Array.} + */ +const t4 = [1, 2]; + +/** + * @satisfies {T2} + */ +const t5 = { a: 'test' }; + +/** + * @satisfies {T2} + */ +const t6 = { a: 'test', b: 'test' }; + +/** + * @satisfies {T3} + */ +const t7 = { a: "a" }; + +/** @satisfies {string} */ const t8 = (1); diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag13.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag13.ts new file mode 100644 index 0000000000000..b022eab234ffc --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag13.ts @@ -0,0 +1,11 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js + +/** @satisfies {{ f: (x: string) => string }} */ +const t1 = { f: s => s.toLowerCase() }; // should work + +/** @satisfies {{ f: (x: string) => string }} */ +const t2 = { g: "oops" }; // should error diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag14.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag14.ts new file mode 100644 index 0000000000000..26dbf9565a17a --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag14.ts @@ -0,0 +1,16 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js + +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @satisfies T1 + */ +const t1 = { a: 1 }; +const t2 = /** @satisfies T1 */ ({ a: 1 }); diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag2.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag2.ts new file mode 100644 index 0000000000000..4d1d41902bb41 --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag2.ts @@ -0,0 +1,11 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/** @typedef {Object. boolean>} Predicates */ + +const p = /** @satisfies {Predicates} */ ({ + isEven: n => n % 2 === 0, + isOdd: n => n % 2 === 1 +}); diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag3.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag3.ts new file mode 100644 index 0000000000000..6a77a622d1828 --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag3.ts @@ -0,0 +1,14 @@ +// @strict: true +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/** @type {{ f(s: string): void } & Record }} */ +let obj = /** @satisfies {{ g(s: string): void } & Record} */ ({ + f(s) { }, // "incorrect" implicit any on 's' + g(s) { } +}); + +// This needs to not crash (outer node is not expression) +/** @satisfies {{ f(s: string): void }} */ ({ f(x) { } }) diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag4.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag4.ts new file mode 100644 index 0000000000000..e9941cae8a2d6 --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag4.ts @@ -0,0 +1,19 @@ +// @noEmit: true +// @module: esnext +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/** + * @typedef {Object} Foo + * @property {number} a + */ +export default /** @satisfies {Foo} */ ({}); + +// @filename: /b.js +/** + * @typedef {Object} Foo + * @property {number} a + */ + +export default /** @satisfies {Foo} */ ({ a: 1 }); \ No newline at end of file diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag5.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag5.ts new file mode 100644 index 0000000000000..1d93127b1faf3 --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag5.ts @@ -0,0 +1,14 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/** @typedef {{ move(distance: number): void }} Movable */ + +const car = /** @satisfies {Movable & Record} */ ({ + start() { }, + move(d) { + // d should be number + }, + stop() { } +}) diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag6.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag6.ts new file mode 100644 index 0000000000000..4ac9b087f0990 --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag6.ts @@ -0,0 +1,19 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/** + * @typedef {Object} Point2d + * @property {number} x + * @property {number} y + */ + +// Undesirable behavior today with type annotation +const a = /** @satisfies {Partial} */ ({ x: 10 }); + +// Should OK +console.log(a.x.toFixed()); + +// Should error +let p = a.y; diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag7.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag7.ts new file mode 100644 index 0000000000000..e65ac9c167d97 --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag7.ts @@ -0,0 +1,19 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/** @typedef {"a" | "b" | "c" | "d"} Keys */ + +const p = /** @satisfies {Record} */ ({ + a: 0, + b: "hello", + x: 8 // Should error, 'x' isn't in 'Keys' +}) + +// Should be OK -- retain info that a is number and b is string +let a = p.a.toFixed(); +let b = p.b.substring(1); + +// Should error even though 'd' is in 'Keys' +let d = p.d; diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag8.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag8.ts new file mode 100644 index 0000000000000..05997240e8d26 --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag8.ts @@ -0,0 +1,13 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js + +/** @typedef {Object.} Facts */ + +// Should be able to detect a failure here +const x = /** @satisfies {Facts} */ ({ + m: true, + s: "false" +}) diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag9.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag9.ts new file mode 100644 index 0000000000000..c8738f182ec2d --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag9.ts @@ -0,0 +1,18 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/** + * @typedef {Object} Color + * @property {number} r + * @property {number} g + * @property {number} b + */ + +// All of these should be Colors, but I only use some of them here. +export const Palette = /** @satisfies {Record} */ ({ + white: { r: 255, g: 255, b: 255 }, + black: { r: 0, g: 0, d: 0 }, // <- oops! 'd' in place of 'b' + blue: { r: 0, g: 0, b: 255 }, +}); diff --git a/tests/cases/fourslash/gotoDefinitionSatisfiesTag.ts b/tests/cases/fourslash/gotoDefinitionSatisfiesTag.ts new file mode 100644 index 0000000000000..0b5ebe8e2af61 --- /dev/null +++ b/tests/cases/fourslash/gotoDefinitionSatisfiesTag.ts @@ -0,0 +1,18 @@ +/// + +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js + +/////** +//// * @typedef {Object} [|/*def*/T|] +//// * @property {number} a +//// */ +//// +/////** @satisfies {/*use*/[|T|]} comment */ +////const foo = { a: 1 }; + +goTo.marker("use"); +verify.goToDefinitionIs("def"); diff --git a/tests/cases/fourslash/jsdocSatisfiesTagCompletion1.ts b/tests/cases/fourslash/jsdocSatisfiesTagCompletion1.ts new file mode 100644 index 0000000000000..6e9e257e51e98 --- /dev/null +++ b/tests/cases/fourslash/jsdocSatisfiesTagCompletion1.ts @@ -0,0 +1,15 @@ +/// + +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/////** +//// * @satisfies {/**/} +//// */ +////const t = { a: 1 }; + +verify.completions( + { marker: "", exact: completion.globalTypes }, +); diff --git a/tests/cases/fourslash/jsdocSatisfiesTagCompletion2.ts b/tests/cases/fourslash/jsdocSatisfiesTagCompletion2.ts new file mode 100644 index 0000000000000..4680c83e17b90 --- /dev/null +++ b/tests/cases/fourslash/jsdocSatisfiesTagCompletion2.ts @@ -0,0 +1,15 @@ +/// + +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/////** +//// * @/**/ +//// */ +////const t = { a: 1 }; + +verify.completions( + { marker: "", includes: ["satisfies"] }, +); diff --git a/tests/cases/fourslash/jsdocSatisfiesTagFindAllReferences.ts b/tests/cases/fourslash/jsdocSatisfiesTagFindAllReferences.ts new file mode 100644 index 0000000000000..6d2cc6f928b78 --- /dev/null +++ b/tests/cases/fourslash/jsdocSatisfiesTagFindAllReferences.ts @@ -0,0 +1,17 @@ +/// + +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js + +/////** +//// * @typedef {Object} T +//// * @property {number} a +//// */ +//// +/////** @satisfies {/**/T} comment */ +////const foo = { a: 1 }; + +verify.baselineFindAllReferences(""); diff --git a/tests/cases/fourslash/jsdocSatisfiesTagRename.ts b/tests/cases/fourslash/jsdocSatisfiesTagRename.ts new file mode 100644 index 0000000000000..f9cd90f20a9f2 --- /dev/null +++ b/tests/cases/fourslash/jsdocSatisfiesTagRename.ts @@ -0,0 +1,17 @@ +/// + +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js + +/////** +//// * @typedef {Object} T +//// * @property {number} a +//// */ +//// +/////** @satisfies {/**/T} comment */ +////const foo = { a: 1 }; + +verify.baselineRename("", { }); diff --git a/tests/cases/fourslash/quickInfoSatisfiesTag.ts b/tests/cases/fourslash/quickInfoSatisfiesTag.ts new file mode 100644 index 0000000000000..0c27105219ee2 --- /dev/null +++ b/tests/cases/fourslash/quickInfoSatisfiesTag.ts @@ -0,0 +1,11 @@ +/// + +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/////** @satisfies {number} comment */ +////const /*1*/a = 1; + +verify.baselineQuickInfo();