Skip to content

Commit b2f4cfa

Browse files
rhendricerikd
authored andcommitted
Add support for exotic parameter syntaxes
* rest parameters * default arguments * object/array destructuring in parameters
1 parent da5af46 commit b2f4cfa

File tree

6 files changed

+60
-13
lines changed

6 files changed

+60
-13
lines changed

src/Language/JavaScript/Parser/AST.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ data JSStatement
142142
| JSForConstOf !JSAnnot !JSAnnot !JSAnnot !JSExpression !JSBinOp !JSExpression !JSAnnot !JSStatement -- ^for,lb,var,vardecl,in,expr,rb,stmt
143143
| JSForOf !JSAnnot !JSAnnot !JSExpression !JSBinOp !JSExpression !JSAnnot !JSStatement -- ^for,lb,expr,in,expr,rb,stmt
144144
| JSForVarOf !JSAnnot !JSAnnot !JSAnnot !JSExpression !JSBinOp !JSExpression !JSAnnot !JSStatement -- ^for,lb,var,vardecl,in,expr,rb,stmt
145-
| JSFunction !JSAnnot !JSIdent !JSAnnot !(JSCommaList JSIdent) !JSAnnot !JSBlock !JSSemi -- ^fn,name, lb,parameter list,rb,block,autosemi
145+
| JSFunction !JSAnnot !JSIdent !JSAnnot !(JSCommaList JSExpression) !JSAnnot !JSBlock !JSSemi -- ^fn,name, lb,parameter list,rb,block,autosemi
146146
| JSIf !JSAnnot !JSAnnot !JSExpression !JSAnnot !JSStatement -- ^if,(,expr,),stmt
147147
| JSIfElse !JSAnnot !JSAnnot !JSExpression !JSAnnot !JSStatement !JSAnnot !JSStatement -- ^if,(,expr,),stmt,else,rest
148148
| JSLabelled !JSIdent !JSAnnot !JSStatement -- ^identifier,colon,stmt
@@ -181,7 +181,7 @@ data JSExpression
181181
| JSExpressionPostfix !JSExpression !JSUnaryOp -- ^expression, operator
182182
| JSExpressionTernary !JSExpression !JSAnnot !JSExpression !JSAnnot !JSExpression -- ^cond, ?, trueval, :, falseval
183183
| JSArrowExpression !JSArrowParameterList !JSAnnot !JSStatement -- ^parameter list,arrow,block`
184-
| JSFunctionExpression !JSAnnot !JSIdent !JSAnnot !(JSCommaList JSIdent) !JSAnnot !JSBlock -- ^fn,name,lb, parameter list,rb,block`
184+
| JSFunctionExpression !JSAnnot !JSIdent !JSAnnot !(JSCommaList JSExpression) !JSAnnot !JSBlock -- ^fn,name,lb, parameter list,rb,block`
185185
| JSMemberDot !JSExpression !JSAnnot !JSExpression -- ^firstpart, dot, name
186186
| JSMemberExpression !JSExpression !JSAnnot !(JSCommaList JSExpression) !JSAnnot -- expr, lb, args, rb
187187
| JSMemberNew !JSAnnot !JSExpression !JSAnnot !(JSCommaList JSExpression) !JSAnnot -- ^new, name, lb, args, rb
@@ -196,7 +196,7 @@ data JSExpression
196196

197197
data JSArrowParameterList
198198
= JSUnparenthesizedArrowParameter !JSIdent
199-
| JSParenthesizedArrowParameterList !JSAnnot !(JSCommaList JSIdent) !JSAnnot
199+
| JSParenthesizedArrowParameterList !JSAnnot !(JSCommaList JSExpression) !JSAnnot
200200
deriving (Data, Eq, Show, Typeable)
201201

202202
data JSBinOp

src/Language/JavaScript/Parser/Grammar7.y

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,6 @@ PrimaryExpression : 'this' { AST.JSLiteral (mkJSAnnot $1) "thi
484484
| Literal { $1 {- 'PrimaryExpression2' -} }
485485
| ArrayLiteral { $1 {- 'PrimaryExpression3' -} }
486486
| ObjectLiteral { $1 {- 'PrimaryExpression4' -} }
487-
| SpreadExpression { $1 {- 'PrimaryExpression5' -} }
488487
| TemplateLiteral { mkJSTemplateLiteral Nothing $1 {- 'PrimaryExpression6' -} }
489488
| LParen Expression RParen { AST.JSExpressionParen $1 $2 $3 }
490489

@@ -499,7 +498,7 @@ Identifier : 'ident' { AST.JSIdentifier (mkJSAnnot $1) (tokenLiteral $1) }
499498

500499

501500
SpreadExpression :: { AST.JSExpression }
502-
SpreadExpression : Spread Expression { AST.JSSpreadExpression $1 $2 {- 'SpreadExpression' -} }
501+
SpreadExpression : Spread AssignmentExpression { AST.JSSpreadExpression $1 $2 {- 'SpreadExpression' -} }
503502

504503
TemplateLiteral :: { JSUntaggedTemplate }
505504
TemplateLiteral : 'tmplnosub' { JSUntaggedTemplate (mkJSAnnot $1) (tokenLiteral $1) [] }
@@ -584,7 +583,7 @@ PropertyName : IdentifierName { propName $1 {- 'PropertyName1' -} }
584583
-- PropertySetParameterList : See 11.1.5
585584
-- Identifier
586585
PropertySetParameterList :: { AST.JSExpression }
587-
PropertySetParameterList : Identifier { $1 {- 'PropertySetParameterList' -} }
586+
PropertySetParameterList : AssignmentExpression { $1 {- 'PropertySetParameterList' -} }
588587

589588
-- MemberExpression : See 11.2
590589
-- PrimaryExpression
@@ -861,6 +860,7 @@ AssignmentExpression :: { AST.JSExpression }
861860
AssignmentExpression : ConditionalExpression { $1 {- 'AssignmentExpression1' -} }
862861
| LeftHandSideExpression AssignmentOperator AssignmentExpression
863862
{ AST.JSAssignExpression $1 $2 $3 {- 'AssignmentExpression2' -} }
863+
| SpreadExpression { $1 }
864864

865865
-- AssignmentExpressionNoIn : See 11.13
866866
-- ConditionalExpressionNoIn
@@ -1167,12 +1167,9 @@ ArrowFunctionExpression : ArrowParameterList Arrow StatementOrBlock
11671167
{ AST.JSArrowExpression $1 $2 $3 }
11681168

11691169
ArrowParameterList :: { AST.JSArrowParameterList }
1170-
ArrowParameterList : Identifier
1171-
{ AST.JSUnparenthesizedArrowParameter (identName $1) }
1170+
ArrowParameterList : PrimaryExpression {%^ toArrowParameterList $1 }
11721171
| LParen RParen
11731172
{ AST.JSParenthesizedArrowParameterList $1 AST.JSLNil $2 }
1174-
| LParen FormalParameterList RParen
1175-
{ AST.JSParenthesizedArrowParameterList $1 $2 $3 }
11761173

11771174
StatementOrBlock :: { AST.JSStatement }
11781175
StatementOrBlock : Block MaybeSemi { blockToStatement $1 $2 }
@@ -1204,9 +1201,9 @@ IdentifierOpt : Identifier { identName $1 {- 'IdentifierOpt1' -} }
12041201
-- FormalParameterList : See clause 13
12051202
-- Identifier
12061203
-- FormalParameterList , Identifier
1207-
FormalParameterList :: { AST.JSCommaList AST.JSIdent }
1208-
FormalParameterList : Identifier { AST.JSLOne (identName $1) {- 'FormalParameterList1' -} }
1209-
| FormalParameterList Comma Identifier { AST.JSLCons $1 $2 (identName $3) {- 'FormalParameterList2' -} }
1204+
FormalParameterList :: { AST.JSCommaList AST.JSExpression }
1205+
FormalParameterList : AssignmentExpression { AST.JSLOne $1 {- 'FormalParameterList1' -} }
1206+
| FormalParameterList Comma AssignmentExpression { AST.JSLCons $1 $2 $3 {- 'FormalParameterList2' -} }
12101207

12111208
-- FunctionBody : See clause 13
12121209
-- SourceElementsopt
@@ -1416,4 +1413,13 @@ identifierToProperty :: AST.JSExpression -> AST.JSObjectProperty
14161413
identifierToProperty (AST.JSIdentifier a s) = AST.JSPropertyIdentRef a s
14171414
identifierToProperty x = error $ "Cannot convert '" ++ show x ++ "' to a JSObjectProperty."
14181415

1416+
toArrowParameterList :: AST.JSExpression -> Token -> Alex AST.JSArrowParameterList
1417+
toArrowParameterList (AST.JSIdentifier a s) = const . return $ AST.JSUnparenthesizedArrowParameter (AST.JSIdentName a s)
1418+
toArrowParameterList (AST.JSExpressionParen lb x rb) = const . return $ AST.JSParenthesizedArrowParameterList lb (commasToCommaList x) rb
1419+
toArrowParameterList _ = parseError
1420+
1421+
commasToCommaList :: AST.JSExpression -> AST.JSCommaList AST.JSExpression
1422+
commasToCommaList (AST.JSCommaExpression l c r) = AST.JSLCons (commasToCommaList l) c r
1423+
commasToCommaList x = AST.JSLOne x
1424+
14191425
}

test/Test/Language/Javascript/ExpressionParser.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ testExpressionParser = describe "Parse expressions:" $ do
5353
testExpr "{x:1,}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'x') [JSDecimal '1'],JSComma]))"
5454
testExpr "{x}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyIdentRef 'x']))"
5555
testExpr "{x,}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyIdentRef 'x',JSComma]))"
56+
testExpr "{set x([a,b]=y) {this.a=a;this.b=b}}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyAccessor JSAccessorSet (JSIdentifier 'x') [JSOpAssign ('=',JSArrayLiteral [JSIdentifier 'a',JSComma,JSIdentifier 'b'],JSIdentifier 'y')] (JSBlock [JSOpAssign ('=',JSMemberDot (JSLiteral 'this',JSIdentifier 'a'),JSIdentifier 'a'),JSSemicolon,JSOpAssign ('=',JSMemberDot (JSLiteral 'this',JSIdentifier 'b'),JSIdentifier 'b')])]))"
5657
testExpr "a={if:1,interface:2}" `shouldBe` "Right (JSAstExpression (JSOpAssign ('=',JSIdentifier 'a',JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'if') [JSDecimal '1'],JSPropertyNameandValue (JSIdentifier 'interface') [JSDecimal '2']])))"
5758
testExpr "a={\n values: 7,\n}\n" `shouldBe` "Right (JSAstExpression (JSOpAssign ('=',JSIdentifier 'a',JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'values') [JSDecimal '7'],JSComma])))"
5859
testExpr "x={get foo() {return 1},set foo(a) {x=a}}" `shouldBe` "Right (JSAstExpression (JSOpAssign ('=',JSIdentifier 'x',JSObjectLiteral [JSPropertyAccessor JSAccessorGet (JSIdentifier 'foo') [] (JSBlock [JSReturn JSDecimal '1' ]),JSPropertyAccessor JSAccessorSet (JSIdentifier 'foo') [JSIdentifier 'a'] (JSBlock [JSOpAssign ('=',JSIdentifier 'x',JSIdentifier 'a')])])))"
@@ -126,11 +127,19 @@ testExpressionParser = describe "Parse expressions:" $ do
126127
testExpr "function(){}" `shouldBe` "Right (JSAstExpression (JSFunctionExpression '' () (JSBlock [])))"
127128
testExpr "function(a){}" `shouldBe` "Right (JSAstExpression (JSFunctionExpression '' (JSIdentifier 'a') (JSBlock [])))"
128129
testExpr "function(a,b){}" `shouldBe` "Right (JSAstExpression (JSFunctionExpression '' (JSIdentifier 'a',JSIdentifier 'b') (JSBlock [])))"
130+
testExpr "function(...a){}" `shouldBe` "Right (JSAstExpression (JSFunctionExpression '' (JSSpreadExpression (JSIdentifier 'a')) (JSBlock [])))"
131+
testExpr "function(a=1){}" `shouldBe` "Right (JSAstExpression (JSFunctionExpression '' (JSOpAssign ('=',JSIdentifier 'a',JSDecimal '1')) (JSBlock [])))"
132+
testExpr "function([a,b]){}" `shouldBe` "Right (JSAstExpression (JSFunctionExpression '' (JSArrayLiteral [JSIdentifier 'a',JSComma,JSIdentifier 'b']) (JSBlock [])))"
133+
testExpr "function([a,...b]){}" `shouldBe` "Right (JSAstExpression (JSFunctionExpression '' (JSArrayLiteral [JSIdentifier 'a',JSComma,JSSpreadExpression (JSIdentifier 'b')]) (JSBlock [])))"
134+
testExpr "function({a,b}){}" `shouldBe` "Right (JSAstExpression (JSFunctionExpression '' (JSObjectLiteral [JSPropertyIdentRef 'a',JSPropertyIdentRef 'b']) (JSBlock [])))"
129135
testExpr "a => {}" `shouldBe` "Right (JSAstExpression (JSArrowExpression (JSIdentifier 'a') => JSStatementBlock []))"
130136
testExpr "(a) => { a + 2 }" `shouldBe` "Right (JSAstExpression (JSArrowExpression ((JSIdentifier 'a')) => JSStatementBlock [JSExpressionBinary ('+',JSIdentifier 'a',JSDecimal '2')]))"
131137
testExpr "(a, b) => {}" `shouldBe` "Right (JSAstExpression (JSArrowExpression ((JSIdentifier 'a',JSIdentifier 'b')) => JSStatementBlock []))"
132138
testExpr "(a, b) => a + b" `shouldBe` "Right (JSAstExpression (JSArrowExpression ((JSIdentifier 'a',JSIdentifier 'b')) => JSExpressionBinary ('+',JSIdentifier 'a',JSIdentifier 'b')))"
133139
testExpr "() => { 42 }" `shouldBe` "Right (JSAstExpression (JSArrowExpression (()) => JSStatementBlock [JSDecimal '42']))"
140+
testExpr "(a, ...b) => b" `shouldBe` "Right (JSAstExpression (JSArrowExpression ((JSIdentifier 'a',JSSpreadExpression (JSIdentifier 'b'))) => JSIdentifier 'b'))"
141+
testExpr "(a,b=1) => a + b" `shouldBe` "Right (JSAstExpression (JSArrowExpression ((JSIdentifier 'a',JSOpAssign ('=',JSIdentifier 'b',JSDecimal '1'))) => JSExpressionBinary ('+',JSIdentifier 'a',JSIdentifier 'b')))"
142+
testExpr "([a,b]) => a + b" `shouldBe` "Right (JSAstExpression (JSArrowExpression ((JSArrayLiteral [JSIdentifier 'a',JSComma,JSIdentifier 'b'])) => JSExpressionBinary ('+',JSIdentifier 'a',JSIdentifier 'b')))"
134143

135144
it "member expression" $ do
136145
testExpr "x[y]" `shouldBe` "Right (JSAstExpression (JSMemberSquare (JSIdentifier 'x',JSIdentifier 'y')))"

test/Test/Language/Javascript/Minify.hs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,20 @@ testMinifyExpr = describe "Minify expressions:" $ do
107107
minifyExpr " function ( ) { } " `shouldBe` "function(){}"
108108
minifyExpr " function ( a ) { } " `shouldBe` "function(a){}"
109109
minifyExpr " function ( a , b ) { return a + b ; } " `shouldBe` "function(a,b){return a+b}"
110+
minifyExpr " function ( a , ...b ) { return b ; } " `shouldBe` "function(a,...b){return b}"
111+
minifyExpr " function ( a = 1 , b = 2 ) { return a + b ; } " `shouldBe` "function(a=1,b=2){return a+b}"
112+
minifyExpr " function ( [ a , b ] ) { return b ; } " `shouldBe` "function([a,b]){return b}"
113+
minifyExpr " function ( { a , b , } ) { return a + b ; } " `shouldBe` "function({a,b}){return a+b}"
110114

111115
minifyExpr "a => {}" `shouldBe` "a=>{}"
112116
minifyExpr "(a) => {}" `shouldBe` "(a)=>{}"
113117
minifyExpr "( a ) => { a + 2 }" `shouldBe` "(a)=>a+2"
114118
minifyExpr "(a, b) => a + b" `shouldBe` "(a,b)=>a+b"
115119
minifyExpr "() => { 42 }" `shouldBe` "()=>42"
120+
minifyExpr "(a, ...b) => b" `shouldBe` "(a,...b)=>b"
121+
minifyExpr "(a = 1, b = 2) => a + b" `shouldBe` "(a=1,b=2)=>a+b"
122+
minifyExpr "( [ a , b ] ) => a + b" `shouldBe` "([a,b])=>a+b"
123+
minifyExpr "( { a , b , } ) => a + b" `shouldBe` "({a,b})=>a+b"
116124

117125
it "calls" $ do
118126
minifyExpr " a ( ) " `shouldBe` "a()"
@@ -123,6 +131,7 @@ testMinifyExpr = describe "Minify expressions:" $ do
123131
it "property accessor" $ do
124132
minifyExpr " { get foo ( ) { return x } } " `shouldBe` "{get foo(){return x}}"
125133
minifyExpr " { set foo ( a ) { x = a } } " `shouldBe` "{set foo(a){x=a}}"
134+
minifyExpr " { set foo ( [ a , b ] ) { x = a } } " `shouldBe` "{set foo([a,b]){x=a}}"
126135

127136
it "string concatenation" $ do
128137
minifyExpr " 'ab' + \"cd\" " `shouldBe` "'abcd'"
@@ -203,6 +212,10 @@ testMinifyStmt = describe "Minify statements:" $ do
203212
minifyStmt " function f ( ) { } ; " `shouldBe` "function f(){}"
204213
minifyStmt " function f ( a ) { } ; " `shouldBe` "function f(a){}"
205214
minifyStmt " function f ( a , b ) { return a + b ; } ; " `shouldBe` "function f(a,b){return a+b}"
215+
minifyStmt " function f ( a , ... b ) { return b ; } ; " `shouldBe` "function f(a,...b){return b}"
216+
minifyStmt " function f ( a = 1 , b = 2 ) { return a + b ; } ; " `shouldBe` "function f(a=1,b=2){return a+b}"
217+
minifyStmt " function f ( [ a , b ] ) { return a + b ; } ; " `shouldBe` "function f([a,b]){return a+b}"
218+
minifyStmt " function f ( { a , b , } ) { return a + b ; } ; " `shouldBe` "function f({a,b}){return a+b}"
206219

207220
it "with" $ do
208221
minifyStmt " with ( x ) { } ; " `shouldBe` "with(x){}"

test/Test/Language/Javascript/RoundTrip.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,23 @@ testRoundTrip = describe "Roundtrip:" $ do
6464
testRT "/*a*/x/*b*/>=/*c*/y"
6565
testRT "/*a*/x /*b*/instanceof /*c*/y"
6666
testRT "/*a*/x/*b*/=/*c*/{/*d*/get/*e*/ foo/*f*/(/*g*/)/*h*/ {/*i*/return/*j*/ 1/*k*/}/*l*/,/*m*/set/*n*/ foo/*o*/(/*p*/a/*q*/) /*r*/{/*s*/x/*t*/=/*u*/a/*v*/}/*w*/}"
67+
testRT "x = { set foo(/*a*/[/*b*/a/*c*/,/*d*/b/*e*/]/*f*/=/*g*/y/*h*/) {} }"
6768
testRT "... /*a*/ x"
6869

6970
testRT "a => {}"
7071
testRT "(a) => { a + 2 }"
7172
testRT "(a, b) => {}"
7273
testRT "(a, b) => a + b"
7374
testRT "() => { 42 }"
75+
testRT "(...a) => a"
76+
testRT "(a=1, b=2) => a + b"
77+
testRT "([a, b]) => a + b"
78+
testRT "({a, b}) => a + b"
79+
80+
testRT "function (...a) {}"
81+
testRT "function (a=1, b=2) {}"
82+
testRT "function ([a, ...b]) {}"
83+
testRT "function ({a, b: c}) {}"
7484

7585
testRT "/*a*/`<${/*b*/x/*c*/}>`/*d*/"
7686
testRT "`\\${}`"

test/Test/Language/Javascript/StatementParser.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ testStatementParser = describe "Parse statements:" $ do
110110
testStmt "try{}catch(a){}catch(b){}" `shouldBe` "Right (JSAstStatement (JSTry (JSBlock [],[JSCatch (JSIdentifier 'a',JSBlock []),JSCatch (JSIdentifier 'b',JSBlock [])],JSFinally ())))"
111111
testStmt "try{}catch(a if true){}catch(b){}" `shouldBe` "Right (JSAstStatement (JSTry (JSBlock [],[JSCatch (JSIdentifier 'a') if JSLiteral 'true' (JSBlock []),JSCatch (JSIdentifier 'b',JSBlock [])],JSFinally ())))"
112112

113+
it "function" $ do
114+
testStmt "function x(){}" `shouldBe` "Right (JSAstStatement (JSFunction 'x' () (JSBlock [])))"
115+
testStmt "function x(a){}" `shouldBe` "Right (JSAstStatement (JSFunction 'x' (JSIdentifier 'a') (JSBlock [])))"
116+
testStmt "function x(a,b){}" `shouldBe` "Right (JSAstStatement (JSFunction 'x' (JSIdentifier 'a',JSIdentifier 'b') (JSBlock [])))"
117+
testStmt "function x(...a){}" `shouldBe` "Right (JSAstStatement (JSFunction 'x' (JSSpreadExpression (JSIdentifier 'a')) (JSBlock [])))"
118+
testStmt "function x(a=1){}" `shouldBe` "Right (JSAstStatement (JSFunction 'x' (JSOpAssign ('=',JSIdentifier 'a',JSDecimal '1')) (JSBlock [])))"
119+
testStmt "function x([a]){}" `shouldBe` "Right (JSAstStatement (JSFunction 'x' (JSArrayLiteral [JSIdentifier 'a']) (JSBlock [])))"
120+
testStmt "function x({a}){}" `shouldBe` "Right (JSAstStatement (JSFunction 'x' (JSObjectLiteral [JSPropertyIdentRef 'a']) (JSBlock [])))"
121+
113122

114123
testStmt :: String -> String
115124
testStmt str = showStrippedMaybe (parseUsing parseStatement str "src")

0 commit comments

Comments
 (0)