Skip to content

Commit

Permalink
feat(51086): add satisfies jsdoc tag
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk committed Dec 5, 2022
1 parent 6a3c9ea commit 59b288f
Show file tree
Hide file tree
Showing 48 changed files with 1,179 additions and 35 deletions.
38 changes: 28 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ import {
getJSDocHost,
getJSDocParameterTags,
getJSDocRoot,
getJSDocSatisfiesExpressionType,
getJSDocTags,
getJSDocThisTag,
getJSDocType,
Expand Down Expand Up @@ -338,8 +339,8 @@ import {
hasAccessorModifier,
hasAmbientModifier,
hasContextSensitiveParameters,
HasDecorators,
hasDecorators,
HasDecorators,
hasDynamicName,
hasEffectiveModifier,
hasEffectiveModifiers,
Expand All @@ -348,8 +349,8 @@ import {
hasExtension,
HasIllegalDecorators,
HasIllegalModifiers,
HasInitializer,
hasInitializer,
HasInitializer,
hasJSDocNodes,
hasJSDocParameterTags,
hasJsonModuleEmitEnabled,
Expand Down Expand Up @@ -539,6 +540,7 @@ import {
isJSDocParameterTag,
isJSDocPropertyLikeTag,
isJSDocReturnTag,
isJSDocSatisfiesExpression,
isJSDocSignature,
isJSDocTemplateTag,
isJSDocTypeAlias,
Expand Down Expand Up @@ -713,6 +715,7 @@ import {
JSDocPropertyTag,
JSDocProtectedTag,
JSDocPublicTag,
JSDocSatisfiesTag,
JSDocSignature,
JSDocTemplateTag,
JSDocTypedefTag,
Expand Down Expand Up @@ -28682,6 +28685,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression);
return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node);
case SyntaxKind.ParenthesizedExpression: {
if (isJSDocSatisfiesExpression(parent)) {
return getTypeFromTypeNode(getJSDocSatisfiesExpressionType(parent));
}
// Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast.
const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined;
return !tag ? getContextualType(parent as ParenthesizedExpression, contextFlags) :
Expand Down Expand Up @@ -33674,15 +33680,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function checkSatisfiesExpression(node: SatisfiesExpression) {
checkSourceElement(node.type);
const exprType = checkExpression(node.expression);
return checkSatisfiesExpressionWorker(node.expression, node.type);
}

const targetType = getTypeFromTypeNode(node.type);
function checkSatisfiesExpressionWorker(expression: Expression, target: TypeNode, checkMode?: CheckMode) {
const exprType = checkExpression(expression, checkMode);
const targetType = getTypeFromTypeNode(target);
if (isErrorType(targetType)) {
return targetType;
}

checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, node.type, node.expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1);

checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, target, expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1);
return exprType;
}

Expand Down Expand Up @@ -36406,9 +36413,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type {
if (hasJSDocNodes(node) && isJSDocTypeAssertion(node)) {
const type = getJSDocTypeAssertionType(node);
return checkAssertionWorker(type, type, node.expression, checkMode);
if (hasJSDocNodes(node)) {
if (isJSDocSatisfiesExpression(node)) {
return checkSatisfiesExpressionWorker(node.expression, getJSDocSatisfiesExpressionType(node), checkMode);
}
if (isJSDocTypeAssertion(node)) {
const type = getJSDocTypeAssertionType(node);
return checkAssertionWorker(type, type, node.expression, checkMode);
}
}
return checkExpression(node.expression, checkMode);
}
Expand Down Expand Up @@ -38632,6 +38644,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
checkSourceElement(node.typeExpression);
}

function checkJSDocSatisfiesTag(node: JSDocSatisfiesTag) {
checkSourceElement(node.typeExpression);
}

function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) {
if (node.name) {
resolveJSDocMemberName(node.name, /*ignoreErrors*/ true);
Expand Down Expand Up @@ -43138,6 +43154,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.JSDocProtectedTag:
case SyntaxKind.JSDocPrivateTag:
return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag);
case SyntaxKind.JSDocSatisfiesTag:
return checkJSDocSatisfiesTag(node as JSDocSatisfiesTag);
case SyntaxKind.IndexedAccessType:
return checkIndexedAccessType(node as IndexedAccessTypeNode);
case SyntaxKind.MappedType:
Expand Down
6 changes: 4 additions & 2 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ import {
JSDocOptionalType,
JSDocPropertyLikeTag,
JSDocReturnTag,
JSDocSatisfiesTag,
JSDocSeeTag,
JSDocSignature,
JSDocTag,
Expand Down Expand Up @@ -2122,7 +2123,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
case SyntaxKind.JSDocReturnTag:
case SyntaxKind.JSDocThisTag:
case SyntaxKind.JSDocTypeTag:
return emitJSDocSimpleTypedTag(node as JSDocTypeTag);
case SyntaxKind.JSDocSatisfiesTag:
return emitJSDocSimpleTypedTag(node as JSDocTypeTag | JSDocReturnTag | JSDocThisTag | JSDocTypeTag | JSDocSatisfiesTag);
case SyntaxKind.JSDocTemplateTag:
return emitJSDocTemplateTag(node as JSDocTemplateTag);
case SyntaxKind.JSDocTypedefTag:
Expand Down Expand Up @@ -4299,7 +4301,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
write("*/");
}

function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) {
function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag | JSDocSatisfiesTag) {
emitJSDocTagName(tag.tagName);
emitJSDocTypeExpression(tag.typeExpression);
emitJSDocComment(tag.comment);
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ import {
JSDocPublicTag,
JSDocReadonlyTag,
JSDocReturnTag,
JSDocSatisfiesTag,
JSDocSeeTag,
JSDocSignature,
JSDocTag,
Expand Down Expand Up @@ -871,6 +872,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
get updateJSDocOverrideTag() { return getJSDocSimpleTagUpdateFunction<JSDocOverrideTag>(SyntaxKind.JSDocOverrideTag); },
get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
get createJSDocSatisfiesTag() { return getJSDocTypeLikeTagCreateFunction<JSDocSatisfiesTag>(SyntaxKind.JSDocSatisfiesTag); },
get updateJSDocSatisfiesTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocSatisfiesTag>(SyntaxKind.JSDocSatisfiesTag); },
createJSDocUnknownTag,
updateJSDocUnknownTag,
createJSDocText,
Expand Down Expand Up @@ -5317,6 +5320,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
// createJSDocReturnTag
// createJSDocThisTag
// createJSDocEnumTag
// createJSDocSatisfiesTag
function createJSDocTypeLikeTagWorker<T extends JSDocTag & { typeExpression?: JSDocTypeExpression }>(kind: T["kind"], tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: string | NodeArray<JSDocComment>) {
const node = createBaseJSDocTag<T>(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment);
node.typeExpression = typeExpression;
Expand All @@ -5328,6 +5332,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
// updateJSDocReturnTag
// updateJSDocThisTag
// updateJSDocEnumTag
// updateJSDocSatisfiesTag
function updateJSDocTypeLikeTagWorker<T extends JSDocTag & { typeExpression?: JSDocTypeExpression }>(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, comment: string | NodeArray<JSDocComment> | undefined) {
return node.tagName !== tagName
|| node.typeExpression !== typeExpression
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ import {
JSDocPublicTag,
JSDocReadonlyTag,
JSDocReturnTag,
JSDocSatisfiesTag,
JSDocSeeTag,
JSDocSignature,
JSDocTemplateTag,
Expand Down Expand Up @@ -1176,6 +1177,10 @@ export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag {
return node.kind === SyntaxKind.JSDocImplementsTag;
}

export function isJSDocSatisfiesTag(node: Node): node is JSDocSatisfiesTag {
return node.kind === SyntaxKind.JSDocSatisfiesTag;
}

// Synthesized list

/** @internal */
Expand Down
27 changes: 22 additions & 5 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ import {
isJSDocFunctionType,
isJSDocNullableType,
isJSDocReturnTag,
isJSDocSatisfiesTag,
isJSDocTypeTag,
isJsxOpeningElement,
isJsxOpeningFragment,
Expand Down Expand Up @@ -189,6 +190,7 @@ import {
JSDocPublicTag,
JSDocReadonlyTag,
JSDocReturnTag,
JSDocSatisfiesTag,
JSDocSeeTag,
JSDocSignature,
JSDocSyntaxKind,
Expand Down Expand Up @@ -1100,10 +1102,12 @@ const forEachChildTable: ForEachChildTable = {
visitNode(cbNode, node.typeExpression) ||
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
},
[SyntaxKind.JSDocReturnTag]: forEachChildInJSDocReturnTag,
[SyntaxKind.JSDocTypeTag]: forEachChildInJSDocReturnTag,
[SyntaxKind.JSDocThisTag]: forEachChildInJSDocReturnTag,
[SyntaxKind.JSDocEnumTag]: forEachChildInJSDocReturnTag,
[SyntaxKind.JSDocReturnTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocTypeTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocThisTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocEnumTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocSatisfiesTag]: forEachChildInJSDocTypeLikeTag,

[SyntaxKind.JSDocSignature]: function forEachChildInJSDocSignature<T>(node: JSDocSignature, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return forEach(node.typeParameters, cbNode) ||
forEach(node.parameters, cbNode) ||
Expand Down Expand Up @@ -1197,7 +1201,7 @@ function forEachChildInJSDocParameterOrPropertyTag<T>(node: JSDocParameterTag |
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
}

function forEachChildInJSDocReturnTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
function forEachChildInJSDocTypeLikeTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocSatisfiesTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return visitNode(cbNode, node.tagName) ||
visitNode(cbNode, node.typeExpression) ||
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
Expand Down Expand Up @@ -8782,6 +8786,9 @@ namespace Parser {
case "callback":
tag = parseCallbackTag(start, tagName, margin, indentText);
break;
case "satisfies":
tag = parseSatisfiesTag(start, tagName, margin, indentText);
break;
case "see":
tag = parseSeeTag(start, tagName, margin, indentText);
break;
Expand Down Expand Up @@ -9137,6 +9144,16 @@ namespace Parser {
return finishNode(factory.createJSDocAugmentsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start);
}

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*/ true);
const comments = margin !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), margin, indentText) : undefined;
return finishNode(factory.createJSDocSatisfiesTag(tagName, typeExpression, comments), start);
}

function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression } {
const usedBrace = parseOptional(SyntaxKind.OpenBraceToken);
const pos = getNodePos();
Expand Down
14 changes: 14 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ export const enum SyntaxKind {
JSDocTypedefTag,
JSDocSeeTag,
JSDocPropertyTag,
JSDocSatisfiesTag,

// Synthesized list
SyntaxList,
Expand Down Expand Up @@ -954,6 +955,7 @@ export type ForEachChildNodes =
| JSDocReadonlyTag
| JSDocDeprecatedTag
| JSDocOverrideTag
| JSDocSatisfiesTag
;

/** @internal */
Expand Down Expand Up @@ -3857,6 +3859,16 @@ export interface JSDocTypeLiteral extends JSDocType {
readonly isArrayType: boolean;
}

export interface JSDocSatisfiesTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocSatisfiesTag;
readonly typeExpression: JSDocTypeExpression;
}

/** @internal */
export interface JSDocSatisfiesExpression extends ParenthesizedExpression {
readonly _jsDocSatisfiesExpressionBrand: never;
}

// NOTE: Ensure this is up-to-date with src/debug/debug.ts
export const enum FlowFlags {
Unreachable = 1 << 0, // Unreachable code
Expand Down Expand Up @@ -8237,6 +8249,8 @@ export interface NodeFactory {
updateJSDocDeprecatedTag(node: JSDocDeprecatedTag, tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocDeprecatedTag;
createJSDocOverrideTag(tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocOverrideTag;
updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocOverrideTag;
createJSDocSatisfiesTag(tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment?: string | NodeArray<JSDocComment>): JSDocSatisfiesTag;
updateJSDocSatisfiesTag(node: JSDocSatisfiesTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression, comment: string | NodeArray<JSDocComment> | undefined): JSDocSatisfiesTag;
createJSDocText(text: string): JSDocText;
updateJSDocText(node: JSDocText, text: string): JSDocText;
createJSDocComment(comment?: string | NodeArray<JSDocComment> | undefined, tags?: readonly JSDocTag[] | undefined): JSDoc;
Expand Down
17 changes: 16 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ import {
getJSDocPublicTagNoCache,
getJSDocReadonlyTagNoCache,
getJSDocReturnType,
getJSDocSatisfiesTag,
getJSDocTags,
getJSDocType,
getJSDocTypeParameterTags,
Expand All @@ -193,8 +194,8 @@ import {
getTrailingCommentRanges,
HasExpressionInitializer,
hasExtension,
HasInitializer,
hasInitializer,
HasInitializer,
HasJSDoc,
hasJSDocNodes,
HasModifiers,
Expand Down Expand Up @@ -330,6 +331,7 @@ import {
JSDocMemberName,
JSDocParameterTag,
JSDocPropertyLikeTag,
JSDocSatisfiesExpression,
JSDocSignature,
JSDocTag,
JSDocTemplateTag,
Expand Down Expand Up @@ -9128,3 +9130,16 @@ export function canUsePropertyAccess(name: string, languageVersion: ScriptTarget
export function hasTabstop(node: Node): boolean {
return getSnippetElement(node)?.kind === SnippetKind.TabStop;
}

/** @internal */
export function isJSDocSatisfiesExpression(node: Node): node is JSDocSatisfiesExpression {
return isInJSFile(node) && isParenthesizedExpression(node) && !!getJSDocSatisfiesTag(node);
}

/** @internal */
export function getJSDocSatisfiesExpressionType(node: JSDocSatisfiesExpression) {
const tag = getJSDocSatisfiesTag(node);
const type = tag && tag.typeExpression && tag.typeExpression.type;
Debug.assertIsDefined(type);
return type;
}
8 changes: 7 additions & 1 deletion src/compiler/utilitiesPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ import {
getJSDocCommentsAndTags,
getJSDocTypeParameterDeclarations,
hasAccessorModifier,
hasDecorators,
HasDecorators,
hasDecorators,
HasExpressionInitializer,
HasInitializer,
HasJSDoc,
Expand Down Expand Up @@ -129,6 +129,7 @@ import {
isJSDocPublicTag,
isJSDocReadonlyTag,
isJSDocReturnTag,
isJSDocSatisfiesTag,
isJSDocSignature,
isJSDocTemplateTag,
isJSDocThisTag,
Expand Down Expand Up @@ -175,6 +176,7 @@ import {
JSDocPublicTag,
JSDocReadonlyTag,
JSDocReturnTag,
JSDocSatisfiesTag,
JSDocSignature,
JSDocTag,
JSDocTemplateTag,
Expand Down Expand Up @@ -1087,6 +1089,10 @@ export function getJSDocTemplateTag(node: Node): JSDocTemplateTag | undefined {
return getFirstJSDocTag(node, isJSDocTemplateTag);
}

export function getJSDocSatisfiesTag(node: Node): JSDocSatisfiesTag | undefined {
return getFirstJSDocTag(node, isJSDocSatisfiesTag);
}

/** Gets the JSDoc type tag for the node if present and valid */
export function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined {
// We should have already issued an error if there were multiple type jsdocs, so just use the first one.
Expand Down
Loading

0 comments on commit 59b288f

Please sign in to comment.