Skip to content

Commit f16edec

Browse files
committed
JS: parse private elements in classes as variables that can be renamed
1 parent 0efcf90 commit f16edec

File tree

4 files changed

+55
-16
lines changed

4 files changed

+55
-16
lines changed

js/ast.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ const (
9494
FunctionDecl // function
9595
ArgumentDecl // function and method arguments
9696
LexicalDecl // let, const, class
97+
PrivateDecl // private class field
9798
CatchDecl // catch statement argument
9899
ExprDecl // function expression name or class expression name
99100
)
@@ -110,6 +111,8 @@ func (decl DeclType) String() string {
110111
return "ArgumentDecl"
111112
case LexicalDecl:
112113
return "LexicalDecl"
114+
case PrivateDecl:
115+
return "PrivateDecl"
113116
case CatchDecl:
114117
return "CatchDecl"
115118
case ExprDecl:
@@ -1478,14 +1481,37 @@ func (n FuncDecl) JS(w io.Writer) {
14781481
n.Body.JS(w)
14791482
}
14801483

1484+
// ClassElementName is either a private method/field or a property name for a method/field.
1485+
type ClassElementName struct {
1486+
PropertyName
1487+
Private *Var // can be nil
1488+
}
1489+
1490+
func (n ClassElementName) String() string {
1491+
if n.Private != nil {
1492+
return n.Private.String()
1493+
} else {
1494+
return n.PropertyName.String()
1495+
}
1496+
}
1497+
1498+
// JS writes JavaScript to writer.
1499+
func (n ClassElementName) JS(w io.Writer) {
1500+
if n.Private != nil {
1501+
n.Private.JS(w)
1502+
} else {
1503+
n.PropertyName.JS(w)
1504+
}
1505+
}
1506+
14811507
// MethodDecl is a method definition in a class declaration.
14821508
type MethodDecl struct {
14831509
Static bool
14841510
Async bool
14851511
Generator bool
14861512
Get bool
14871513
Set bool
1488-
Name PropertyName
1514+
Name ClassElementName
14891515
Params Params
14901516
Body BlockStmt
14911517
}
@@ -1559,7 +1585,7 @@ func (n MethodDecl) JS(w io.Writer) {
15591585
// Field is a field definition in a class declaration.
15601586
type Field struct {
15611587
Static bool
1562-
Name PropertyName
1588+
Name ClassElementName
15631589
Init IExpr
15641590
}
15651591

@@ -1622,6 +1648,7 @@ type ClassDecl struct {
16221648
Name *Var // can be nil
16231649
Extends IExpr // can be nil
16241650
List []ClassElement
1651+
Scope // for private elements
16251652
}
16261653

16271654
func (n ClassDecl) String() string {
@@ -2052,7 +2079,7 @@ func (n IndexExpr) JS(w io.Writer) {
20522079
// DotExpr is a member/call expression, super property, or optional chain with a dot expression.
20532080
type DotExpr struct {
20542081
X IExpr
2055-
Y LiteralExpr
2082+
Y IExpr // LiteralExpr or Var
20562083
Prec OpPrec
20572084
Optional bool
20582085
}

js/parse.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,7 @@ func (p *Parser) parseAnyClass(expr bool) (classDecl *ClassDecl) {
10401040
if !p.consume("class declaration", OpenBraceToken) {
10411041
return
10421042
}
1043+
parent := p.enterScope(&classDecl.Scope, false)
10431044
for {
10441045
if p.tt == ErrorToken {
10451046
p.fail("class declaration")
@@ -1054,6 +1055,7 @@ func (p *Parser) parseAnyClass(expr bool) (classDecl *ClassDecl) {
10541055

10551056
classDecl.List = append(classDecl.List, p.parseClassElement())
10561057
}
1058+
p.exitScope(parent)
10571059
return
10581060
}
10591061

@@ -1116,10 +1118,15 @@ func (p *Parser) parseClassElement() ClassElement {
11161118
isField = true
11171119
} else {
11181120
if p.tt == PrivateIdentifierToken {
1119-
method.Name.Literal = LiteralExpr{p.tt, p.data}
1121+
var ok bool
1122+
method.Name.Private, ok = p.scope.Declare(PrivateDecl, p.data)
1123+
if !ok {
1124+
p.failMessage("identifier %s has already been declared", string(p.data))
1125+
return ClassElement{}
1126+
}
11201127
p.next()
11211128
} else {
1122-
method.Name = p.parsePropertyName("method or field definition")
1129+
method.Name.PropertyName = p.parsePropertyName("method or field definition")
11231130
}
11241131
if (data == nil || method.Static) && p.tt != OpenParenToken {
11251132
isField = true
@@ -1404,7 +1411,7 @@ func (p *Parser) parseObjectLiteral() (object ObjectExpr) {
14041411
method.Get = false
14051412
method.Set = false
14061413
} else if !method.Name.IsSet() { // did not parse async [LT]
1407-
method.Name = p.parsePropertyName("object literal")
1414+
method.Name.PropertyName = p.parsePropertyName("object literal")
14081415
if !method.Name.IsSet() {
14091416
return
14101417
}
@@ -1426,7 +1433,7 @@ func (p *Parser) parseObjectLiteral() (object ObjectExpr) {
14261433
} else if p.tt == ColonToken {
14271434
// PropertyName : AssignmentExpression
14281435
p.next()
1429-
property.Name = &method.Name
1436+
property.Name = &method.Name.PropertyName
14301437
property.Value = p.parseAssignExprOrParam()
14311438
} else if method.Name.IsComputed() || !p.isIdentifierReference(method.Name.Literal.TokenType) {
14321439
p.fail("object literal", ColonToken, OpenParenToken)
@@ -1435,7 +1442,7 @@ func (p *Parser) parseObjectLiteral() (object ObjectExpr) {
14351442
// IdentifierReference (= AssignmentExpression)?
14361443
name := method.Name.Literal.Data
14371444
method.Name.Literal.Data = parse.Copy(method.Name.Literal.Data) // copy so that renaming doesn't rename the key
1438-
property.Name = &method.Name // set key explicitly so after renaming the original is still known
1445+
property.Name = &method.Name.PropertyName // set key explicitly so after renaming the original is still known
14391446
if p.assumeArrowFunc {
14401447
var ok bool
14411448
property.Value, ok = p.scope.Declare(ArgumentDecl, name)
@@ -1853,7 +1860,7 @@ func (p *Parser) parseExpression(prec OpPrec) IExpr {
18531860
p.fail("expression")
18541861
return nil
18551862
}
1856-
left = &LiteralExpr{p.tt, p.data}
1863+
left = p.scope.Use(p.data)
18571864
p.next()
18581865
if p.tt != InToken {
18591866
p.fail("relational expression", InToken)
@@ -1953,10 +1960,11 @@ func (p *Parser) parseExpressionSuffix(left IExpr, prec, precLeft OpPrec) IExpr
19531960
if precLeft < OpMember {
19541961
exprPrec = OpCall
19551962
}
1956-
if p.tt != PrivateIdentifierToken {
1957-
p.tt = IdentifierToken
1963+
if p.tt == PrivateIdentifierToken {
1964+
left = &DotExpr{left, p.scope.Use(p.data), exprPrec, false}
1965+
} else {
1966+
left = &DotExpr{left, LiteralExpr{IdentifierToken, p.data}, exprPrec, false}
19581967
}
1959-
left = &DotExpr{left, LiteralExpr{p.tt, p.data}, exprPrec, false}
19601968
p.next()
19611969
if precLeft < OpMember {
19621970
precLeft = OpCall

js/parse_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,7 @@ func (sv *ScopeVars) AddExpr(iexpr IExpr) {
812812
sv.AddStmt(item)
813813
}
814814
case *ClassDecl:
815+
sv.AddScope(expr.Scope)
815816
for _, item := range expr.List {
816817
if item.Method != nil {
817818
sv.AddScope(item.Method.Body.Scope)
@@ -904,6 +905,7 @@ func (sv *ScopeVars) AddStmt(istmt IStmt) {
904905
sv.AddStmt(item)
905906
}
906907
case *ClassDecl:
908+
sv.AddScope(stmt.Scope)
907909
for _, item := range stmt.List {
908910
if item.Method != nil {
909911
sv.AddScope(item.Method.Body.Scope)
@@ -974,8 +976,10 @@ func TestParseScope(t *testing.T) {
974976
{"a=function(b){var b}", "/b=2", "a=1/"},
975977
{"export function a(){}", "a=1", ""},
976978
{"export default function a(){}", "a=1", ""},
977-
{"class a{b(){}}", "a=1/", "/"}, // classes are not tracked
978-
{"!class a{b(){}}", "/", "/"},
979+
{"class a{b(){}}", "a=1//", "//"},
980+
{"!class a{b(){}}", "//", "//"},
981+
{"class a{#b; b(){ this.#b }}", "a=1/#b=2/", "//#b=2"},
982+
{"!class a{#b; b(){ x().#b }}", "/#b=2/", "x=1/x=1/x=1,#b=2"},
979983
{"a => a%5", "/a=1", "/"},
980984
{"a => a%b", "/a=2", "b=1/b=1"},
981985
{"var a;a => a%5", "a=1/a=2", "/"},
@@ -1005,7 +1009,7 @@ func TestParseScope(t *testing.T) {
10051009
{"let a; {let b = a}", "a=1/b=2", "/a=1"},
10061010
{"let a; {var b}", "a=1,b=2/", "/b=2"}, // may remove b from uses
10071011
{"let a; {var b = a}", "a=1,b=2/", "/b=2,a=1"},
1008-
{"let a; {class b{}}", "a=1/b=2", "/"},
1012+
{"let a; {class b{}}", "a=1/b=2/", "//"},
10091013
{"a = 5; var a;", "a=1", ""},
10101014
{"a = 5; let a;", "a=1", ""},
10111015
{"a = 5; {var a}", "a=1/", "/a=1"},

js/walk.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ func Walk(v IVisitor, n INode) {
242242
Walk(v, n.Y)
243243
case *DotExpr:
244244
Walk(v, n.X)
245-
Walk(v, &n.Y)
245+
Walk(v, n.Y)
246246
case *NewTargetExpr:
247247
return
248248
case *ImportMetaExpr:

0 commit comments

Comments
 (0)