packages feed

hydra-0.15.0: src/main/haskell/Hydra/Dsl/JavaScript/Helpers.hs

-- | A DSL for constructing JavaScript syntax trees in Haskell.
module Hydra.Dsl.JavaScript.Helpers where

import Hydra.JavaScript.Syntax
import qualified Data.Int as I


-- * Identifiers

-- | Create an identifier
-- Example: ident "foo"
ident :: String -> Identifier
ident = Identifier

-- | Create a qualified name from a list of strings
-- Example: qualifiedName ["module", "submodule", "name"]
qualifiedName :: [String] -> QualifiedName
qualifiedName = fmap Identifier


-- * Literals

-- | Create a string literal with double quotes
-- Example: stringLit "hello"
stringLit :: String -> Literal
stringLit s = LiteralString $ StringLiteral s False

-- | Create a string literal with single quotes
-- Example: stringLitSingle "hello"
stringLitSingle :: String -> Literal
stringLitSingle s = LiteralString $ StringLiteral s True

-- | Create an integer literal
-- Example: intLit 42
intLit :: I.Int64 -> Literal
intLit = LiteralNumber . NumericLiteralInteger

-- | Create a floating-point literal
-- Example: floatLit 3.14
floatLit :: Double -> Literal
floatLit = LiteralNumber . NumericLiteralFloat

-- | Create a boolean literal
-- Example: boolLit True
boolLit :: Bool -> Literal
boolLit = LiteralBoolean

-- | The null literal
nullLit :: Literal
nullLit = LiteralNull

-- | The undefined literal
undefinedLit :: Literal
undefinedLit = LiteralUndefined

-- | Create a BigInt literal
-- Example: bigIntLit 123
bigIntLit :: Integer -> Literal
bigIntLit = LiteralBigInt


-- * Expressions

-- | Create an identifier expression
-- Example: identExpr "foo"
identExpr :: String -> Expression
identExpr = ExpressionIdentifier . ident

-- | Create a literal expression
-- Example: litExpr (stringLit "hello")
litExpr :: Literal -> Expression
litExpr = ExpressionLiteral

-- | Create a string expression
-- Example: stringExpr "hello"
stringExpr :: String -> Expression
stringExpr = litExpr . stringLit

-- | Create an integer expression
-- Example: intExpr 42
intExpr :: I.Int64 -> Expression
intExpr = litExpr . intLit

-- | Create a floating-point expression
-- Example: floatExpr 3.14
floatExpr :: Double -> Expression
floatExpr = litExpr . floatLit

-- | Create a boolean expression
-- Example: boolExpr True
boolExpr :: Bool -> Expression
boolExpr = litExpr . boolLit

-- | The null expression
nullExpr :: Expression
nullExpr = litExpr nullLit

-- | The undefined expression
undefinedExpr :: Expression
undefinedExpr = litExpr undefinedLit

-- | Create an array expression
-- Example: arrayExpr [intExpr 1, intExpr 2, intExpr 3]
arrayExpr :: [Expression] -> Expression
arrayExpr = ExpressionArray . fmap ArrayElementExpression

-- | Create an object expression from key-value pairs
-- Example: objectExpr [("name", stringExpr "John"), ("age", intExpr 30)]
objectExpr :: [(String, Expression)] -> Expression
objectExpr pairs = ExpressionObject $ fmap toProperty pairs
  where
    toProperty (k, v) = Property (identExpr k) v PropertyKindInit False False

-- | Create a function call expression
-- Example: callExpr (identExpr "foo") [intExpr 1, intExpr 2]
callExpr :: Expression -> [Expression] -> Expression
callExpr callee args = ExpressionCall $ CallExpression callee args False

-- | Create an optional chaining call expression
-- Example: optionalCallExpr (identExpr "foo") [intExpr 1]
optionalCallExpr :: Expression -> [Expression] -> Expression
optionalCallExpr callee args = ExpressionCall $ CallExpression callee args True

-- | Create a member access expression (obj.prop)
-- Example: memberExpr (identExpr "obj") "prop"
memberExpr :: Expression -> String -> Expression
memberExpr obj prop = ExpressionMember $ MemberExpression obj (identExpr prop) False False

