Skip to content

Commit e9413cc

Browse files
committed
parse decorators in js (but still generate errors)
1 parent 033c5da commit e9413cc

File tree

3 files changed

+145
-120
lines changed

3 files changed

+145
-120
lines changed

internal/js_parser/js_parser.go

Lines changed: 139 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,8 +1916,9 @@ func (p *parser) parseStringLiteral() js_ast.Expr {
19161916
}
19171917

19181918
type propertyOpts struct {
1919-
decorators []js_ast.Expr
1920-
decoratorScope *js_ast.Scope
1919+
decorators []js_ast.Expr
1920+
decoratorScope *js_ast.Scope
1921+
decoratorContext decoratorContextFlags
19211922

19221923
asyncRange logger.Range
19231924
generatorRange logger.Range
@@ -2317,7 +2318,7 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op
23172318
yield = allowExpr
23182319
}
23192320

2320-
fn, hadBody := p.parseFn(nil, opts.classKeyword, fnOrArrowDataParse{
2321+
fn, hadBody := p.parseFn(nil, opts.classKeyword, opts.decoratorContext, fnOrArrowDataParse{
23212322
needsAsyncLoc: key.Loc,
23222323
asyncRange: opts.asyncRange,
23232324
await: await,
@@ -2810,7 +2811,7 @@ func (p *parser) parseFnExpr(loc logger.Loc, isAsync bool, asyncRange logger.Ran
28102811
yield = allowExpr
28112812
}
28122813

2813-
fn, _ := p.parseFn(name, logger.Range{}, fnOrArrowDataParse{
2814+
fn, _ := p.parseFn(name, logger.Range{}, 0, fnOrArrowDataParse{
28142815
needsAsyncLoc: loc,
28152816
asyncRange: asyncRange,
28162817
await: await,
@@ -3454,33 +3455,20 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF
34543455
return p.parseFnExpr(loc, false /* isAsync */, logger.Range{})
34553456

34563457
case js_lexer.TClass:
3457-
classKeyword := p.lexer.Range()
3458-
p.markSyntaxFeature(compat.Class, classKeyword)
3459-
p.lexer.Next()
3460-
var name *js_ast.LocRef
3458+
return p.parseClassExpr(loc, nil)
34613459

3462-
p.pushScopeForParsePass(js_ast.ScopeClassName, loc)
3460+
case js_lexer.TAt:
3461+
// Parse decorators before class statements, which are potentially exported
3462+
scopeIndex := len(p.scopesInOrder)
3463+
decorators := p.parseDecorators(p.currentScope, logger.Range{}, decoratorBeforeClassExpr)
34633464

3464-
// Parse an optional class name
3465-
if p.lexer.Token == js_lexer.TIdentifier {
3466-
if nameText := p.lexer.Identifier.String; !p.options.ts.Parse || nameText != "implements" {
3467-
if p.fnOrArrowDataParse.await != allowIdent && nameText == "await" {
3468-
p.log.AddError(&p.tracker, p.lexer.Range(), "Cannot use \"await\" as an identifier here:")
3469-
}
3470-
name = &js_ast.LocRef{Loc: p.lexer.Loc(), Ref: p.newSymbol(js_ast.SymbolOther, nameText)}
3471-
p.lexer.Next()
3472-
}
3465+
// "@decorator class {}"
3466+
// "@decorator class Foo {}"
3467+
if p.lexer.Token != js_lexer.TClass && p.lexer.Token != js_lexer.TExport {
3468+
p.logDecoratorWithoutFollowingClassError(loc, scopeIndex)
34733469
}
34743470

3475-
// Even anonymous classes can have TypeScript type parameters
3476-
if p.options.ts.Parse {
3477-
p.skipTypeScriptTypeParameters(allowInOutVarianceAnnotations | allowConstModifier)
3478-
}
3479-
3480-
class := p.parseClass(classKeyword, name, parseClassOpts{})
3481-
3482-
p.popScope()
3483-
return js_ast.Expr{Loc: loc, Data: &js_ast.EClass{Class: class}}
3471+
return p.parseClassExpr(loc, decorators)
34843472

34853473
case js_lexer.TNew:
34863474
p.lexer.Next()
@@ -5607,7 +5595,12 @@ func (p *parser) parseBinding() js_ast.Binding {
56075595
return js_ast.Binding{}
56085596
}
56095597

5610-
func (p *parser) parseFn(name *js_ast.LocRef, classKeyword logger.Range, data fnOrArrowDataParse) (fn js_ast.Fn, hadBody bool) {
5598+
func (p *parser) parseFn(
5599+
name *js_ast.LocRef,
5600+
classKeyword logger.Range,
5601+
decoratorContext decoratorContextFlags,
5602+
data fnOrArrowDataParse,
5603+
) (fn js_ast.Fn, hadBody bool) {
56115604
fn.Name = name
56125605
fn.HasRestArg = false
56135606
fn.IsAsync = data.await == allowExpr
@@ -5703,12 +5696,10 @@ func (p *parser) parseFn(name *js_ast.LocRef, classKeyword logger.Range, data fn
57035696
p.fnOrArrowDataParse.needsAsyncLoc = oldFnOrArrowData.needsAsyncLoc
57045697
}
57055698

5706-
decorators = p.parseDecorators(data.decoratorScope)
5699+
decorators = p.parseDecorators(data.decoratorScope, classKeyword, decoratorContext|decoratorInFnArgs)
57075700

57085701
p.fnOrArrowDataParse.await = oldAwait
57095702
p.fnOrArrowDataParse.needsAsyncLoc = oldNeedsAsyncLoc
5710-
} else if classKeyword.Len > 0 {
5711-
p.logInvalidDecoratorError(classKeyword)
57125703
}
57135704

57145705
if !fn.HasRestArg && p.lexer.Token == js_lexer.TDotDotDot {
@@ -5899,9 +5890,45 @@ func (p *parser) parseClassStmt(loc logger.Loc, opts parseStmtOpts) js_ast.Stmt
58995890
return js_ast.Stmt{Loc: loc, Data: &js_ast.SClass{Class: class, IsExport: opts.isExport}}
59005891
}
59015892

5893+
func (p *parser) parseClassExpr(loc logger.Loc, decorators []js_ast.Expr) js_ast.Expr {
5894+
classKeyword := p.lexer.Range()
5895+
p.markSyntaxFeature(compat.Class, classKeyword)
5896+
p.lexer.Next()
5897+
var name *js_ast.LocRef
5898+
5899+
opts := parseClassOpts{
5900+
decorators: decorators,
5901+
decoratorScope: p.currentScope,
5902+
decoratorContext: decoratorInClassExpr,
5903+
}
5904+
p.pushScopeForParsePass(js_ast.ScopeClassName, loc)
5905+
5906+
// Parse an optional class name
5907+
if p.lexer.Token == js_lexer.TIdentifier {
5908+
if nameText := p.lexer.Identifier.String; !p.options.ts.Parse || nameText != "implements" {
5909+
if p.fnOrArrowDataParse.await != allowIdent && nameText == "await" {
5910+
p.log.AddError(&p.tracker, p.lexer.Range(), "Cannot use \"await\" as an identifier here:")
5911+
}
5912+
name = &js_ast.LocRef{Loc: p.lexer.Loc(), Ref: p.newSymbol(js_ast.SymbolOther, nameText)}
5913+
p.lexer.Next()
5914+
}
5915+
}
5916+
5917+
// Even anonymous classes can have TypeScript type parameters
5918+
if p.options.ts.Parse {
5919+
p.skipTypeScriptTypeParameters(allowInOutVarianceAnnotations | allowConstModifier)
5920+
}
5921+
5922+
class := p.parseClass(classKeyword, name, opts)
5923+
5924+
p.popScope()
5925+
return js_ast.Expr{Loc: loc, Data: &js_ast.EClass{Class: class}}
5926+
}
5927+
59025928
type parseClassOpts struct {
59035929
decorators []js_ast.Expr
59045930
decoratorScope *js_ast.Scope
5931+
decoratorContext decoratorContextFlags
59055932
isTypeScriptDeclare bool
59065933
}
59075934

@@ -5951,10 +5978,11 @@ func (p *parser) parseClass(classKeyword logger.Range, name *js_ast.LocRef, clas
59515978
scopeIndex := p.pushScopeForParsePass(js_ast.ScopeClassBody, bodyLoc)
59525979

59535980
opts := propertyOpts{
5954-
isClass: true,
5955-
decoratorScope: classOpts.decoratorScope,
5956-
classHasExtends: extendsOrNil.Data != nil,
5957-
classKeyword: classKeyword,
5981+
isClass: true,
5982+
decoratorScope: classOpts.decoratorScope,
5983+
decoratorContext: classOpts.decoratorContext,
5984+
classHasExtends: extendsOrNil.Data != nil,
5985+
classKeyword: classKeyword,
59585986
}
59595987
hasConstructor := false
59605988

@@ -5966,12 +5994,7 @@ func (p *parser) parseClass(classKeyword logger.Range, name *js_ast.LocRef, clas
59665994

59675995
// Parse decorators for this property
59685996
firstDecoratorLoc := p.lexer.Loc()
5969-
if opts.decoratorScope != nil {
5970-
opts.decorators = p.parseDecorators(opts.decoratorScope)
5971-
} else {
5972-
opts.decorators = nil
5973-
p.logInvalidDecoratorError(classKeyword)
5974-
}
5997+
opts.decorators = p.parseDecorators(opts.decoratorScope, classKeyword, opts.decoratorContext)
59755998

59765999
// This property may turn out to be a type in TypeScript, which should be ignored
59776000
if property, ok := p.parseProperty(p.saveExprCommentsHere(), js_ast.PropertyNormal, opts, nil); ok {
@@ -5981,7 +6004,7 @@ func (p *parser) parseClass(classKeyword logger.Range, name *js_ast.LocRef, clas
59816004
if key, ok := property.Key.Data.(*js_ast.EString); ok && helpers.UTF16EqualsString(key.Value, "constructor") {
59826005
if len(opts.decorators) > 0 {
59836006
p.log.AddError(&p.tracker, logger.Range{Loc: firstDecoratorLoc},
5984-
"TypeScript does not allow decorators on class constructors")
6007+
"Decorators are not allowed on class constructors")
59856008
}
59866009
if property.Flags.Has(js_ast.PropertyIsMethod) && !property.Flags.Has(js_ast.PropertyIsStatic) && !property.Flags.Has(js_ast.PropertyIsComputed) {
59876010
if hasConstructor {
@@ -6171,7 +6194,7 @@ func (p *parser) parseFnStmt(loc logger.Loc, opts parseStmtOpts, isAsync bool, a
61716194
yield = allowExpr
61726195
}
61736196

6174-
fn, hadBody := p.parseFn(name, logger.Range{}, fnOrArrowDataParse{
6197+
fn, hadBody := p.parseFn(name, logger.Range{}, 0, fnOrArrowDataParse{
61756198
needsAsyncLoc: loc,
61766199
asyncRange: asyncRange,
61776200
await: await,
@@ -6234,35 +6257,55 @@ type deferredDecorators struct {
62346257
firstAtLoc logger.Loc
62356258
}
62366259

6237-
func (p *parser) parseDecorators(decoratorScope *js_ast.Scope) []js_ast.Expr {
6238-
var decorators []js_ast.Expr
6239-
6240-
if p.options.ts.Parse {
6241-
// TypeScript decorators cause us to temporarily revert to the scope that
6242-
// encloses the class declaration, since that's where the generated code
6243-
// for TypeScript decorators will be inserted.
6244-
oldScope := p.currentScope
6245-
p.currentScope = decoratorScope
6260+
type decoratorContextFlags uint8
62466261

6247-
for p.lexer.Token == js_lexer.TAt {
6248-
p.lexer.Next()
6262+
const (
6263+
decoratorBeforeClassExpr = 1 << iota
6264+
decoratorInClassExpr
6265+
decoratorInFnArgs
6266+
)
62496267

6250-
// Parse a new/call expression with "exprFlagDecorator" so we ignore
6251-
// EIndex expressions, since they may be part of a computed property:
6252-
//
6253-
// class Foo {
6254-
// @foo ['computed']() {}
6255-
// }
6256-
//
6257-
// This matches the behavior of the TypeScript compiler.
6258-
value := p.parseExprWithFlags(js_ast.LNew, exprFlagDecorator)
6259-
decorators = append(decorators, value)
6268+
func (p *parser) parseDecorators(decoratorScope *js_ast.Scope, classKeyword logger.Range, context decoratorContextFlags) (decorators []js_ast.Expr) {
6269+
if p.lexer.Token == js_lexer.TAt {
6270+
if p.options.ts.Parse {
6271+
if (context & decoratorInClassExpr) != 0 {
6272+
p.lexer.AddRangeErrorWithNotes(p.lexer.Range(), "Experimental decorators can only be used with class declarations in TypeScript",
6273+
[]logger.MsgData{p.tracker.MsgData(classKeyword, "This is a class expression, not a class declaration:")})
6274+
} else if (context & decoratorBeforeClassExpr) != 0 {
6275+
p.log.AddError(&p.tracker, p.lexer.Range(), "Experimental decorators cannot be used in expression position in TypeScript")
6276+
}
6277+
} else {
6278+
if (context & decoratorInFnArgs) != 0 {
6279+
p.log.AddError(&p.tracker, p.lexer.Range(), "Parameter decorators are not allowed in JavaScript")
6280+
} else {
6281+
p.log.AddError(&p.tracker, p.lexer.Range(), "JavaScript decorators are not currently supported")
6282+
}
62606283
}
6284+
}
62616285

6262-
// Avoid "popScope" because this decorator scope is not hierarchical
6263-
p.currentScope = oldScope
6286+
// TypeScript decorators cause us to temporarily revert to the scope that
6287+
// encloses the class declaration, since that's where the generated code
6288+
// for TypeScript decorators will be inserted.
6289+
oldScope := p.currentScope
6290+
p.currentScope = decoratorScope
6291+
6292+
for p.lexer.Token == js_lexer.TAt {
6293+
p.lexer.Next()
6294+
6295+
// Parse a new/call expression with "exprFlagDecorator" so we ignore
6296+
// EIndex expressions, since they may be part of a computed property:
6297+
//
6298+
// class Foo {
6299+
// @foo ['computed']() {}
6300+
// }
6301+
//
6302+
// This matches the behavior of the TypeScript compiler.
6303+
value := p.parseExprWithFlags(js_ast.LNew, exprFlagDecorator)
6304+
decorators = append(decorators, value)
62646305
}
62656306

6307+
// Avoid "popScope" because this decorator scope is not hierarchical
6308+
p.currentScope = oldScope
62666309
return decorators
62676310
}
62686311

@@ -6636,45 +6679,40 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
66366679

66376680
case js_lexer.TAt:
66386681
// Parse decorators before class statements, which are potentially exported
6639-
if p.options.ts.Parse {
6640-
scopeIndex := len(p.scopesInOrder)
6641-
decorators := p.parseDecorators(p.currentScope)
6682+
scopeIndex := len(p.scopesInOrder)
6683+
decorators := p.parseDecorators(p.currentScope, logger.Range{}, 0)
66426684

6643-
// If this turns out to be a "declare class" statement, we need to undo the
6644-
// scopes that were potentially pushed while parsing the decorator arguments.
6645-
// That can look like any one of the following:
6646-
//
6647-
// "@decorator declare class Foo {}"
6648-
// "@decorator declare abstract class Foo {}"
6649-
// "@decorator export declare class Foo {}"
6650-
// "@decorator export declare abstract class Foo {}"
6651-
//
6652-
opts.decorators = &deferredDecorators{
6653-
firstAtLoc: loc,
6654-
values: decorators,
6655-
scopeIndex: scopeIndex,
6656-
}
6657-
6658-
// "@decorator class Foo {}"
6659-
// "@decorator abstract class Foo {}"
6660-
// "@decorator declare class Foo {}"
6661-
// "@decorator declare abstract class Foo {}"
6662-
// "@decorator export class Foo {}"
6663-
// "@decorator export abstract class Foo {}"
6664-
// "@decorator export declare class Foo {}"
6665-
// "@decorator export declare abstract class Foo {}"
6666-
// "@decorator export default class Foo {}"
6667-
// "@decorator export default abstract class Foo {}"
6668-
if p.lexer.Token != js_lexer.TClass && p.lexer.Token != js_lexer.TExport &&
6669-
!p.lexer.IsContextualKeyword("abstract") && !p.lexer.IsContextualKeyword("declare") {
6670-
p.logDecoratorWithoutFollowingClassError(opts.decorators.firstAtLoc, opts.decorators.scopeIndex)
6671-
}
6685+
// If this turns out to be a "declare class" statement, we need to undo the
6686+
// scopes that were potentially pushed while parsing the decorator arguments.
6687+
// That can look like any one of the following:
6688+
//
6689+
// "@decorator declare class Foo {}"
6690+
// "@decorator declare abstract class Foo {}"
6691+
// "@decorator export declare class Foo {}"
6692+
// "@decorator export declare abstract class Foo {}"
6693+
//
6694+
opts.decorators = &deferredDecorators{
6695+
firstAtLoc: loc,
6696+
values: decorators,
6697+
scopeIndex: scopeIndex,
6698+
}
66726699

6673-
return p.parseStmt(opts)
6700+
// "@decorator class Foo {}"
6701+
// "@decorator abstract class Foo {}"
6702+
// "@decorator declare class Foo {}"
6703+
// "@decorator declare abstract class Foo {}"
6704+
// "@decorator export class Foo {}"
6705+
// "@decorator export abstract class Foo {}"
6706+
// "@decorator export declare class Foo {}"
6707+
// "@decorator export declare abstract class Foo {}"
6708+
// "@decorator export default class Foo {}"
6709+
// "@decorator export default abstract class Foo {}"
6710+
if p.lexer.Token != js_lexer.TClass && p.lexer.Token != js_lexer.TExport &&
6711+
(!p.options.ts.Parse || (!p.lexer.IsContextualKeyword("abstract") && !p.lexer.IsContextualKeyword("declare"))) {
6712+
p.logDecoratorWithoutFollowingClassError(opts.decorators.firstAtLoc, opts.decorators.scopeIndex)
66746713
}
66756714

6676-
p.lexer.Unexpected()
6677-
return js_ast.Stmt{}
6715+
return p.parseStmt(opts)
66786716

66796717
case js_lexer.TClass:
66806718
if opts.lexicalDecl != lexicalDeclAllowAll {

internal/js_parser/ts_parser.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,19 +1236,6 @@ func (p *parser) skipTypeScriptTypeStmt(opts parseStmtOpts) {
12361236
p.lexer.ExpectOrInsertSemicolon()
12371237
}
12381238

1239-
func (p *parser) logInvalidDecoratorError(classKeyword logger.Range) {
1240-
if p.options.ts.Parse && p.lexer.Token == js_lexer.TAt {
1241-
// Forbid decorators inside class expressions
1242-
p.lexer.AddRangeErrorWithNotes(p.lexer.Range(), "Decorators can only be used with class declarations in TypeScript",
1243-
[]logger.MsgData{p.tracker.MsgData(classKeyword, "This is a class expression, not a class declaration:")})
1244-
1245-
// Parse and discard decorators for error recovery
1246-
scopeIndex := len(p.scopesInOrder)
1247-
p.parseDecorators(p.currentScope)
1248-
p.discardScopesUpTo(scopeIndex)
1249-
}
1250-
}
1251-
12521239
func (p *parser) parseTypeScriptEnumStmt(loc logger.Loc, opts parseStmtOpts) js_ast.Stmt {
12531240
p.lexer.Expect(js_lexer.TEnum)
12541241
nameLoc := p.lexer.Loc()

0 commit comments

Comments
 (0)