Skip to content

Commit 7baefdb

Browse files
committed
fix a panic with "export default interface\n"
1 parent a8313d2 commit 7baefdb

3 files changed

Lines changed: 48 additions & 9 deletions

File tree

internal/js_parser/js_parser.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6700,6 +6700,7 @@ type parseStmtOpts struct {
67006700
isModuleScope bool
67016701
isNamespaceScope bool
67026702
isExport bool
6703+
isExportDefault bool
67036704
isNameOptional bool // For "export default" pseudo-statements
67046705
isTypeScriptDeclare bool
67056706
isForLoopInit bool
@@ -6839,6 +6840,8 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
68396840
return defaultName
68406841
}
68416842

6843+
// "export default async function() {}"
6844+
// "export default async function foo() {}"
68426845
if p.lexer.IsContextualKeyword("async") {
68436846
asyncRange := p.lexer.Range()
68446847
p.lexer.Next()
@@ -6872,20 +6875,27 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
68726875
DefaultName: defaultName, Value: js_ast.Stmt{Loc: loc, Data: &js_ast.SExpr{Value: expr}}}}
68736876
}
68746877

6875-
if p.lexer.Token == js_lexer.TFunction || p.lexer.Token == js_lexer.TClass || p.lexer.IsContextualKeyword("interface") {
6878+
// "export default class {}"
6879+
// "export default class Foo {}"
6880+
// "export default function() {}"
6881+
// "export default function foo() {}"
6882+
// "export default interface Foo {}"
6883+
// "export default interface + 1"
6884+
if p.lexer.Token == js_lexer.TFunction || p.lexer.Token == js_lexer.TClass ||
6885+
(p.options.ts.Parse && p.lexer.IsContextualKeyword("interface")) {
68766886
stmt := p.parseStmt(parseStmtOpts{
68776887
deferredDecorators: opts.deferredDecorators,
68786888
isNameOptional: true,
6889+
isExportDefault: true,
68796890
lexicalDecl: lexicalDeclAllowAll,
68806891
hasNoSideEffectsComment: opts.hasNoSideEffectsComment,
68816892
})
6882-
if _, ok := stmt.Data.(*js_ast.STypeScript); ok {
6883-
return stmt // This was just a type annotation
6884-
}
68856893

68866894
// Use the statement name if present, since it's a better name
68876895
var defaultName ast.LocRef
68886896
switch s := stmt.Data.(type) {
6897+
case *js_ast.STypeScript, *js_ast.SExpr:
6898+
return stmt // Handle the "interface" case above
68896899
case *js_ast.SFunction:
68906900
if s.Fn.Name != nil {
68916901
defaultName = ast.LocRef{Loc: defaultLoc, Ref: s.Fn.Name.Ref}
@@ -6901,7 +6911,6 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
69016911
default:
69026912
panic("Internal error")
69036913
}
6904-
69056914
return js_ast.Stmt{Loc: loc, Data: &js_ast.SExportDefault{DefaultName: defaultName, Value: stmt}}
69066915
}
69076916

@@ -6910,6 +6919,7 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
69106919
expr := p.parseExpr(js_ast.LComma)
69116920

69126921
// "export default abstract class {}"
6922+
// "export default abstract class Foo {}"
69136923
if p.options.ts.Parse && isIdentifier && name == "abstract" && !p.lexer.HasNewlineBefore {
69146924
if _, ok := expr.Data.(*js_ast.EIdentifier); ok && (p.lexer.Token == js_lexer.TClass || opts.deferredDecorators != nil) {
69156925
stmt := p.parseClassStmt(loc, parseStmtOpts{
@@ -7741,18 +7751,18 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
77417751

77427752
default:
77437753
isIdentifier := p.lexer.Token == js_lexer.TIdentifier
7754+
nameRange := p.lexer.Range()
77447755
name := p.lexer.Identifier.String
77457756

77467757
// Parse either an async function, an async expression, or a normal expression
77477758
var expr js_ast.Expr
77487759
if isIdentifier && p.lexer.Raw() == "async" {
7749-
asyncRange := p.lexer.Range()
77507760
p.lexer.Next()
77517761
if p.lexer.Token == js_lexer.TFunction && !p.lexer.HasNewlineBefore {
77527762
p.lexer.Next()
7753-
return p.parseFnStmt(asyncRange.Loc, opts, true /* isAsync */, asyncRange)
7763+
return p.parseFnStmt(nameRange.Loc, opts, true /* isAsync */, nameRange)
77547764
}
7755-
expr = p.parseSuffix(p.parseAsyncPrefixExpr(asyncRange, js_ast.LLowest, 0), js_ast.LLowest, nil, 0)
7765+
expr = p.parseSuffix(p.parseAsyncPrefixExpr(nameRange, js_ast.LLowest, 0), js_ast.LLowest, nil, 0)
77567766
} else {
77577767
var stmt js_ast.Stmt
77587768
expr, stmt, _ = p.parseExprOrLetOrUsingStmt(opts)
@@ -7800,11 +7810,20 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
78007810

78017811
case "interface":
78027812
// "interface Foo {}"
7803-
if !p.lexer.HasNewlineBefore {
7813+
// "export default interface Foo {}"
7814+
// "export default interface \n Foo {}"
7815+
if !p.lexer.HasNewlineBefore || opts.isExportDefault {
78047816
p.skipTypeScriptInterfaceStmt(parseStmtOpts{isModuleScope: opts.isModuleScope})
78057817
return js_ast.Stmt{Loc: loc, Data: js_ast.STypeScriptShared}
78067818
}
78077819

7820+
// "interface \n Foo {}"
7821+
// "export interface \n Foo {}"
7822+
if opts.isExport {
7823+
p.log.AddError(&p.tracker, nameRange, "Unexpected \"interface\"")
7824+
panic(js_lexer.LexerPanic{})
7825+
}
7826+
78087827
case "abstract":
78097828
if !p.lexer.HasNewlineBefore && (p.lexer.Token == js_lexer.TClass || opts.deferredDecorators != nil) {
78107829
return p.parseClassStmt(loc, opts)

internal/js_parser/js_parser_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2944,6 +2944,14 @@ func TestExport(t *testing.T) {
29442944
expectParseError(t, "export let", "<stdin>: ERROR: Expected identifier but found end of file\n")
29452945
expectParseError(t, "export const", "<stdin>: ERROR: Expected identifier but found end of file\n")
29462946

2947+
// Do not parse TypeScript export syntax in JavaScript
2948+
expectParseError(t, "export enum Foo {}", "<stdin>: ERROR: Unexpected \"enum\"\n")
2949+
expectParseError(t, "export interface Foo {}", "<stdin>: ERROR: Unexpected \"interface\"\n")
2950+
expectParseError(t, "export namespace Foo {}", "<stdin>: ERROR: Unexpected \"namespace\"\n")
2951+
expectParseError(t, "export abstract class Foo {}", "<stdin>: ERROR: Unexpected \"abstract\"\n")
2952+
expectParseError(t, "export declare class Foo {}", "<stdin>: ERROR: Unexpected \"declare\"\n")
2953+
expectParseError(t, "export declare function foo() {}", "<stdin>: ERROR: Unexpected \"declare\"\n")
2954+
29472955
// String export alias with "export {}"
29482956
expectPrinted(t, "let x; export {x as ''}", "let x;\nexport { x as \"\" };\n")
29492957
expectPrinted(t, "let x; export {x as '🍕'}", "let x;\nexport { x as \"🍕\" };\n")

internal/js_parser/ts_parser_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,18 @@ func TestTSInterface(t *testing.T) {
899899
expectPrintedTS(t, "interface A<T extends number> extends B.C<D, E>, F.G<H, I> {} x", "x;\n")
900900
expectPrintedTS(t, "export interface A<T extends number> extends B.C<D, E>, F.G<H, I> {} x", "x;\n")
901901
expectPrintedTS(t, "export default interface Foo {} x", "x;\n")
902+
expectParseErrorTS(t, "export default interface + x",
903+
"<stdin>: ERROR: \"interface\" is a reserved word and cannot be used in an ECMAScript module\n"+
904+
"<stdin>: NOTE: This file is considered to be an ECMAScript module because of the \"export\" keyword here:\n")
905+
906+
// Check ASI for "interface"
907+
expectPrintedTS(t, "interface\nFoo\n{}", "interface;\nFoo;\n{\n}\n")
908+
expectPrintedTS(t, "export default interface\nFoo {} x", "x;\n")
909+
expectPrintedTS(t, "export default interface\nFoo\n{} x", "x;\n")
910+
expectParseErrorTS(t, "interface\nFoo {}", "<stdin>: ERROR: Expected \";\" but found \"{\"\n")
911+
expectParseErrorTS(t, "export interface\nFoo {}", "<stdin>: ERROR: Unexpected \"interface\"\n")
912+
expectParseErrorTS(t, "export interface\nFoo\n{}", "<stdin>: ERROR: Unexpected \"interface\"\n")
913+
expectParseErrorTS(t, "export default interface\nFoo", "<stdin>: ERROR: Expected \"{\" but found end of file\n")
902914
}
903915

904916
func TestTSNamespace(t *testing.T) {

0 commit comments

Comments
 (0)