-- | Create a computed member access expression (obj[expr])
-- Example: computedMemberExpr (identExpr "arr") (intExpr 0)
computedMemberExpr :: Expression -> Expression -> Expression
computedMemberExpr obj prop = ExpressionMember $ MemberExpression obj prop True False

-- | Create an optional chaining member expression (obj?.prop)
-- Example: optionalMemberExpr (identExpr "obj") "prop"
optionalMemberExpr :: Expression -> String -> Expression
optionalMemberExpr obj prop = ExpressionMember $ MemberExpression obj (identExpr prop) False True

-- | Create a conditional (ternary) expression
-- Example: condExpr (identExpr "x") (intExpr 1) (intExpr 0)
condExpr :: Expression -> Expression -> Expression -> Expression
condExpr test consequent alternate = ExpressionConditional $
  ConditionalExpression test consequent alternate

-- | Create a binary expression
-- Example: binaryExpr BinaryOperatorAdd (intExpr 1) (intExpr 2)
binaryExpr :: BinaryOperator -> Expression -> Expression -> Expression
binaryExpr op left right = ExpressionBinary $ BinaryExpression op left right

-- | Create an addition expression
-- Example: addExpr (intExpr 1) (intExpr 2)
addExpr :: Expression -> Expression -> Expression
addExpr = binaryExpr BinaryOperatorAdd

-- | Create a subtraction expression
-- Example: subExpr (intExpr 5) (intExpr 3)
subExpr :: Expression -> Expression -> Expression
subExpr = binaryExpr BinaryOperatorSubtract

-- | Create a multiplication expression
-- Example: mulExpr (intExpr 3) (intExpr 4)
mulExpr :: Expression -> Expression -> Expression
mulExpr = binaryExpr BinaryOperatorMultiply

-- | Create a division expression
-- Example: divExpr (intExpr 10) (intExpr 2)
divExpr :: Expression -> Expression -> Expression
divExpr = binaryExpr BinaryOperatorDivide

-- | Create a strict equality expression (===)
-- Example: eqExpr (identExpr "x") (intExpr 1)
eqExpr :: Expression -> Expression -> Expression
eqExpr = binaryExpr BinaryOperatorStrictEqual

-- | Create a strict inequality expression (!==)
-- Example: neqExpr (identExpr "x") (intExpr 1)
neqExpr :: Expression -> Expression -> Expression
neqExpr = binaryExpr BinaryOperatorStrictNotEqual

-- | Create a logical AND expression
-- Example: andExpr (identExpr "a") (identExpr "b")
andExpr :: Expression -> Expression -> Expression
andExpr = binaryExpr BinaryOperatorAnd

-- | Create a logical OR expression
-- Example: orExpr (identExpr "a") (identExpr "b")
orExpr :: Expression -> Expression -> Expression
orExpr = binaryExpr BinaryOperatorOr

-- | Create a unary expression
-- Example: unaryExpr UnaryOperatorNot (identExpr "x")
unaryExpr :: UnaryOperator -> Expression -> Expression
unaryExpr op arg = ExpressionUnary $ UnaryExpression op arg True

-- | Create a logical NOT expression
-- Example: notExpr (identExpr "x")
notExpr :: Expression -> Expression
notExpr = unaryExpr UnaryOperatorNot

-- | Create a negation expression
-- Example: negateExpr (identExpr "x")
negateExpr :: Expression -> Expression
negateExpr = unaryExpr UnaryOperatorNegate

-- | Create an assignment expression
-- Example: assignExpr (identPattern "x") (intExpr 42)
assignExpr :: Pattern -> Expression -> Expression
assignExpr left right = ExpressionAssignment $
  AssignmentExpression AssignmentOperatorAssign left right

-- | The 'this' expression
thisExpr :: Expression
thisExpr = ExpressionThis

-- | Create a 'new' expression
-- Example: newExpr (identExpr "Date") []
newExpr :: Expression -> [Expression] -> Expression
newExpr callee args = ExpressionNew $ CallExpression callee args False

-- | Create an await expression
-- Example: awaitExpr (callExpr (identExpr "fetch") [stringExpr "url"])
awaitExpr :: Expression -> Expression
awaitExpr = ExpressionAwait

