diff --git a/src/Language/JavaScript/Parser/AST.hs b/src/Language/JavaScript/Parser/AST.hs index 6338a73..c307552 100644 --- a/src/Language/JavaScript/Parser/AST.hs +++ b/src/Language/JavaScript/Parser/AST.hs @@ -25,6 +25,8 @@ module Language.JavaScript.Parser.AST , JSCommaTrailingList (..) , JSArrowParameterList (..) , JSTemplatePart (..) + , JSClassHeritage (..) + , JSClassElement (..) -- Modules , JSModuleItem (..) @@ -128,6 +130,7 @@ data JSStatement = JSStatementBlock !JSAnnot ![JSStatement] !JSAnnot !JSSemi -- ^lbrace, stmts, rbrace, autosemi | JSBreak !JSAnnot !JSIdent !JSSemi -- ^break,optional identifier, autosemi | JSLet !JSAnnot !(JSCommaList JSExpression) !JSSemi -- ^const, decl, autosemi + | JSClass !JSAnnot !JSIdent !JSClassHeritage !JSAnnot ![JSClassElement] !JSAnnot !JSSemi -- ^class, name, optional extends clause, lb, body, rb, autosemi | JSConstant !JSAnnot !(JSCommaList JSExpression) !JSSemi -- ^const, decl, autosemi | JSContinue !JSAnnot !JSIdent !JSSemi -- ^continue, optional identifier,autosemi | JSDoWhile !JSAnnot !JSStatement !JSAnnot !JSAnnot !JSExpression !JSAnnot !JSSemi -- ^do,stmt,while,lb,expr,rb,autosemi @@ -177,6 +180,7 @@ data JSExpression | JSCallExpression !JSExpression !JSAnnot !(JSCommaList JSExpression) !JSAnnot -- ^expr, bl, args, rb | JSCallExpressionDot !JSExpression !JSAnnot !JSExpression -- ^expr, dot, expr | JSCallExpressionSquare !JSExpression !JSAnnot !JSExpression !JSAnnot -- ^expr, [, expr, ] + | JSClassExpression !JSAnnot !JSIdent !JSClassHeritage !JSAnnot ![JSClassElement] !JSAnnot -- ^class, optional identifier, optional extends clause, lb, body, rb | JSCommaExpression !JSExpression !JSAnnot !JSExpression -- ^expression components | JSExpressionBinary !JSExpression !JSBinOp !JSExpression -- ^lhs, op, rhs | JSExpressionParen !JSAnnot !JSExpression !JSAnnot -- ^lb,expression,rb @@ -339,6 +343,17 @@ data JSTemplatePart = JSTemplatePart !JSExpression !JSAnnot !String -- ^expr, rb, suffix deriving (Data, Eq, Show, Typeable) +data JSClassHeritage + = JSExtends !JSAnnot !JSExpression + | JSExtendsNone + deriving (Data, Eq, Show, Typeable) + +data JSClassElement + = JSClassInstanceMethod !JSMethodDefinition + | JSClassStaticMethod !JSAnnot !JSMethodDefinition + | JSClassSemi !JSAnnot + deriving (Data, Eq, Show, Typeable) + -- ----------------------------------------------------------------------------- -- | Show the AST elements stripped of their JSAnnot data. @@ -358,6 +373,7 @@ instance ShowStripped JSStatement where ss (JSStatementBlock _ xs _ _) = "JSStatementBlock " ++ ss xs ss (JSBreak _ JSIdentNone s) = "JSBreak" ++ commaIf (ss s) ss (JSBreak _ (JSIdentName _ n) s) = "JSBreak " ++ singleQuote n ++ commaIf (ss s) + ss (JSClass _ n h _lb xs _rb _) = "JSClass " ++ ssid n ++ " (" ++ ss h ++ ") " ++ ss xs ss (JSContinue _ JSIdentNone s) = "JSContinue" ++ commaIf (ss s) ss (JSContinue _ (JSIdentName _ n) s) = "JSContinue " ++ singleQuote n ++ commaIf (ss s) ss (JSConstant _ xs _as) = "JSConstant " ++ ss xs @@ -399,6 +415,7 @@ instance ShowStripped JSExpression where ss (JSCallExpression ex _ xs _) = "JSCallExpression ("++ ss ex ++ ",JSArguments " ++ ss xs ++ ")" ss (JSCallExpressionDot ex _os xs) = "JSCallExpressionDot (" ++ ss ex ++ "," ++ ss xs ++ ")" ss (JSCallExpressionSquare ex _os xs _cs) = "JSCallExpressionSquare (" ++ ss ex ++ "," ++ ss xs ++ ")" + ss (JSClassExpression _ n h _lb xs _rb) = "JSClassExpression " ++ ssid n ++ " (" ++ ss h ++ ") " ++ ss xs ss (JSDecimal _ s) = "JSDecimal " ++ singleQuote s ss (JSCommaExpression l _ r) = "JSExpression [" ++ ss l ++ "," ++ ss r ++ "]" ss (JSExpressionBinary x2 op x3) = "JSExpressionBinary (" ++ ss op ++ "," ++ ss x2 ++ "," ++ ss x3 ++ ")" @@ -580,6 +597,15 @@ instance ShowStripped JSArrayElement where instance ShowStripped JSTemplatePart where ss (JSTemplatePart e _ s) = "(" ++ ss e ++ "," ++ singleQuote s ++ ")" +instance ShowStripped JSClassHeritage where + ss JSExtendsNone = "" + ss (JSExtends _ x) = ss x + +instance ShowStripped JSClassElement where + ss (JSClassInstanceMethod m) = ss m + ss (JSClassStaticMethod _ m) = "JSClassStaticMethod (" ++ ss m ++ ")" + ss (JSClassSemi _) = "JSClassSemi" + instance ShowStripped a => ShowStripped (JSCommaList a) where ss xs = "(" ++ commaJoin (map ss $ fromCommaList xs) ++ ")" diff --git a/src/Language/JavaScript/Parser/Grammar7.y b/src/Language/JavaScript/Parser/Grammar7.y index 70a339c..bd82223 100644 --- a/src/Language/JavaScript/Parser/Grammar7.y +++ b/src/Language/JavaScript/Parser/Grammar7.y @@ -89,6 +89,7 @@ import qualified Language.JavaScript.Parser.AST as AST 'break' { BreakToken {} } 'case' { CaseToken {} } 'catch' { CatchToken {} } + 'class' { ClassToken {} } 'const' { ConstToken {} } 'continue' { ContinueToken {} } 'debugger' { DebuggerToken {} } @@ -98,6 +99,7 @@ import qualified Language.JavaScript.Parser.AST as AST 'else' { ElseToken {} } 'enum' { EnumToken {} } 'export' { ExportToken {} } + 'extends' { ExtendsToken {} } 'false' { FalseToken {} } 'finally' { FinallyToken {} } 'for' { ForToken {} } @@ -114,6 +116,8 @@ import qualified Language.JavaScript.Parser.AST as AST 'of' { OfToken {} } 'return' { ReturnToken {} } 'set' { SetToken {} } + 'static' { StaticToken {} } + 'super' { SuperToken {} } 'switch' { SwitchToken {} } 'this' { ThisToken {} } 'throw' { ThrowToken {} } @@ -330,6 +334,7 @@ IdentifierName : Identifier {$1} | 'break' { AST.JSIdentifier (mkJSAnnot $1) "break" } | 'case' { AST.JSIdentifier (mkJSAnnot $1) "case" } | 'catch' { AST.JSIdentifier (mkJSAnnot $1) "catch" } + | 'class' { AST.JSIdentifier (mkJSAnnot $1) "class" } | 'const' { AST.JSIdentifier (mkJSAnnot $1) "const" } | 'continue' { AST.JSIdentifier (mkJSAnnot $1) "continue" } | 'debugger' { AST.JSIdentifier (mkJSAnnot $1) "debugger" } @@ -339,6 +344,7 @@ IdentifierName : Identifier {$1} | 'else' { AST.JSIdentifier (mkJSAnnot $1) "else" } | 'enum' { AST.JSIdentifier (mkJSAnnot $1) "enum" } | 'export' { AST.JSIdentifier (mkJSAnnot $1) "export" } + | 'extends' { AST.JSIdentifier (mkJSAnnot $1) "extends" } | 'false' { AST.JSIdentifier (mkJSAnnot $1) "false" } | 'finally' { AST.JSIdentifier (mkJSAnnot $1) "finally" } | 'for' { AST.JSIdentifier (mkJSAnnot $1) "for" } @@ -351,6 +357,8 @@ IdentifierName : Identifier {$1} | 'null' { AST.JSIdentifier (mkJSAnnot $1) "null" } | 'of' { AST.JSIdentifier (mkJSAnnot $1) "of" } | 'return' { AST.JSIdentifier (mkJSAnnot $1) "return" } + | 'static' { AST.JSIdentifier (mkJSAnnot $1) "static" } + | 'super' { AST.JSIdentifier (mkJSAnnot $1) "super" } | 'switch' { AST.JSIdentifier (mkJSAnnot $1) "switch" } | 'this' { AST.JSIdentifier (mkJSAnnot $1) "this" } | 'throw' { AST.JSIdentifier (mkJSAnnot $1) "throw" } @@ -435,6 +443,18 @@ Function : 'function' { mkJSAnnot $1 {- 'Function' -} } New :: { AST.JSAnnot } New : 'new' { mkJSAnnot $1 } +Class :: { AST.JSAnnot } +Class : 'class' { mkJSAnnot $1 } + +Extends :: { AST.JSAnnot } +Extends : 'extends' { mkJSAnnot $1 } + +Static :: { AST.JSAnnot } +Static : 'static' { mkJSAnnot $1 } + +Super :: { AST.JSExpression } +Super : 'super' { AST.JSLiteral (mkJSAnnot $1) "super" } + Eof :: { AST.JSAnnot } Eof : 'tail' { mkJSAnnot $1 {- 'Eof' -} } @@ -486,6 +506,7 @@ PrimaryExpression : 'this' { AST.JSLiteral (mkJSAnnot $1) "thi | Literal { $1 {- 'PrimaryExpression2' -} } | ArrayLiteral { $1 {- 'PrimaryExpression3' -} } | ObjectLiteral { $1 {- 'PrimaryExpression4' -} } + | ClassExpression { $1 } | GeneratorExpression { $1 } | TemplateLiteral { mkJSTemplateLiteral Nothing $1 {- 'PrimaryExpression6' -} } | LParen Expression RParen { AST.JSExpressionParen $1 $2 $3 } @@ -625,6 +646,8 @@ MemberExpression : PrimaryExpression { $1 {- 'MemberExpression1' -} } | MemberExpression LSquare Expression RSquare { AST.JSMemberSquare $1 $2 $3 $4 {- 'MemberExpression3' -} } | MemberExpression Dot IdentifierName { AST.JSMemberDot $1 $2 $3 {- 'MemberExpression4' -} } | MemberExpression TemplateLiteral { mkJSTemplateLiteral (Just $1) $2 } + | Super LSquare Expression RSquare { AST.JSMemberSquare $1 $2 $3 $4 } + | Super Dot IdentifierName { AST.JSMemberDot $1 $2 $3 } | New MemberExpression Arguments { mkJSMemberNew $1 $2 $3 {- 'MemberExpression5' -} } -- NewExpression : See 11.2 @@ -642,6 +665,8 @@ NewExpression : MemberExpression { $1 {- 'NewExpressio CallExpression :: { AST.JSExpression } CallExpression : MemberExpression Arguments { mkJSMemberExpression $1 $2 {- 'CallExpression1' -} } + | Super Arguments + { mkJSCallExpression $1 $2 } | CallExpression Arguments { mkJSCallExpression $1 $2 {- 'CallExpression2' -} } | CallExpression LSquare Expression RSquare @@ -1272,6 +1297,44 @@ FormalParameterList : AssignmentExpression { AST.JSLOn FunctionBody :: { AST.JSBlock } FunctionBody : Block { $1 {- 'FunctionBody1' -} } +-- ClassDeclaration : +-- class BindingIdentifier ClassTail +-- class ClassTail +-- ClassExpression : +-- class BindingIdentifieropt ClassTail +-- ClassTail : +-- ClassHeritageopt { ClassBodyopt } +ClassDeclaration :: { AST.JSStatement } +ClassDeclaration : Class Identifier ClassHeritage LBrace ClassBody RBrace { AST.JSClass $1 (identName $2) $3 $4 $5 $6 AST.JSSemiAuto } + +ClassExpression :: { AST.JSExpression } +ClassExpression : Class Identifier ClassHeritage LBrace ClassBody RBrace { AST.JSClassExpression $1 (identName $2) $3 $4 $5 $6 } + | Class ClassHeritage LBrace ClassBody RBrace { AST.JSClassExpression $1 AST.JSIdentNone $2 $3 $4 $5 } + +-- ClassHeritage : +-- extends LeftHandSideExpression +ClassHeritage :: { AST.JSClassHeritage } +ClassHeritage : Extends LeftHandSideExpression { AST.JSExtends $1 $2 } + | { AST.JSExtendsNone } + +-- ClassBody : +-- ClassElementList +-- ClassElementList : +-- ClassElement +-- ClassElementList ClassElement +ClassBody :: { [AST.JSClassElement] } +ClassBody : { [] } + | ClassBody ClassElement { $1 ++ [$2] } + +-- ClassElement : +-- MethodDefinition +-- static MethodDefinition +-- ; +ClassElement :: { AST.JSClassElement } +ClassElement : MethodDefinition { AST.JSClassInstanceMethod $1 } + | Static MethodDefinition { AST.JSClassStaticMethod $1 $2 } + | Semi { AST.JSClassSemi $1 } + -- Program : See clause 14 -- SourceElementsopt @@ -1357,7 +1420,7 @@ ImportSpecifier : IdentifierName -- [ ] export Declaration -- [ ] Declaration : -- [ ] HoistableDeclaration --- [ ] ClassDeclaration +-- [x] ClassDeclaration -- [x] LexicalDeclaration -- [ ] HoistableDeclaration : -- [x] FunctionDeclaration @@ -1378,6 +1441,8 @@ ExportDeclaration : ExportClause FromClause AutoSemi { AST.JSExport $1 $2 {- 'ExportDeclaration4' -} } | GeneratorDeclaration AutoSemi { AST.JSExport $1 $2 {- 'ExportDeclaration5' -} } + | ClassDeclaration AutoSemi + { AST.JSExport $1 $2 {- 'ExportDeclaration6' -} } -- ExportClause : -- { } @@ -1431,6 +1496,7 @@ expressionToStatement (AST.JSFunctionExpression a b@(AST.JSIdentName{}) c d e f) expressionToStatement (AST.JSGeneratorExpression a b c@(AST.JSIdentName{}) d e f g) s = AST.JSGenerator a b c d e f g s expressionToStatement (AST.JSAssignExpression lhs op rhs) s = AST.JSAssignStatement lhs op rhs s expressionToStatement (AST.JSMemberExpression e l a r) s = AST.JSMethodCall e l a r s +expressionToStatement (AST.JSClassExpression a b@(AST.JSIdentName{}) c d e f) s = AST.JSClass a b c d e f s expressionToStatement exp s = AST.JSExpressionStatement exp s diff --git a/src/Language/JavaScript/Parser/Lexer.x b/src/Language/JavaScript/Parser/Lexer.x index d60f6c5..62d8e18 100644 --- a/src/Language/JavaScript/Parser/Lexer.x +++ b/src/Language/JavaScript/Parser/Lexer.x @@ -533,6 +533,7 @@ keywordNames = , ( "case", CaseToken ) , ( "catch", CatchToken ) + , ( "class", ClassToken ) , ( "const", ConstToken ) -- not a keyword, nominally a future reserved word, but actually in use , ( "continue", ContinueToken ) @@ -544,6 +545,7 @@ keywordNames = , ( "enum", EnumToken ) -- not a keyword, nominally a future reserved word, but actually in use , ( "export", ExportToken ) + , ( "extends", ExtendsToken ) , ( "false", FalseToken ) -- boolean literal @@ -562,6 +564,8 @@ keywordNames = , ( "of", OfToken ) , ( "return", ReturnToken ) + , ( "static", StaticToken ) + , ( "super", SuperToken ) , ( "switch", SwitchToken ) , ( "this", ThisToken ) , ( "throw", ThrowToken ) @@ -591,12 +595,12 @@ keywordNames = -- Future Reserved Words - , ( "class", FutureToken ) + -- ( "class", FutureToken ) **** an actual token, used in productions -- ( "code", FutureToken ) **** not any more -- ( "const", FutureToken ) **** an actual token, used in productions -- enum **** an actual token, used in productions - , ( "extends", FutureToken ) - , ( "super", FutureToken ) + -- ( "extends", FutureToken ) **** an actual token, used in productions + -- ( "super", FutureToken ) **** an actual token, used in productions -- Strict mode FutureReservedWords @@ -611,7 +615,7 @@ keywordNames = , ( "private", FutureToken ) , ( "protected", FutureToken ) , ( "public", FutureToken ) - , ( "static", FutureToken ) + -- ( "static", FutureToken ) **** an actual token, used in productions -- ( "strict", FutureToken ) *** not any more -- ( "yield", FutureToken) **** an actual token, used in productions ] diff --git a/src/Language/JavaScript/Parser/Token.hs b/src/Language/JavaScript/Parser/Token.hs index 99eb223..36b3b77 100644 --- a/src/Language/JavaScript/Parser/Token.hs +++ b/src/Language/JavaScript/Parser/Token.hs @@ -59,6 +59,7 @@ data Token | BreakToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | CaseToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | CatchToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } + | ClassToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | ConstToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | LetToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | ContinueToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } @@ -68,6 +69,7 @@ data Token | DoToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | ElseToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | EnumToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } + | ExtendsToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | FalseToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | FinallyToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | ForToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } @@ -80,6 +82,8 @@ data Token | NullToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | OfToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | ReturnToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } + | StaticToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } + | SuperToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | SwitchToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | ThisToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | ThrowToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } diff --git a/src/Language/JavaScript/Pretty/Printer.hs b/src/Language/JavaScript/Pretty/Printer.hs index 23e622e..995933d 100644 --- a/src/Language/JavaScript/Pretty/Printer.hs +++ b/src/Language/JavaScript/Pretty/Printer.hs @@ -77,6 +77,7 @@ instance RenderJS JSExpression where (|>) pacc (JSCallExpression ex lb xs rb) = pacc |> ex |> lb |> "(" |> xs |> rb |> ")" (|>) pacc (JSCallExpressionDot ex os xs) = pacc |> ex |> os |> "." |> xs (|>) pacc (JSCallExpressionSquare ex als xs ars) = pacc |> ex |> als |> "[" |> xs |> ars |> "]" + (|>) pacc (JSClassExpression annot n h lb xs rb) = pacc |> annot |> "class" |> n |> h |> lb |> "{" |> xs |> rb |> "}" (|>) pacc (JSCommaExpression le c re) = pacc |> le |> c |> "," |> re (|>) pacc (JSExpressionBinary lhs op rhs) = pacc |> lhs |> op |> rhs (|>) pacc (JSExpressionParen alp e arp) = pacc |> alp |> "(" |> e |> arp |> ")" @@ -224,6 +225,7 @@ instance RenderJS [JSSwitchParts] where instance RenderJS JSStatement where (|>) pacc (JSStatementBlock alb blk arb s) = pacc |> alb |> "{" |> blk |> arb |> "}" |> s (|>) pacc (JSBreak annot mi s) = pacc |> annot |> "break" |> mi |> s + (|>) pacc (JSClass annot n h lb xs rb s) = pacc |> annot |> "class" |> n |> h |> lb |> "{" |> xs |> rb |> "}" |> s (|>) pacc (JSContinue annot mi s) = pacc |> annot |> "continue" |> mi |> s (|>) pacc (JSConstant annot xs s) = pacc |> annot |> "const" |> xs |> s (|>) pacc (JSDoWhile ad x1 aw alb x2 arb x3) = pacc |> ad |> "do" |> x1 |> aw |> "while" |> alb |> "(" |> x2 |> arb |> ")" |> x3 @@ -362,4 +364,16 @@ instance RenderJS [JSTemplatePart] where instance RenderJS JSTemplatePart where (|>) pacc (JSTemplatePart e a s) = pacc |> e |> a |> s +instance RenderJS JSClassHeritage where + (|>) pacc (JSExtends a e) = pacc |> a |> "extends" |> e + (|>) pacc JSExtendsNone = pacc + +instance RenderJS [JSClassElement] where + (|>) = foldl' (|>) + +instance RenderJS JSClassElement where + (|>) pacc (JSClassInstanceMethod m) = pacc |> m + (|>) pacc (JSClassStaticMethod a m) = pacc |> a |> "static" |> m + (|>) pacc (JSClassSemi a) = pacc |> a |> ";" + -- EOF diff --git a/src/Language/JavaScript/Process/Minify.hs b/src/Language/JavaScript/Process/Minify.hs index e0e3e09..4f52b42 100644 --- a/src/Language/JavaScript/Process/Minify.hs +++ b/src/Language/JavaScript/Process/Minify.hs @@ -41,6 +41,7 @@ fixSpace = fix spaceAnnot fixStmt :: JSAnnot -> JSSemi -> JSStatement -> JSStatement fixStmt a s (JSStatementBlock _lb ss _rb _) = fixStatementBlock a s ss fixStmt a s (JSBreak _ i _) = JSBreak a (fixSpace i) s +fixStmt a s (JSClass _ n h _ ms _ _) = JSClass a (fixSpace n) (fixSpace h) emptyAnnot (fixEmpty ms) emptyAnnot s fixStmt a s (JSConstant _ ss _) = JSConstant a (fixVarList ss) s fixStmt a s (JSContinue _ i _) = JSContinue a (fixSpace i) s fixStmt a s (JSDoWhile _ st _ _ e _ _) = JSDoWhile a (mkStatementBlock noSemi st) emptyAnnot emptyAnnot (fixEmpty e) emptyAnnot s @@ -157,6 +158,7 @@ instance MinifyJS JSExpression where fix a (JSCallExpression ex _ xs _) = JSCallExpression (fix a ex) emptyAnnot (fixEmpty xs) emptyAnnot fix a (JSCallExpressionDot ex _ xs) = JSCallExpressionDot (fix a ex) emptyAnnot (fixEmpty xs) fix a (JSCallExpressionSquare ex _ xs _) = JSCallExpressionSquare (fix a ex) emptyAnnot (fixEmpty xs) emptyAnnot + fix a (JSClassExpression _ n h _ ms _) = JSClassExpression a (fixSpace n) (fixSpace h) emptyAnnot (fixEmpty ms) emptyAnnot fix a (JSCommaExpression le _ re) = JSCommaExpression (fix a le) emptyAnnot (fixEmpty re) fix a (JSExpressionBinary lhs op rhs) = fixBinOpExpression a op lhs rhs fix _ (JSExpressionParen _ e _) = JSExpressionParen emptyAnnot (fixEmpty e) emptyAnnot @@ -415,6 +417,18 @@ instance MinifyJS JSTemplatePart where fix _ (JSTemplatePart e _ s) = JSTemplatePart (fixEmpty e) emptyAnnot s +instance MinifyJS JSClassHeritage where + fix _ JSExtendsNone = JSExtendsNone + fix a (JSExtends _ e) = JSExtends a (fixSpace e) + + +instance MinifyJS [JSClassElement] where + fix _ [] = [] + fix a (JSClassInstanceMethod m:t) = JSClassInstanceMethod (fix a m) : fixEmpty t + fix a (JSClassStaticMethod _ m:t) = JSClassStaticMethod a (fixSpace m) : fixEmpty t + fix a (JSClassSemi _:t) = fix a t + + spaceAnnot :: JSAnnot spaceAnnot = JSAnnot tokenPosnEmpty [WhiteSpace tokenPosnEmpty " "] diff --git a/test/Test/Language/Javascript/ExpressionParser.hs b/test/Test/Language/Javascript/ExpressionParser.hs index 1b0afb1..63e7e22 100644 --- a/test/Test/Language/Javascript/ExpressionParser.hs +++ b/test/Test/Language/Javascript/ExpressionParser.hs @@ -190,6 +190,11 @@ testExpressionParser = describe "Parse expressions:" $ do testExpr "yield a + b" `shouldBe` "Right (JSAstExpression (JSYieldExpression (JSExpressionBinary ('+',JSIdentifier 'a',JSIdentifier 'b'))))" testExpr "yield* g()" `shouldBe` "Right (JSAstExpression (JSYieldFromExpression (JSMemberExpression (JSIdentifier 'g',JSArguments ()))))" + it "class expression" $ do + testExpr "class Foo extends Bar { a(x,y) {} *b() {} }" `shouldBe` "Right (JSAstExpression (JSClassExpression 'Foo' (JSIdentifier 'Bar') [JSMethodDefinition (JSIdentifier 'a') (JSIdentifier 'x',JSIdentifier 'y') (JSBlock []),JSGeneratorMethodDefinition (JSIdentifier 'b') () (JSBlock [])]))" + testExpr "class { static get [a]() {}; }" `shouldBe` "Right (JSAstExpression (JSClassExpression '' () [JSClassStaticMethod (JSPropertyAccessor JSAccessorGet (JSPropertyComputed (JSIdentifier 'a')) () (JSBlock [])),JSClassSemi]))" + testExpr "class Foo extends Bar { a(x,y) { super(x); } }" `shouldBe` "Right (JSAstExpression (JSClassExpression 'Foo' (JSIdentifier 'Bar') [JSMethodDefinition (JSIdentifier 'a') (JSIdentifier 'x',JSIdentifier 'y') (JSBlock [JSCallExpression (JSLiteral 'super',JSArguments (JSIdentifier 'x')),JSSemicolon])]))" + testExpr :: String -> String testExpr str = showStrippedMaybe (parseUsing parseExpression str "src") diff --git a/test/Test/Language/Javascript/Minify.hs b/test/Test/Language/Javascript/Minify.hs index 37142b5..0b5abce 100644 --- a/test/Test/Language/Javascript/Minify.hs +++ b/test/Test/Language/Javascript/Minify.hs @@ -159,6 +159,14 @@ testMinifyExpr = describe "Minify expressions:" $ do minifyExpr " ` a + b + ${ c + d } + ... ` " `shouldBe` "` a + b + ${c+d} + ... `" minifyExpr " tagger () ` a + b ` " `shouldBe` "tagger()` a + b `" + it "class" $ do + minifyExpr " class Foo {\n a() {\n return 0;\n };\n static [ b ] ( x ) {}\n } " `shouldBe` "class Foo{a(){return 0}static[b](x){}}" + minifyExpr " class { static get a() { return 0; } static set a(v) {} } " `shouldBe` "class{static get a(){return 0}static set a(v){}}" + minifyExpr " class { ; ; ; } " `shouldBe` "class{}" + minifyExpr " class Foo extends Bar {} " `shouldBe` "class Foo extends Bar{}" + minifyExpr " class extends (getBase()) {} " `shouldBe` "class extends(getBase()){}" + minifyExpr " class extends [ Bar1, Bar2 ][getBaseIndex()] {} " `shouldBe` "class extends[Bar1,Bar2][getBaseIndex()]{}" + testMinifyStmt :: Spec testMinifyStmt = describe "Minify statements:" $ do @@ -265,6 +273,11 @@ testMinifyStmt = describe "Minify statements:" $ do it "string concatenation" $ minifyStmt " f (\"ab\"+\"cd\") " `shouldBe` "f('abcd')" + it "class" $ do + minifyStmt " class Foo {\n a() {\n return 0;\n }\n static b ( x ) {}\n } " `shouldBe` "class Foo{a(){return 0}static b(x){}}" + minifyStmt " class Foo extends Bar {} " `shouldBe` "class Foo extends Bar{}" + minifyStmt " class Foo extends (getBase()) {} " `shouldBe` "class Foo extends(getBase()){}" + minifyStmt " class Foo extends [ Bar1, Bar2 ][getBaseIndex()] {} " `shouldBe` "class Foo extends[Bar1,Bar2][getBaseIndex()]{}" testMinifyProg :: Spec testMinifyProg = describe "Minify programs:" $ do diff --git a/test/Test/Language/Javascript/RoundTrip.hs b/test/Test/Language/Javascript/RoundTrip.hs index d34bf45..d116d56 100644 --- a/test/Test/Language/Javascript/RoundTrip.hs +++ b/test/Test/Language/Javascript/RoundTrip.hs @@ -153,6 +153,7 @@ testRoundTrip = describe "Roundtrip:" $ do testRTModule "export const a = 1 ; " testRTModule "export function f () { } ; " testRTModule "export function * f () { } ; " + testRTModule "export class Foo\nextends Bar\n{ get a () { return 1 ; } static b ( x,y ) {} ; } ; " testRT :: String -> Expectation diff --git a/test/Test/Language/Javascript/StatementParser.hs b/test/Test/Language/Javascript/StatementParser.hs index 14518f4..5e0c98c 100644 --- a/test/Test/Language/Javascript/StatementParser.hs +++ b/test/Test/Language/Javascript/StatementParser.hs @@ -125,6 +125,11 @@ testStatementParser = describe "Parse statements:" $ do testStmt "function* x(a,b){}" `shouldBe` "Right (JSAstStatement (JSGenerator 'x' (JSIdentifier 'a',JSIdentifier 'b') (JSBlock [])))" testStmt "function* x(a,...b){}" `shouldBe` "Right (JSAstStatement (JSGenerator 'x' (JSIdentifier 'a',JSSpreadExpression (JSIdentifier 'b')) (JSBlock [])))" + it "class" $ do + testStmt "class Foo extends Bar { a(x,y) {} *b() {} }" `shouldBe` "Right (JSAstStatement (JSClass 'Foo' (JSIdentifier 'Bar') [JSMethodDefinition (JSIdentifier 'a') (JSIdentifier 'x',JSIdentifier 'y') (JSBlock []),JSGeneratorMethodDefinition (JSIdentifier 'b') () (JSBlock [])]))" + testStmt "class Foo { static get [a]() {}; }" `shouldBe` "Right (JSAstStatement (JSClass 'Foo' () [JSClassStaticMethod (JSPropertyAccessor JSAccessorGet (JSPropertyComputed (JSIdentifier 'a')) () (JSBlock [])),JSClassSemi]))" + testStmt "class Foo extends Bar { a(x,y) { super[x](y); } }" `shouldBe` "Right (JSAstStatement (JSClass 'Foo' (JSIdentifier 'Bar') [JSMethodDefinition (JSIdentifier 'a') (JSIdentifier 'x',JSIdentifier 'y') (JSBlock [JSMethodCall (JSMemberSquare (JSLiteral 'super',JSIdentifier 'x'),JSArguments (JSIdentifier 'y')),JSSemicolon])]))" + testStmt :: String -> String testStmt str = showStrippedMaybe (parseUsing parseStatement str "src")