Skip to content

Commit e19d791

Browse files
committed
improve handling of duplicate tags. fix handling a parenthesized initializer expression. rename utilities.
1 parent fe1ee79 commit e19d791

11 files changed

+76
-35
lines changed

Diff for: src/compiler/checker.ts

+25-15
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,
@@ -283,7 +284,6 @@ import {
283284
getJSDocParameterTags,
284285
getJSDocRoot,
285286
getJSDocSatisfiesExpressionType,
286-
getJSDocSatisfiesTypeNode,
287287
getJSDocTags,
288288
getJSDocThisTag,
289289
getJSDocType,
@@ -558,6 +558,7 @@ import {
558558
isJSDocPropertyTag,
559559
isJSDocReturnTag,
560560
isJSDocSatisfiesExpression,
561+
isJSDocSatisfiesTag,
561562
isJSDocSignature,
562563
isJSDocTemplateTag,
563564
isJSDocTypeAlias,
@@ -971,6 +972,7 @@ import {
971972
tryExtractTSExtension,
972973
tryGetClassImplementingOrExtendingExpressionWithTypeArguments,
973974
tryGetExtensionFromPath,
975+
tryGetJSDocSatisfiesTypeNode,
974976
tryGetModuleSpecifierFromDeclaration,
975977
tryGetPropertyAccessOrIdentifierToString,
976978
TryStatement,
@@ -10230,19 +10232,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1023010232
// Use the type of the initializer expression if one is present and the declaration is
1023110233
// not a parameter of a contextually typed function
1023210234
if (hasOnlyExpressionInitializer(declaration) && !!declaration.initializer) {
10233-
if (isInJSFile(declaration)) {
10234-
const initializer = declaration.initializer;
10235-
if (!isJSDocSatisfiesExpression(initializer)) {
10236-
const typeNode = getJSDocSatisfiesTypeNode(declaration);
10237-
if (typeNode) {
10238-
return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode);
10239-
}
10240-
}
10241-
if (!isParameter(declaration)) {
10242-
const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfDeclaration(declaration), getDeclaredExpandoInitializer(declaration));
10243-
if (containerObjectType) {
10244-
return containerObjectType;
10245-
}
10235+
if (isInJSFile(declaration) && !isParameter(declaration)) {
10236+
const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfDeclaration(declaration), getDeclaredExpandoInitializer(declaration));
10237+
if (containerObjectType) {
10238+
return containerObjectType;
1024610239
}
1024710240
}
1024810241
const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode));
@@ -28169,7 +28162,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2816928162
}
2817028163

2817128164
function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, contextFlags: ContextFlags | undefined): Type | undefined {
28172-
const typeNode = getEffectiveTypeAnnotationNode(declaration) || (isInJSFile(declaration) ? getJSDocSatisfiesTypeNode(declaration) : undefined);
28165+
const typeNode = getEffectiveTypeAnnotationNode(declaration) || (isInJSFile(declaration) ? tryGetJSDocSatisfiesTypeNode(declaration) : undefined);
2817328166
if (typeNode) {
2817428167
return getTypeFromTypeNode(typeNode);
2817528168
}
@@ -36228,6 +36221,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3622836221
contextualType?: Type | undefined
3622936222
) {
3623036223
const initializer = getEffectiveInitializer(declaration)!;
36224+
if (isInJSFile(declaration)) {
36225+
const typeNode = tryGetJSDocSatisfiesTypeNode(declaration);
36226+
if (typeNode) {
36227+
// const tags = getAllJSDocTags
36228+
return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode);
36229+
}
36230+
}
3623136231
const type = getQuickTypeOfExpression(initializer) ||
3623236232
(contextualType ?
3623336233
checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal)
@@ -38850,6 +38850,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3885038850

3885138851
function checkJSDocSatisfiesTag(node: JSDocSatisfiesTag) {
3885238852
checkSourceElement(node.typeExpression);
38853+
const host = getEffectiveJSDocHost(node);
38854+
if (host) {
38855+
const tags = getAllJSDocTags(host, isJSDocSatisfiesTag);
38856+
if (length(tags) > 1) {
38857+
for (let i = 1; i < length(tags); i++) {
38858+
const tagName = tags[i].tagName;
38859+
error(tagName, Diagnostics._0_tag_already_specified, idText(tagName));
38860+
}
38861+
}
38862+
}
3885338863
}
3885438864

