Skip to content

Commit

Permalink
feat(17227): add abstract JSDoc tag
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk committed Jul 21, 2021
1 parent e064817 commit dfeab9e
Show file tree
Hide file tree
Showing 28 changed files with 1,358 additions and 83 deletions.
85 changes: 58 additions & 27 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7083,6 +7083,7 @@ namespace ts {
...!length(baseTypes) ? [] : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))],
...!length(implementsExpressions) ? [] : [factory.createHeritageClause(SyntaxKind.ImplementsKeyword, implementsExpressions)]
];
const modifiers = originalDecl && hasEffectiveModifier(originalDecl, ModifierFlags.Abstract) ? factory.createModifiersFromModifierFlags(ModifierFlags.Abstract) : undefined;
const symbolProps = getNonInterhitedProperties(classType, baseTypes, getPropertiesOfType(classType));
const publicSymbolProps = filter(symbolProps, s => {
// `valueDeclaration` could be undefined if inherited from
Expand Down Expand Up @@ -7129,7 +7130,7 @@ namespace ts {
context.enclosingDeclaration = oldEnclosing;
addResult(setTextRange(factory.createClassDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
modifiers,
localName,
typeParamDecls,
heritageClauses,
Expand Down Expand Up @@ -7470,20 +7471,11 @@ namespace ts {
initializer: Expression | undefined
) => T, methodKind: SignatureDeclaration["kind"], useAccessors: boolean): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]) {
return function serializePropertySymbol(p: Symbol, isStatic: boolean, baseType: Type | undefined): (T | AccessorDeclaration | (T | AccessorDeclaration)[]) {
const modifierFlags = getDeclarationModifierFlagsFromSymbol(p);
const isPrivate = !!(modifierFlags & ModifierFlags.Private);
if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) {
// Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols
// need to be merged namespace members
return [];
}
if (p.flags & SymbolFlags.Prototype ||
(baseType && getPropertyOfType(baseType, p.escapedName)
&& isReadonlySymbol(getPropertyOfType(baseType, p.escapedName)!) === isReadonlySymbol(p)
&& (p.flags & SymbolFlags.Optional) === (getPropertyOfType(baseType, p.escapedName)!.flags & SymbolFlags.Optional)
&& isTypeIdenticalTo(getTypeOfSymbol(p), getTypeOfPropertyOfType(baseType, p.escapedName)!))) {
if (isOmittedSerializationProperty(p, isStatic, baseType)) {
return [];
}
const modifierFlags = getDeclarationModifierFlagsFromSymbol(p);
const isPrivate = !!(modifierFlags & ModifierFlags.Private);
const flag = (modifierFlags & ~ModifierFlags.Async) | (isStatic ? ModifierFlags.Static : 0);
const name = getPropertyNameNodeForSymbol(p, context);
const firstPropertyLikeDecl = p.declarations?.find(or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression));
Expand Down Expand Up @@ -7569,6 +7561,29 @@ namespace ts {
};
}

function isOmittedSerializationProperty(prop: Symbol, isStatic: boolean, type: Type | undefined) {
// Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols
// need to be merged namespace members
if (isStatic && (prop.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias)) || (prop.flags & SymbolFlags.Prototype)) {
return true;
}
if (type) {
const baseProp = getPropertyOfType(type, prop.escapedName);
const basePropType = getTypeOfPropertyOfType(type, prop.escapedName);
if (baseProp && basePropType) {
if (getDeclarationModifierFlagsFromSymbol(baseProp) & ModifierFlags.Abstract) {
return prop === baseProp;
}
return (
(prop.flags & SymbolFlags.Optional) === (baseProp.flags & SymbolFlags.Optional) &&
isReadonlySymbol(baseProp) === isReadonlySymbol(prop) &&
isTypeIdenticalTo(getTypeOfSymbol(prop), basePropType)
);
}
}
return false;
}

function serializePropertySymbolForInterface(p: Symbol, baseType: Type | undefined) {
return serializePropertySymbolForInterfaceWorker(p, /*isStatic*/ false, baseType);
}
Expand Down Expand Up @@ -29639,7 +29654,7 @@ namespace ts {
return resolveErrorCall(node);
}
const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol);
if (valueDecl && hasSyntacticModifier(valueDecl, ModifierFlags.Abstract)) {
if (valueDecl && hasEffectiveModifier(valueDecl, ModifierFlags.Abstract)) {
error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class);
return resolveErrorCall(node);
}
Expand Down Expand Up @@ -31180,7 +31195,7 @@ namespace ts {
if (type && type.flags & TypeFlags.Never) {
error(errorNode, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point);
}
else if (type && !hasExplicitReturn) {
else if (type && !hasExplicitReturn && !hasEffectiveModifier(func, ModifierFlags.Abstract)) {
// minimal check: function has syntactic return type annotation and no explicit return statements in the body
// this function does not conform to the specification.
error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value);
Expand Down Expand Up @@ -33645,8 +33660,9 @@ namespace ts {
// Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration
checkFunctionOrMethodDeclaration(node);

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

Expand Down Expand Up @@ -33775,7 +33791,7 @@ namespace ts {
checkSignatureDeclaration(node);
if (node.kind === SyntaxKind.GetAccessor) {
if (!(node.flags & NodeFlags.Ambient) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) {
if (!(node.flags & NodeFlags.HasExplicitReturn)) {
if (!(node.flags & NodeFlags.HasExplicitReturn) && !(isInJSFile(node) && hasEffectiveModifier(node, ModifierFlags.Abstract))) {
error(node.name, Diagnostics.A_get_accessor_must_return_a_value);
}
}
Expand Down Expand Up @@ -37766,7 +37782,7 @@ namespace ts {
// It is an error to inherit an abstract member without implementing it or being declared abstract.
// If there is no declaration for the derived class (as in the case of class expressions),
// then the class cannot be declared abstract.
if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasSyntacticModifier(derivedClassDecl, ModifierFlags.Abstract))) {
if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasEffectiveModifier(derivedClassDecl, ModifierFlags.Abstract))) {
// Searches other base types for a declaration that would satisfy the inherited abstract member.
// (The class may have more than one base type via declaration merging with an interface with the
// same name.)
Expand Down Expand Up @@ -37874,7 +37890,7 @@ namespace ts {
const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType));
for (const prop of properties) {
const existing = seen.get(prop.escapedName);
if (existing && !isPropertyIdenticalTo(existing, prop)) {
if (existing && !isPropertyIdenticalTo(existing, prop) && !(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.Abstract)) {
seen.delete(prop.escapedName);
}
}
Expand Down Expand Up @@ -42004,13 +42020,11 @@ namespace ts {
return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{");
}
}
if (accessor.body) {
if (hasSyntacticModifier(accessor, ModifierFlags.Abstract)) {
return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation);
}
if (accessor.parent.kind === SyntaxKind.TypeLiteral || accessor.parent.kind === SyntaxKind.InterfaceDeclaration) {
return grammarErrorOnNode(accessor.body, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts);
}
if (hasAbstractDeclarationBody(accessor)) {
return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation);
}
if (accessor.body && (accessor.parent.kind === SyntaxKind.TypeLiteral || accessor.parent.kind === SyntaxKind.InterfaceDeclaration)) {
return grammarErrorOnNode(accessor.body, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts);
}
if (accessor.typeParameters) {
return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters);
Expand Down Expand Up @@ -42039,6 +42053,23 @@ namespace ts {
return false;
}

function hasAbstractDeclarationBody(node: MethodDeclaration | AccessorDeclaration) {
if (hasEffectiveModifier(node, ModifierFlags.Abstract) && node.body) {
if (isInJSFile(node)) {
const statement = singleOrUndefined(node.body.statements);
if (statement && isThrowStatement(statement)) {
return false;
}
const returnType = getReturnTypeOfSignature(getSignatureFromDeclaration(node));
if (returnType === neverType) {
return false;
}
return !!length(node.body.statements);
}
return true;
}
}

/** Does the accessor have the right number of parameters?
* A get accessor has no parameters or a single `this` parameter.
* A set accessor has one parameter or a `this` parameter and one more parameter.
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,8 @@ namespace ts {
get updateJSDocThisTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocThisTag>(SyntaxKind.JSDocThisTag); },
get createJSDocEnumTag() { return getJSDocTypeLikeTagCreateFunction<JSDocEnumTag>(SyntaxKind.JSDocEnumTag); },
get updateJSDocEnumTag() { return getJSDocTypeLikeTagUpdateFunction<JSDocEnumTag>(SyntaxKind.JSDocEnumTag); },
get createJSDocAbstractTag() { return getJSDocSimpleTagCreateFunction<JSDocAbstractTag>(SyntaxKind.JSDocAbstractTag); },
get updateJSDocAbstractTag() { return getJSDocSimpleTagUpdateFunction<JSDocAbstractTag>(SyntaxKind.JSDocAbstractTag); },
get createJSDocAuthorTag() { return getJSDocSimpleTagCreateFunction<JSDocAuthorTag>(SyntaxKind.JSDocAuthorTag); },
get updateJSDocAuthorTag() { return getJSDocSimpleTagUpdateFunction<JSDocAuthorTag>(SyntaxKind.JSDocAuthorTag); },
get createJSDocClassTag() { return getJSDocSimpleTagCreateFunction<JSDocClassTag>(SyntaxKind.JSDocClassTag); },
Expand Down Expand Up @@ -6104,6 +6106,7 @@ namespace ts {
case SyntaxKind.JSDocReturnTag: return "returns";
case SyntaxKind.JSDocThisTag: return "this";
case SyntaxKind.JSDocEnumTag: return "enum";
case SyntaxKind.JSDocAbstractTag: return "abstract";
case SyntaxKind.JSDocAuthorTag: return "author";
case SyntaxKind.JSDocClassTag: return "class";
case SyntaxKind.JSDocPublicTag: return "public";
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,10 @@ namespace ts {
return node.kind === SyntaxKind.JSDocAugmentsTag;
}

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

export function isJSDocAuthorTag(node: Node): node is JSDocAuthorTag {
return node.kind === SyntaxKind.JSDocAuthorTag;
}
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@ namespace ts {
case SyntaxKind.JSDocTypeLiteral:
return forEach((node as JSDocTypeLiteral).jsDocPropertyTags, cbNode);
case SyntaxKind.JSDocTag:
case SyntaxKind.JSDocAbstractTag:
case SyntaxKind.JSDocClassTag:
case SyntaxKind.JSDocPublicTag:
case SyntaxKind.JSDocPrivateTag:
Expand Down Expand Up @@ -7793,6 +7794,9 @@ namespace ts {

let tag: JSDocTag | undefined;
switch (tagName.escapedText) {
case "abstract":
tag = parseSimpleTag(start, factory.createJSDocAbstractTag, tagName, margin, indentText);
break;
case "author":
tag = parseAuthorTag(start, tagName, margin, indentText);
break;
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ namespace ts {
JSDocTag,
JSDocAugmentsTag,
JSDocImplementsTag,
JSDocAbstractTag,
JSDocAuthorTag,
JSDocDeprecatedTag,
JSDocClassTag,
Expand Down Expand Up @@ -3259,6 +3260,10 @@ namespace ts {
readonly class: ExpressionWithTypeArguments & { readonly expression: Identifier | PropertyAccessEntityNameExpression };
}

export interface JSDocAbstractTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocAbstractTag;
}

export interface JSDocAuthorTag extends JSDocTag {
readonly kind: SyntaxKind.JSDocAuthorTag;
}
Expand Down Expand Up @@ -7379,6 +7384,8 @@ namespace ts {
updateJSDocCallbackTag(node: JSDocCallbackTag, tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray<JSDocComment> | undefined): JSDocCallbackTag;
createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocAugmentsTag;
updateJSDocAugmentsTag(node: JSDocAugmentsTag, tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment: string | NodeArray<JSDocComment> | undefined): JSDocAugmentsTag;
createJSDocAbstractTag(tagName: Identifier | undefined, comment?: string | NodeArray<JSDocComment>): JSDocAbstractTag;
updateJSDocAbstractTag(node: JSDocAbstractTag, tagName: Identifier | undefined, comment: string | NodeArray<JSDocComment> | undefined): JSDocAbstractTag;
createJSDocImplementsTag(tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment?: string | NodeArray<JSDocComment>): JSDocImplementsTag;
updateJSDocImplementsTag(node: JSDocImplementsTag, tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment: string | NodeArray<JSDocComment> | undefined): JSDocImplementsTag;
createJSDocAuthorTag(tagName: Identifier | undefined, comment?: string | NodeArray<JSDocComment>): JSDocAuthorTag;
Expand Down
1 change: 1 addition & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4813,6 +4813,7 @@ namespace ts {
if (getJSDocProtectedTagNoCache(node)) flags |= ModifierFlags.Protected;
if (getJSDocReadonlyTagNoCache(node)) flags |= ModifierFlags.Readonly;
if (getJSDocOverrideTagNoCache(node)) flags |= ModifierFlags.Override;
if (getJSDocAbstractTagNoCache(node)) flags |= ModifierFlags.Abstract;
}
if (getJSDocDeprecatedTagNoCache(node)) flags |= ModifierFlags.Deprecated;
}
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilitiesPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,10 @@ namespace ts {
return getFirstJSDocTag(node, isJSDocOverrideTag, /*noCache*/ true);
}

export function getJSDocAbstractTagNoCache(node: Node): JSDocAbstractTag | undefined {
return getFirstJSDocTag(node, isJSDocAbstractTag, /*noCache*/ true);
}

/** Gets the JSDoc deprecated tag for the node if present */
export function getJSDocDeprecatedTag(node: Node): JSDocDeprecatedTag | undefined {
return getFirstJSDocTag(node, isJSDocDeprecatedTag);
Expand Down
Loading

0 comments on commit dfeab9e

Please sign in to comment.