Skip to content

Add the 5 modifier jsdoc tags #927

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 11 additions & 3 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ func (n *Node) TemplateLiteralLikeData() *TemplateLiteralLikeBase {
return n.data.TemplateLiteralLikeData()
}

type mutableNode Node

func (n *Node) AsMutable() *mutableNode { return (*mutableNode)(n) }
func (n *mutableNode) SetModifiers(modifiers *ModifierList) { n.data.setModifiers(modifiers) }

func (n *Node) Symbol() *Symbol {
data := n.DeclarationData()
if data != nil {
Expand Down Expand Up @@ -1633,6 +1638,7 @@ type nodeData interface {
Clone(v NodeFactoryCoercible) *Node
Name() *DeclarationName
Modifiers() *ModifierList
setModifiers(modifiers *ModifierList)
FlowNodeData() *FlowNodeBase
DeclarationData() *DeclarationBase
ExportableData() *ExportableBase
Expand Down Expand Up @@ -1660,6 +1666,7 @@ func (node *NodeDefault) VisitEachChild(v *NodeVisitor) *Node { re
func (node *NodeDefault) Clone(v NodeFactoryCoercible) *Node { return nil }
func (node *NodeDefault) Name() *DeclarationName { return nil }
func (node *NodeDefault) Modifiers() *ModifierList { return nil }
func (node *NodeDefault) setModifiers(modifiers *ModifierList) {}
func (node *NodeDefault) FlowNodeData() *FlowNodeBase { return nil }
func (node *NodeDefault) DeclarationData() *DeclarationBase { return nil }
func (node *NodeDefault) ExportableData() *ExportableBase { return nil }
Expand Down Expand Up @@ -4761,9 +4768,10 @@ type NamedMemberBase struct {
PostfixToken *TokenNode // TokenNode. Optional
}

func (node *NamedMemberBase) DeclarationData() *DeclarationBase { return &node.DeclarationBase }
func (node *NamedMemberBase) Modifiers() *ModifierList { return node.modifiers }
func (node *NamedMemberBase) Name() *DeclarationName { return node.name }
func (node *NamedMemberBase) DeclarationData() *DeclarationBase { return &node.DeclarationBase }
func (node *NamedMemberBase) Modifiers() *ModifierList { return node.modifiers }
func (node *NamedMemberBase) setModifiers(modifiers *ModifierList) { node.modifiers = modifiers }
func (node *NamedMemberBase) Name() *DeclarationName { return node.name }

// CallSignatureDeclaration

Expand Down
38 changes: 19 additions & 19 deletions internal/checker/grammarchecks.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,11 @@ func (c *Checker) checkGrammarModifiers(node *ast.Node /*Union[HasModifiers, Has
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_already_seen, "override")
} else if flags&ast.ModifierFlagsAmbient != 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_cannot_be_used_with_1_modifier, "override", "declare")
} else if flags&ast.ModifierFlagsReadonly != 0 {
} else if flags&ast.ModifierFlagsReadonly != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "override", "readonly")
} else if flags&ast.ModifierFlagsAccessor != 0 {
} else if flags&ast.ModifierFlagsAccessor != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "override", "accessor")
} else if flags&ast.ModifierFlagsAsync != 0 {
} else if flags&ast.ModifierFlagsAsync != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "override", "async")
}
flags |= ast.ModifierFlagsOverride
Expand All @@ -324,22 +324,22 @@ func (c *Checker) checkGrammarModifiers(node *ast.Node /*Union[HasModifiers, Has

if flags&ast.ModifierFlagsAccessibilityModifier != 0 {
return c.grammarErrorOnNode(modifier, diagnostics.Accessibility_modifier_already_seen)
} else if flags&ast.ModifierFlagsOverride != 0 {
} else if flags&ast.ModifierFlagsOverride != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, text, "override")
} else if flags&ast.ModifierFlagsStatic != 0 {
} else if flags&ast.ModifierFlagsStatic != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, text, "static")
} else if flags&ast.ModifierFlagsAccessor != 0 {
} else if flags&ast.ModifierFlagsAccessor != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, text, "accessor")
} else if flags&ast.ModifierFlagsReadonly != 0 {
} else if flags&ast.ModifierFlagsReadonly != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, text, "readonly")
} else if flags&ast.ModifierFlagsAsync != 0 {
} else if flags&ast.ModifierFlagsAsync != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, text, "async")
} else if node.Parent.Kind == ast.KindModuleBlock || node.Parent.Kind == ast.KindSourceFile {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_cannot_appear_on_a_module_or_namespace_element, text)
} else if flags&ast.ModifierFlagsAbstract != 0 {
if modifier.Kind == ast.KindPrivateKeyword {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_cannot_be_used_with_1_modifier, text, "abstract")
} else {
} else if modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, text, "abstract")
}
} else if ast.IsPrivateIdentifierClassElementDeclaration(node) {
Expand All @@ -349,19 +349,19 @@ func (c *Checker) checkGrammarModifiers(node *ast.Node /*Union[HasModifiers, Has
case ast.KindStaticKeyword:
if flags&ast.ModifierFlagsStatic != 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_already_seen, "static")
} else if flags&ast.ModifierFlagsReadonly != 0 {
} else if flags&ast.ModifierFlagsReadonly != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "static", "readonly")
} else if flags&ast.ModifierFlagsAsync != 0 {
} else if flags&ast.ModifierFlagsAsync != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "static", "async")
} else if flags&ast.ModifierFlagsAccessor != 0 {
} else if flags&ast.ModifierFlagsAccessor != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "static", "accessor")
} else if node.Parent.Kind == ast.KindModuleBlock || node.Parent.Kind == ast.KindSourceFile {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_cannot_appear_on_a_module_or_namespace_element, "static")
} else if node.Kind == ast.KindParameter {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_cannot_appear_on_a_parameter, "static")
} else if flags&ast.ModifierFlagsAbstract != 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_cannot_be_used_with_1_modifier, "static", "abstract")
} else if flags&ast.ModifierFlagsOverride != 0 {
} else if flags&ast.ModifierFlagsOverride != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "static", "override")
}
flags |= ast.ModifierFlagsStatic
Expand Down Expand Up @@ -394,11 +394,11 @@ func (c *Checker) checkGrammarModifiers(node *ast.Node /*Union[HasModifiers, Has
}
if flags&ast.ModifierFlagsExport != 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_already_seen, "export")
} else if flags&ast.ModifierFlagsAmbient != 0 {
} else if flags&ast.ModifierFlagsAmbient != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "export", "declare")
} else if flags&ast.ModifierFlagsAbstract != 0 {
} else if flags&ast.ModifierFlagsAbstract != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "export", "abstract")
} else if flags&ast.ModifierFlagsAsync != 0 {
} else if flags&ast.ModifierFlagsAsync != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "export", "async")
} else if ast.IsClassLike(node.Parent) && !ast.IsJSTypeAliasDeclaration(node) {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_cannot_appear_on_class_elements_of_this_kind, "export")
Expand All @@ -423,7 +423,7 @@ func (c *Checker) checkGrammarModifiers(node *ast.Node /*Union[HasModifiers, Has
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_cannot_appear_on_a_using_declaration, "default")
} else if blockScopeKind == ast.NodeFlagsAwaitUsing {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_cannot_appear_on_an_await_using_declaration, "default")
} else if flags&ast.ModifierFlagsExport == 0 {
} else if flags&ast.ModifierFlagsExport == 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "export", "default")
} else if sawExportBeforeDecorators {
return c.grammarErrorOnNode(firstDecorator, diagnostics.Decorators_are_not_valid_here)
Expand Down Expand Up @@ -480,10 +480,10 @@ func (c *Checker) checkGrammarModifiers(node *ast.Node /*Union[HasModifiers, Has
if flags&ast.ModifierFlagsAsync != 0 && lastAsync != nil {
return c.grammarErrorOnNode(lastAsync, diagnostics.X_0_modifier_cannot_be_used_with_1_modifier, "async", "abstract")
}
if flags&ast.ModifierFlagsOverride != 0 {
if flags&ast.ModifierFlagsOverride != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "abstract", "override")
}
if flags&ast.ModifierFlagsAccessor != 0 {
if flags&ast.ModifierFlagsAccessor != 0 && modifier.Flags&ast.NodeFlagsReparsed == 0 {
return c.grammarErrorOnNode(modifier, diagnostics.X_0_modifier_must_precede_1_modifier, "abstract", "accessor")
}
}
Expand Down
69 changes: 53 additions & 16 deletions internal/parser/reparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,39 +105,43 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
}
switch tag.Kind {
case ast.KindJSDocTypeTag:
if parent.Kind == ast.KindVariableStatement && parent.AsVariableStatement().DeclarationList != nil {
for _, declaration := range parent.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
if declaration.AsVariableDeclaration().Type == nil {
declaration.AsVariableDeclaration().Type = p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, declaration)
break
switch parent.Kind {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a drive-by reformat. As usual, best reviewed ignoring whitespace.

case ast.KindVariableStatement:
if parent.AsVariableStatement().DeclarationList != nil {
for _, declaration := range parent.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
if declaration.AsVariableDeclaration().Type == nil {
declaration.AsVariableDeclaration().Type = p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, declaration)
break
}
}
}
} else if parent.Kind == ast.KindVariableDeclaration {
case ast.KindVariableDeclaration:
if parent.AsVariableDeclaration().Type == nil {
parent.AsVariableDeclaration().Type = p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, parent)
}
} else if parent.Kind == ast.KindPropertyDeclaration {
case ast.KindPropertyDeclaration:
declaration := parent.AsPropertyDeclaration()
if declaration.Type == nil {
declaration.Type = p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, parent)
}
} else if parent.Kind == ast.KindPropertyAssignment {
case ast.KindPropertyAssignment:
prop := parent.AsPropertyAssignment()
prop.Initializer = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), prop.Initializer)
} else if parent.Kind == ast.KindExportAssignment {
case ast.KindExportAssignment:
export := parent.AsExportAssignment()
export.Expression = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), export.Expression)
} else if parent.Kind == ast.KindReturnStatement {
case ast.KindReturnStatement:
ret := parent.AsReturnStatement()
ret.Expression = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), ret.Expression)
} else if parent.Kind == ast.KindParenthesizedExpression {
case ast.KindParenthesizedExpression:
paren := parent.AsParenthesizedExpression()
paren.Expression = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), paren.Expression)
} else if parent.Kind == ast.KindExpressionStatement &&
parent.AsExpressionStatement().Expression.Kind == ast.KindBinaryExpression {
bin := parent.AsExpressionStatement().Expression.AsBinaryExpression()
if ast.GetAssignmentDeclarationKind(bin) != ast.JSDeclarationKindNone {
bin.Right = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), bin.Right)
case ast.KindExpressionStatement:
if parent.AsExpressionStatement().Expression.Kind == ast.KindBinaryExpression {
bin := parent.AsExpressionStatement().Expression.AsBinaryExpression()
if ast.GetAssignmentDeclarationKind(bin) != ast.JSDeclarationKindNone {
bin.Right = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), bin.Right)
}
}
}
case ast.KindJSDocTemplateTag:
Expand Down Expand Up @@ -178,6 +182,39 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
fun.FunctionLikeData().Type = p.makeNewType(tag.AsJSDocReturnTag().TypeExpression, fun)
}
}
case ast.KindJSDocReadonlyTag, ast.KindJSDocPrivateTag, ast.KindJSDocPublicTag, ast.KindJSDocProtectedTag, ast.KindJSDocOverrideTag:
switch parent.Kind {
case ast.KindPropertyDeclaration, ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor:
// !!! BinaryExpression (this.p assignments)
Copy link
Preview

Copilot AI May 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider implementing handling for BinaryExpression parent nodes (e.g. this.property = ... inside constructors) so that JSDoc modifiers on assignments are consistently applied, or remove the stray comment if unneeded.

Suggested change
// !!! BinaryExpression (this.p assignments)
// Handle JSDoc modifiers for declarations

Copilot uses AI. Check for mistakes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't implement handling until I decide how to store modifiers for this.p=. Waiting on a decision at a design meeting.

var keyword ast.Kind
switch tag.Kind {
case ast.KindJSDocReadonlyTag:
keyword = ast.KindReadonlyKeyword
case ast.KindJSDocPrivateTag:
keyword = ast.KindPrivateKeyword
case ast.KindJSDocPublicTag:
keyword = ast.KindPublicKeyword
case ast.KindJSDocProtectedTag:
keyword = ast.KindProtectedKeyword
case ast.KindJSDocOverrideTag:
keyword = ast.KindOverrideKeyword
}
modifier := p.factory.NewModifier(keyword)
modifier.Loc = tag.Loc
modifier.Flags = p.contextFlags | ast.NodeFlagsReparsed
var nodes []*ast.Node
var loc core.TextRange
if parent.Modifiers() == nil {
nodes = p.nodeSlicePool.NewSlice(1)
nodes[0] = modifier
loc = tag.Loc
} else {
nodes = append(parent.Modifiers().Nodes, modifier)
loc = parent.Modifiers().Loc
}
parent.AsMutable().SetModifiers(p.newModifierList(loc, nodes))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this ever happen on any parent kind, or is there a specific subset of nodes that do this? I'm not entirely certain how I feel about the AsMutable pattern, so curious if it's just shorthand for a couple of types.

}
// !!! @extends, @implements, @this, @satisfies
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
jsdocAccessibilityTag.js(50,14): error TS2341: Property 'priv' is private and only accessible within class 'A'.
jsdocAccessibilityTag.js(58,9): error TS2341: Property 'priv' is private and only accessible within class 'A'.
jsdocAccessibilityTag.js(58,24): error TS2445: Property 'prot' is protected and only accessible within class 'A' and its subclasses.
jsdocAccessibilityTag.js(59,9): error TS2341: Property 'priv' is private and only accessible within class 'A'.
jsdocAccessibilityTag.js(59,24): error TS2445: Property 'prot' is protected and only accessible within class 'A' and its subclasses.


==== jsdocAccessibilityTag.js (5 errors) ====
class A {
/**
* Ap docs
*
* @private
*/
priv = 4;
/**
* Aq docs
*
* @protected
*/
prot = 5;
/**
* Ar docs
*
* @public
*/
pub = 6;
/** @public */
get ack() { return this.priv }
/** @private */
set ack(value) { }
}
class C {
constructor() {
/**
* Cp docs
*
* @private
*/
this.priv2 = 1;
/**
* Cq docs
*
* @protected
*/
this.prot2 = 2;
/**
* Cr docs
*
* @public
*/
this.pub2 = 3;
}
h() { return this.priv2 }
}
class B extends A {
m() {
this.priv + this.prot + this.pub
~~~~
!!! error TS2341: Property 'priv' is private and only accessible within class 'A'.
}
}
class D extends C {
n() {
this.priv2 + this.prot2 + this.pub2
}
}
new A().priv + new A().prot + new A().pub
~~~~
!!! error TS2341: Property 'priv' is private and only accessible within class 'A'.
~~~~
!!! error TS2445: Property 'prot' is protected and only accessible within class 'A' and its subclasses.
new B().priv + new B().prot + new B().pub
~~~~
!!! error TS2341: Property 'priv' is private and only accessible within class 'A'.
~~~~
!!! error TS2445: Property 'prot' is protected and only accessible within class 'A' and its subclasses.
new C().priv2 + new C().prot2 + new C().pub2
new D().priv2 + new D().prot2 + new D().pub2

Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
0.js(23,5): error TS4114: This member must have an 'override' modifier because it overrides a member in the base class 'A'.
0.js(27,5): error TS4114: This member must have an 'override' modifier because it overrides a member in the base class 'A'.
0.js(32,5): error TS4113: This member cannot have an 'override' modifier because it is not declared in the base class 'A'.
0.js(40,5): error TS4112: This member cannot have an 'override' modifier because its containing class 'C' does not extend another class.


==== 0.js (2 errors) ====
==== 0.js (3 errors) ====
class A {

/**
Expand All @@ -26,8 +27,6 @@
* @returns {boolean}
*/
foo (a) {
~~~
!!! error TS4114: This member must have an 'override' modifier because it overrides a member in the base class 'A'.
return super.foo(a)
}

Expand All @@ -39,6 +38,8 @@

/** @override */
baz () {
~~~
!!! error TS4113: This member cannot have an 'override' modifier because it is not declared in the base class 'A'.

}
}
Expand All @@ -47,6 +48,8 @@
class C {
/** @override */
foo () {
~~~
!!! error TS4112: This member cannot have an 'override' modifier because its containing class 'C' does not extend another class.

}
}
Loading