3885538865
function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) {

Diff for: src/compiler/parser.ts

-4
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ import {
144144
isJSDocFunctionType,
145145
isJSDocNullableType,
146146
isJSDocReturnTag,
147-
isJSDocSatisfiesTag,
148147
isJSDocTypeTag,
149148
isJsxOpeningElement,
150149
isJsxOpeningFragment,
@@ -9153,9 +9152,6 @@ namespace Parser {
91539152
}
91549153

91559154
function parseSatisfiesTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocSatisfiesTag {
9156-
if (some(tags, isJSDocSatisfiesTag)) {
9157-
parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText);
9158-
}
91599155
const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ false);
91609156
const comments = margin !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), margin, indentText) : undefined;
91619157
return finishNode(factory.createJSDocSatisfiesTag(tagName, typeExpression, comments), start);

Diff for: src/compiler/utilities.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,8 @@ import {
195195
HasExpressionInitializer,
196196
hasExtension,
197197
HasFlowNode,
198-
hasInitializer,
199198
HasInitializer,
199+
hasInitializer,
200200
HasJSDoc,
201201
hasJSDocNodes,
202202
HasModifiers,
@@ -275,6 +275,7 @@ import {
275275
isJSDocOverloadTag,
276276
isJSDocParameterTag,
277277
isJSDocPropertyLikeTag,
278+
isJSDocSatisfiesTag,
278279
isJSDocSignature,
279280
isJSDocTag,
280281
isJSDocTemplateTag,
@@ -3755,11 +3756,11 @@ function filterOwnedJSDocTags(hostNode: Node, jsDoc: JSDoc | JSDocTag) {
37553756
}
37563757

37573758
/**
3758-
* Determines whether a host node owns a jsDoc tag. A `@type` tag attached to a
3759+
* Determines whether a host node owns a jsDoc tag. A `@type`/`@satisfies` tag attached to a
37593760
* a ParenthesizedExpression belongs only to the ParenthesizedExpression.
37603761
*/
37613762
function ownsJSDocTag(hostNode: Node, tag: JSDocTag) {
3762-
return !isJSDocTypeTag(tag)
3763+
return !(isJSDocTypeTag(tag) || isJSDocSatisfiesTag(tag))
37633764
|| !tag.parent
37643765
|| !isJSDoc(tag.parent)
37653766
|| !isParenthesizedExpression(tag.parent.parent)
@@ -9494,18 +9495,16 @@ export function isNonNullAccess(node: Node): node is AccessExpression {
94949495

94959496
/** @internal */
94969497
export function isJSDocSatisfiesExpression(node: Node): node is JSDocSatisfiesExpression {
9497-
return isInJSFile(node) && isParenthesizedExpression(node) && !!getJSDocSatisfiesTag(node);
9498+
return isInJSFile(node) && isParenthesizedExpression(node) && hasJSDocNodes(node) && !!getJSDocSatisfiesTag(node);
94989499
}
94999500

95009501
/** @internal */
95019502
export function getJSDocSatisfiesExpressionType(node: JSDocSatisfiesExpression) {
9502-
const type = getJSDocSatisfiesTypeNode(node);
9503-
Debug.assertIsDefined(type);
9504-
return type;
9503+
return Debug.checkDefined(tryGetJSDocSatisfiesTypeNode(node));
95059504
}
95069505

95079506
/** @internal */
9508-
export function getJSDocSatisfiesTypeNode(node: Node) {
9507+
export function tryGetJSDocSatisfiesTypeNode(node: Node) {
95099508
const tag = getJSDocSatisfiesTag(node);
95109509
return tag && tag.typeExpression && tag.typeExpression.type;
95119510
}

Diff for: tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/a.js(13,5): error TS1223: 'satisfies' tag already specified.
2+
/a.js(18,5): error TS1223: 'satisfies' tag already specified.
23

34

4-
==== /a.js (1 errors) ====
5+
==== /a.js (2 errors) ====
56
/**
67
* @typedef {Object} T1
78
* @property {number} a
@@ -15,8 +16,15 @@
1516
/**
1617
* @satisfies {T1}
1718
* @satisfies {T2}
18-
~~~~~~~~~~
19+
~~~~~~~~~
1920
!!! error TS1223: 'satisfies' tag already specified.
2021
*/
2122
const t1 = { a: 1 };
23+
24+
/**
25+
* @satisfies {number}
26+
~~~~~~~~~
27+
!!! error TS1223: 'satisfies' tag already specified.
28+
*/
29+
const t2 = /** @satisfies {number} */ (1);
2230

Diff for: tests/baselines/reference/checkJsdocSatisfiesTag11.symbols

+6
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ const t1 = { a: 1 };
1717
>t1 : Symbol(t1, Decl(a.js, 14, 5))
1818
>a : Symbol(a, Decl(a.js, 14, 12))
1919

20+
/**
21+
* @satisfies {number}
22+
*/
23+
const t2 = /** @satisfies {number} */ (1);
24+
>t2 : Symbol(t2, Decl(a.js, 19, 5))
25+

Diff for: tests/baselines/reference/checkJsdocSatisfiesTag11.types

+8
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ const t1 = { a: 1 };
1919
>a : number
2020
>1 : 1
2121

22+
/**
23+
* @satisfies {number}
24+
*/
25+
const t2 = /** @satisfies {number} */ (1);
26+
>t2 : 1
27+
>(1) : 1
28+
>1 : 1
29+

Diff for: tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
/a.js(24,20): error TS1360: Type '{ a: number; b: number; }' does not satisfy the expected type 'T1'.
22
Object literal may only specify known properties, and 'b' does not exist in type 'T1'.
3-
/a.js(27,16): error TS1360: Type '{}' does not satisfy the expected type 'T1'.
4-
Property 'a' is missing in type '{}' but required in type 'T1'.
53
/a.js(44,25): error TS1360: Type '{ a: string; b: string; }' does not satisfy the expected type 'T2'.
64
Object literal may only specify known properties, and 'b' does not exist in type 'T2'.
5+
/a.js(51,17): error TS1360: Type 'number' does not satisfy the expected type 'string'.
76

87

98
==== /a.js (3 errors) ====
@@ -37,10 +36,6 @@
3736

3837
/**
3938
* @satisfies {T1}
40-
~~
41-
!!! error TS1360: Type '{}' does not satisfy the expected type 'T1'.
42-
!!! error TS1360: Property 'a' is missing in type '{}' but required in type 'T1'.
43-
!!! related TS2728 /a.js:3:4: 'a' is declared here.
4439
*/
4540
const t3 = {};
4641

@@ -66,4 +61,8 @@
6661
* @satisfies {T3}
6762
*/
6863
const t7 = { a: "a" };
64+
65+
/** @satisfies {string} */ const t8 = (1);
66+
~~~~~~
67+
!!! error TS1360: Type 'number' does not satisfy the expected type 'string'.
6968

Diff for: tests/baselines/reference/checkJsdocSatisfiesTag12.symbols

+3
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,6 @@ const t7 = { a: "a" };
6363
>t7 : Symbol(t7, Decl(a.js, 48, 5))
6464
>a : Symbol(a, Decl(a.js, 48, 12))
6565

66+
/** @satisfies {string} */ const t8 = (1);
67+
>t8 : Symbol(t8, Decl(a.js, 50, 32))
68+

Diff for: tests/baselines/reference/checkJsdocSatisfiesTag12.types

+5
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,8 @@ const t7 = { a: "a" };
7979
>a : "a"
8080
>"a" : "a"
8181

82+
/** @satisfies {string} */ const t8 = (1);
83+
>t8 : 1
84+
>(1) : 1
85+
>1 : 1
86+

Diff for: tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts

+5
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,8 @@
1818
* @satisfies {T2}
1919
*/
2020
const t1 = { a: 1 };
21+
22+
/**
23+
* @satisfies {number}
24+
*/
25+
const t2 = /** @satisfies {number} */ (1);

Diff for: tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts

+2
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,5 @@ const t6 = { a: 'test', b: 'test' };
5252
* @satisfies {T3}
5353
*/
5454
const t7 = { a: "a" };
55+
56+
/** @satisfies {string} */ const t8 = (1);

0 commit comments

Comments
 (0)