Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
59b288f
feat(51086): add satisfies jsdoc tag
a-tarasyuk Dec 2, 2022
dbfcd01
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Dec 13, 2022
566fb91
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Dec 13, 2022
659c141
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Dec 14, 2022
7d823ae
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Dec 14, 2022
b57066f
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Dec 16, 2022
14bb0d6
add tests
a-tarasyuk Dec 17, 2022
6e2627f
handle JSDocSatisfies tag on declaration positions with initializer e…
a-tarasyuk Dec 17, 2022
b25fc68
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Jan 6, 2023
6020674
forbid omitted braces
a-tarasyuk Jan 10, 2023
3c70107
wrap JavaScript checks into a single condition
a-tarasyuk Jan 10, 2023
70edb6d
add tests
a-tarasyuk Jan 10, 2023
c929c19
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Jan 10, 2023
33ee5d1
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Jan 10, 2023
b9205de
fix tests
a-tarasyuk Jan 10, 2023
398e119
fix typo
a-tarasyuk Jan 11, 2023
fe1ee79
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Jan 11, 2023
62b9c22
improve handling of duplicate tags. fix handling a parenthesized init…
a-tarasyuk Jan 12, 2023
e07c71e
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Jan 13, 2023
98f7db6
update test
a-tarasyuk Jan 13, 2023
6b43a92
add satisfies tag completions
a-tarasyuk Jan 14, 2023
ffb310d
satisfies tag quick info
a-tarasyuk Jan 14, 2023
9fc9292
satisfies tag rename
a-tarasyuk Jan 14, 2023
3ad2a6e
satisfies tag find all references
a-tarasyuk Jan 14, 2023
c90c148
goto definition on satisfies tag
a-tarasyuk Jan 14, 2023
a9e7fda
add jsDoc parsing satisfies tag tests
a-tarasyuk Jan 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
improve handling of duplicate tags. fix handling a parenthesized init…
…ializer expression. rename utilities.
  • Loading branch information
a-tarasyuk committed Jan 13, 2023
commit 62b9c22e5355d598f5367c6b26ec2780a197c641
39 changes: 24 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,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)
Expand Down Expand Up @@ -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) {
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) {
Comment thread
sandersn marked this conversation as resolved.
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);