Skip to content

Commit

Permalink
improve handling of duplicate tags. fix handling a parenthesized init…
Browse files Browse the repository at this point in the history
…ializer expression. rename utilities.
  • Loading branch information
a-tarasyuk committed Jan 13, 2023
1 parent fe1ee79 commit e19d791
Show file tree
Hide file tree
Showing 11 changed files with 76 additions and 35 deletions.
40 changes: 25 additions & 15 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ import {
GetAccessorDeclaration,
getAliasDeclarationFromName,
getAllAccessorDeclarations,
getAllJSDocTags,
getAllowSyntheticDefaultImports,
getAncestor,
getAssignedExpandoInitializer,
Expand Down Expand Up @@ -283,7 +284,6 @@ import {
getJSDocParameterTags,
getJSDocRoot,
getJSDocSatisfiesExpressionType,
getJSDocSatisfiesTypeNode,
getJSDocTags,
getJSDocThisTag,
getJSDocType,
Expand Down Expand Up @@ -558,6 +558,7 @@ import {
isJSDocPropertyTag,
isJSDocReturnTag,
isJSDocSatisfiesExpression,
isJSDocSatisfiesTag,
isJSDocSignature,
isJSDocTemplateTag,
isJSDocTypeAlias,
Expand Down Expand Up @@ -971,6 +972,7 @@ import {
tryExtractTSExtension,
tryGetClassImplementingOrExtendingExpressionWithTypeArguments,
tryGetExtensionFromPath,
tryGetJSDocSatisfiesTypeNode,
tryGetModuleSpecifierFromDeclaration,
tryGetPropertyAccessOrIdentifierToString,
TryStatement,
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -36228,6 +36221,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
contextualType?: Type | undefined
) {
const initializer = getEffectiveInitializer(declaration)!;
if (isInJSFile(declaration)) {
const typeNode = tryGetJSDocSatisfiesTypeNode(declaration);
if (typeNode) {
// const tags = getAllJSDocTags
return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode);
}
}
const type = getQuickTypeOfExpression(initializer) ||
(contextualType ?
checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal)
Expand Down Expand Up @@ -38850,6 +38850,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) {
Expand Down
4 changes: 0 additions & 4 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ import {
isJSDocFunctionType,
isJSDocNullableType,
isJSDocReturnTag,
isJSDocSatisfiesTag,
isJSDocTypeTag,
isJsxOpeningElement,
isJsxOpeningFragment,
Expand Down Expand Up @@ -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);
Expand Down
15 changes: 7 additions & 8 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ import {
HasExpressionInitializer,
hasExtension,
HasFlowNode,
hasInitializer,
HasInitializer,
hasInitializer,
HasJSDoc,
hasJSDocNodes,
HasModifiers,
Expand Down Expand Up @@ -275,6 +275,7 @@ import {
isJSDocOverloadTag,
isJSDocParameterTag,
isJSDocPropertyLikeTag,
isJSDocSatisfiesTag,
isJSDocSignature,
isJSDocTag,
isJSDocTemplateTag,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}
12 changes: 10 additions & 2 deletions tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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);

6 changes: 6 additions & 0 deletions tests/baselines/reference/checkJsdocSatisfiesTag11.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -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))

8 changes: 8 additions & 0 deletions tests/baselines/reference/checkJsdocSatisfiesTag11.types
Original file line number Diff line number Diff line change
Expand Up @@ -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

11 changes: 5 additions & 6 deletions tests/baselines/reference/checkJsdocSatisfiesTag12.errors.txt
Original file line number Diff line number Diff line change
@@ -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) ====
Expand Down Expand Up @@ -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 = {};

Expand All @@ -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'.

3 changes: 3 additions & 0 deletions tests/baselines/reference/checkJsdocSatisfiesTag12.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -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))

5 changes: 5 additions & 0 deletions tests/baselines/reference/checkJsdocSatisfiesTag12.types
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,8 @@ const t7 = { a: "a" };
>a : "a"
>"a" : "a"

/** @satisfies {string} */ const t8 = (1);
>t8 : 1
>(1) : 1
>1 : 1

5 changes: 5 additions & 0 deletions tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag11.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@
* @satisfies {T2}
*/
const t1 = { a: 1 };

/**
* @satisfies {number}
*/
const t2 = /** @satisfies {number} */ (1);
2 changes: 2 additions & 0 deletions tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag12.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ const t6 = { a: 'test', b: 'test' };
* @satisfies {T3}
*/
const t7 = { a: "a" };

/** @satisfies {string} */ const t8 = (1);

0 comments on commit e19d791

Please sign in to comment.