-- | Create a yield expression
-- Example: yieldExpr (Just (intExpr 1))
yieldExpr :: Maybe Expression -> Expression
yieldExpr = ExpressionYield

-- | Create a spread expression
-- Example: spreadExpr (identExpr "arr")
spreadExpr :: Expression -> Expression
spreadExpr = ExpressionSpread . SpreadElement

-- | Create a parenthesized expression
-- Example: parenExpr (addExpr (intExpr 1) (intExpr 2))
parenExpr :: Expression -> Expression
parenExpr = ExpressionParenthesized


-- * Arrow Functions

-- | Create an arrow function with an expression body
-- Example: arrowExpr ["x", "y"] (addExpr (identExpr "x") (identExpr "y"))
arrowExpr :: [String] -> Expression -> Expression
arrowExpr params body = ExpressionArrow $ ArrowFunctionExpression
  (fmap identPattern params)
  (ArrowFunctionBodyExpression body)
  False

-- | Create an async arrow function with an expression body
-- Example: asyncArrowExpr ["x"] (awaitExpr (callExpr (identExpr "fetch") [identExpr "x"]))
asyncArrowExpr :: [String] -> Expression -> Expression
asyncArrowExpr params body = ExpressionArrow $ ArrowFunctionExpression
  (fmap identPattern params)
  (ArrowFunctionBodyExpression body)
  True

-- | Create an arrow function with a block body
-- Example: arrowBlockExpr ["x"] [returnStmt (Just (identExpr "x"))]
arrowBlockExpr :: [String] -> [Statement] -> Expression
arrowBlockExpr params body = ExpressionArrow $ ArrowFunctionExpression
  (fmap identPattern params)
  (ArrowFunctionBodyBlock body)
  False


-- * Patterns

-- | Create an identifier pattern
-- Example: identPattern "x"
identPattern :: String -> Pattern
identPattern = PatternIdentifier . ident

-- | Create an object destructuring pattern
-- Example: objectPattern [identPattern "a", identPattern "b"]
objectPattern :: [Pattern] -> Pattern
objectPattern ps = PatternObject $ ObjectPattern $ fmap toProperty ps
  where
    toProperty p = case p of
      PatternIdentifier i -> ObjectPatternPropertyProperty $
        Property (ExpressionIdentifier i) (ExpressionIdentifier i) PropertyKindInit False True
      _ -> ObjectPatternPropertyProperty $
        Property nullExpr (patternToExpr p) PropertyKindInit False False
    patternToExpr (PatternIdentifier i) = ExpressionIdentifier i
    patternToExpr _ = nullExpr

-- | Create an array destructuring pattern
-- Example: arrayPattern [Just (identPattern "a"), Nothing, Just (identPattern "b")]
arrayPattern :: [Maybe Pattern] -> Pattern
arrayPattern = PatternArray

-- | Create a rest pattern
-- Example: restPattern "args"
restPattern :: String -> Pattern
restPattern = PatternRest . RestElement . identPattern

-- | Create a pattern with a default value
-- Example: defaultPattern "x" (intExpr 0)
defaultPattern :: String -> Expression -> Pattern
defaultPattern name def = PatternAssignment $
  AssignmentPattern (identPattern name) def


-- * Statements

-- | Create an expression statement
-- Example: exprStmt (callExpr (identExpr "console.log") [stringExpr "hello"])
exprStmt :: Expression -> Statement
exprStmt = StatementExpression

-- | Create a block statement
-- Example: blockStmt [exprStmt (intExpr 1), exprStmt (intExpr 2)]
blockStmt :: [Statement] -> Statement
blockStmt = StatementBlock

-- | Create a return statement
-- Example: returnStmt (Just (intExpr 42))
returnStmt :: Maybe Expression -> Statement
returnStmt = StatementReturn

-- | Create a throw statement
-- Example: throwStmt (newExpr (identExpr "Error") [stringExpr "oops"])
throwStmt :: Expression -> Statement
throwStmt = StatementThrow . ThrowStatement

