8000 feat(51086): satisfies support in JSDoc (#51753) · microsoft/TypeScript@9c9d4b0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9c9d4b0

Browse files
authored
feat(51086): satisfies support in JSDoc (#51753)
1 parent 577cc1b commit 9c9d4b0

File tree

77 files changed

+2073
-44
lines changed

Some content is hidden

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

77 files changed

+2073
-44
lines changed

src/compiler/checker.ts

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ import {
222222
GetAccessorDeclaration,
223223
getAliasDeclarationFromName,
224224
getAllAccessorDeclarations,
225+
getAllJSDocTags,
225226
getAllowSyntheticDefaultImports,
226227
getAncestor,
227228
getAssignedExpandoInitializer,
@@ -284,6 +285,7 @@ import {
284285
getJSDocHost,
285286
getJSDocParameterTags,
286287
getJSDocRoot,
288+
getJSDocSatisfiesExpressionType,
287289
getJSDocTags,
288290
getJSDocThisTag,
289291
getJSDocType,
@@ -557,6 +559,8 @@ import {
557559
isJSDocPropertyLikeTag,
558560
isJSDocPropertyTag,
559561
isJSDocReturnTag,
562+
isJSDocSatisfiesExpression,
563+
isJSDocSatisfiesTag,
560564
isJSDocSignature,
561565
isJSDocTemplateTag,
562566
isJSDocTypeAlias,
@@ -732,6 +736,7 @@ import {
732736
JSDocPropertyTag,
733737
JSDocProtectedTag,
734738
JSDocPublicTag,
10000 739+
JSDocSatisfiesTag,
735740
JSDocSignature,
736741
JSDocTemplateTag,
737742
JSDocTypedefTag,
@@ -972,6 +977,7 @@ import {
972977
tryExtractTSExtension,
973978
tryGetClassImplementingOrExtendingExpressionWithTypeArguments,
974979
tryGetExtensionFromPath,
980+
tryGetJSDocSatisfiesTypeNode,
975981
tryGetModuleSpecifierFromDeclaration,
976982
tryGetPropertyAccessOrIdentifierToString,
977983
TryStatement,
@@ -28201,7 +28207,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2820128207
}
2820228208

2820328209
function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, contextFlags: ContextFlags | undefined): Type | undefined {
28204-
const typeNode = getEffectiveTypeAnnotationNode(declaration);
28210+
const typeNode = getEffectiveTypeAnnotationNode(declaration) || (isInJSFile(declaration) ? tryGetJSDocSatisfiesTypeNode(declaration) : undefined);
2820528211
if (typeNode) {
2820628212
return getTypeFromTypeNode(typeNode);
2820728213
}
@@ -28884,11 +28890,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2888428890
Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression);
2888528891
return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node);
2888628892
case SyntaxKind.ParenthesizedExpression: {
28887-
// Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast.
28888-
const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined;
28889-
return !tag ? getContextualType(parent as ParenthesizedExpression, contextFlags) :
28890-
isJSDocTypeTag(tag) && isConstTypeReference(tag.typeExpression.type) ? getContextualType(parent as ParenthesizedExpression, contextFlags) :
28891-
getTypeFromTypeNode(tag.typeExpression.type);
28893+
if (isInJSFile(parent)) {
28894+
if (isJSDocSatisfiesExpression(parent)) {
28895+
return getTypeFromTypeNode(getJSDocSatisfiesExpressionType(parent));
28896+
}
28897+
// Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast.
28898+
const typeTag = getJSDocTypeTag(parent);
28899+
if (typeTag && !isConstTypeReference(typeTag.typeExpression.type)) {
28900+
return getTypeFromTypeNode(typeTag.typeExpression.type);
28901+
}
28902+
}
28903+
return getContextualType(parent as ParenthesizedExpression, contextFlags);
2889228904
}
2889328905
case SyntaxKind.NonNullExpression:
2889428906
return getContextualType(parent as NonNullExpression, contextFlags);
@@ -33908,15 +33920,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3390833920

3390933921
function checkSatisfiesExpression(node: SatisfiesExpression) {
3391033922
checkSourceElement(node.type);
33911-
const exprType = checkExpression(node.expression);
33923+
return checkSatisfiesExpressionWorker(node.expression, node.type);
33924+
}
3391233925

33913-
const targetType = getTypeFromTypeNode(node.type);
33926+
function checkSatisfiesExpressionWorker(expression: Expression, target: TypeNode, checkMode?: CheckMode) {
33927+
const exprType = checkExpression(expression, checkMode);
33928+
const targetType = getTypeFromTypeNode(target);
3391433929
if (isErrorType(targetType)) {
3391533930
return targetType;
3391633931
}
33917-
33918-
checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, node.type, node.expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1);
33919-
33932+
checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, target, expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1);
3392033933
return exprType;
3392133934
}
3392233935

@@ -36254,6 +36267,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3625436267
contextualType?: Type | undefined
3625536268
) {
3625636269
const initializer = getEffectiveInitializer(declaration)!;
36270+
if (isInJSFile(declaration)) {
36271+
const typeNode = tryGetJSDocSatisfiesTypeNode(declaration);
36272+
if (typeNode) {
36273+
return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode);
36274+
}
36275+
}
3625736276
const type = getQuickTypeOfExpression(initializer) ||
3625836277
(contextualType ?
3625936278
checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal)
@@ -36636,9 +36655,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3663636655
}
3663736656

