Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Language/JavaScript/Parser/AST.hs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ data JSVarInitializer
data JSObjectProperty
= JSPropertyAccessor !JSAccessor !JSPropertyName !JSAnnot ![JSExpression] !JSAnnot !JSBlock -- ^(get|set), name, lb, params, rb, block
| JSPropertyNameandValue !JSPropertyName !JSAnnot ![JSExpression] -- ^name, colon, value
| JSPropertyIdentRef !JSAnnot !String
deriving (Data, Eq, Show, Typeable)

data JSPropertyName
Expand Down Expand Up @@ -457,6 +458,7 @@ instance ShowStripped JSIdent where
instance ShowStripped JSObjectProperty where
ss (JSPropertyNameandValue x1 _colon x2s) = "JSPropertyNameandValue (" ++ ss x1 ++ ") " ++ ss x2s
ss (JSPropertyAccessor s x1 _lb1 x2s _rb1 x3) = "JSPropertyAccessor " ++ ss s ++ " (" ++ ss x1 ++ ") " ++ ss x2s ++ " (" ++ ss x3 ++ ")"
ss (JSPropertyIdentRef _ s) = "JSPropertyIdentRef " ++ singleQuote s

instance ShowStripped JSPropertyName where
ss (JSPropertyIdent _ s) = "JSIdentifier " ++ singleQuote s
Expand Down
99 changes: 53 additions & 46 deletions src/Language/JavaScript/Parser/Grammar7.y
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,54 @@ OpAssign : '*=' { AST.JSTimesAssign (mkJSAnnot $1) }
| '^=' { AST.JSBwXorAssign (mkJSAnnot $1) }
| '|=' { AST.JSBwOrAssign (mkJSAnnot $1) }

-- IdentifierName :: See 7.6
-- IdentifierStart
-- IdentifierName IdentifierPart
-- Note: This production needs to precede the productions for all keyword
-- statements and PrimaryExpression. Contra the Happy documentation, in the
-- case of a reduce/reduce conflict, the *later* rule takes precedence, and
-- the ambiguity of, for example, `{break}` needs to resolve in favor of
-- `break` as a keyword and not as an identifier in property shorthand
-- syntax.
-- TODO: make this include any reserved word too, including future ones
IdentifierName :: { AST.JSExpression }
IdentifierName : Identifier {$1}
| 'break' { AST.JSIdentifier (mkJSAnnot $1) "break" }
| 'case' { AST.JSIdentifier (mkJSAnnot $1) "case" }
| 'catch' { AST.JSIdentifier (mkJSAnnot $1) "catch" }
| 'const' { AST.JSIdentifier (mkJSAnnot $1) "const" }
| 'continue' { AST.JSIdentifier (mkJSAnnot $1) "continue" }
| 'debugger' { AST.JSIdentifier (mkJSAnnot $1) "debugger" }
| 'default' { AST.JSIdentifier (mkJSAnnot $1) "default" }
| 'delete' { AST.JSIdentifier (mkJSAnnot $1) "delete" }
| 'do' { AST.JSIdentifier (mkJSAnnot $1) "do" }
| 'else' { AST.JSIdentifier (mkJSAnnot $1) "else" }
| 'enum' { AST.JSIdentifier (mkJSAnnot $1) "enum" }
| 'export' { AST.JSIdentifier (mkJSAnnot $1) "export" }
| 'false' { AST.JSIdentifier (mkJSAnnot $1) "false" }
| 'finally' { AST.JSIdentifier (mkJSAnnot $1) "finally" }
| 'for' { AST.JSIdentifier (mkJSAnnot $1) "for" }
| 'function' { AST.JSIdentifier (mkJSAnnot $1) "function" }
| 'if' { AST.JSIdentifier (mkJSAnnot $1) "if" }
| 'in' { AST.JSIdentifier (mkJSAnnot $1) "in" }
| 'instanceof' { AST.JSIdentifier (mkJSAnnot $1) "instanceof" }
| 'let' { AST.JSIdentifier (mkJSAnnot $1) "let" }
| 'new' { AST.JSIdentifier (mkJSAnnot $1) "new" }
| 'null' { AST.JSIdentifier (mkJSAnnot $1) "null" }
| 'of' { AST.JSIdentifier (mkJSAnnot $1) "of" }
| 'return' { AST.JSIdentifier (mkJSAnnot $1) "return" }
| 'switch' { AST.JSIdentifier (mkJSAnnot $1) "switch" }
| 'this' { AST.JSIdentifier (mkJSAnnot $1) "this" }
| 'throw' { AST.JSIdentifier (mkJSAnnot $1) "throw" }
| 'true' { AST.JSIdentifier (mkJSAnnot $1) "true" }
| 'try' { AST.JSIdentifier (mkJSAnnot $1) "try" }
| 'typeof' { AST.JSIdentifier (mkJSAnnot $1) "typeof" }
| 'var' { AST.JSIdentifier (mkJSAnnot $1) "var" }
| 'void' { AST.JSIdentifier (mkJSAnnot $1) "void" }
| 'while' { AST.JSIdentifier (mkJSAnnot $1) "while" }
| 'with' { AST.JSIdentifier (mkJSAnnot $1) "with" }
| 'future' { AST.JSIdentifier (mkJSAnnot $1) (tokenLiteral $1) }