-- | Create a break statement
-- Example: breakStmt Nothing
breakStmt :: Maybe String -> Statement
breakStmt = StatementBreak . fmap ident

-- | Create a continue statement
-- Example: continueStmt Nothing
continueStmt :: Maybe String -> Statement
continueStmt = StatementContinue . fmap ident

-- | Create an if statement
-- Example: ifStmt (identExpr "x") (returnStmt (Just (intExpr 1))) Nothing
ifStmt :: Expression -> Statement -> Maybe Statement -> Statement
ifStmt test consequent alternate = StatementIf $ IfStatement test consequent alternate

-- | Create a while statement
-- Example: whileStmt (boolExpr True) (blockStmt [breakStmt Nothing])
whileStmt :: Expression -> Statement -> Statement
whileStmt test body = StatementWhile $ WhileStatement test body

-- | Create a for statement
-- Example: forStmt (Just (ForInitVariable (constDecl "i" (intExpr 0)))) (Just (binaryExpr BinaryOperatorLessThan (identExpr "i") (intExpr 10))) (Just (unaryExpr UnaryOperatorIncrement (identExpr "i"))) (blockStmt [])
forStmt :: Maybe ForInit -> Maybe Expression -> Maybe Expression -> Statement -> Statement
forStmt init test update body = StatementFor $ ForStatement init test update body

-- | Create a for-of statement
-- Example: forOfStmt False (ForInLeftVariable (constDecl "item" Nothing)) (identExpr "items") (blockStmt [])
forOfStmt :: Bool -> ForInLeft -> Expression -> Statement -> Statement
forOfStmt await left right body = StatementForOf $ ForOfStatement await left right body

-- | Create a try statement
-- Example: tryStmt [exprStmt (callExpr (identExpr "riskyOp") [])] (Just (catchClause (Just (identPattern "e")) [throwStmt (identExpr "e")])) Nothing
tryStmt :: [Statement] -> Maybe CatchClause -> Maybe [Statement] -> Statement
tryStmt block handler finalizer = StatementTry $ TryStatement block handler finalizer

-- | Create a catch clause
-- Example: catchClause (Just (identPattern "e")) [throwStmt (identExpr "e")]
catchClause :: Maybe Pattern -> [Statement] -> CatchClause
catchClause = CatchClause

-- | Create a switch statement
-- Example: switchStmt (identExpr "x") [switchCase (Just (intExpr 1)) [returnStmt (Just (stringExpr "one"))], switchCase Nothing [returnStmt (Just (stringExpr "default"))]]
switchStmt :: Expression -> [SwitchCase] -> Statement
switchStmt discriminant cases = StatementSwitch $ SwitchStatement discriminant cases

-- | Create a switch case
-- Example: switchCase (Just (intExpr 1)) [returnStmt (Just (stringExpr "one"))]
switchCase :: Maybe Expression -> [Statement] -> SwitchCase
switchCase = SwitchCase

-- | An empty statement
emptyStmt :: Statement
emptyStmt = StatementEmpty

-- | A debugger statement
debuggerStmt :: Statement
debuggerStmt = StatementDebugger


-- * Variable Declarations

-- | Create a const declaration
-- Example: constDecl "x" (Just (intExpr 42))
constDecl :: String -> Maybe Expression -> VariableDeclaration
constDecl name init = VariableDeclaration VariableKindConst
  [VariableDeclarator (identPattern name) init]

-- | Create a let declaration
-- Example: letDecl "x" (Just (intExpr 42))
letDecl :: String -> Maybe Expression -> VariableDeclaration
letDecl name init = VariableDeclaration VariableKindLet
  [VariableDeclarator (identPattern name) init]

-- | Create a var declaration
-- Example: varDecl "x" (Just (intExpr 42))
varDecl :: String -> Maybe Expression -> VariableDeclaration
varDecl name init = VariableDeclaration VariableKindVar
  [VariableDeclarator (identPattern name) init]

-- | Create a variable declaration statement
-- Example: varDeclStmt (constDecl "x" (Just (intExpr 42)))
varDeclStmt :: VariableDeclaration -> Statement
varDeclStmt = StatementVariableDeclaration


-- * Function Declarations

