Skip to content

Commit 7d2c6d9

Browse files
rhendricerikd
authored andcommitted
Add support for ES6 property shorthand syntax
1 parent 66fea68 commit 7d2c6d9

File tree

7 files changed

+62
-46
lines changed

7 files changed

+62
-46
lines changed

src/Language/JavaScript/Parser/AST.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ data JSVarInitializer
283283
data JSObjectProperty
284284
= JSPropertyAccessor !JSAccessor !JSPropertyName !JSAnnot ![JSExpression] !JSAnnot !JSBlock -- ^(get|set), name, lb, params, rb, block
285285
| JSPropertyNameandValue !JSPropertyName !JSAnnot ![JSExpression] -- ^name, colon, value
286+
| JSPropertyIdentRef !JSAnnot !String
286287
deriving (Data, Eq, Show, Typeable)
287288

288289
data JSPropertyName
@@ -463,6 +464,7 @@ instance ShowStripped JSIdent where
463464
instance ShowStripped JSObjectProperty where
464465
ss (JSPropertyNameandValue x1 _colon x2s) = "JSPropertyNameandValue (" ++ ss x1 ++ ") " ++ ss x2s
465466
ss (JSPropertyAccessor s x1 _lb1 x2s _rb1 x3) = "JSPropertyAccessor " ++ ss s ++ " (" ++ ss x1 ++ ") " ++ ss x2s ++ " (" ++ ss x3 ++ ")"
467+
ss (JSPropertyIdentRef _ s) = "JSPropertyIdentRef " ++ singleQuote s
466468

467469
instance ShowStripped JSPropertyName where
468470
ss (JSPropertyIdent _ s) = "JSIdentifier " ++ singleQuote s

src/Language/JavaScript/Parser/Grammar7.y

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,54 @@ OpAssign : '*=' { AST.JSTimesAssign (mkJSAnnot $1) }
309309
| '^=' { AST.JSBwXorAssign (mkJSAnnot $1) }
310310
| '|=' { AST.JSBwOrAssign (mkJSAnnot $1) }
311311

312+
-- IdentifierName :: See 7.6
313+
-- IdentifierStart
314+
-- IdentifierName IdentifierPart
315+
-- Note: This production needs to precede the productions for all keyword
316+
-- statements and PrimaryExpression. Contra the Happy documentation, in the
317+
-- case of a reduce/reduce conflict, the *later* rule takes precedence, and
318+
-- the ambiguity of, for example, `{break}` needs to resolve in favor of
319+
-- `break` as a keyword and not as an identifier in property shorthand
320+
-- syntax.
321+
-- TODO: make this include any reserved word too, including future ones
322+
IdentifierName :: { AST.JSExpression }
323+
IdentifierName : Identifier {$1}
324+
| 'break' { AST.JSIdentifier (mkJSAnnot $1) "break" }
325+
| 'case' { AST.JSIdentifier (mkJSAnnot $1) "case" }
326+
| 'catch' { AST.JSIdentifier (mkJSAnnot $1) "catch" }
327+
| 'const' { AST.JSIdentifier (mkJSAnnot $1) "const" }
328+
| 'continue' { AST.JSIdentifier (mkJSAnnot $1) "continue" }
329+
| 'debugger' { AST.JSIdentifier (mkJSAnnot $1) "debugger" }
330+
| 'default' { AST.JSIdentifier (mkJSAnnot $1) "default" }
331+
| 'delete' { AST.JSIdentifier (mkJSAnnot $1) "delete" }
332+
| 'do' { AST.JSIdentifier (mkJSAnnot $1) "do" }
333+
| 'else' { AST.JSIdentifier (mkJSAnnot $1) "else" }
334+
| 'enum' { AST.JSIdentifier (mkJSAnnot $1) "enum" }
335+
| 'export' { AST.JSIdentifier (mkJSAnnot $1) "export" }
336+
| 'false' { AST.JSIdentifier (mkJSAnnot $1) "false" }
337+
| 'finally' { AST.JSIdentifier (mkJSAnnot $1) "finally" }
338+
| 'for' { AST.JSIdentifier (mkJSAnnot $1) "for" }
339+
| 'function' { AST.JSIdentifier (mkJSAnnot $1) "function" }
340+
| 'if' { AST.JSIdentifier (mkJSAnnot $1) "if" }
341+
| 'in' { AST.JSIdentifier (mkJSAnnot $1) "in" }
342+
| 'instanceof' { AST.JSIdentifier (mkJSAnnot $1) "instanceof" }
343+
| 'let' { AST.JSIdentifier (mkJSAnnot $1) "let" }
344+
| 'new' { AST.JSIdentifier (mkJSAnnot $1) "new" }
345+
| 'null' { AST.JSIdentifier (mkJSAnnot $1) "null" }
346+
| 'of' { AST.JSIdentifier (mkJSAnnot $1) "of" }
347+
| 'return' { AST.JSIdentifier (mkJSAnnot $1) "return" }
348+
| 'switch' { AST.JSIdentifier (mkJSAnnot $1) "switch" }
349+
| 'this' { AST.JSIdentifier (mkJSAnnot $1) "this" }
350+
| 'throw' { AST.JSIdentifier (mkJSAnnot $1) "throw" }
351+
| 'true' { AST.JSIdentifier (mkJSAnnot $1) "true" }
352+
| 'try' { AST.JSIdentifier (mkJSAnnot $1) "try" }
353+
| 'typeof' { AST.JSIdentifier (mkJSAnnot $1) "typeof" }
354+
| 'var' { AST.JSIdentifier (mkJSAnnot $1) "var" }
355+
| 'void' { AST.JSIdentifier (mkJSAnnot $1) "void" }
356+
| 'while' { AST.JSIdentifier (mkJSAnnot $1) "while" }
357+
| 'with' { AST.JSIdentifier (mkJSAnnot $1) "with" }
358+
| 'future' { AST.JSIdentifier (mkJSAnnot $1) (tokenLiteral $1) }
359+
312360
Var :: { AST.JSAnnot }
313361
Var : 'var' { mkJSAnnot $1 }
314362