Var :: { AST.JSAnnot }
Var : 'var' { mkJSAnnot $1 }

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

-- Identifier :: See 7.6
-- IdentifierName but not ReservedWord
-- IdentifierName :: See 7.6
-- IdentifierStart
-- IdentifierName IdentifierPart
Identifier :: { AST.JSExpression }
Identifier : 'ident' { AST.JSIdentifier (mkJSAnnot $1) (tokenLiteral $1) }
| 'as' { AST.JSIdentifier (mkJSAnnot $1) "as" }
| 'get' { AST.JSIdentifier (mkJSAnnot $1) "get" }
| 'set' { AST.JSIdentifier (mkJSAnnot $1) "set" }
| 'from' { AST.JSIdentifier (mkJSAnnot $1) "from" }

-- TODO: make this include any reserved word too, including future ones
IdentifierName :: { AST.JSExpression }
IdentifierName : Identifier {$1}
| 'as' { AST.JSIdentifier (mkJSAnnot $1) "as" }
| 'break' { AST.JSIdentifier (mkJSAnnot $1) "break" }
| 'case' { AST.JSIdentifier (mkJSAnnot $1) "case" }
| 'catch' { AST.JSIdentifier (mkJSAnnot $1) "catch" }
| 'const' { AST.JSIdentifier (mkJSAnnot $1) "const" }
| 'continue' { AST.JSIdentifier (mkJSAnnot $1) "continue" }
| 'debugger' { AST.JSIdentifier (mkJSAnnot $1) "debugger" }
| 'default' { AST.JSIdentifier (mkJSAnnot $1) "default" }
| 'delete' { AST.JSIdentifier (mkJSAnnot $1) "delete" }
| 'do' { AST.JSIdentifier (mkJSAnnot $1) "do" }
| 'else' { AST.JSIdentifier (mkJSAnnot $1) "else" }
| 'enum' { AST.JSIdentifier (mkJSAnnot $1) "enum" }
| 'export' { AST.JSIdentifier (mkJSAnnot $1) "export" }
| 'false' { AST.JSIdentifier (mkJSAnnot $1) "false" }
| 'finally' { AST.JSIdentifier (mkJSAnnot $1) "finally" }
| 'for' { AST.JSIdentifier (mkJSAnnot $1) "for" }
| 'function' { AST.JSIdentifier (mkJSAnnot $1) "function" }
| 'from' { AST.JSIdentifier (mkJSAnnot $1) "from" }
| 'get' { AST.JSIdentifier (mkJSAnnot $1) "get" }
| 'if' { AST.JSIdentifier (mkJSAnnot $1) "if" }
| 'in' { AST.JSIdentifier (mkJSAnnot $1) "in" }
| 'instanceof' { AST.JSIdentifier (mkJSAnnot $1) "instanceof" }
| 'let' { AST.JSIdentifier (mkJSAnnot $1) "let" }
| 'new' { AST.JSIdentifier (mkJSAnnot $1) "new" }
| 'null' { AST.JSIdentifier (mkJSAnnot $1) "null" }
| 'of' { AST.JSIdentifier (mkJSAnnot $1) "of" }
| 'return' { AST.JSIdentifier (mkJSAnnot $1) "return" }
| 'set' { AST.JSIdentifier (mkJSAnnot $1) "set" }
| 'switch' { AST.JSIdentifier (mkJSAnnot $1) "switch" }
| 'this' { AST.JSIdentifier (mkJSAnnot $1) "this" }
| 'throw' { AST.JSIdentifier (mkJSAnnot $1) "throw" }
| 'true' { AST.JSIdentifier (mkJSAnnot $1) "true" }
| 'try' { AST.JSIdentifier (mkJSAnnot $1) "try" }
| 'typeof' { AST.JSIdentifier (mkJSAnnot $1) "typeof" }
| 'var' { AST.JSIdentifier (mkJSAnnot $1) "var" }
| 'void' { AST.JSIdentifier (mkJSAnnot $1) "void" }
| 'while' { AST.JSIdentifier (mkJSAnnot $1) "while" }
| 'with' { AST.JSIdentifier (mkJSAnnot $1) "with" }
| 'future' { AST.JSIdentifier (mkJSAnnot $1) (tokenLiteral $1) }


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

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

}
1 change: 1 addition & 0 deletions src/Language/JavaScript/Pretty/Printer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ instance RenderJS JSBlock where
instance RenderJS JSObjectProperty where
(|>) pacc (JSPropertyAccessor s n alp ps arp b) = pacc |> s |> n |> alp |> "(" |> ps |> arp |> ")" |> b
(|>) pacc (JSPropertyNameandValue n c vs) = pacc |> n |> c |> ":" |> vs
(|>) pacc (JSPropertyIdentRef a s) = pacc |> a |> s