-- | Create a function declaration
-- Example: funcDecl "add" ["x", "y"] [returnStmt (Just (addExpr (identExpr "x") (identExpr "y")))]
funcDecl :: String -> [String] -> [Statement] -> FunctionDeclaration
funcDecl name params body = FunctionDeclaration
  (ident name)
  (fmap identPattern params)
  body
  False
  False

-- | Create an async function declaration
-- Example: asyncFuncDecl "fetchData" ["url"] [returnStmt (Just (awaitExpr (callExpr (identExpr "fetch") [identExpr "url"])))]
asyncFuncDecl :: String -> [String] -> [Statement] -> FunctionDeclaration
asyncFuncDecl name params body = FunctionDeclaration
  (ident name)
  (fmap identPattern params)
  body
  True
  False

-- | Create a function declaration statement
-- Example: funcDeclStmt (funcDecl "add" ["x", "y"] [returnStmt (Just (addExpr (identExpr "x") (identExpr "y")))])
funcDeclStmt :: FunctionDeclaration -> Statement
funcDeclStmt = StatementFunctionDeclaration


-- * Class Declarations

-- | Create a class declaration
-- Example: classDecl "Person" Nothing [methodDef "greet" [] [returnStmt (Just (stringExpr "hello"))]]
classDecl :: String -> Maybe Expression -> [MethodDefinition] -> ClassDeclaration
classDecl name superClass body = ClassDeclaration (ident name) superClass body

-- | Create a method definition
-- Example: methodDef "greet" [] [returnStmt (Just (stringExpr "hello"))]
methodDef :: String -> [String] -> [Statement] -> MethodDefinition
methodDef name params body = MethodDefinition
  (identExpr name)
  (FunctionExpression Nothing (fmap identPattern params) body False False)
  MethodKindMethod
  False
  False

-- | Create a constructor definition
-- Example: constructorDef ["name"] [exprStmt (assignExpr (PatternIdentifier (ident "this.name")) (identExpr "name"))]
constructorDef :: [String] -> [Statement] -> MethodDefinition
constructorDef params body = MethodDefinition
  (identExpr "constructor")
  (FunctionExpression Nothing (fmap identPattern params) body False False)
  MethodKindConstructor
  False
  False

-- | Create a static method definition
-- Example: staticMethodDef "create" [] [returnStmt (Just (newExpr (identExpr "Person") []))]
staticMethodDef :: String -> [String] -> [Statement] -> MethodDefinition
staticMethodDef name params body = MethodDefinition
  (identExpr name)
  (FunctionExpression Nothing (fmap identPattern params) body False False)
  MethodKindMethod
  False
  True

-- | Create a class declaration statement
-- Example: classDeclStmt (classDecl "Person" Nothing [])
classDeclStmt :: ClassDeclaration -> Statement
classDeclStmt = StatementClassDeclaration


-- * Module Items

-- | Create a statement module item
-- Example: stmtItem (varDeclStmt (constDecl "x" (Just (intExpr 42))))
stmtItem :: Statement -> ModuleItem
stmtItem = ModuleItemStatement

-- | Create an import declaration
-- Example: importDecl [importNamed "foo" "foo"] "./module.js"
importDecl :: [ImportClause] -> String -> ImportDeclaration
importDecl specifiers source = ImportDeclaration specifiers (StringLiteral source False)

-- | Create a named import
-- Example: importNamed "foo" "bar"  -- import { foo as bar }
importNamed :: String -> String -> ImportClause
importNamed imported local = ImportClauseNamed $
  ImportSpecifier (ident imported) (ident local)

-- | Create a default import
-- Example: importDefault "foo"  -- import foo from ...
importDefault :: String -> ImportClause
importDefault = ImportClauseDefault . ImportDefaultSpecifier . ident

-- | Create a namespace import
-- Example: importNamespace "foo"  -- import * as foo from ...
importNamespace :: String -> ImportClause
importNamespace = ImportClauseNamespace . ImportNamespaceSpecifier . ident

-- | Create an import module item
-- Example: importItem (importDecl [importDefault "React"] "react")
importItem :: ImportDeclaration -> ModuleItem
importItem = ModuleItemImport

