Skip to content

Commit dfeab9e

Browse files
committed
feat(17227): add abstract JSDoc tag
1 parent e064817 commit dfeab9e

28 files changed

+1358
-83
lines changed

src/compiler/checker.ts

+58-27
Original file line numberDiff line numberDiff line change
@@ -7083,6 +7083,7 @@ namespace ts {
70837083
...!length(baseTypes) ? [] : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))],
70847084
...!length(implementsExpressions) ? [] : [factory.createHeritageClause(SyntaxKind.ImplementsKeyword, implementsExpressions)]
70857085
];
7086+
const modifiers = originalDecl && hasEffectiveModifier(originalDecl, ModifierFlags.Abstract) ? factory.createModifiersFromModifierFlags(ModifierFlags.Abstract) : undefined;
70867087
const symbolProps = getNonInterhitedProperties(classType, baseTypes, getPropertiesOfType(classType));
70877088
const publicSymbolProps = filter(symbolProps, s => {
70887089
// `valueDeclaration` could be undefined if inherited from
@@ -7129,7 +7130,7 @@ namespace ts {
71297130
context.enclosingDeclaration = oldEnclosing;
71307131
addResult(setTextRange(factory.createClassDeclaration(
71317132
/*decorators*/ undefined,
7132-
/*modifiers*/ undefined,
7133+
modifiers,
71337134
localName,
71347135
typeParamDecls,
71357136
heritageClauses,
@@ -7470,20 +7471,11 @@ namespace ts {
74707471
initializer: Expression | undefined
74717472
) => T, methodKind: SignatureDeclaration["kind"], useAccessors: boolean): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]) {
74727473
return function serializePropertySymbol(p: Symbol, isStatic: boolean, baseType: Type | undefined): (T | AccessorDeclaration | (T | AccessorDeclaration)[]) {
7473-
const modifierFlags = getDeclarationModifierFlagsFromSymbol(p);
7474-
const isPrivate = !!(modifierFlags & ModifierFlags.Private);
7475-
if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) {
7476-
// Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols
7477-
// need to be merged namespace members
7478-
return [];
7479-
}
7480-
if (p.flags & SymbolFlags.Prototype ||
7481-
(baseType && getPropertyOfType(baseType, p.escapedName)
7482-
&& isReadonlySymbol(getPropertyOfType(baseType, p.escapedName)!) === isReadonlySymbol(p)
7483-
&& (p.flags & SymbolFlags.Optional) === (getPropertyOfType(baseType, p.escapedName)!.flags & SymbolFlags.Optional)
7484-
&& isTypeIdenticalTo(getTypeOfSymbol(p), getTypeOfPropertyOfType(baseType, p.escapedName)!))) {
7474+
if (isOmittedSerializationProperty(p, isStatic, baseType)) {
74857475
return [];
74867476
}
7477+
const modifierFlags = getDeclarationModifierFlagsFromSymbol(p);
7478+
const isPrivate = !!(modifierFlags & ModifierFlags.Private);
74877479
const flag = (modifierFlags & ~ModifierFlags.Async) | (isStatic ? ModifierFlags.Static : 0);
74887480
const name = getPropertyNameNodeForSymbol(p, context);
74897481
const firstPropertyLikeDecl = p.declarations?.find(or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression));
@@ -7569,6 +7561,29 @@ namespace ts {
75697561
};
75707562
}
75717563

7564+
function isOmittedSerializationProperty(prop: Symbol, isStatic: boolean, type: Type | undefined) {
7565+
// Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols
7566+
// need to be merged namespace members
7567+
if (isStatic && (prop.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias)) || (prop.flags & SymbolFlags.Prototype)) {
7568+
return true;
7569+
}
7570+
if (type) {
7571+
const baseProp = getPropertyOfType(type, prop.escapedName);
7572+
const basePropType = getTypeOfPropertyOfType(type, prop.escapedName);
7573+
if (baseProp && basePropType) {
7574+
if (getDeclarationModifierFlagsFromSymbol(baseProp) & ModifierFlags.Abstract) {
7575+
return prop === baseProp;
7576+
}
7577+
return (
7578+
(prop.flags & SymbolFlags.Optional) === (baseProp.flags & SymbolFlags.Optional) &&
7579+
isReadonlySymbol(baseProp) === isReadonlySymbol(prop) &&
7580+
isTypeIdenticalTo(getTypeOfSymbol(prop), basePropType)
7581+
);
7582+
}
7583+
}
7584+
return false;
7585+
}
7586+
75727587
function serializePropertySymbolForInterface(p: Symbol, baseType: Type | undefined) {
75737588
return serializePropertySymbolForInterfaceWorker(p, /*isStatic*/ false, baseType);
75747589
}
@@ -29639,7 +29654,7 @@ namespace ts {
2963929654
return resolveErrorCall(node);
2964029655
}
2964129656
const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol);
29642-
if (valueDecl && hasSyntacticModifier(valueDecl, ModifierFlags.Abstract)) {
29657+
if (valueDecl && hasEffectiveModifier(valueDecl, ModifierFlags.Abstract)) {
2964329658
error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class);
2964429659
return resolveErrorCall(node);
2964529660
}
@@ -31180,7 +31195,7 @@ namespace ts {
3118031195
if (type && type.flags & TypeFlags.Never) {
3118131196
error(errorNode, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point);
3118231197
}
31183-
else if (type && !hasExplicitReturn) {
31198+
else if (type && !hasExplicitReturn && !hasEffectiveModifier(func, ModifierFlags.Abstract)) {
3118431199
// minimal check: function has syntactic return type annotation and no explicit return statements in the body
3118531200
// this function does not conform to the specification.
3118631201
error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value);
@@ -33645,8 +33660,9 @@ namespace ts {
3364533660
// Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration
3364633661
checkFunctionOrMethodDeclaration(node);
3364733662

33648-
// method signatures already report "implementation not allowed in ambient context" elsewhere
33649-
if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.MethodDeclaration && node.body) {
33663+
// Abstract methods cannot have an implementation.
33664+
// Extra checks are to avoid reporting multiple errors relating to the "abstractness" of the node.
33665+
if (isMethodDeclaration(node) && hasAbstractDeclarationBody(node)) {
3365033666
error(node, Diagnostics.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract, declarationNameToString(node.name));
3365133667
}
3365233668

@@ -33775,7 +33791,7 @@ namespace ts {
3377533791
checkSignatureDeclaration(node);
3377633792
if (node.kind === SyntaxKind.GetAccessor) {
3377733793
if (!(node.flags & NodeFlags.Ambient) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) {
33778-
if (!(node.flags & NodeFlags.HasExplicitReturn)) {
33794+
if (!(node.flags & NodeFlags.HasExplicitReturn) && !(isInJSFile(node) && hasEffectiveModifier(node, ModifierFlags.Abstract))) {
3377933795
error(node.name, Diagnostics.A_get_accessor_must_return_a_value);
3378033796
}
3378133797
}
@@ -37766,7 +37782,7 @@ namespace ts {
3776637782
// It is an error to inherit an abstract member without implementing it or being declared abstract.
3776737783
// If there is no declaration for the derived class (as in the case of class expressions),
3776837784
// then the class cannot be declared abstract.
37769-
if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasSyntacticModifier(derivedClassDecl, ModifierFlags.Abstract))) {
37785+
if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasEffectiveModifier(derivedClassDecl, ModifierFlags.Abstract))) {
3777037786
// Searches other base types for a declaration that would satisfy the inherited abstract member.
3777137787
// (The class may have more than one base type via declaration merging with an interface with the
3777237788
// same name.)
@@ -37874,7 +37890,7 @@ namespace ts {
3787437890
const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType));
3787537891
for (const prop of properties) {
3787637892
const existing = seen.get(prop.escapedName);
37877-
if (existing && !isPropertyIdenticalTo(existing, prop)) {
37893+
if (existing && !isPropertyIdenticalTo(existing, prop) && !(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.Abstract)) {
3787837894
seen.delete(prop.escapedName);
3787937895
}
3788037896
}
@@ -42004,13 +42020,11 @@ namespace ts {
4200442020
return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{");
4200542021
}
4200642022
}
42007-
if (accessor.body) {
42008-
if (hasSyntacticModifier(accessor, ModifierFlags.Abstract)) {
42009-
return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation);
42010-
}
42011-
if (accessor.parent.kind === SyntaxKind.TypeLiteral || accessor.parent.kind === SyntaxKind.InterfaceDeclaration) {
42012-
return grammarErrorOnNode(accessor.body, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts);
42013-
}
42023+
if (hasAbstractDeclarationBody(accessor)) {
42024+
return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation);
42025+
}
42026+
if (accessor.body && (accessor.parent.kind === SyntaxKind.TypeLiteral || accessor.parent.kind === SyntaxKind.InterfaceDeclaration)) {
42027+
return grammarErrorOnNode(accessor.body, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts);
4201442028
}
4201542029
if (accessor.typeParameters) {
4201642030
return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters);
@@ -42039,6 +42053,23 @@ namespace ts {
4203942053
return false;
4204042054
}
4204142055

42056+
function hasAbstractDeclarationBody(node: MethodDeclaration | AccessorDeclaration) {
42057+
if (hasEffectiveModifier(node, ModifierFlags.Abstract) && node.body) {
42058+
if (isInJSFile(node)) {
42059+
const statement = singleOrUndefined(node.body.statements);
42060+
if (statement && isThrowStatement(statement)) {
42061+
return false;
42062+
}
42063+
const returnType = getReturnTypeOfSignature(getSignatureFromDeclaration(node));
42064+
if (returnType === neverType) {
42065+
return false;
42066+
}
42067+
return !!length(node.body.statements);
42068+
}
42069+
return true;
42070+
}
42071+
}
42072+
4204242073
/** Does the accessor have the right number of parameters?
4204342074
* A get accessor has no parameters or a single `this` parameter.
4204442075
* A set accessor has one parameter or a `this` parameter and one more parameter.

src/compiler/factory/nodeFactory.ts

+3
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,8 @@ namespace ts {
364364
get updateJSDocThisTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocThisTag>(SyntaxKind.JSDocThisTag); },
365365
get createJSDocEnumTag() { return getJSDocTypeLikeTagCreateFunction<JSDocEnumTag>(SyntaxKind.JSDocEnumTag); },
366366
get updateJSDocEnumTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocEnumTag>(SyntaxKind.JSDocEnumTag); },
367+
get createJSDocAbstractTag() { return getJSDocSimpleTagCreateFunction<JSDocAbstractTag>(SyntaxKind.JSDocAbstractTag); },
368+
get updateJSDocAbstractTag() { return getJSDocSimpleTagUpdateFunction<JSDocAbstractTag>(SyntaxKind.JSDocAbstractTag); },
367369
get createJSDocAuthorTag() { return getJSDocSimpleTagCreateFunction<JSDocAuthorTag>(SyntaxKind.JSDocAuthorTag); },
368370
get updateJSDocAuthorTag() { return getJSDocSimpleTagUpdateFunction<JSDocAuthorTag>(SyntaxKind.JSDocAuthorTag); },
369371
get createJSDocClassTag() { return getJSDocSimpleTagCreateFunction<JSDocClassTag>(SyntaxKind.JSDocClassTag); },
@@ -6104,6 +6106,7 @@ namespace ts {
61046106
case SyntaxKind.JSDocReturnTag: return "returns";
61056107
case SyntaxKind.JSDocThisTag: return "this";
61066108
case SyntaxKind.JSDocEnumTag: return "enum";
6109+
case SyntaxKind.JSDocAbstractTag: return "abstract";
61076110
case SyntaxKind.JSDocAuthorTag: return "author";
61086111
case SyntaxKind.JSDocClassTag: return "class";
61096112
case SyntaxKind.JSDocPublicTag: return "public";

src/compiler/factory/nodeTests.ts

+4
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,10 @@ namespace ts {
840840
return node.kind === SyntaxKind.JSDocAugmentsTag;
841841
}
842842

843+
export function isJSDocAbstractTag(node: Node): node is JSDocAbstractTag {
844+
return node.kind === SyntaxKind.JSDocAbstractTag;
845+
}
846+
843847
export function isJSDocAuthorTag(node: Node): node is JSDocAuthorTag {
844848
return node.kind === SyntaxKind.JSDocAuthorTag;
845849
}

src/compiler/parser.ts

+4
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ namespace ts {
555555
case SyntaxKind.JSDocTypeLiteral:
556556
return forEach((node as JSDocTypeLiteral).jsDocPropertyTags, cbNode);
557557
case SyntaxKind.JSDocTag:
558+
case SyntaxKind.JSDocAbstractTag:
558559
case SyntaxKind.JSDocClassTag:
559560
case SyntaxKind.JSDocPublicTag:
560561
case SyntaxKind.JSDocPrivateTag:
@@ -7793,6 +7794,9 @@ namespace ts {
77937794

77947795
let tag: JSDocTag | undefined;
77957796
switch (tagName.escapedText) {
7797+
case "abstract":
7798+
tag = parseSimpleTag(start, factory.createJSDocAbstractTag, tagName, margin, indentText);
7799+
break;
77967800
case "author":
77977801
tag = parseAuthorTag(start, tagName, margin, indentText);
77987802
break;

src/compiler/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ namespace ts {
384384
JSDocTag,
385385
JSDocAugmentsTag,
386386
JSDocImplementsTag,
387+
JSDocAbstractTag,
387388
JSDocAuthorTag,
388389
JSDocDeprecatedTag,
389390
JSDocClassTag,
@@ -3259,6 +3260,10 @@ namespace ts {
32593260
readonly class: ExpressionWithTypeArguments & { readonly expression: Identifier | PropertyAccessEntityNameExpression };
32603261
}
32613262

3263+
export interface JSDocAbstractTag extends JSDocTag {
3264+
readonly kind: SyntaxKind.JSDocAbstractTag;
3265+
}
3266+
32623267
export interface JSDocAuthorTag extends JSDocTag {
32633268
readonly kind: SyntaxKind.JSDocAuthorTag;
32643269
}
@@ -7379,6 +7384,8 @@ namespace ts {
73797384
updateJSDocCallbackTag(node: JSDocCallbackTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray<JSDocComment> | undefined): JSDocCallbackTag;
73807385
createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocAugmentsTag;
73817386
updateJSDocAugmentsTag(node: JSDocAugmentsTag, tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment: string | NodeArray<JSDocComment> | undefined): JSDocAugmentsTag;
7387+
createJSDocAbstractTag(tagName: Identifier | undefined, comment?: string | NodeArray<JSDocComment>): JSDocAbstractTag;
7388+
updateJSDocAbstractTag(node: JSDocAbstractTag, tagName: Identifier | undefined, comment: string | NodeArray<JSDocComment> | undefined): JSDocAbstractTag;
73827389
createJSDocImplementsTag(tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocImplementsTag;
73837390
updateJSDocImplementsTag(node: JSDocImplementsTag, tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment: string | NodeArray<JSDocComment> | undefined): JSDocImplementsTag;
73847391
createJSDocAuthorTag(tagName: Identifier | undefined, comment?: string | NodeArray<JSDocComment>): JSDocAuthorTag;

src/compiler/utilities.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4813,6 +4813,7 @@ namespace ts {
48134813
if (getJSDocProtectedTagNoCache(node)) flags |= ModifierFlags.Protected;
48144814
if (getJSDocReadonlyTagNoCache(node)) flags |= ModifierFlags.Readonly;
48154815
if (getJSDocOverrideTagNoCache(node)) flags |= ModifierFlags.Override;
4816+
if (getJSDocAbstractTagNoCache(node)) flags |= ModifierFlags.Abstract;
48164817
}
48174818
if (getJSDocDeprecatedTagNoCache(node)) flags |= ModifierFlags.Deprecated;
48184819
}

src/compiler/utilitiesPublic.ts

+4
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,10 @@ namespace ts {
777777
return getFirstJSDocTag(node, isJSDocOverrideTag, /*noCache*/ true);
778778
}
779779

780+
export function getJSDocAbstractTagNoCache(node: Node): JSDocAbstractTag | undefined {
781+
return getFirstJSDocTag(node, isJSDocAbstractTag, /*noCache*/ true);
782+
}
783+
780784
/** Gets the JSDoc deprecated tag for the node if present */
781785
export function getJSDocDeprecatedTag(node: Node): JSDocDeprecatedTag | undefined {
782786
return getFirstJSDocTag(node, isJSDocDeprecatedTag);

0 commit comments

Comments
 (0)