@@ -437,59 +485,13 @@ PrimaryExpression : 'this' { AST.JSLiteral (mkJSAnnot $1) "thi
437485

438486
-- Identifier :: See 7.6
439487
-- IdentifierName but not ReservedWord
440-
-- IdentifierName :: See 7.6
441-
-- IdentifierStart
442-
-- IdentifierName IdentifierPart
443488
Identifier :: { AST.JSExpression }
444489
Identifier : 'ident' { AST.JSIdentifier (mkJSAnnot $1) (tokenLiteral $1) }
445490
| 'as' { AST.JSIdentifier (mkJSAnnot $1) "as" }
446491
| 'get' { AST.JSIdentifier (mkJSAnnot $1) "get" }
447492
| 'set' { AST.JSIdentifier (mkJSAnnot $1) "set" }
448493
| 'from' { AST.JSIdentifier (mkJSAnnot $1) "from" }
449494

450-
-- TODO: make this include any reserved word too, including future ones
451-
IdentifierName :: { AST.JSExpression }
452-
IdentifierName : Identifier {$1}
453-
| 'as' { AST.JSIdentifier (mkJSAnnot $1) "as" }
454-
| 'break' { AST.JSIdentifier (mkJSAnnot $1) "break" }
455-
| 'case' { AST.JSIdentifier (mkJSAnnot $1) "case" }
456-
| 'catch' { AST.JSIdentifier (mkJSAnnot $1) "catch" }
457-
| 'const' { AST.JSIdentifier (mkJSAnnot $1) "const" }
458-
| 'continue' { AST.JSIdentifier (mkJSAnnot $1) "continue" }
459-
| 'debugger' { AST.JSIdentifier (mkJSAnnot $1) "debugger" }
460-
| 'default' { AST.JSIdentifier (mkJSAnnot $1) "default" }
461-
| 'delete' { AST.JSIdentifier (mkJSAnnot $1) "delete" }
462-
| 'do' { AST.JSIdentifier (mkJSAnnot $1) "do" }
463-
| 'else' { AST.JSIdentifier (mkJSAnnot $1) "else" }
464-
| 'enum' { AST.JSIdentifier (mkJSAnnot $1) "enum" }
465-
| 'export' { AST.JSIdentifier (mkJSAnnot $1) "export" }
466-
| 'false' { AST.JSIdentifier (mkJSAnnot $1) "false" }
467-
| 'finally' { AST.JSIdentifier (mkJSAnnot $1) "finally" }
468-
| 'for' { AST.JSIdentifier (mkJSAnnot $1) "for" }
469-
| 'function' { AST.JSIdentifier (mkJSAnnot $1) "function" }
470-
| 'from' { AST.JSIdentifier (mkJSAnnot $1) "from" }
471-
| 'get' { AST.JSIdentifier (mkJSAnnot $1) "get" }
472-
| 'if' { AST.JSIdentifier (mkJSAnnot $1) "if" }
473-
| 'in' { AST.JSIdentifier (mkJSAnnot $1) "in" }
474-
| 'instanceof' { AST.JSIdentifier (mkJSAnnot $1) "instanceof" }
475-
| 'let' { AST.JSIdentifier (mkJSAnnot $1) "let" }
476-
| 'new' { AST.JSIdentifier (mkJSAnnot $1) "new" }
477-
| 'null' { AST.JSIdentifier (mkJSAnnot $1) "null" }
478-
| 'of' { AST.JSIdentifier (mkJSAnnot $1) "of" }
479-
| 'return' { AST.JSIdentifier (mkJSAnnot $1) "return" }
480-
| 'set' { AST.JSIdentifier (mkJSAnnot $1) "set" }
481-
| 'switch' { AST.JSIdentifier (mkJSAnnot $1) "switch" }
482-
| 'this' { AST.JSIdentifier (mkJSAnnot $1) "this" }
483-
| 'throw' { AST.JSIdentifier (mkJSAnnot $1) "throw" }
484-
| 'true' { AST.JSIdentifier (mkJSAnnot $1) "true" }
485-
| 'try' { AST.JSIdentifier (mkJSAnnot $1) "try" }
486-
| 'typeof' { AST.JSIdentifier (mkJSAnnot $1) "typeof" }
487-
| 'var' { AST.JSIdentifier (mkJSAnnot $1) "var" }
488-
| 'void' { AST.JSIdentifier (mkJSAnnot $1) "void" }
489-
| 'while' { AST.JSIdentifier (mkJSAnnot $1) "while" }
490-
| 'with' { AST.JSIdentifier (mkJSAnnot $1) "with" }
491-
| 'future' { AST.JSIdentifier (mkJSAnnot $1) (tokenLiteral $1) }
492-
493495

494496
SpreadExpression :: { AST.JSExpression }
495497
SpreadExpression : Spread Expression { AST.JSSpreadExpression $1 $2 {- 'SpreadExpression' -} }
@@ -548,6 +550,7 @@ PropertyNameandValueList : PropertyAssignment { A
548550
-- TODO: not clear if get/set are keywords, or just used in a specific context. Puzzling.
549551
PropertyAssignment :: { AST.JSObjectProperty }
550552
PropertyAssignment : PropertyName Colon AssignmentExpression { AST.JSPropertyNameandValue $1 $2 [$3] }
553+
| IdentifierName { identifierToProperty $1 }
551554
-- Should be "get" in next, but is not a Token
552555
| 'get' PropertyName LParen RParen FunctionBody
553556
{ AST.JSPropertyAccessor (AST.JSAccessorGet (mkJSAnnot $1)) $2 $3 [] $4 $5 }
@@ -1387,4 +1390,8 @@ propName (AST.JSOctal a s) = AST.JSPropertyNumber a s
13871390
propName (AST.JSStringLiteral a s) = AST.JSPropertyString a s
13881391
propName x = error $ "Cannot convert '" ++ show x ++ "' to a JSPropertyName."
13891392

1393+
identifierToProperty :: AST.JSExpression -> AST.JSObjectProperty
1394+
identifierToProperty (AST.JSIdentifier a s) = AST.JSPropertyIdentRef a s
1395+
identifierToProperty x = error $ "Cannot convert '" ++ show x ++ "' to a JSObjectProperty."
1396+
13901397
}

src/Language/JavaScript/Pretty/Printer.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ instance RenderJS JSBlock where
269269
instance RenderJS JSObjectProperty where
270270
(|>) pacc (JSPropertyAccessor s n alp ps arp b) = pacc |> s |> n |> alp |> "(" |> ps |> arp |> ")" |> b
271271
(|>) pacc (JSPropertyNameandValue n c vs) = pacc |> n |> c |> ":" |> vs
272+
(|>) pacc (JSPropertyIdentRef a s) = pacc |> a |> s
272273

273274
instance RenderJS JSPropertyName where
274275
(|>) pacc (JSPropertyIdent a s) = pacc |> a |> s

src/Language/JavaScript/Process/Minify.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ instance MinifyJS JSBlock where
357357
instance MinifyJS JSObjectProperty where
358358
fix a (JSPropertyAccessor s n _ ps _ b) = JSPropertyAccessor (fix a s) (fixSpace n) emptyAnnot (map fixEmpty ps) emptyAnnot (fixEmpty b)
359359
fix a (JSPropertyNameandValue n _ vs) = JSPropertyNameandValue (fix a n) emptyAnnot (map fixEmpty vs)
360+
fix a (JSPropertyIdentRef _ s) = JSPropertyIdentRef a s
360361

361362
instance MinifyJS JSPropertyName where
362363
fix a (JSPropertyIdent _ s) = JSPropertyIdent a s

test/Test/Language/Javascript/ExpressionParser.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ testExpressionParser = describe "Parse expressions:" $ do
5151
testExpr "{x:1}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'x') [JSDecimal '1']]))"
5252
testExpr "{x:1,y:2}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'x') [JSDecimal '1'],JSPropertyNameandValue (JSIdentifier 'y') [JSDecimal '2']]))"
5353
testExpr "{x:1,}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'x') [JSDecimal '1'],JSComma]))"
54+
testExpr "{x}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyIdentRef 'x']))"
55+
testExpr "{x,}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyIdentRef 'x',JSComma]))"
5456
testExpr "a={if:1,interface:2}" `shouldBe` "Right (JSAstExpression (JSOpAssign ('=',JSIdentifier 'a',JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'if') [JSDecimal '1'],JSPropertyNameandValue (JSIdentifier 'interface') [JSDecimal '2']])))"
5557
testExpr "a={\n values: 7,\n}\n" `shouldBe` "Right (JSAstExpression (JSOpAssign ('=',JSIdentifier 'a',JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'values') [JSDecimal '7'],JSComma])))"
5658
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')])])))"

test/Test/Language/Javascript/Minify.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ testMinifyExpr = describe "Minify expressions:" $ do
4141
minifyExpr " { b : 2 , } " `shouldBe` "{b:2}"
4242
minifyExpr " { c : 3 , d : 4 , } " `shouldBe` "{c:3,d:4}"
4343
minifyExpr " { 'str' : true , 42 : false , } " `shouldBe` "{'str':true,42:false}"
44+
minifyExpr " { x , } " `shouldBe` "{x}"
4445

4546
it "parentheses" $ do
4647
minifyExpr " ( 'hello' ) " `shouldBe` "('hello')"

test/Test/Language/Javascript/RoundTrip.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ testRoundTrip = describe "Roundtrip:" $ do
3939
it "object literals" $ do
4040
testRT "/*a*/{/*b*/}"
4141
testRT "/*a*/{/*b*/x/*c*/:/*d*/1/*e*/}"
42+
testRT "/*a*/{/*b*/x/*c*/}"
43+
testRT "/*a*/{/*b*/of/*c*/}"
4244
testRT "x=/*a*/{/*b*/x/*c*/:/*d*/1/*e*/,/*f*/y/*g*/:/*h*/2/*i*/}"
4345
testRT "x=/*a*/{/*b*/x/*c*/:/*d*/1/*e*/,/*f*/y/*g*/:/*h*/2/*i*/,/*j*/z/*k*/:/*l*/3/*m*/}"
4446
testRT "a=/*a*/{/*b*/x/*c*/:/*d*/1/*e*/,/*f*/}"

0 commit comments

Comments
 (0)