From 59b288f34f8a949d723343cf76a202f74f65e3a5 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Fri, 2 Dec 2022 21:02:47 +0200 Subject: [PATCH 01/16] feat(51086): add satisfies jsdoc tag --- src/compiler/checker.ts | 38 ++++++--- src/compiler/emitter.ts | 6 +- src/compiler/factory/nodeFactory.ts | 5 ++ src/compiler/factory/nodeTests.ts | 5 ++ src/compiler/parser.ts | 27 ++++-- src/compiler/types.ts | 14 ++++ src/compiler/utilities.ts | 17 +++- src/compiler/utilitiesPublic.ts | 8 +- .../reference/api/tsserverlibrary.d.ts | 25 ++++-- tests/baselines/reference/api/typescript.d.ts | 25 ++++-- .../checkJsdocSatisfiesTag1.errors.txt | 51 +++++++++++ .../reference/checkJsdocSatisfiesTag1.symbols | 57 +++++++++++++ .../reference/checkJsdocSatisfiesTag1.types | 84 +++++++++++++++++++ .../checkJsdocSatisfiesTag10.errors.txt | 26 ++++++ .../checkJsdocSatisfiesTag10.symbols | 39 +++++++++ .../reference/checkJsdocSatisfiesTag10.types | 49 +++++++++++ .../reference/checkJsdocSatisfiesTag2.symbols | 18 ++++ .../reference/checkJsdocSatisfiesTag2.types | 30 +++++++ .../checkJsdocSatisfiesTag3.errors.txt | 15 ++++ .../reference/checkJsdocSatisfiesTag3.symbols | 20 +++++ .../reference/checkJsdocSatisfiesTag3.types | 24 ++++++ .../checkJsdocSatisfiesTag4.errors.txt | 22 +++++ .../reference/checkJsdocSatisfiesTag4.symbols | 17 ++++ .../reference/checkJsdocSatisfiesTag4.types | 21 +++++ .../reference/checkJsdocSatisfiesTag5.symbols | 20 +++++ .../reference/checkJsdocSatisfiesTag5.types | 22 +++++ .../checkJsdocSatisfiesTag6.errors.txt | 21 +++++ .../reference/checkJsdocSatisfiesTag6.symbols | 28 +++++++ .../reference/checkJsdocSatisfiesTag6.types | 35 ++++++++ .../checkJsdocSatisfiesTag7.errors.txt | 26 ++++++ .../reference/checkJsdocSatisfiesTag7.symbols | 39 +++++++++ .../reference/checkJsdocSatisfiesTag7.types | 49 +++++++++++ .../checkJsdocSatisfiesTag8.errors.txt | 14 ++++ .../reference/checkJsdocSatisfiesTag8.symbols | 15 ++++ .../reference/checkJsdocSatisfiesTag8.types | 19 +++++ .../checkJsdocSatisfiesTag9.errors.txt | 22 +++++ .../reference/checkJsdocSatisfiesTag9.symbols | 32 +++++++ .../reference/checkJsdocSatisfiesTag9.types | 46 ++++++++++ .../jsdoc/checkJsdocSatisfiesTag1.ts | 37 ++++++++ .../jsdoc/checkJsdocSatisfiesTag10.ts | 19 +++++ .../jsdoc/checkJsdocSatisfiesTag2.ts | 11 +++ .../jsdoc/checkJsdocSatisfiesTag3.ts | 14 ++++ .../jsdoc/checkJsdocSatisfiesTag4.ts | 19 +++++ .../jsdoc/checkJsdocSatisfiesTag5.ts | 14 ++++ .../jsdoc/checkJsdocSatisfiesTag6.ts | 19 +++++ .../jsdoc/checkJsdocSatisfiesTag7.ts | 19 +++++ .../jsdoc/checkJsdocSatisfiesTag8.ts | 13 +++ .../jsdoc/checkJsdocSatisfiesTag9.ts | 18 ++++ 48 files changed, 1179 insertions(+), 35 deletions(-) create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag1.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag1.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag1.types create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag10.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag10.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag10.types create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag2.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag2.types create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag3.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag3.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag3.types create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag4.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag4.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag4.types create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag5.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag5.types create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag6.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag6.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag6.types create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag7.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag7.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag7.types create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag8.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag8.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag8.types create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag9.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag9.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag9.types create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag1.ts create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag10.ts create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag2.ts create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag3.ts create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag4.ts create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag5.ts create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag6.ts create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag7.ts create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag8.ts create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag9.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 11164845ba139..02d26678e4bc2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -274,6 +274,7 @@ import { getJSDocHost, getJSDocParameterTags, getJSDocRoot, + getJSDocSatisfiesExpressionType, getJSDocTags, getJSDocThisTag, getJSDocType, @@ -338,8 +339,8 @@ import { hasAccessorModifier, hasAmbientModifier, hasContextSensitiveParameters, - HasDecorators, hasDecorators, + HasDecorators, hasDynamicName, hasEffectiveModifier, hasEffectiveModifiers, @@ -348,8 +349,8 @@ import { hasExtension, HasIllegalDecorators, HasIllegalModifiers, - HasInitializer, hasInitializer, + HasInitializer, hasJSDocNodes, hasJSDocParameterTags, hasJsonModuleEmitEnabled, @@ -539,6 +540,7 @@ import { isJSDocParameterTag, isJSDocPropertyLikeTag, isJSDocReturnTag, + isJSDocSatisfiesExpression, isJSDocSignature, isJSDocTemplateTag, isJSDocTypeAlias, @@ -713,6 +715,7 @@ import { JSDocPropertyTag, JSDocProtectedTag, JSDocPublicTag, + JSDocSatisfiesTag, JSDocSignature, JSDocTemplateTag, JSDocTypedefTag, @@ -28682,6 +28685,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node); case SyntaxKind.ParenthesizedExpression: { + if (isJSDocSatisfiesExpression(parent)) { + return getTypeFromTypeNode(getJSDocSatisfiesExpressionType(parent)); + } // 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) : @@ -33674,15 +33680,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; } @@ -36406,9 +36413,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); } @@ -38632,6 +38644,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkSourceElement(node.typeExpression); } + function checkJSDocSatisfiesTag(node: JSDocSatisfiesTag) { + checkSourceElement(node.typeExpression); + } + function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) { if (node.name) { resolveJSDocMemberName(node.name, /*ignoreErrors*/ true); @@ -43138,6 +43154,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 d6da4d7d1f4f8..57fe27c59ce1e 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -261,6 +261,7 @@ import { JSDocOptionalType, JSDocPropertyLikeTag, JSDocReturnTag, + JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, JSDocTag, @@ -2122,7 +2123,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri case SyntaxKind.JSDocReturnTag: case SyntaxKind.JSDocThisTag: case SyntaxKind.JSDocTypeTag: - return emitJSDocSimpleTypedTag(node as JSDocTypeTag); + case SyntaxKind.JSDocSatisfiesTag: + return emitJSDocSimpleTypedTag(node as JSDocTypeTag | JSDocReturnTag | JSDocThisTag | JSDocTypeTag | JSDocSatisfiesTag); case SyntaxKind.JSDocTemplateTag: return emitJSDocTemplateTag(node as JSDocTemplateTag); case SyntaxKind.JSDocTypedefTag: @@ -4299,7 +4301,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri write("*/"); } - function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) { + function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag | 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 4e872d7babb73..f7ad0fe6cfad8 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -249,6 +249,7 @@ import { JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, + JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, JSDocTag, @@ -871,6 +872,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode get updateJSDocOverrideTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocOverrideTag); }, get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocDeprecatedTag); }, get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocDeprecatedTag); }, + get createJSDocSatisfiesTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocSatisfiesTag); }, + get updateJSDocSatisfiesTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocSatisfiesTag); }, createJSDocUnknownTag, updateJSDocUnknownTag, createJSDocText, @@ -5317,6 +5320,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; @@ -5328,6 +5332,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 14e296610c54f..0522c173cc270 100644 --- a/src/compiler/factory/nodeTests.ts +++ b/src/compiler/factory/nodeTests.ts @@ -105,6 +105,7 @@ import { JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, + JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, JSDocTemplateTag, @@ -1176,6 +1177,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; +} + // Synthesized list /** @internal */ diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f1f8bd002e875..93c2d2a7abd51 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -143,6 +143,7 @@ import { isJSDocFunctionType, isJSDocNullableType, isJSDocReturnTag, + isJSDocSatisfiesTag, isJSDocTypeTag, isJsxOpeningElement, isJsxOpeningFragment, @@ -189,6 +190,7 @@ import { JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, + JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, JSDocSyntaxKind, @@ -1100,10 +1102,12 @@ const forEachChildTable: ForEachChildTable = { visitNode(cbNode, node.typeExpression) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); }, - [SyntaxKind.JSDocReturnTag]: forEachChildInJSDocReturnTag, - [SyntaxKind.JSDocTypeTag]: forEachChildInJSDocReturnTag, - [SyntaxKind.JSDocThisTag]: forEachChildInJSDocReturnTag, - [SyntaxKind.JSDocEnumTag]: forEachChildInJSDocReturnTag, + [SyntaxKind.JSDocReturnTag]: forEachChildInJSDocTypeLikeTag, + [SyntaxKind.JSDocTypeTag]: forEachChildInJSDocTypeLikeTag, + [SyntaxKind.JSDocThisTag]: forEachChildInJSDocTypeLikeTag, + [SyntaxKind.JSDocEnumTag]: forEachChildInJSDocTypeLikeTag, + [SyntaxKind.JSDocSatisfiesTag]: forEachChildInJSDocTypeLikeTag, + [SyntaxKind.JSDocSignature]: function forEachChildInJSDocSignature(node: JSDocSignature, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return forEach(node.typeParameters, cbNode) || forEach(node.parameters, cbNode) || @@ -1197,7 +1201,7 @@ function forEachChildInJSDocParameterOrPropertyTag(node: JSDocParameterTag | (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); } -function forEachChildInJSDocReturnTag(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { +function forEachChildInJSDocTypeLikeTag(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag | 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)); @@ -8782,6 +8786,9 @@ namespace Parser { case "callback": tag = parseCallbackTag(start, tagName, margin, indentText); break; + case "satisfies": + tag = parseSatisfiesTag(start, tagName, margin, indentText); + break; case "see": tag = parseSeeTag(start, tagName, margin, indentText); break; @@ -9137,6 +9144,16 @@ 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 { + if (some(tags, isJSDocSatisfiesTag)) { + parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); + } + + const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + 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 7ce54d3dd6c02..56b53ca15ed13 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -430,6 +430,7 @@ export const enum SyntaxKind { JSDocTypedefTag, JSDocSeeTag, JSDocPropertyTag, + JSDocSatisfiesTag, // Synthesized list SyntaxList, @@ -954,6 +955,7 @@ export type ForEachChildNodes = | JSDocReadonlyTag | JSDocDeprecatedTag | JSDocOverrideTag + | JSDocSatisfiesTag ; /** @internal */ @@ -3857,6 +3859,16 @@ export interface JSDocTypeLiteral extends JSDocType { 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 @@ -8237,6 +8249,8 @@ export interface NodeFactory { updateJSDocDeprecatedTag(node: JSDocDeprecatedTag, tagName: Identifier, comment?: string | NodeArray): JSDocDeprecatedTag; createJSDocOverrideTag(tagName: Identifier, comment?: string | NodeArray): JSDocOverrideTag; updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray): JSDocOverrideTag; + 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 36cfe74f91e01..6a1dcf48dda0b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -167,6 +167,7 @@ import { getJSDocPublicTagNoCache, getJSDocReadonlyTagNoCache, getJSDocReturnType, + getJSDocSatisfiesTag, getJSDocTags, getJSDocType, getJSDocTypeParameterTags, @@ -193,8 +194,8 @@ import { getTrailingCommentRanges, HasExpressionInitializer, hasExtension, - HasInitializer, hasInitializer, + HasInitializer, HasJSDoc, hasJSDocNodes, HasModifiers, @@ -330,6 +331,7 @@ import { JSDocMemberName, JSDocParameterTag, JSDocPropertyLikeTag, + JSDocSatisfiesExpression, JSDocSignature, JSDocTag, JSDocTemplateTag, @@ -9128,3 +9130,16 @@ export function canUsePropertyAccess(name: string, languageVersion: ScriptTarget export function hasTabstop(node: Node): boolean { return getSnippetElement(node)?.kind === SnippetKind.TabStop; } + +/** @internal */ +export function isJSDocSatisfiesExpression(node: Node): node is JSDocSatisfiesExpression { + return isInJSFile(node) && isParenthesizedExpression(node) && !!getJSDocSatisfiesTag(node); +} + +/** @internal */ +export function getJSDocSatisfiesExpressionType(node: JSDocSatisfiesExpression) { + const tag = getJSDocSatisfiesTag(node); + const type = tag && tag.typeExpression && tag.typeExpression.type; + Debug.assertIsDefined(type); + return type; +} diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index e6c0c5a05602a..9977293ab93b5 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -77,8 +77,8 @@ import { getJSDocCommentsAndTags, getJSDocTypeParameterDeclarations, hasAccessorModifier, - hasDecorators, HasDecorators, + hasDecorators, HasExpressionInitializer, HasInitializer, HasJSDoc, @@ -129,6 +129,7 @@ import { isJSDocPublicTag, isJSDocReadonlyTag, isJSDocReturnTag, + isJSDocSatisfiesTag, isJSDocSignature, isJSDocTemplateTag, isJSDocThisTag, @@ -175,6 +176,7 @@ import { JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, + JSDocSatisfiesTag, JSDocSignature, JSDocTag, JSDocTemplateTag, @@ -1087,6 +1089,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/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e3e8466ccd1e7..0e0ce46ccb43f 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4349,14 +4349,15 @@ declare namespace ts { JSDocTypedefTag = 348, JSDocSeeTag = 349, JSDocPropertyTag = 350, - SyntaxList = 351, - NotEmittedStatement = 352, - PartiallyEmittedExpression = 353, - CommaListExpression = 354, - MergeDeclarationMarker = 355, - EndOfDeclarationMarker = 356, - SyntheticReferenceExpression = 357, - Count = 358, + JSDocSatisfiesTag = 351, + SyntaxList = 352, + NotEmittedStatement = 353, + PartiallyEmittedExpression = 354, + CommaListExpression = 355, + MergeDeclarationMarker = 356, + EndOfDeclarationMarker = 357, + SyntheticReferenceExpression = 358, + Count = 359, FirstAssignment = 63, LastAssignment = 78, FirstCompoundAssignment = 64, @@ -5966,6 +5967,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, @@ -7803,6 +7808,8 @@ declare namespace ts { updateJSDocDeprecatedTag(node: JSDocDeprecatedTag, tagName: Identifier, comment?: string | NodeArray): JSDocDeprecatedTag; createJSDocOverrideTag(tagName: Identifier, comment?: string | NodeArray): JSDocOverrideTag; updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray): JSDocOverrideTag; + 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; @@ -8643,6 +8650,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; /** @@ -9043,6 +9051,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 setTextRange(range: T, location: TextRange | undefined): T; function canHaveModifiers(node: Node): node is HasModifiers; function canHaveDecorators(node: Node): node is HasDecorators; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7bc06c330da8a..327676035bd7a 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -413,14 +413,15 @@ declare namespace ts { JSDocTypedefTag = 348, JSDocSeeTag = 349, JSDocPropertyTag = 350, - SyntaxList = 351, - NotEmittedStatement = 352, - PartiallyEmittedExpression = 353, - CommaListExpression = 354, - MergeDeclarationMarker = 355, - EndOfDeclarationMarker = 356, - SyntheticReferenceExpression = 357, - Count = 358, + JSDocSatisfiesTag = 351, + SyntaxList = 352, + NotEmittedStatement = 353, + PartiallyEmittedExpression = 354, + CommaListExpression = 355, + MergeDeclarationMarker = 356, + EndOfDeclarationMarker = 357, + SyntheticReferenceExpression = 358, + Count = 359, FirstAssignment = 63, LastAssignment = 78, FirstCompoundAssignment = 64, @@ -2030,6 +2031,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, @@ -3867,6 +3872,8 @@ declare namespace ts { updateJSDocDeprecatedTag(node: JSDocDeprecatedTag, tagName: Identifier, comment?: string | NodeArray): JSDocDeprecatedTag; createJSDocOverrideTag(tagName: Identifier, comment?: string | NodeArray): JSDocOverrideTag; updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray): JSDocOverrideTag; + 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; @@ -4707,6 +4714,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; /** @@ -5107,6 +5115,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 setTextRange(range: T, location: TextRange | undefined): T; function canHaveModifiers(node: Node): node is HasModifiers; function canHaveDecorators(node: Node): node is HasDecorators; diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag1.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag1.errors.txt new file mode 100644 index 0000000000000..cfc3f378c4cae --- /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 {Array.} */ ([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..0f645b66b10c8 --- /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 {Array.} */ ([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..ea3bc9f7c6c48 --- /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 {Array.} */ ([1, 2]); +>t6 : number[] +>([1, 2]) : number[] +>[1, 2] : 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/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/cases/conformance/jsdoc/checkJsdocSatisfiesTag1.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag1.ts new file mode 100644 index 0000000000000..b1557ea5dc97a --- /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 {Array.} */ ([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/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 }, +}); From 14bb0d6fd6f2f1063650dfc5b85b74b7fce8b1f1 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 17 Dec 2022 19:33:35 +0200 Subject: [PATCH 02/16] add tests --- .../checkJsdocSatisfiesTag11.errors.txt | 22 +++++++++++++++++++ .../checkJsdocSatisfiesTag11.symbols | 19 ++++++++++++++++ .../reference/checkJsdocSatisfiesTag11.types | 21 ++++++++++++++++++ .../jsdoc/checkJsdocSatisfiesTag11.ts | 20 +++++++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag11.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag11.types create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt new file mode 100644 index 0000000000000..d036e50acaf17 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt @@ -0,0 +1,22 @@ +/a.js(13,5): error TS1223: 'satisfies' tag already specified. + + +==== /a.js (1 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 }; + \ 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..19edf99007cf0 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag11.symbols @@ -0,0 +1,19 @@ +=== /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)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag11.types b/tests/baselines/reference/checkJsdocSatisfiesTag11.types new file mode 100644 index 0000000000000..bc810abf0f74e --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag11.types @@ -0,0 +1,21 @@ +=== /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 + diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts new file mode 100644 index 0000000000000..8ed152572e56f --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts @@ -0,0 +1,20 @@ +// @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 }; From 6e2627fb10ef1852c0360cff14c4646816033217 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 17 Dec 2022 21:14:38 +0200 Subject: [PATCH 03/16] handle JSDocSatisfies tag on declaration positions with initializer expression --- src/compiler/checker.ts | 18 +++-- src/compiler/utilities.ts | 9 ++- .../checkJsdocSatisfiesTag12.errors.txt | 59 ++++++++++++++++ .../checkJsdocSatisfiesTag12.symbols | 53 +++++++++++++++ .../reference/checkJsdocSatisfiesTag12.types | 67 +++++++++++++++++++ .../jsdoc/checkJsdocSatisfiesTag12.ts | 44 ++++++++++++ 6 files changed, 244 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag12.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag12.types create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b58bd376e6fbd..f7a262fc2833f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -284,6 +284,7 @@ import { getJSDocParameterTags, getJSDocRoot, getJSDocSatisfiesExpressionType, + getJSDocSatisfiesTypeNode, getJSDocTags, getJSDocThisTag, getJSDocType, @@ -10222,10 +10223,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Use the type of the initializer expression if one is present and the declaration is // not a parameter of a contextually typed function if (hasOnlyExpressionInitializer(declaration) && !!declaration.initializer) { - if (isInJSFile(declaration) && !isParameter(declaration)) { - const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfDeclaration(declaration), getDeclaredExpandoInitializer(declaration)); - if (containerObjectType) { - return containerObjectType; + if (isInJSFile(declaration)) { + const initializer = declaration.initializer; + if (!isJSDocSatisfiesExpression(initializer)) { + const typeNode = getJSDocSatisfiesTypeNode(declaration); + if (typeNode) { + return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode); + } + } + if (!isParameter(declaration)) { + const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfDeclaration(declaration), getDeclaredExpandoInitializer(declaration)); + if (containerObjectType) { + return containerObjectType; + } } } const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode)); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a58096692bfc0..026f00a89954a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -9492,8 +9492,13 @@ export function isJSDocSatisfiesExpression(node: Node): node is JSDocSatisfiesEx /** @internal */ export function getJSDocSatisfiesExpressionType(node: JSDocSatisfiesExpression) { - const tag = getJSDocSatisfiesTag(node); - const type = tag && tag.typeExpression && tag.typeExpression.type; + const type = getJSDocSatisfiesTypeNode(node); Debug.assertIsDefined(type); return type; } + +/** @internal */ +export function getJSDocSatisfiesTypeNode(node: Node) { + const tag = getJSDocSatisfiesTag(node); + return tag && tag.typeExpression && tag.typeExpression.type; +} diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt new file mode 100644 index 0000000000000..938de0ff74228 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt @@ -0,0 +1,59 @@ +/a.js(19,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(22,16): error TS1360: Type '{}' does not satisfy the expected type 'T1'. + Property 'a' is missing in type '{}' but required in type 'T1'. +/a.js(39,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 (3 errors) ==== + /** + * @typedef {Object} T1 + * @property {number} a + */ + + /** + * @typedef {Object} T2 + * @property {string} 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} + ~~ +!!! 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. + */ + 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'. + \ 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..7083311e98621 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols @@ -0,0 +1,53 @@ +=== /a.js === +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {string} a + */ + +/** + * @satisfies {T1} + */ +const t1 = { a: 1 }; +>t1 : Symbol(t1, Decl(a.js, 13, 5)) +>a : Symbol(a, Decl(a.js, 13, 12)) + +/** + * @satisfies {T1} + */ +const t2 = { a: 1, b: 1 }; +>t2 : Symbol(t2, Decl(a.js, 18, 5)) +>a : Symbol(a, Decl(a.js, 18, 12)) +>b : Symbol(b, Decl(a.js, 18, 18)) + +/** + * @satisfies {T1} + */ +const t3 = {}; +>t3 : Symbol(t3, Decl(a.js, 23, 5)) + +/** + * @satisfies {Array.} + */ +const t4 = [1, 2]; +>t4 : Symbol(t4, Decl(a.js, 28, 5)) + +/** + * @satisfies {T2} + */ +const t5 = { a: 'test' }; +>t5 : Symbol(t5, Decl(a.js, 33, 5)) +>a : Symbol(a, Decl(a.js, 33, 12)) + +/** + * @satisfies {T2} + */ +const t6 = { a: 'test', b: 'test' }; +>t6 : Symbol(t6, Decl(a.js, 38, 5)) +>a : Symbol(a, Decl(a.js, 38, 12)) +>b : Symbol(b, Decl(a.js, 38, 23)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag12.types b/tests/baselines/reference/checkJsdocSatisfiesTag12.types new file mode 100644 index 0000000000000..fbca19c90a8ba --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.types @@ -0,0 +1,67 @@ +=== /a.js === +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {string} 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" + diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts new file mode 100644 index 0000000000000..3eb7291735f55 --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts @@ -0,0 +1,44 @@ +// @noEmit: true +// @allowJS: true +// @checkJs: true + +// @filename: /a.js +/** + * @typedef {Object} T1 + * @property {number} a + */ + +/** + * @typedef {Object} T2 + * @property {string} 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' }; From 6020674a0e0234ffbc4f2f3b63d0712db4a8daa6 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Tue, 10 Jan 2023 11:12:10 +0200 Subject: [PATCH 04/16] forbid omitted braces --- src/compiler/parser.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 9dc825f99fdaf..c459d3b1a1f2d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -9162,8 +9162,7 @@ namespace Parser { if (some(tags, isJSDocSatisfiesTag)) { parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); } - - const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + 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); } From 3c7010764dd42560539e9c108910868d5f20ffa5 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Tue, 10 Jan 2023 11:15:29 +0200 Subject: [PATCH 05/16] wrap JavaScript checks into a single condition --- src/compiler/checker.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e7f37d7532538..ba0a731b1c105 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28833,14 +28833,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node); case SyntaxKind.ParenthesizedExpression: { - if (isJSDocSatisfiesExpression(parent)) { - return getTypeFromTypeNode(getJSDocSatisfiesExpressionType(parent)); - } - // 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); From 70edb6da9b4ea7f82159bbee25d7d9a348e3076a Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Tue, 10 Jan 2023 13:11:37 +0200 Subject: [PATCH 06/16] add tests --- src/compiler/checker.ts | 2 +- .../checkJsdocSatisfiesTag12.errors.txt | 16 +++++++-- .../checkJsdocSatisfiesTag12.symbols | 36 ++++++++++++------- .../reference/checkJsdocSatisfiesTag12.types | 14 ++++++++ .../checkJsdocSatisfiesTag13.errors.txt | 14 ++++++++ .../checkJsdocSatisfiesTag13.symbols | 15 ++++++++ .../reference/checkJsdocSatisfiesTag13.types | 20 +++++++++++ .../checkJsdocSatisfiesTag14.errors.txt | 26 ++++++++++++++ .../checkJsdocSatisfiesTag14.symbols | 17 +++++++++ .../reference/checkJsdocSatisfiesTag14.types | 22 ++++++++++++ .../jsdoc/checkJsdocSatisfiesTag12.ts | 10 ++++++ .../jsdoc/checkJsdocSatisfiesTag13.ts | 11 ++++++ .../jsdoc/checkJsdocSatisfiesTag14.ts | 16 +++++++++ 13 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag13.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag13.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag13.types create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag14.errors.txt create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag14.symbols create mode 100644 tests/baselines/reference/checkJsdocSatisfiesTag14.types create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag13.ts create mode 100644 tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag14.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ba0a731b1c105..5528a37b19995 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28151,7 +28151,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) ? getJSDocSatisfiesTypeNode(declaration) : undefined); if (typeNode) { return getTypeFromTypeNode(typeNode); } diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt index 938de0ff74228..d770056fbd2f7 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt @@ -1,8 +1,8 @@ -/a.js(19,20): error TS1360: Type '{ a: number; b: number; }' does not satisfy the expected type 'T1'. +/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(22,16): error TS1360: Type '{}' does not satisfy the expected type 'T1'. +/a.js(27,16): error TS1360: Type '{}' does not satisfy the expected type 'T1'. Property 'a' is missing in type '{}' but required in type 'T1'. -/a.js(39,25): error TS1360: Type '{ a: string; b: string; }' does not satisfy the expected type 'T2'. +/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'. @@ -17,6 +17,11 @@ * @property {string} a */ + /** + * @typedef {Object} T3 + * @property {"a" | "b"} a + */ + /** * @satisfies {T1} */ @@ -56,4 +61,9 @@ ~~~~~~~~~ !!! 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 {T2} + */ + const t7 = { a: "a" }; \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols index 7083311e98621..fd78da99f4d97 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols @@ -9,45 +9,57 @@ * @property {string} a */ +/** + * @typedef {Object} T3 + * @property {"a" | "b"} a + */ + /** * @satisfies {T1} */ const t1 = { a: 1 }; ->t1 : Symbol(t1, Decl(a.js, 13, 5)) ->a : Symbol(a, Decl(a.js, 13, 12)) +>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, 18, 5)) ->a : Symbol(a, Decl(a.js, 18, 12)) ->b : Symbol(b, Decl(a.js, 18, 18)) +>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, 23, 5)) +>t3 : Symbol(t3, Decl(a.js, 28, 5)) /** * @satisfies {Array.} */ const t4 = [1, 2]; ->t4 : Symbol(t4, Decl(a.js, 28, 5)) +>t4 : Symbol(t4, Decl(a.js, 33, 5)) /** * @satisfies {T2} */ const t5 = { a: 'test' }; ->t5 : Symbol(t5, Decl(a.js, 33, 5)) ->a : Symbol(a, Decl(a.js, 33, 12)) +>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, 38, 5)) ->a : Symbol(a, Decl(a.js, 38, 12)) ->b : Symbol(b, Decl(a.js, 38, 23)) +>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 {T2} + */ +const t7 = { a: "a" }; +>t7 : Symbol(t7, Decl(a.js, 48, 5)) +>a : Symbol(a, Decl(a.js, 48, 12)) diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag12.types b/tests/baselines/reference/checkJsdocSatisfiesTag12.types index fbca19c90a8ba..6325d514690b2 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag12.types +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.types @@ -9,6 +9,11 @@ * @property {string} a */ +/** + * @typedef {Object} T3 + * @property {"a" | "b"} a + */ + /** * @satisfies {T1} */ @@ -65,3 +70,12 @@ const t6 = { a: 'test', b: 'test' }; >b : string >'test' : "test" +/** + * @satisfies {T2} + */ +const t7 = { a: "a" }; +>t7 : { a: string; } +>{ a: "a" } : { a: string; } +>a : string +>"a" : "a" + 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/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts index 3eb7291735f55..2f3ea1c10fe2f 100644 --- a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts @@ -13,6 +13,11 @@ * @property {string} a */ +/** + * @typedef {Object} T3 + * @property {"a" | "b"} a + */ + /** * @satisfies {T1} */ @@ -42,3 +47,8 @@ const t5 = { a: 'test' }; * @satisfies {T2} */ const t6 = { a: 'test', b: 'test' }; + +/** + * @satisfies {T2} + */ +const t7 = { a: "a" }; 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 }); From b9205de7349baa61172954f077b6d3ba25ecb00e Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Tue, 10 Jan 2023 21:49:27 +0200 Subject: [PATCH 07/16] fix tests --- src/compiler/utilities.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 6f22de22cde7d..93fb5caf7439f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -9492,6 +9492,7 @@ export function isNonNullAccess(node: Node): node is AccessExpression { || kind === SyntaxKind.ElementAccessExpression) && isNonNullExpression((node as AccessExpression).expression); } +/** @internal */ export function isJSDocSatisfiesExpression(node: Node): node is JSDocSatisfiesExpression { return isInJSFile(node) && isParenthesizedExpression(node) && !!getJSDocSatisfiesTag(node); } From 398e1194a2267c1e7b871f415b55fa312b9913c4 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Wed, 11 Jan 2023 21:43:57 +0200 Subject: [PATCH 08/16] fix typo --- .../reference/checkJsdocSatisfiesTag12.errors.txt | 2 +- .../baselines/reference/checkJsdocSatisfiesTag12.symbols | 2 +- tests/baselines/reference/checkJsdocSatisfiesTag12.types | 8 ++++---- tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt index d770056fbd2f7..5ac523dfe552b 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt @@ -63,7 +63,7 @@ !!! error TS1360: Object literal may only specify known properties, and 'b' does not exist in type 'T2'. /** - * @satisfies {T2} + * @satisfies {T3} */ const t7 = { a: "a" }; \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols index fd78da99f4d97..aaaa12602063e 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols @@ -57,7 +57,7 @@ const t6 = { a: 'test', b: 'test' }; >b : Symbol(b, Decl(a.js, 43, 23)) /** - * @satisfies {T2} + * @satisfies {T3} */ const t7 = { a: "a" }; >t7 : Symbol(t7, Decl(a.js, 48, 5)) diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag12.types b/tests/baselines/reference/checkJsdocSatisfiesTag12.types index 6325d514690b2..568fafa6e1cd4 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag12.types +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.types @@ -71,11 +71,11 @@ const t6 = { a: 'test', b: 'test' }; >'test' : "test" /** - * @satisfies {T2} + * @satisfies {T3} */ const t7 = { a: "a" }; ->t7 : { a: string; } ->{ a: "a" } : { a: string; } ->a : string +>t7 : { a: "a"; } +>{ a: "a" } : { a: "a"; } +>a : "a" >"a" : "a" diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts index 2f3ea1c10fe2f..023b9d92bd9b7 100644 --- a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts @@ -49,6 +49,6 @@ const t5 = { a: 'test' }; const t6 = { a: 'test', b: 'test' }; /** - * @satisfies {T2} + * @satisfies {T3} */ const t7 = { a: "a" }; From 62b9c22e5355d598f5367c6b26ec2780a197c641 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Fri, 13 Jan 2023 01:00:20 +0200 Subject: [PATCH 09/16] improve handling of duplicate tags. fix handling a parenthesized initializer expression. rename utilities. --- src/compiler/checker.ts | 39 ++++++++++++------- src/compiler/parser.ts | 4 -- src/compiler/utilities.ts | 15 ++++--- .../checkJsdocSatisfiesTag11.errors.txt | 12 +++++- .../checkJsdocSatisfiesTag11.symbols | 6 +++ .../reference/checkJsdocSatisfiesTag11.types | 8 ++++ .../checkJsdocSatisfiesTag12.errors.txt | 11 +++--- .../checkJsdocSatisfiesTag12.symbols | 3 ++ .../reference/checkJsdocSatisfiesTag12.types | 5 +++ .../jsdoc/checkJsdocSatisfiesTag11.ts | 5 +++ .../jsdoc/checkJsdocSatisfiesTag12.ts | 2 + 11 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8cf148da42738..f44eb0c5dab03 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -222,6 +222,7 @@ import { GetAccessorDeclaration, getAliasDeclarationFromName, getAllAccessorDeclarations, + getAllJSDocTags, getAllowSyntheticDefaultImports, getAncestor, getAssignedExpandoInitializer, @@ -283,7 +284,6 @@ import { getJSDocParameterTags, getJSDocRoot, getJSDocSatisfiesExpressionType, - getJSDocSatisfiesTypeNode, getJSDocTags, getJSDocThisTag, getJSDocType, @@ -558,6 +558,7 @@ import { isJSDocPropertyTag, isJSDocReturnTag, isJSDocSatisfiesExpression, + isJSDocSatisfiesTag, isJSDocSignature, isJSDocTemplateTag, isJSDocTypeAlias, @@ -971,6 +972,7 @@ import { tryExtractTSExtension, tryGetClassImplementingOrExtendingExpressionWithTypeArguments, tryGetExtensionFromPath, + tryGetJSDocSatisfiesTypeNode, tryGetModuleSpecifierFromDeclaration, tryGetPropertyAccessOrIdentifierToString, TryStatement, @@ -10230,19 +10232,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Use the type of the initializer expression if one is present and the declaration is // not a parameter of a contextually typed function if (hasOnlyExpressionInitializer(declaration) && !!declaration.initializer) { - if (isInJSFile(declaration)) { - const initializer = declaration.initializer; - if (!isJSDocSatisfiesExpression(initializer)) { - const typeNode = getJSDocSatisfiesTypeNode(declaration); - if (typeNode) { - return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode); - } - } - if (!isParameter(declaration)) { - const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfDeclaration(declaration), getDeclaredExpandoInitializer(declaration)); - if (containerObjectType) { - return containerObjectType; - } + if (isInJSFile(declaration) && !isParameter(declaration)) { + const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfDeclaration(declaration), getDeclaredExpandoInitializer(declaration)); + if (containerObjectType) { + return containerObjectType; } } const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode)); @@ -28169,7 +28162,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { - const typeNode = getEffectiveTypeAnnotationNode(declaration) || (isInJSFile(declaration) ? getJSDocSatisfiesTypeNode(declaration) : undefined); + const typeNode = getEffectiveTypeAnnotationNode(declaration) || (isInJSFile(declaration) ? tryGetJSDocSatisfiesTypeNode(declaration) : undefined); if (typeNode) { return getTypeFromTypeNode(typeNode); } @@ -36228,6 +36221,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) @@ -38850,6 +38849,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { 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) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 29ab7ff360292..58640bc09a0cd 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -144,7 +144,6 @@ import { isJSDocFunctionType, isJSDocNullableType, isJSDocReturnTag, - isJSDocSatisfiesTag, isJSDocTypeTag, isJsxOpeningElement, isJsxOpeningFragment, @@ -9153,9 +9152,6 @@ namespace Parser { } function parseSatisfiesTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocSatisfiesTag { - if (some(tags, isJSDocSatisfiesTag)) { - parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); - } 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); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 93fb5caf7439f..1c73d81e2aefe 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -195,8 +195,8 @@ import { HasExpressionInitializer, hasExtension, HasFlowNode, - hasInitializer, HasInitializer, + hasInitializer, HasJSDoc, hasJSDocNodes, HasModifiers, @@ -275,6 +275,7 @@ import { isJSDocOverloadTag, isJSDocParameterTag, isJSDocPropertyLikeTag, + isJSDocSatisfiesTag, isJSDocSignature, isJSDocTag, isJSDocTemplateTag, @@ -3755,11 +3756,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) @@ -9494,18 +9495,16 @@ export function isNonNullAccess(node: Node): node is AccessExpression { /** @internal */ export function isJSDocSatisfiesExpression(node: Node): node is JSDocSatisfiesExpression { - return isInJSFile(node) && isParenthesizedExpression(node) && !!getJSDocSatisfiesTag(node); + return isInJSFile(node) && isParenthesizedExpression(node) && hasJSDocNodes(node) && !!getJSDocSatisfiesTag(node); } /** @internal */ export function getJSDocSatisfiesExpressionType(node: JSDocSatisfiesExpression) { - const type = getJSDocSatisfiesTypeNode(node); - Debug.assertIsDefined(type); - return type; + return Debug.checkDefined(tryGetJSDocSatisfiesTypeNode(node)); } /** @internal */ -export function getJSDocSatisfiesTypeNode(node: Node) { +export function tryGetJSDocSatisfiesTypeNode(node: Node) { const tag = getJSDocSatisfiesTag(node); return tag && tag.typeExpression && tag.typeExpression.type; } diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt index d036e50acaf17..62e873fdcf1c7 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt +++ b/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt @@ -1,7 +1,8 @@ /a.js(13,5): error TS1223: 'satisfies' tag already specified. +/a.js(18,5): error TS1223: 'satisfies' tag already specified. -==== /a.js (1 errors) ==== +==== /a.js (2 errors) ==== /** * @typedef {Object} T1 * @property {number} a @@ -15,8 +16,15 @@ /** * @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 index 19edf99007cf0..2cf68fe55027d 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag11.symbols +++ b/tests/baselines/reference/checkJsdocSatisfiesTag11.symbols @@ -17,3 +17,9 @@ 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 index bc810abf0f74e..c02af90fc2f84 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag11.types +++ b/tests/baselines/reference/checkJsdocSatisfiesTag11.types @@ -19,3 +19,11 @@ const t1 = { a: 1 }; >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 index 5ac523dfe552b..5ac43d867ac36 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt @@ -1,9 +1,8 @@ /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(27,16): error TS1360: Type '{}' does not satisfy the expected type 'T1'. - Property 'a' is missing in type '{}' but required 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) ==== @@ -37,10 +36,6 @@ /** * @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. */ const t3 = {}; @@ -66,4 +61,8 @@ * @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 index aaaa12602063e..62526d8a278f4 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.symbols @@ -63,3 +63,6 @@ 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 index 568fafa6e1cd4..4c767ef749d66 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag12.types +++ b/tests/baselines/reference/checkJsdocSatisfiesTag12.types @@ -79,3 +79,8 @@ const t7 = { a: "a" }; >a : "a" >"a" : "a" +/** @satisfies {string} */ const t8 = (1); +>t8 : 1 +>(1) : 1 +>1 : 1 + diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts index 8ed152572e56f..694bc1601e408 100644 --- a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts @@ -18,3 +18,8 @@ * @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 index 023b9d92bd9b7..e8e6ad971fb3c 100644 --- a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts @@ -52,3 +52,5 @@ const t6 = { a: 'test', b: 'test' }; * @satisfies {T3} */ const t7 = { a: "a" }; + +/** @satisfies {string} */ const t8 = (1); From 98f7db6802698e7fd9676bd7549e478e53569e29 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Fri, 13 Jan 2023 19:25:34 +0200 Subject: [PATCH 10/16] update test --- .../reference/checkJsdocSatisfiesTag1.errors.txt | 2 +- tests/baselines/reference/checkJsdocSatisfiesTag1.symbols | 2 +- tests/baselines/reference/checkJsdocSatisfiesTag1.types | 8 ++++---- tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag1.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag1.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag1.errors.txt index cfc3f378c4cae..9ba25ad3d60d4 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag1.errors.txt +++ b/tests/baselines/reference/checkJsdocSatisfiesTag1.errors.txt @@ -42,7 +42,7 @@ /** @type {(m: string) => string} */ const t5 = /** @satisfies {T3} */((m) => m.substring(0)); - const t6 = /** @satisfies {Array.} */ ([1, 2]); + 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/baselines/reference/checkJsdocSatisfiesTag1.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag1.symbols index 0f645b66b10c8..251532e5c77d7 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag1.symbols +++ b/tests/baselines/reference/checkJsdocSatisfiesTag1.symbols @@ -43,7 +43,7 @@ const t5 = /** @satisfies {T3} */((m) => m.substring(0)); >m : Symbol(m, Decl(a.js, 27, 35)) >substring : Symbol(String.substring, Decl(lib.es5.d.ts, --, --)) -const t6 = /** @satisfies {Array.} */ ([1, 2]); +const t6 = /** @satisfies {[number, number]} */ ([1, 2]); >t6 : Symbol(t6, Decl(a.js, 28, 5)) const t7 = /** @satisfies {T4} */ ({ a: 'test' }); diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag1.types b/tests/baselines/reference/checkJsdocSatisfiesTag1.types index ea3bc9f7c6c48..9a7f071e8fef6 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag1.types +++ b/tests/baselines/reference/checkJsdocSatisfiesTag1.types @@ -59,10 +59,10 @@ const t5 = /** @satisfies {T3} */((m) => m.substring(0)); >substring : (start: number, end?: number) => string >0 : 0 -const t6 = /** @satisfies {Array.} */ ([1, 2]); ->t6 : number[] ->([1, 2]) : number[] ->[1, 2] : number[] +const t6 = /** @satisfies {[number, number]} */ ([1, 2]); +>t6 : [number, number] +>([1, 2]) : [number, number] +>[1, 2] : [number, number] >1 : 1 >2 : 2 diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag1.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag1.ts index b1557ea5dc97a..804f1e7d0294d 100644 --- a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag1.ts +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag1.ts @@ -32,6 +32,6 @@ const t4 = /** @satisfies {T2} */ ({ a: "a" }); /** @type {(m: string) => string} */ const t5 = /** @satisfies {T3} */((m) => m.substring(0)); -const t6 = /** @satisfies {Array.} */ ([1, 2]); +const t6 = /** @satisfies {[number, number]} */ ([1, 2]); const t7 = /** @satisfies {T4} */ ({ a: 'test' }); const t8 = /** @satisfies {T4} */ ({ a: 'test', b: 'test' }); From 6b43a92f138ffe0b306de3d8073fae99660cd5ba Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 14 Jan 2023 09:33:26 +0200 Subject: [PATCH 11/16] add satisfies tag completions --- src/compiler/types.ts | 4 ++-- src/services/completions.ts | 5 ++++- src/services/jsDoc.ts | 1 + .../baselines/reference/api/tsserverlibrary.d.ts | 4 ++-- tests/baselines/reference/api/typescript.d.ts | 4 ++-- .../fourslash/jsdocSatisfiesTagCompletion1.ts | 15 +++++++++++++++ .../fourslash/jsdocSatisfiesTagCompletion2.ts | 15 +++++++++++++++ 7 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 tests/cases/fourslash/jsdocSatisfiesTagCompletion1.ts create mode 100644 tests/cases/fourslash/jsdocSatisfiesTagCompletion2.ts diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6b9a8d52a97a5..52e33ada64d4a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -479,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, } 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..0b4571b887178 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -152,6 +152,7 @@ const jsDocTagNames = [ "readonly", "requires", "returns", + "satisfies", "see", "since", "static", diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 8a562fe8cb7eb..695ea34540129 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4372,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; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index ee8757e8fdb92..32b2585c29f7e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -437,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; 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"] }, +); From ffb310d1d20b56e96aaef2da69de24483e4687ee Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 14 Jan 2023 11:29:56 +0200 Subject: [PATCH 12/16] satisfies tag quick info --- src/services/jsDoc.ts | 4 +- .../reference/quickInfoSatisfiesTag.baseline | 63 +++++++++++++++++++ .../cases/fourslash/quickInfoSatisfiesTag.ts | 11 ++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/quickInfoSatisfiesTag.baseline create mode 100644 tests/cases/fourslash/quickInfoSatisfiesTag.ts diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 0b4571b887178..866ef2d9df2a0 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -52,6 +52,7 @@ import { JSDocImplementsTag, JSDocParameterTag, JSDocPropertyTag, + JSDocSatisfiesTag, JSDocSeeTag, JSDocTag, JSDocTagInfo, @@ -291,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/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/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(); From 9fc9292faf90a59ee953ee3d12ba0a1b298aee73 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 14 Jan 2023 11:37:05 +0200 Subject: [PATCH 13/16] satisfies tag rename --- .../reference/jsdocSatisfiesTagRename.baseline | 9 +++++++++ .../cases/fourslash/jsdocSatisfiesTagRename.ts | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/baselines/reference/jsdocSatisfiesTagRename.baseline create mode 100644 tests/cases/fourslash/jsdocSatisfiesTagRename.ts 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/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("", { }); From 3ad2a6e5fb967eeae67637273b403f6681be9088 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 14 Jan 2023 11:39:44 +0200 Subject: [PATCH 14/16] satisfies tag find all references --- ...tisfiesTagFindAllReferences.baseline.jsonc | 116 ++++++++++++++++++ .../jsdocSatisfiesTagFindAllReferences.ts | 17 +++ 2 files changed, 133 insertions(+) create mode 100644 tests/baselines/reference/jsdocSatisfiesTagFindAllReferences.baseline.jsonc create mode 100644 tests/cases/fourslash/jsdocSatisfiesTagFindAllReferences.ts 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/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(""); From c90c148c96c2595b065e3864c07630ccdc2ecd72 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 14 Jan 2023 11:42:19 +0200 Subject: [PATCH 15/16] goto definition on satisfies tag --- .../fourslash/gotoDefinitionSatisfiesTag.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/cases/fourslash/gotoDefinitionSatisfiesTag.ts 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"); From a9e7fda954b6186c4f3a4f29c53662054b6e26ae Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 14 Jan 2023 11:48:59 +0200 Subject: [PATCH 16/16] add jsDoc parsing satisfies tag tests --- src/testRunner/unittests/jsDocParsing.ts | 4 ++ ...Comments.parsesCorrectly.satisfiesTag.json | 44 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.satisfiesTag.json 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