instance RenderJS JSPropertyName where
(|>) pacc (JSPropertyIdent a s) = pacc |> a |> s
Expand Down
1 change: 1 addition & 0 deletions src/Language/JavaScript/Process/Minify.hs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ instance MinifyJS JSBlock where
instance MinifyJS JSObjectProperty where
fix a (JSPropertyAccessor s n _ ps _ b) = JSPropertyAccessor (fix a s) (fixSpace n) emptyAnnot (map fixEmpty ps) emptyAnnot (fixEmpty b)
fix a (JSPropertyNameandValue n _ vs) = JSPropertyNameandValue (fix a n) emptyAnnot (map fixEmpty vs)
fix a (JSPropertyIdentRef _ s) = JSPropertyIdentRef a s

instance MinifyJS JSPropertyName where
fix a (JSPropertyIdent _ s) = JSPropertyIdent a s
Expand Down
2 changes: 2 additions & 0 deletions test/Test/Language/Javascript/ExpressionParser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ testExpressionParser = describe "Parse expressions:" $ do
testExpr "{x:1}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'x') [JSDecimal '1']]))"
testExpr "{x:1,y:2}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'x') [JSDecimal '1'],JSPropertyNameandValue (JSIdentifier 'y') [JSDecimal '2']]))"
testExpr "{x:1,}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'x') [JSDecimal '1'],JSComma]))"
testExpr "{x}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyIdentRef 'x']))"
testExpr "{x,}" `shouldBe` "Right (JSAstExpression (JSObjectLiteral [JSPropertyIdentRef 'x',JSComma]))"
testExpr "a={if:1,interface:2}" `shouldBe` "Right (JSAstExpression (JSOpAssign ('=',JSIdentifier 'a',JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'if') [JSDecimal '1'],JSPropertyNameandValue (JSIdentifier 'interface') [JSDecimal '2']])))"
testExpr "a={\n values: 7,\n}\n" `shouldBe` "Right (JSAstExpression (JSOpAssign ('=',JSIdentifier 'a',JSObjectLiteral [JSPropertyNameandValue (JSIdentifier 'values') [JSDecimal '7'],JSComma])))"
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')])])))"
Expand Down
1 change: 1 addition & 0 deletions test/Test/Language/Javascript/Minify.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ testMinifyExpr = describe "Minify expressions:" $ do
minifyExpr " { b : 2 , } " `shouldBe` "{b:2}"
minifyExpr " { c : 3 , d : 4 , } " `shouldBe` "{c:3,d:4}"
minifyExpr " { 'str' : true , 42 : false , } " `shouldBe` "{'str':true,42:false}"
minifyExpr " { x , } " `shouldBe` "{x}"

it "parentheses" $ do
minifyExpr " ( 'hello' ) " `shouldBe` "('hello')"
Expand Down
2 changes: 2 additions & 0 deletions test/Test/Language/Javascript/RoundTrip.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ testRoundTrip = describe "Roundtrip:" $ do
it "object literals" $ do
testRT "/*a*/{/*b*/}"
testRT "/*a*/{/*b*/x/*c*/:/*d*/1/*e*/}"
testRT "/*a*/{/*b*/x/*c*/}"
testRT "/*a*/{/*b*/of/*c*/}"
testRT "x=/*a*/{/*b*/x/*c*/:/*d*/1/*e*/,/*f*/y/*g*/:/*h*/2/*i*/}"
testRT "x=/*a*/{/*b*/x/*c*/:/*d*/1/*e*/,/*f*/y/*g*/:/*h*/2/*i*/,/*j*/z/*k*/:/*l*/3/*m*/}"
testRT "a=/*a*/{/*b*/x/*c*/:/*d*/1/*e*/,/*f*/}"
Expand Down