3663836657
function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type {
36639-
if (hasJSDocNodes(node) && isJSDocTypeAssertion(node)) {
36640-
const type = getJSDocTypeAssertionType(node);
36641-
return checkAssertionWorker(type, type, node.expression, checkMode);
36658+
if (hasJSDocNodes(node)) {
36659+
if (isJSDocSatisfiesExpression(node)) {
36660+
return checkSatisfiesExpressionWorker(node.expression, getJSDocSatisfiesExpressionType(node), checkMode);
36661+
}
36662+
if (isJSDocTypeAssertion(node)) {
36663+
const type = getJSDocTypeAssertionType(node);
36664+
return checkAssertionWorker(type, type, node.expression, checkMode);
36665+
}
3664236666
}
3664336667
return checkExpression(node.expression, checkMode);
3664436668
}
@@ -38873,6 +38897,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3887338897
checkSourceElement(node.typeExpression);
3887438898
}
3887538899

38900+
function checkJSDocSatisfiesTag(node: JSDocSatisfiesTag) {
38901+
checkSourceElement(node.typeExpression);
38902+
const host = getEffectiveJSDocHost(node);
38903+
if (host) {
38904+
const tags = getAllJSDocTags(host, isJSDocSatisfiesTag);
38905+
if (length(tags) > 1) {
38906+
for (let i = 1; i < length(tags); i++) {
38907+
const tagName = tags[i].tagName;
38908+
error(tagName, Diagnostics._0_tag_already_specified, idText(tagName));
38909+
}
38910+
}
38911+
}
38912+
}
38913+
3887638914
function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) {
3887738915
if (node.name) {
3887838916
resolveJSDocMemberName(node.name, /*ignoreErrors*/ true);
@@ -43386,6 +43424,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4338643424
case SyntaxKind.JSDocProtectedTag:
4338743425
case SyntaxKind.JSDocPrivateTag:
4338843426
return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag);
43427+
case SyntaxKind.JSDocSatisfiesTag:
43428+
return checkJSDocSatisfiesTag(node as JSDocSatisfiesTag);
4338943429
case SyntaxKind.IndexedAccessType:
4339043430
return checkIndexedAccessType(node as IndexedAccessTypeNode);
4339143431
case SyntaxKind.MappedType:

src/compiler/emitter.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,13 @@ import {
265265
JSDocOverloadTag,
266266
JSDocPropertyLikeTag,
267267
JSDocReturnTag,
268+
JSDocSatisfiesTag,
268269
JSDocSeeTag,
269270
JSDocSignature,
270271
JSDocTag,
271272
JSDocTemplateTag,
272273
JSDocThisTag,
274+
JSDocThrowsTag,
273275
JSDocTypedefTag,
274276
JSDocTypeExpression,
275277
JSDocTypeLiteral,
@@ -2135,7 +2137,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
21352137
case SyntaxKind.JSDocThisTag:
21362138
case SyntaxKind.JSDocTypeTag:
21372139
case SyntaxKind.JSDocThrowsTag:
2138-
return emitJSDocSimpleTypedTag(node as JSDocTypeTag);
2140+
case SyntaxKind.JSDocSatisfiesTag:
2141+
return emitJSDocSimpleTypedTag(node as JSDocTypeTag | JSDocReturnTag | JSDocThisTag | JSDocTypeTag | JSDocThrowsTag | JSDocSatisfiesTag);
21392142
case SyntaxKind.JSDocTemplateTag:
21402143
return emitJSDocTemplateTag(node as JSDocTemplateTag);
21412144
case SyntaxKind.JSDocTypedefTag:
@@ -4336,7 +4339,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
43364339
write("*/");
43374340
}
43384341

4339-
function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) {
4342+
function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag | JSDocThrowsTag | JSDocSatisfiesTag) {
43404343
emitJSDocTagName(tag.tagName);
43414344
emitJSDocTypeExpression(tag.typeExpression);
43424345
emitJSDocComment(tag.comment);

src/compiler/factory/nodeFactory.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ import {
250250
JSDocPublicTag,
251251
JSDocReadonlyTag,
252252
JSDocReturnTag,
253+
JSDocSatisfiesTag,
253254
JSDocSeeTag,
254255
JSDocSignature,
255256
JSDocTag,
@@ -876,6 +877,9 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
876877
get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
877878
get createJSDocThrowsTag() { return getJSDocTypeLikeTagCreateFunction<JSDocThrowsTag>(SyntaxKind.JSDocThrowsTag); },
878879
get updateJSDocThrowsTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocThrowsTag>(SyntaxKind.JSDocThrowsTag); },
880+
get createJSDocSatisfiesTag() { return getJSDocTypeLikeTagCreateFunction<JSDocSatisfiesTag>(SyntaxKind.JSDocSatisfiesTag); },
881+
get updateJSDocSatisfiesTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocSatisfiesTag>(SyntaxKind.JSDocSatisfiesTag); },
882+
879883
createJSDocEnumTag,
880884
updateJSDocEnumTag,
881885
createJSDocUnknownTag,
@@ -5417,6 +5421,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
54175421
// createJSDocReturnTag
54185422
// createJSDocThisTag
54195423
// createJSDocEnumTag
5424+
// createJSDocSatisfiesTag
54205425
function createJSDocTypeLikeTagWorker<T extends JSDocTag & { typeExpression?: JSDocTypeExpression }>(kind: T["kind"], tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: string | NodeArray<JSDocComment>) {
54215426
const node = createBaseJSDocTag<T>(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment);
54225427
node.typeExpression = typeExpression;
@@ -5428,6 +5433,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
54285433
// updateJSDocReturnTag
54295434
// updateJSDocThisTag
54305435
// updateJSDocEnumTag
5436+
// updateJSDocSatisfiesTag
54315437
function updateJSDocTypeLikeTagWorker<T extends JSDocTag & { typeExpression?: JSDocTypeExpression }>(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, comment: string | NodeArray<JSDocComment> | undefined) {
54325438
return node.tagName !== tagName
54335439
|| node.typeExpression !== typeExpression

src/compiler/factory/nodeTests.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ import {
107107
JSDocPublicTag,
108108
JSDocReadonlyTag,
109109
JSDocReturnTag,
110+
JSDocSatisfiesTag,
110111
JSDocSeeTag,
111112
JSDocSignature,
112113
JSDocTemplateTag,
@@ -1188,6 +1189,10 @@ export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag {
11881189
return node.kind === SyntaxKind.JSDocImplementsTag;
11891190
}
11901191

1192+
export function isJSDocSatisfiesTag(node: Node): node is JSDocSatisfiesTag {
1193+
return node.kind === SyntaxKind.JSDocSatisfiesTag;
1194+
}
1195+
11911196
export function isJSDocThrowsTag(node: Node): node is JSDocThrowsTag {
11921197
return node.kind === SyntaxKind.JSDocThrowsTag;
11931198
}

src/compiler/parser.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ import {
190190
JSDocPublicTag,
191191
JSDocReadonlyTag,
192192
JSDocReturnTag,
193+
JSDocSatisfiesTag,
193194
JSDocSeeTag,
194195
JSDocSignature,
195196
JSDocSyntaxKind,
@@ -1108,6 +1109,7 @@ const forEachChildTable: ForEachChildTable = {
11081109
[SyntaxKind.JSDocTypeTag]: forEachChildInJSDocTypeLikeTag,
11091110
[SyntaxKind.JSDocThisTag]: forEachChildInJSDocTypeLikeTag,
11101111
[SyntaxKind.JSDocEnumTag]: forEachChildInJSDocTypeLikeTag,
1112+
[SyntaxKind.JSDocSatisfiesTag]: forEachChildInJSDocTypeLikeTag,
11111113
[SyntaxKind.JSDocThrowsTag]: forEachChildInJSDocTypeLikeTag,
11121114
[SyntaxKind.JSDocOverloadTag]: forEachChildInJSDocTypeLikeTag,
11131115
[SyntaxKind.JSDocSignature]: function forEachChildInJSDocSignature<T>(node: JSDocSignature, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
@@ -1203,7 +1205,7 @@ function forEachChildInJSDocParameterOrPropertyTag<T>(node: JSDocParameterTag |
12031205
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
12041206
}
12051207

1206-
function forEachChildInJSDocTypeLikeTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocThrowsTag | JSDocOverloadTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
1208+
function forEachChildInJSDocTypeLikeTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocThrowsTag | JSDocOverloadTag | JSDocSatisfiesTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
12071209
return visitNode(cbNode, node.tagName) ||
12081210
visitNode(cbNode, node.typeExpression) ||
12091211
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
@@ -8779,6 +8781,9 @@ namespace Parser {
87798781
case "overload":
87808782
tag = parseOverloadTag(start, tagName, margin, indentText);
87818783
break;
8784+
case "satisfies":
8785+
tag = parseSatisfiesTag(start, tagName, margin, indentText);
8786+
break;
87828787
case "see":
87838788
tag = parseSeeTag(start, tagName, margin, indentText);
87848789
break;
@@ -9144,6 +9149,12 @@ namespace Parser {
91449149
return finishNode(factory.createJSDocAugmentsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start);
91459150
}
91469151

9152+
function parseSatisfiesTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocSatisfiesTag {
9153+
const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ false);
9154+
const comments = margin !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), margin, indentText) : undefined;
9155+
return finishNode(factory.createJSDocSatisfiesTag(tagName, typeExpression, comments), start);
9156+
}
9157+
91479158
function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression } {
91489159
const usedBrace = parseOptional(SyntaxKind.OpenBraceToken);
91499160
const pos = getNodePos();

src/compiler/types.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ export const enum SyntaxKind {
434434
JSDocSeeTag,
435435
JSDocPropertyTag,
436436
JSDocThrowsTag,
437+
JSDocSatisfiesTag,
437438

438439
// Synthesized list
439440
SyntaxList,
@@ -478,9 +479,9 @@ export const enum SyntaxKind {
478479
LastStatement = DebuggerStatement,
479480
FirstNode = QualifiedName,
480481
FirstJSDocNode = JSDocTypeExpression,
481-
LastJSDocNode = JSDocThrowsTag,
482+
LastJSDocNode = JSDocSatisfiesTag,
482483
FirstJSDocTagNode = JSDocTag,
483-
LastJSDocTagNode = JSDocThrowsTag,
484+
LastJSDocTagNode = JSDocSatisfiesTag,
484485
/** @internal */ FirstContextualKeyword = AbstractKeyword,
485486
/** @internal */ LastContextualKeyword = OfKeyword,
486487
}
@@ -1010,6 +1011,7 @@ export type ForEachChildNodes =
10101011
| JSDocDeprecatedTag
10111012
| JSDocThrowsTag
10121013
| JSDocOverrideTag
1014+
| JSDocSatisfiesTag
10131015
| JSDocOverloadTag
10141016
;
10151017

@@ -4105,6 +4107,16 @@ export interface JSDocTypeLiteral extends JSDocType, Declaration {
41054107
readonly isArrayType: boolean;
41064108
}
41074109

4110+
export interface JSDocSatisfiesTag extends JSDocTag {
4111+
readonly kind: SyntaxKind.JSDocSatisfiesTag;
4112+
readonly typeExpression: JSDocTypeExpression;
4113+
}
4114+
4115+
/** @internal */
4116+
export interface JSDocSatisfiesExpression extends ParenthesizedExpression {
4117+
readonly _jsDocSatisfiesExpressionBrand: never;
4118+
}
4119+
41084120
// NOTE: Ensure this is up-to-date with src/debug/debug.ts
41094121
export const enum FlowFlags {
41104122
Unreachable = 1 << 0, // Unreachable code
@@ -8580,6 +8592,8 @@ export interface NodeFactory {
85808592
updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocOverrideTag;
85818593
createJSDocThrowsTag(tagName: Identifier, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray<JSDocComment>): JSDocThrowsTag;
85828594
updateJSDocThrowsTag(node: JSDocThrowsTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray<JSDocComment> | undefined): JSDocThrowsTag;
8595+
createJSDocSatisfiesTag(tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment?: string | NodeArray<JSDocComment>): JSDocSatisfiesTag;
8596+
updateJSDocSatisfiesTag(node: JSDocSatisfiesTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray<JSDocComment> | undefined): JSDocSatisfiesTag;
85838597
createJSDocText(text: string): JSDocText;
85848598
updateJSDocText(node: JSDocText, text: string): JSDocText;
85858599
createJSDocComment(comment?: string | NodeArray<JSDocComment> | undefined, tags?: readonly JSDocTag[] | undefined): JSDoc;

0 commit comments

Comments
 (0)
0