Skip to content

Commit 59b288f

Browse files
committed
feat(51086): add satisfies jsdoc tag
1 parent 6a3c9ea commit 59b288f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1179
-35
lines changed

src/compiler/checker.ts

+28-10
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ import {
274274
getJSDocHost,
275275
getJSDocParameterTags,
276276
getJSDocRoot,
277+
getJSDocSatisfiesExpressionType,
277278
getJSDocTags,
278279
getJSDocThisTag,
279280
getJSDocType,
@@ -338,8 +339,8 @@ import {
338339
hasAccessorModifier,
339340
hasAmbientModifier,
340341
hasContextSensitiveParameters,
341-
HasDecorators,
342342
hasDecorators,
343+
HasDecorators,
343344
hasDynamicName,
344345
hasEffectiveModifier,
345346
hasEffectiveModifiers,
@@ -348,8 +349,8 @@ import {
348349
hasExtension,
349350
HasIllegalDecorators,
350351
HasIllegalModifiers,
351-
HasInitializer,
352352
hasInitializer,
353+
HasInitializer,
353354
hasJSDocNodes,
354355
hasJSDocParameterTags,
355356
hasJsonModuleEmitEnabled,
@@ -539,6 +540,7 @@ import {
539540
isJSDocParameterTag,
540541
isJSDocPropertyLikeTag,
541542
isJSDocReturnTag,
543+
isJSDocSatisfiesExpression,
542544
isJSDocSignature,
543545
isJSDocTemplateTag,
544546
isJSDocTypeAlias,
@@ -713,6 +715,7 @@ import {
713715
JSDocPropertyTag,
714716
JSDocProtectedTag,
715717
JSDocPublicTag,
718+
JSDocSatisfiesTag,
716719
JSDocSignature,
717720
JSDocTemplateTag,
718721
JSDocTypedefTag,
@@ -28682,6 +28685,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2868228685
Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression);
2868328686
return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node);
2868428687
case SyntaxKind.ParenthesizedExpression: {
28688+
if (isJSDocSatisfiesExpression(parent)) {
28689+
return getTypeFromTypeNode(getJSDocSatisfiesExpressionType(parent));
28690+
}
2868528691
// Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast.
2868628692
const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined;
2868728693
return !tag ? getContextualType(parent as ParenthesizedExpression, contextFlags) :
@@ -33674,15 +33680,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3367433680

3367533681
function checkSatisfiesExpression(node: SatisfiesExpression) {
3367633682
checkSourceElement(node.type);
33677-
const exprType = checkExpression(node.expression);
33683+
return checkSatisfiesExpressionWorker(node.expression, node.type);
33684+
}
3367833685

33679-
const targetType = getTypeFromTypeNode(node.type);
33686+
function checkSatisfiesExpressionWorker(expression: Expression, target: TypeNode, checkMode?: CheckMode) {
33687+
const exprType = checkExpression(expression, checkMode);
33688+
const targetType = getTypeFromTypeNode(target);
3368033689
if (isErrorType(targetType)) {
3368133690
return targetType;
3368233691
}
33683-
33684-
checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, node.type, node.expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1);
33685-
33692+
checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, target, expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1);
3368633693
return exprType;
3368733694
}
3368833695

@@ -36406,9 +36413,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3640636413
}
3640736414

3640836415
function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type {
36409-
if (hasJSDocNodes(node) && isJSDocTypeAssertion(node)) {
36410-
const type = getJSDocTypeAssertionType(node);
36411-
return checkAssertionWorker(type, type, node.expression, checkMode);
36416+
if (hasJSDocNodes(node)) {
36417+
if (isJSDocSatisfiesExpression(node)) {
36418+
return checkSatisfiesExpressionWorker(node.expression, getJSDocSatisfiesExpressionType(node), checkMode);
36419+
}
36420+
if (isJSDocTypeAssertion(node)) {
36421+
const type = getJSDocTypeAssertionType(node);
36422+
return checkAssertionWorker(type, type, node.expression, checkMode);
36423+
}
3641236424
}
3641336425
return checkExpression(node.expression, checkMode);
3641436426
}
@@ -38632,6 +38644,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3863238644
checkSourceElement(node.typeExpression);
3863338645
}
3863438646

38647+
function checkJSDocSatisfiesTag(node: JSDocSatisfiesTag) {
38648+
checkSourceElement(node.typeExpression);
38649+
}
38650+
3863538651
function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) {
3863638652
if (node.name) {
3863738653
resolveJSDocMemberName(node.name, /*ignoreErrors*/ true);
@@ -43138,6 +43154,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4313843154
case SyntaxKind.JSDocProtectedTag:
4313943155
case SyntaxKind.JSDocPrivateTag:
4314043156
return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag);
43157+
case SyntaxKind.JSDocSatisfiesTag:
43158+
return checkJSDocSatisfiesTag(node as JSDocSatisfiesTag);
4314143159
case SyntaxKind.IndexedAccessType:
4314243160
return checkIndexedAccessType(node as IndexedAccessTypeNode);
4314343161
case SyntaxKind.MappedType:

src/compiler/emitter.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ import {
261261
JSDocOptionalType,
262262
JSDocPropertyLikeTag,
263263
JSDocReturnTag,
264+
JSDocSatisfiesTag,
264265
JSDocSeeTag,
265266
JSDocSignature,
266267
JSDocTag,
@@ -2122,7 +2123,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
21222123
case SyntaxKind.JSDocReturnTag:
21232124
case SyntaxKind.JSDocThisTag:
21242125
case SyntaxKind.JSDocTypeTag:
2125-
return emitJSDocSimpleTypedTag(node as JSDocTypeTag);
2126+
case SyntaxKind.JSDocSatisfiesTag:
2127+
return emitJSDocSimpleTypedTag(node as JSDocTypeTag | JSDocReturnTag | JSDocThisTag | JSDocTypeTag | JSDocSatisfiesTag);
21262128
case SyntaxKind.JSDocTemplateTag:
21272129
return emitJSDocTemplateTag(node as JSDocTemplateTag);
21282130
case SyntaxKind.JSDocTypedefTag:
@@ -4299,7 +4301,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
42994301
write("*/");
43004302
}
43014303

4302-
function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) {
4304+
function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag | JSDocSatisfiesTag) {
43034305
emitJSDocTagName(tag.tagName);
43044306
emitJSDocTypeExpression(tag.typeExpression);
43054307
emitJSDocComment(tag.comment);

src/compiler/factory/nodeFactory.ts

+5
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ import {
249249
JSDocPublicTag,
250250
JSDocReadonlyTag,
251251
JSDocReturnTag,
252+
JSDocSatisfiesTag,
252253
JSDocSeeTag,
253254
JSDocSignature,
254255
JSDocTag,
@@ -871,6 +872,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
871872
get updateJSDocOverrideTag() { return getJSDocSimpleTagUpdateFunction<JSDocOverrideTag>(SyntaxKind.JSDocOverrideTag); },
872873
get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
873874
get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
875+
get createJSDocSatisfiesTag() { return getJSDocTypeLikeTagCreateFunction<JSDocSatisfiesTag>(SyntaxKind.JSDocSatisfiesTag); },
876+
get updateJSDocSatisfiesTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocSatisfiesTag>(SyntaxKind.JSDocSatisfiesTag); },
874877
createJSDocUnknownTag,
875878
updateJSDocUnknownTag,
876879
createJSDocText,
@@ -5317,6 +5320,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
53175320
// createJSDocReturnTag
53185321
// createJSDocThisTag
53195322
// createJSDocEnumTag
5323+
// createJSDocSatisfiesTag
53205324
function createJSDocTypeLikeTagWorker<T extends JSDocTag & { typeExpression?: JSDocTypeExpression }>(kind: T["kind"], tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: string | NodeArray<JSDocComment>) {
53215325
const node = createBaseJSDocTag<T>(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment);
53225326
node.typeExpression = typeExpression;
@@ -5328,6 +5332,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
53285332
// updateJSDocReturnTag
53295333
// updateJSDocThisTag
53305334
// updateJSDocEnumTag
5335+
// updateJSDocSatisfiesTag
53315336
function updateJSDocTypeLikeTagWorker<T extends JSDocTag & { typeExpression?: JSDocTypeExpression }>(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, comment: string | NodeArray<JSDocComment> | undefined) {
53325337
return node.tagName !== tagName
53335338
|| node.typeExpression !== typeExpression

src/compiler/factory/nodeTests.ts

+5
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ import {
105105
JSDocPublicTag,
106106
JSDocReadonlyTag,
107107
JSDocReturnTag,
108+
JSDocSatisfiesTag,
108109
JSDocSeeTag,
109110
JSDocSignature,
110111
JSDocTemplateTag,
@@ -1176,6 +1177,10 @@ export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag {
11761177
return node.kind === SyntaxKind.JSDocImplementsTag;
11771178
}
11781179

1180+
export function isJSDocSatisfiesTag(node: Node): node is JSDocSatisfiesTag {
1181+
return node.kind === SyntaxKind.JSDocSatisfiesTag;
1182+
}
1183+
11791184
// Synthesized list
11801185

11811186
/** @internal */

src/compiler/parser.ts

+22-5
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ import {
143143
isJSDocFunctionType,
144144
isJSDocNullableType,
145145
isJSDocReturnTag,
146+
isJSDocSatisfiesTag,
146147
isJSDocTypeTag,
147148
isJsxOpeningElement,
148149
isJsxOpeningFragment,
@@ -189,6 +190,7 @@ import {
189190
JSDocPublicTag,
190191
JSDocReadonlyTag,
191192
JSDocReturnTag,
193+
JSDocSatisfiesTag,
192194
JSDocSeeTag,
193195
JSDocSignature,
194196
JSDocSyntaxKind,
@@ -1100,10 +1102,12 @@ const forEachChildTable: ForEachChildTable = {
11001102
visitNode(cbNode, node.typeExpression) ||
11011103
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
11021104
},
1103-
[SyntaxKind.JSDocReturnTag]: forEachChildInJSDocReturnTag,
1104-
[SyntaxKind.JSDocTypeTag]: forEachChildInJSDocReturnTag,
1105-
[SyntaxKind.JSDocThisTag]: forEachChildInJSDocReturnTag,
1106-
[SyntaxKind.JSDocEnumTag]: forEachChildInJSDocReturnTag,
1105+
[SyntaxKind.JSDocReturnTag]: forEachChildInJSDocTypeLikeTag,
1106+
[SyntaxKind.JSDocTypeTag]: forEachChildInJSDocTypeLikeTag,
1107+
[SyntaxKind.JSDocThisTag]: forEachChildInJSDocTypeLikeTag,
1108+
[SyntaxKind.JSDocEnumTag]: forEachChildInJSDocTypeLikeTag,
1109+
[SyntaxKind.JSDocSatisfiesTag]: forEachChildInJSDocTypeLikeTag,
1110+
11071111
[SyntaxKind.JSDocSignature]: function forEachChildInJSDocSignature<T>(node: JSDocSignature, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
11081112
return forEach(node.typeParameters, cbNode) ||
11091113
forEach(node.parameters, cbNode) ||
@@ -1197,7 +1201,7 @@ function forEachChildInJSDocParameterOrPropertyTag<T>(node: JSDocParameterTag |
11971201
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
11981202
}
11991203

1200-
function forEachChildInJSDocReturnTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
1204+
function forEachChildInJSDocTypeLikeTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocSatisfiesTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
12011205
return visitNode(cbNode, node.tagName) ||
12021206
visitNode(cbNode, node.typeExpression) ||
12031207
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
@@ -8782,6 +8786,9 @@ namespace Parser {
87828786
case "callback":
87838787
tag = parseCallbackTag(start, tagName, margin, indentText);
87848788
break;
8789+
case "satisfies":
8790+
tag = parseSatisfiesTag(start, tagName, margin, indentText);
8791+
break;
87858792
case "see":
87868793
tag = parseSeeTag(start, tagName, margin, indentText);
87878794
break;
@@ -9137,6 +9144,16 @@ namespace Parser {
91379144
return finishNode(factory.createJSDocAugmentsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start);
91389145
}
91399146

9147+
function parseSatisfiesTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocSatisfiesTag {
9148+
if (some(tags, isJSDocSatisfiesTag)) {
9149+
parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText);
9150+
}
9151+
9152+
const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true);
9153+
const comments = margin !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), margin, indentText) : undefined;
9154+
return finishNode(factory.createJSDocSatisfiesTag(tagName, typeExpression, comments), start);
9155+
}
9156+
91409157
function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression } {
91419158
const usedBrace = parseOptional(SyntaxKind.OpenBraceToken);
91429159
const pos = getNodePos();

src/compiler/types.ts

+14
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ export const enum SyntaxKind {
430430
JSDocTypedefTag,
431431
JSDocSeeTag,
432432
JSDocPropertyTag,
433+
JSDocSatisfiesTag,
433434

434435
// Synthesized list
435436
SyntaxList,
@@ -954,6 +955,7 @@ export type ForEachChildNodes =
954955
| JSDocReadonlyTag
955956
| JSDocDeprecatedTag
956957
| JSDocOverrideTag
958+
| JSDocSatisfiesTag
957959
;
958960

959961
/** @internal */
@@ -3857,6 +3859,16 @@ export interface JSDocTypeLiteral extends JSDocType {
38573859
readonly isArrayType: boolean;
38583860
}
38593861

3862+
export interface JSDocSatisfiesTag extends JSDocTag {
3863+
readonly kind: SyntaxKind.JSDocSatisfiesTag;
3864+
readonly typeExpression: JSDocTypeExpression;
3865+
}
3866+
3867+
/** @internal */
3868+
export interface JSDocSatisfiesExpression extends ParenthesizedExpression {
3869+
readonly _jsDocSatisfiesExpressionBrand: never;
3870+
}
3871+
38603872
// NOTE: Ensure this is up-to-date with src/debug/debug.ts
38613873
export const enum FlowFlags {
38623874
Unreachable = 1 << 0, // Unreachable code
@@ -8237,6 +8249,8 @@ export interface NodeFactory {
82378249
updateJSDocDeprecatedTag(node: JSDocDeprecatedTag, tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocDeprecatedTag;
82388250
createJSDocOverrideTag(tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocOverrideTag;
82398251
updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocOverrideTag;
8252+
createJSDocSatisfiesTag(tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment?: string | NodeArray<JSDocComment>): JSDocSatisfiesTag;
8253+
updateJSDocSatisfiesTag(node: JSDocSatisfiesTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray<JSDocComment> | undefined): JSDocSatisfiesTag;
82408254
createJSDocText(text: string): JSDocText;
82418255
updateJSDocText(node: JSDocText, text: string): JSDocText;
82428256
createJSDocComment(comment?: string | NodeArray<JSDocComment> | undefined, tags?: readonly JSDocTag[] | undefined): JSDoc;

src/compiler/utilities.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ import {
167167
getJSDocPublicTagNoCache,
168168
getJSDocReadonlyTagNoCache,
169169
getJSDocReturnType,
170+
getJSDocSatisfiesTag,
170171
getJSDocTags,
171172
getJSDocType,
172173
getJSDocTypeParameterTags,
@@ -193,8 +194,8 @@ import {
193194
getTrailingCommentRanges,
194195
HasExpressionInitializer,
195196
hasExtension,
196-
HasInitializer,
197197
hasInitializer,
198+
HasInitializer,
198199
HasJSDoc,
199200
hasJSDocNodes,
200201
HasModifiers,
@@ -330,6 +331,7 @@ import {
330331
JSDocMemberName,
331332
JSDocParameterTag,
332333
JSDocPropertyLikeTag,
334+
JSDocSatisfiesExpression,
333335
JSDocSignature,
334336
JSDocTag,
335337
JSDocTemplateTag,
@@ -9128,3 +9130,16 @@ export function canUsePropertyAccess(name: string, languageVersion: ScriptTarget
91289130
export function hasTabstop(node: Node): boolean {
91299131
return getSnippetElement(node)?.kind === SnippetKind.TabStop;
91309132
}
9133+
9134+
/** @internal */
9135+
export function isJSDocSatisfiesExpression(node: Node): node is JSDocSatisfiesExpression {
9136+
return isInJSFile(node) && isParenthesizedExpression(node) && !!getJSDocSatisfiesTag(node);
9137+
}
9138+
9139+
/** @internal */
9140+
export function getJSDocSatisfiesExpressionType(node: JSDocSatisfiesExpression) {
9141+
const tag = getJSDocSatisfiesTag(node);
9142+
const type = tag && tag.typeExpression && tag.typeExpression.type;
9143+
Debug.assertIsDefined(type);
9144+
return type;
9145+
}

src/compiler/utilitiesPublic.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ import {
7777
getJSDocCommentsAndTags,
7878
getJSDocTypeParameterDeclarations,
7979
hasAccessorModifier,
80-
hasDecorators,
8180
HasDecorators,
81+
hasDecorators,
8282
HasExpressionInitializer,
8383
HasInitializer,
8484
HasJSDoc,
@@ -129,6 +129,7 @@ import {
129129
isJSDocPublicTag,
130130
isJSDocReadonlyTag,
131131
isJSDocReturnTag,
132+
isJSDocSatisfiesTag,
132133
isJSDocSignature,
133134
isJSDocTemplateTag,
134135
isJSDocThisTag,
@@ -175,6 +176,7 @@ import {
175176
JSDocPublicTag,
176177
JSDocReadonlyTag,
177178
JSDocReturnTag,
179+
JSDocSatisfiesTag,
178180
JSDocSignature,
179181
JSDocTag,
180182
JSDocTemplateTag,
@@ -1087,6 +1089,10 @@ export function getJSDocTemplateTag(node: Node): JSDocTemplateTag | undefined {
10871089
return getFirstJSDocTag(node, isJSDocTemplateTag);
10881090
}
10891091

1092+
export function getJSDocSatisfiesTag(node: Node): JSDocSatisfiesTag | undefined {
1093+
return getFirstJSDocTag(node, isJSDocSatisfiesTag);
1094+
}
1095+
10901096
/** Gets the JSDoc type tag for the node if present and valid */
10911097
export function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined {
10921098
// We should have already issued an error if there were multiple type jsdocs, so just use the first one.

0 commit comments

Comments
 (0)