Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(51086): satisfies support in JSDoc #51753

Merged
merged 26 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
68 changes: 54 additions & 14 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 @@ -284,6 +285,7 @@ import {
getJSDocHost,
getJSDocParameterTags,
getJSDocRoot,
getJSDocSatisfiesExpressionType,
getJSDocTags,
getJSDocThisTag,
getJSDocType,
Expand Down Expand Up @@ -557,6 +559,8 @@ import {
isJSDocPropertyLikeTag,
isJSDocPropertyTag,
isJSDocReturnTag,
isJSDocSatisfiesExpression,
isJSDocSatisfiesTag,
isJSDocSignature,
isJSDocTemplateTag,
isJSDocTypeAlias,
Expand Down Expand Up @@ -732,6 +736,7 @@ import {
JSDocPropertyTag,
JSDocProtectedTag,
JSDocPublicTag,
JSDocSatisfiesTag,
JSDocSignature,
JSDocTemplateTag,
JSDocTypedefTag,
Expand Down Expand Up @@ -972,6 +977,7 @@ import {
tryExtractTSExtension,
tryGetClassImplementingOrExtendingExpressionWithTypeArguments,
tryGetExtensionFromPath,
tryGetJSDocSatisfiesTypeNode,
tryGetModuleSpecifierFromDeclaration,
tryGetPropertyAccessOrIdentifierToString,
TryStatement,
Expand Down Expand Up @@ -28201,7 +28207,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, contextFlags: ContextFlags | undefined): Type | undefined {
const typeNode = getEffectiveTypeAnnotationNode(declaration);
const typeNode = getEffectiveTypeAnnotationNode(declaration) || (isInJSFile(declaration) ? tryGetJSDocSatisfiesTypeNode(declaration) : undefined);
if (typeNode) {
return getTypeFromTypeNode(typeNode);
}
Expand Down Expand Up @@ -28883,11 +28889,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression);
return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node);
case SyntaxKind.ParenthesizedExpression: {
// 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) :
isJSDocTypeTag(tag) && isConstTypeReference(tag.typeExpression.type) ? getContextualType(parent as ParenthesizedExpression, contextFlags) :
getTypeFromTypeNode(tag.typeExpression.type);
if (isInJSFile(parent)) {
if (isJSDocSatisfiesExpression(parent)) {
return getTypeFromTypeNode(getJSDocSatisfiesExpressionType(parent));
}
// Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast.
const typeTag = getJSDocTypeTag(parent);
if (typeTag && !isConstTypeReference(typeTag.typeExpression.type)) {
return getTypeFromTypeNode(typeTag.typeExpression.type);
}
}
return getContextualType(parent as ParenthesizedExpression, contextFlags);
}
case SyntaxKind.NonNullExpression:
return getContextualType(parent as NonNullExpression, contextFlags);
Expand Down Expand Up @@ -33907,15 +33919,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 @@ -36253,6 +36266,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 @@ -36635,9 +36654,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 @@ -38872,6 +38896,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
checkSourceElement(node.typeExpression);
}

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) {
if (node.name) {
resolveJSDocMemberName(node.name, /*ignoreErrors*/ true);
Expand Down Expand Up @@ -43385,6 +43423,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
7 changes: 5 additions & 2 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,13 @@ import {
JSDocOverloadTag,
JSDocPropertyLikeTag,
JSDocReturnTag,
JSDocSatisfiesTag,
JSDocSeeTag,
JSDocSignature,
JSDocTag,
JSDocTemplateTag,
JSDocThisTag,
JSDocThrowsTag,
JSDocTypedefTag,
JSDocTypeExpression,
JSDocTypeLiteral,
Expand Down Expand Up @@ -2135,7 +2137,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
case SyntaxKind.JSDocThisTag:
case SyntaxKind.JSDocTypeTag:
case SyntaxKind.JSDocThrowsTag:
return emitJSDocSimpleTypedTag(node as JSDocTypeTag);
case SyntaxKind.JSDocSatisfiesTag:
return emitJSDocSimpleTypedTag(node as JSDocTypeTag | JSDocReturnTag | JSDocThisTag | JSDocTypeTag | JSDocThrowsTag | JSDocSatisfiesTag);
case SyntaxKind.JSDocTemplateTag:
return emitJSDocTemplateTag(node as JSDocTemplateTag);
case SyntaxKind.JSDocTypedefTag:
Expand Down Expand Up @@ -4336,7 +4339,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
write("*/");
}

function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) {
function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag | JSDocThrowsTag | JSDocSatisfiesTag) {
emitJSDocTagName(tag.tagName);
emitJSDocTypeExpression(tag.typeExpression);
emitJSDocComment(tag.comment);
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ import {
JSDocPublicTag,
JSDocReadonlyTag,
JSDocReturnTag,
JSDocSatisfiesTag,
JSDocSeeTag,
JSDocSignature,
JSDocTag,
Expand Down Expand Up @@ -876,6 +877,9 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
get createJSDocThrowsTag() { return getJSDocTypeLikeTagCreateFunction<JSDocThrowsTag>(SyntaxKind.JSDocThrowsTag); },
get updateJSDocThrowsTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocThrowsTag>(SyntaxKind.JSDocThrowsTag); },
get createJSDocSatisfiesTag() { return getJSDocTypeLikeTagCreateFunction<JSDocSatisfiesTag>(SyntaxKind.JSDocSatisfiesTag); },
get updateJSDocSatisfiesTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocSatisfiesTag>(SyntaxKind.JSDocSatisfiesTag); },

createJSDocEnumTag,
updateJSDocEnumTag,
createJSDocUnknownTag,
Expand Down Expand Up @@ -5417,6 +5421,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 @@ -5428,6 +5433,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 @@ -107,6 +107,7 @@ import {
JSDocPublicTag,
JSDocReadonlyTag,
JSDocReturnTag,
JSDocSatisfiesTag,
JSDocSeeTag,
JSDocSignature,
JSDocTemplateTag,
Expand Down Expand Up @@ -1188,6 +1189,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;
}

export function isJSDocThrowsTag(node: Node): node is JSDocThrowsTag {
return node.kind === SyntaxKind.JSDocThrowsTag;
}
Expand Down
13 changes: 12 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ import {
JSDocPublicTag,
JSDocReadonlyTag,
JSDocReturnTag,
JSDocSatisfiesTag,
JSDocSeeTag,
JSDocSignature,
JSDocSyntaxKind,
Expand Down Expand Up @@ -1108,6 +1109,7 @@ const forEachChildTable: ForEachChildTable = {
[SyntaxKind.JSDocTypeTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocThisTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocEnumTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocSatisfiesTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocThrowsTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocOverloadTag]: forEachChildInJSDocTypeLikeTag,
[SyntaxKind.JSDocSignature]: function forEachChildInJSDocSignature<T>(node: JSDocSignature, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
Expand Down Expand Up @@ -1203,7 +1205,7 @@ function forEachChildInJSDocParameterOrPropertyTag<T>(node: JSDocParameterTag |
(typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment));
}

function forEachChildInJSDocTypeLikeTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocThrowsTag | JSDocOverloadTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
function forEachChildInJSDocTypeLikeTag<T>(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocThrowsTag | JSDocOverloadTag | 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 @@ -8779,6 +8781,9 @@ namespace Parser {
case "overload":
tag = parseOverloadTag(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 @@ -9144,6 +9149,12 @@ 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 {
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);
}

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

// Synthesized list
SyntaxList,
Expand Down Expand Up @@ -478,9 +479,9 @@ export const enum SyntaxKind {
LastStatement = DebuggerStatement,
FirstNode = QualifiedName,
FirstJSDocNode = JSDocTypeExpression,
LastJSDocNode = JSDocThrowsTag,
LastJSDocNode = JSDocSatisfiesTag,
FirstJSDocTagNode = JSDocTag,
LastJSDocTagNode = JSDocThrowsTag,
LastJSDocTagNode = JSDocSatisfiesTag,
/** @internal */ FirstContextualKeyword = AbstractKeyword,
/** @internal */ LastContextualKeyword = OfKeyword,
}
Expand Down Expand Up @@ -1010,6 +1011,7 @@ export type ForEachChildNodes =
| JSDocDeprecatedTag
| JSDocThrowsTag
| JSDocOverrideTag
| JSDocSatisfiesTag
| JSDocOverloadTag
;

Expand Down Expand Up @@ -4105,6 +4107,16 @@ export interface JSDocTypeLiteral extends JSDocType, Declaration {
readonly isArrayType: boolean;
}

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

/** @internal */
export interface JSDocSatisfiesExpression extends ParenthesizedExpression {
readonly _jsDocSatisfiesExpressionBrand: never;
}
a-tarasyuk marked this conversation as resolved.
Show resolved Hide resolved

// 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 @@ -8580,6 +8592,8 @@ export interface NodeFactory {
updateJSDocOverrideTag(node: JSDocOverrideTag, tagName: Identifier, comment?: string | NodeArray<JSDocComment>): JSDocOverrideTag;
createJSDocThrowsTag(tagName: Identifier, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray<JSDocComment>): JSDocThrowsTag;
updateJSDocThrowsTag(node: JSDocThrowsTag, tagName: Identifier | undefined, typeExpression: JSDocTypeExpression | undefined, comment?: string | NodeArray<JSDocComment> | undefined): JSDocThrowsTag;
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
Loading