-- | Create a named export declaration
-- Example: namedExportDecl [("foo", "foo")] Nothing
namedExportDecl :: [(String, String)] -> Maybe String -> ExportDeclaration
namedExportDecl specifiers source = ExportDeclarationNamed $ NamedExport
  (fmap (\(local, exported) -> ExportSpecifier (ident local) (ident exported)) specifiers)
  (fmap (\s -> StringLiteral s False) source)

-- | Create a default export declaration
-- Example: defaultExportDecl (identExpr "foo")
defaultExportDecl :: Expression -> ExportDeclaration
defaultExportDecl = ExportDeclarationDefault

-- | Create an export declaration for a statement
-- Example: exportDeclStmt (varDeclStmt (constDecl "x" (Just (intExpr 42))))
exportDeclStmt :: Statement -> ExportDeclaration
exportDeclStmt = ExportDeclarationDeclaration

-- | Create an export module item
-- Example: exportItem (defaultExportDecl (identExpr "foo"))
exportItem :: ExportDeclaration -> ModuleItem
exportItem = ModuleItemExport


-- * Programs

-- | Create a module program
-- Example: moduleProgram [importItem (importDecl [importDefault "foo"] "./foo.js"), stmtItem (varDeclStmt (constDecl "x" (Just (intExpr 42))))]
moduleProgram :: [ModuleItem] -> Program
moduleProgram body = Program body SourceTypeModule

-- | Create a script program
-- Example: scriptProgram [stmtItem (varDeclStmt (constDecl "x" (Just (intExpr 42))))]
scriptProgram :: [ModuleItem] -> Program
scriptProgram body = Program body SourceTypeScript


-- * Type Expressions

-- | Create an identifier type expression
-- Example: identType "string"
identType :: String -> TypeExpression
identType = TypeExpressionIdentifier . ident

-- | Create an array type expression
-- Example: arrayType (identType "number")
arrayType :: TypeExpression -> TypeExpression
arrayType = TypeExpressionArray . ArrayTypeExpression

-- | Create a union type expression
-- Example: unionType [identType "string", identType "number"]
unionType :: [TypeExpression] -> TypeExpression
unionType = TypeExpressionUnion

-- | Create an optional type expression
-- Example: optionalType (identType "string")
optionalType :: TypeExpression -> TypeExpression
optionalType = TypeExpressionOptional

-- | Create a parameterized type expression
-- Example: paramType (identType "Array") [identType "string"]
paramType :: TypeExpression -> [TypeExpression] -> TypeExpression
paramType base args = TypeExpressionParameterized $
  ParameterizedTypeExpression base args

-- | The 'any' type
anyType :: TypeExpression
anyType = TypeExpressionAny

-- | The 'void' type
voidType :: TypeExpression
voidType = TypeExpressionVoid

-- | The 'never' type
neverType :: TypeExpression
neverType = TypeExpressionNever


-- * Comments and Documentation

-- | Create a line comment
-- Example: lineComment "This is a comment"
lineComment :: String -> Comment
lineComment = CommentLine

-- | Create a block comment
-- Example: blockComment "This is a\nmultiline comment"
blockComment :: String -> Comment
blockComment = CommentBlock

-- | Create a documentation comment
-- Example: docComment "Description of the function" [paramTag "x" (Just (identType "number")) "The input value"]
docComment :: String -> [DocumentationTag] -> Comment
docComment desc tags = CommentDocumentation $ DocumentationComment desc tags

-- | Create a @param documentation tag
-- Example: paramTag "x" (Just (identType "number")) "The input value"
paramTag :: String -> Maybe TypeExpression -> String -> DocumentationTag
paramTag name typ desc = DocumentationTag "param" typ (Just $ ident name) desc

-- | Create a @returns documentation tag
-- Example: returnsTag (Just (identType "number")) "The result"
returnsTag :: Maybe TypeExpression -> String -> DocumentationTag
returnsTag typ desc = DocumentationTag "returns" typ Nothing desc

-- | Create a @type documentation tag
-- Example: typeTag (identType "number")
typeTag :: TypeExpression -> DocumentationTag
typeTag typ = DocumentationTag "type" (Just typ) Nothing ""