Repository: joernio/joern
Pull request #2782 (2023-05-30 16:03:40)

Add php2cpg local creation and ClosureRefEdge passes
Since PHP doesn't require variable declarations, php2cpg would create a Local node on the first use of a new identifier in scope and add a ref edge from the identifier to the local. This approach works well if the identifier in question is added to the CPG, but this isn't always the case. This led to a situation where identifier nodes are added to the CPG (due to the ref edge being added), but not to the AST, leaving them without AST parents. 

This PR removes most Local creation from the AstCreator and creates them in a follow-up pass instead. This also means that locals captured by closures must be referenced separately, since the locals being referenced may not exist yet, so the ClosureRefPass adds these.

Resolves https://github.com/joernio/joern/issues/2732


Patch:
diff --git a/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/Php2Cpg.scala b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/Php2Cpg.scala
index d22a80842..400595d72 100644
--- a/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/Php2Cpg.scala
+++ b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/Php2Cpg.scala
@@ -1,6 +1,6 @@
 package io.joern.php2cpg
 
-import io.joern.php2cpg.passes.AstCreationPass
+import io.joern.php2cpg.passes.{AstCreationPass, LocalCreationPass}
 import io.joern.x2cpg.X2Cpg.withNewEmptyCpg
 import io.joern.x2cpg.X2CpgFrontend
 import io.joern.x2cpg.passes.frontend.{MetaDataPass, TypeNodePass}
@@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory
 
 import scala.util.{Failure, Try, Success}
 import scala.util.matching.Regex
+import io.joern.php2cpg.passes.ClosureRefPass
 
 class Php2Cpg extends X2CpgFrontend[Config] {
   private val logger = LoggerFactory.getLogger(this.getClass)
@@ -38,6 +39,8 @@ class Php2Cpg extends X2CpgFrontend[Config] {
         val astCreationPass = new AstCreationPass(config, cpg)
         astCreationPass.createAndApply()
         new TypeNodePass(astCreationPass.allUsedTypes, cpg).createAndApply()
+        LocalCreationPass.allLocalCreationPasses(cpg).foreach(_.createAndApply())
+        new ClosureRefPass(cpg).createAndApply()
       }
     } else {
       logger.error(
diff --git a/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/astcreation/AstCreator.scala
index 7b22aa51f..104834cd1 100644
--- a/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/astcreation/AstCreator.scala
@@ -18,6 +18,7 @@ import io.joern.x2cpg.utils.NodeBuilders.{
 }
 import io.shiftleft.codepropertygraph.generated._
 import io.shiftleft.codepropertygraph.generated.nodes.Call.PropertyDefaults
+import io.shiftleft.codepropertygraph.generated.nodes.Local.{PropertyDefaults => LocalDefaults}
 import io.shiftleft.codepropertygraph.generated.nodes._
 import io.shiftleft.passes.IntervalKeyPool
 import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal
@@ -250,8 +251,7 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
     }
     val methodReturn = newMethodReturnNode(returnType, line = line(decl), column = None)
 
-    val declLocals = scope.getLocalsInScope.map(Ast(_))
-    val methodBody = blockAst(blockNode(decl), declLocals ++ methodBodyStmts)
+    val methodBody = blockAst(blockNode(decl), methodBodyStmts)
 
     scope.popScope()
     methodAstWithAnnotations(method, parameters, methodBody, methodReturn, modifiers)
@@ -563,8 +563,8 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
   }
 
   private def astForForeachStmt(stmt: PhpForeachStmt): Ast = {
-    val iteratorAst   = astForExpr(stmt.iterExpr)
-    val iteratorLocal = getTmpLocal(stmt, maybeTypeFullName = None, lineNumber = line(stmt), prefix = "iter_")
+    val iteratorAst    = astForExpr(stmt.iterExpr)
+    val iterIdentifier = getTmpIdentifier(stmt, maybeTypeFullName = None, prefix = "iter_")
 
     val assignItemTargetAst = stmt.keyVar match {
       case Some(key) => astForKeyValPair(key, stmt.valueVar, line(stmt))
@@ -573,12 +573,11 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
 
     // Initializer asts
     // - Iterator assign
-    val iterIdent         = identifierAstFromLocal(iteratorLocal, line(stmt))
     val iterValue         = astForExpr(stmt.iterExpr)
-    val iteratorAssignAst = simpleAssignAst(iterIdent, iterValue, line(stmt))
+    val iteratorAssignAst = simpleAssignAst(Ast(iterIdentifier), iterValue, line(stmt))
 
     // - Assigned item assign
-    val itemInitAst = getItemAssignAstForForeach(stmt, assignItemTargetAst, iteratorLocal)
+    val itemInitAst = getItemAssignAstForForeach(stmt, assignItemTargetAst, iterIdentifier.copy)
 
     // Condition ast
     val isNullName = PhpOperators.isNull
@@ -591,7 +590,7 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
     val conditionAst = callAst(notIsNull, isNullAst :: Nil)
 
     // Update asts
-    val nextIterIdent = identifierAstFromLocal(iteratorLocal, line(stmt))
+    val nextIterIdent = Ast(iterIdentifier.copy)
     val nextSignature = "void()"
     val nextCallCode  = s"${nextIterIdent.rootCodeOrEmpty}->next()"
     val nextCallNode = callNode(
@@ -627,9 +626,9 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
   private def getItemAssignAstForForeach(
     stmt: PhpForeachStmt,
     assignItemTargetAst: Ast,
-    iteratorLocal: NewLocal
+    iteratorIdentifier: NewIdentifier
   ): Ast = {
-    val iteratorIdentifierAst = identifierAstFromLocal(iteratorLocal, line(stmt))
+    val iteratorIdentifierAst = Ast(iteratorIdentifier)
     val currentCallSignature  = s"$UnresolvedSignature(0)"
     val currentCallCode       = s"${iteratorIdentifierAst.rootCodeOrEmpty}->current()"
     val currentCallNode = callNode(
@@ -695,10 +694,11 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
         case _                               => code
       }
 
-      // Local will be added to the method via magic later. Just fix the code and line here.
-      scope.lookupVariable(name).collect { case local: NewLocal => local }.foreach { local =>
-        local.code(s"static ${local.code}")
-        local.lineNumber(line(stmt))
+      val local = localNode(stmt, name, s"static $code", variableAst.rootType.getOrElse(TypeConstants.Any))
+      scope.addToScope(local.name, local)
+
+      variableAst.root.collect { case identifier: NewIdentifier =>
+        diffGraph.addEdge(identifier, local, EdgeTypes.REF)
       }
 
       val defaultAssignAst = maybeValueAst.map { valueAst =>
@@ -707,7 +707,7 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
         callAst(assignNode, variableAst :: valueAst :: Nil)
       }
 
-      defaultAssignAst.toList
+      Ast(local) :: defaultAssignAst.toList
     }
   }
 
@@ -953,9 +953,7 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
   private def astForCall(call: PhpCallExpr): Ast = {
     val arguments = call.args.map(astForCallArg)
 
-    val isMethodCall = call.target.isDefined && !call.isStatic
-
-    val targetAst = Option.when(isMethodCall)(call.target.map(astForExpr)).flatten
+    val targetAst = Option.unless(call.isStatic)(call.target.map(astForExpr)).flatten
 
     val nameAst = Option.unless(call.methodName.isInstanceOf[PhpNameExpr])(astForExpr(call.methodName))
     val name =
@@ -971,7 +969,7 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
     val argsCode = arguments.map(_.rootCodeOrEmpty).mkString(",")
 
     val codePrefix =
-      if (isMethodCall && targetAst.isDefined)
+      if (!call.isStatic && targetAst.isDefined)
         codeForMethodCall(call, targetAst.get, name)
       else if (call.isStatic)
         codeForStaticMethodCall(call, name)
@@ -989,7 +987,7 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
     val fullName = call.target match {
       // Static method call with a known class name
       case Some(nameExpr: PhpNameExpr) if call.isStatic =>
-        s"${nameExpr.name}.$name:$UnresolvedSignature(${arguments.size})"
+        s"${nameExpr.name}${StaticMethodDelimiter}$name"
 
       case None if PhpBuiltins.FuncNames.contains(name) =>
         // No signature/namespace for MFN for builtin functions to ensure stable names as type info improves.
@@ -1048,17 +1046,8 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
   private def astForNameExpr(expr: PhpNameExpr): Ast = {
     val identifier = identifierNode(expr, expr.name, expr.name, TypeConstants.Any)
 
-    scope.lookupVariable(identifier.name) match {
-      case Some(declaringNode) =>
-        diffGraph.addEdge(identifier, declaringNode, EdgeTypes.REF)
-
-      case None =>
-        // With variable variables, it's possible to use a valid variable without having an obvious assignment to it.
-        // If a name is unknown at this point, assume it's a local that had a value assigned in some way at some point.
-
-        val local = localNode(expr, identifier.name, s"$$${identifier.code}", identifier.typeFullName)
-        scope.addToScope(local.name, local)
-        diffGraph.addEdge(identifier, local, EdgeTypes.REF)
+    scope.lookupVariable(identifier.name).foreach { declaringNode =>
+      diffGraph.addEdge(identifier, declaringNode, EdgeTypes.REF)
     }
 
     Ast(identifier)
@@ -1288,38 +1277,24 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
     callAst(callNode, args.toList)
   }
 
-  private def getTmpLocal(
+  private def getTmpIdentifier(
     originNode: PhpNode,
     maybeTypeFullName: Option[String],
-    lineNumber: Option[Integer],
     prefix: String = ""
-  ): NewLocal = {
-    val name = s"$prefix${getNewTmpName()}"
-
+  ): NewIdentifier = {
+    val name         = s"$prefix${getNewTmpName()}"
     val typeFullName = maybeTypeFullName.map(registerType).getOrElse(TypeConstants.Any)
-
-    val local = localNode(originNode, name, s"$$$name", typeFullName)
-
-    scope.addToScope(name, local)
-
-    local
-  }
-
-  private def identifierAstFromLocal(local: NewLocal, lineNumber: Option[Integer] = None): Ast = {
-    val typeFullName = Option(local.typeFullName).map(registerType)
-    val identifier = newIdentifierNode(local.name, typeFullName.getOrElse("ANY"), Seq(), lineNumber)
-      .code(s"$$${local.name}")
-    Ast(identifier).withRefEdge(identifier, local)
+    identifierNode(originNode, name, s"$$$name", typeFullName)
   }
 
   private def astForArrayExpr(expr: PhpArrayExpr): Ast = {
     val idxTracker   = new ArrayIndexTracker
     val typeFullName = registerType(TypeConstants.Array)
 
-    val tmpLocal = getTmpLocal(expr, Some(typeFullName), line(expr))
+    val tmpIdentifier = getTmpIdentifier(expr, Some(typeFullName))
 
     val itemAssignments = expr.items.flatMap {
-      case Some(item) => Option(assignForArrayItem(item, tmpLocal, idxTracker))
+      case Some(item) => Option(assignForArrayItem(item, tmpIdentifier.name, idxTracker))
       case None =>
         idxTracker.next // Skip an index
         None
@@ -1327,9 +1302,8 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
     val arrayBlock = blockNode(expr)
 
     Ast(arrayBlock)
-      .withChild(Ast(tmpLocal))
       .withChildren(itemAssignments)
-      .withChild(identifierAstFromLocal(tmpLocal))
+      .withChild(Ast(tmpIdentifier))
   }
 
   private def astForListExpr(expr: PhpListExpr): Ast = {
@@ -1456,19 +1430,18 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
 
     // Add closure bindings to diffgraph
     localsForUses.foreach { local =>
-      scope.lookupVariable(local.name).map { capturedNode =>
-        val closureBindingId = s"$filename:$methodName:${local.name}"
-        val closureBindingNode = NewClosureBinding()
-          .closureBindingId(closureBindingId)
-          .closureOriginalName(local.name)
-          .evaluationStrategy(EvaluationStrategies.BY_SHARING)
-
-        local.closureBindingId(closureBindingId)
-
-        diffGraph.addNode(closureBindingNode)
-        diffGraph.addEdge(closureBindingNode, capturedNode, EdgeTypes.REF)
-        diffGraph.addEdge(methodRef, closureBindingNode, EdgeTypes.CAPTURE)
-      }
+      val closureBindingId = s"$filename:$methodName:${local.name}"
+      local.closureBindingId(closureBindingId)
+      scope.addToScope(local.name, local)
+
+      val closureBindingNode = NewClosureBinding()
+        .closureBindingId(closureBindingId)
+        .closureOriginalName(local.name)
+        .evaluationStrategy(EvaluationStrategies.BY_SHARING)
+
+      // The ref edge to the captured local is added in the ClosureRefPass
+      diffGraph.addNode(closureBindingNode)
+      diffGraph.addEdge(methodRef, closureBindingNode, EdgeTypes.CAPTURE)
     }
 
     // Create method for closure
@@ -1532,16 +1505,15 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
         (Option(ast), name)
     }
 
-    val tmpLocal = getTmpLocal(expr, Option(className), line(expr))
+    val tmpIdentifier = getTmpIdentifier(expr, Option(className))
 
     // Alloc assign
     val allocCode       = s"$className.<alloc>()"
     val allocNode       = newOperatorCallNode(Operators.alloc, allocCode, Option(className), line(expr))
     val allocAst        = callAst(allocNode, base = maybeNameAst)
-    val allocAssignCode = s"${tmpLocal.code} = ${allocAst.rootCodeOrEmpty}"
+    val allocAssignCode = s"${tmpIdentifier.code} = ${allocAst.rootCodeOrEmpty}"
     val allocAssignNode = newOperatorCallNode(Operators.assignment, allocAssignCode, Option(className), line(expr))
-    val allocAssignIdentifier = identifierAstFromLocal(tmpLocal, line(expr))
-    val allocAssignAst        = callAst(allocAssignNode, allocAssignIdentifier :: allocAst :: Nil)
+    val allocAssignAst  = callAst(allocAssignNode, Ast(tmpIdentifier) :: allocAst :: Nil)
 
     // Init node
     val initArgs      = expr.args.map(astForCallArg)
@@ -1557,14 +1529,13 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
       Some(initSignature),
       Some(TypeConstants.Any)
     )
-    val initReceiver = identifierAstFromLocal(tmpLocal, line(expr))
+    val initReceiver = Ast(tmpIdentifier.copy)
     val initCallAst  = callAst(initCallNode, initArgs, base = Option(initReceiver))
 
     // Return identifier
-    val returnIdentifierAst = identifierAstFromLocal(tmpLocal, line(expr))
+    val returnIdentifierAst = Ast(tmpIdentifier.copy)
 
     Ast(blockNode(expr, "", TypeConstants.Any))
-      .withChild(Ast(tmpLocal))
       .withChild(allocAssignAst)
       .withChild(initCallAst)
       .withChild(returnIdentifierAst)
@@ -1590,9 +1561,9 @@ class AstCreator(filename: String, phpAst: PhpFile, global: Global)
         scalar
     }
   }
-  private def assignForArrayItem(item: PhpArrayItem, arrayLocal: NewLocal, idxTracker: ArrayIndexTracker): Ast = {
+  private def assignForArrayItem(item: PhpArrayItem, name: String, idxTracker: ArrayIndexTracker): Ast = {
     // It's perhaps a bit clumsy to reconstruct PhpExpr nodes here, but reuse astForArrayDimExpr for consistency
-    val variable = PhpVariable(PhpNameExpr(arrayLocal.name, item.attributes), item.attributes)
+    val variable = PhpVariable(PhpNameExpr(name, item.attributes), item.attributes)
 
     val dimension = item.key match {
       case Some(key: PhpSimpleScalar) => dimensionFromSimpleScalar(key, idxTracker)
diff --git a/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/passes/ClosureRefPass.scala b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/passes/ClosureRefPass.scala
new file mode 100644
index 000000000..bfcb76cad
--- /dev/null
+++ b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/passes/ClosureRefPass.scala
@@ -0,0 +1,66 @@
+package io.joern.php2cpg.passes
+
+import io.shiftleft.codepropertygraph.Cpg
+import io.shiftleft.codepropertygraph.generated.nodes.{ClosureBinding, Method, MethodRef}
+import io.shiftleft.passes.ConcurrentWriterCpgPass
+import io.shiftleft.semanticcpg.language._
+import org.slf4j.LoggerFactory
+import overflowdb.traversal.Traversal
+import io.shiftleft.codepropertygraph.generated.EdgeTypes
+import io.shiftleft.codepropertygraph.generated.nodes.AstNode
+import io.shiftleft.codepropertygraph.generated.nodes.Local
+
+class ClosureRefPass(cpg: Cpg) extends ConcurrentWriterCpgPass[ClosureBinding](cpg) {
+  private val logger = LoggerFactory.getLogger(this.getClass)
+
+  override def generateParts(): Array[ClosureBinding] = cpg.all.collectAll[ClosureBinding].toArray
+
+  /** The AstCreator adds closureBindingIds and ClosureBindings for captured locals, but does not add the required REF
+    * edges from the ClosureBinding to the captured node since the captured node may be a Local that is created by the
+    * LocalCreationPass and does not exist during AST creation.
+    *
+    * This pass attempts to find the captured node in the method containing the MethodRef to the closure method, since
+    * that is the scope in which the closure would have originally been created.
+    */
+  override def runOnPart(diffGraph: DiffGraphBuilder, closureBinding: ClosureBinding): Unit = {
+    closureBinding.captureIn.collectAll[MethodRef].toList match {
+      case Nil =>
+        logger.error(s"No MethodRef corresponding to closureBinding ${closureBinding.closureBindingId}")
+
+      case methodRef :: Nil =>
+        addRefToCapturedNode(diffGraph, closureBinding, getMethod(methodRef))
+
+      case methodRefs =>
+        logger.error(s"Mutliple MethodRefs corresponding to closureBinding ${closureBinding.closureBindingId}")
+        logger.debug(s"${closureBinding.closureBindingId} MethodRefs = ${methodRefs}")
+    }
+  }
+
+  private def getMethod(methodRef: MethodRef): Option[Method] = {
+    methodRef.repeat(_.astParent)(_.until(_.isMethod).emit(_.isMethod)).isMethod.headOption
+  }
+
+  private def addRefToCapturedNode(
+    diffGraph: DiffGraphBuilder,
+    closureBinding: ClosureBinding,
+    method: Option[Method]
+  ): Unit = {
+    method match {
+      case None =>
+        logger.warn(s"No parent method for methodRef for ${closureBinding.closureBindingId}. REF edge will be missing")
+
+      case Some(method) =>
+        closureBinding.closureOriginalName.foreach { name =>
+          lazy val locals = method.repeat(_.astChildren.filterNot(_.isMethod))(_.emit(_.isLocal)).collectAll[Local]
+          val maybeCaptured =
+            method.parameter
+              .find(_.name == name)
+              .orElse(locals.find(_.name == name))
+
+          maybeCaptured.foreach { captured =>
+            diffGraph.addEdge(closureBinding, captured, EdgeTypes.REF)
+          }
+        }
+    }
+  }
+}
diff --git a/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/passes/LocalCreationPass.scala b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/passes/LocalCreationPass.scala
new file mode 100644
index 000000000..4a0635aed
--- /dev/null
+++ b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/passes/LocalCreationPass.scala
@@ -0,0 +1,128 @@
+package io.joern.php2cpg.passes
+
+import io.shiftleft.passes.ConcurrentWriterCpgPass
+import io.shiftleft.codepropertygraph.Cpg
+import io.shiftleft.codepropertygraph.generated.EdgeTypes
+import io.shiftleft.codepropertygraph.generated.nodes.{
+  AstNode,
+  Call,
+  Identifier,
+  Method,
+  NamespaceBlock,
+  NewLocal,
+  NewNode,
+  TypeDecl
+}
+import io.shiftleft.semanticcpg.language._
+import io.joern.php2cpg.astcreation.AstCreator
+import io.joern.php2cpg.parser.Domain
+import io.joern.php2cpg.parser.Domain.PhpOperators
+import io.joern.x2cpg.AstNodeBuilder
+import io.shiftleft.codepropertygraph.generated.PropertyNames
+
+object LocalCreationPass {
+  def allLocalCreationPasses(cpg: Cpg): Iterator[LocalCreationPass[_ <: AstNode]] =
+    Iterator(new NamespaceLocalPass(cpg), new MethodLocalPass(cpg))
+}
+
+abstract class LocalCreationPass[ScopeType <: AstNode](cpg: Cpg)
+    extends ConcurrentWriterCpgPass[ScopeType](cpg)
+    with AstNodeBuilder[AstNode, LocalCreationPass[ScopeType]] {
+  override protected def line(node: AstNode)                       = node.lineNumber
+  override protected def column(node: AstNode)                     = node.columnNumber
+  override protected def lineEnd(node: AstNode): Option[Integer]   = None
+  override protected def columnEnd(node: AstNode): Option[Integer] = None
+
+  protected def getIdentifiersInScope(node: AstNode): List[Identifier] = {
+    node match {
+      case identifier: Identifier                              => identifier :: Nil
+      case _: TypeDecl | _: Method | _: NamespaceBlock         => Nil
+      case _ if node.astChildren.isEmpty                       => Nil
+      case call: Call if call.name == PhpOperators.declareFunc =>
+        // TODO Handle declares properly
+        // but for now don't change behaviour.
+        Nil
+      case _ => node.astChildren.flatMap(getIdentifiersInScope).toList
+    }
+  }
+
+  protected def localsForIdentifiers(
+    identifierMap: Map[String, List[Identifier]]
+  ): List[(NewLocal, List[Identifier])] = {
+    identifierMap
+      .map { case identifierName -> identifiers =>
+        val code = s"$$$identifierName"
+        val local =
+          localNode(identifiers.head, identifierName, code, AstCreator.TypeConstants.Any, closureBindingId = None)
+        (local -> identifiers)
+      }
+      .toList
+      .sortBy { case (local, _) => local.name }
+  }
+
+  protected def addRefEdges(diffGraph: DiffGraphBuilder, localPairs: List[(NewLocal, List[Identifier])]): Unit = {
+    localPairs.foreach { case (local, identifiers) =>
+      identifiers.foreach { identifier =>
+        diffGraph.addEdge(identifier, local, EdgeTypes.REF)
+      }
+    }
+  }
+
+  protected def prependLocalsToBody(diffGraph: DiffGraphBuilder, bodyNode: AstNode, locals: List[NewLocal]): Unit = {
+    val originalChildren = bodyNode.astChildren.l
+
+    bodyNode.outE(EdgeTypes.AST).foreach(diffGraph.removeEdge)
+
+    locals.zipWithIndex.foreach { case (local, idx) =>
+      local.order(idx + 1)
+    }
+
+    val localCount = locals.size
+
+    originalChildren.foreach { node =>
+      diffGraph.setNodeProperty(node, PropertyNames.ORDER, node.order + localCount)
+    }
+
+    (locals ++ originalChildren).foreach { node =>
+      diffGraph.addEdge(bodyNode, node, EdgeTypes.AST)
+    }
+  }
+
+  protected def addLocalsToAst(
+    diffGraph: DiffGraphBuilder,
+    bodyNode: AstNode,
+    excludeIdentifierFn: Identifier => Boolean
+  ): Unit = {
+    val identifierMap =
+      getIdentifiersInScope(bodyNode)
+        .filter(_.refOut.isEmpty)
+        .filterNot(excludeIdentifierFn)
+        .groupBy(_.name)
+
+    val localPairs = localsForIdentifiers(identifierMap)
+
+    if (localPairs.nonEmpty) {
+      val locals = localPairs.map { case (local, _) => local }
+
+      addRefEdges(diffGraph, localPairs)
+      prependLocalsToBody(diffGraph, bodyNode, locals)
+    }
+  }
+}
+
+class NamespaceLocalPass(cpg: Cpg) extends LocalCreationPass[NamespaceBlock](cpg) {
+  override def generateParts(): Array[NamespaceBlock] = cpg.namespaceBlock.toArray
+
+  override def runOnPart(diffGraph: DiffGraphBuilder, namespace: NamespaceBlock): Unit = {
+    addLocalsToAst(diffGraph, namespace, excludeIdentifierFn = _ => false)
+  }
+}
+
+class MethodLocalPass(cpg: Cpg) extends LocalCreationPass[Method](cpg) {
+  override def generateParts(): Array[Method] = cpg.method.internal.toArray
+
+  override def runOnPart(diffGraph: DiffGraphBuilder, method: Method): Unit = {
+    val parameters = method.parameter.name.toSet
+    addLocalsToAst(diffGraph, method.body, excludeIdentifierFn = identifier => parameters.contains(identifier.name))
+  }
+}
diff --git a/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/utils/Scope.scala b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/utils/Scope.scala
index 48e9ed69d..c366dba9a 100644
--- a/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/utils/Scope.scala
+++ b/joern-cli/frontends/php2cpg/src/main/scala/io/joern/php2cpg/utils/Scope.scala
@@ -4,7 +4,7 @@ import io.joern.php2cpg.astcreation.AstCreator.NameConstants
 import io.joern.php2cpg.utils.PhpScopeElement
 import io.joern.x2cpg.Ast
 import io.joern.x2cpg.datastructures.{Scope => X2CpgScope}
-import io.shiftleft.codepropertygraph.generated.nodes.{NewLocal, NewMethod, NewNamespaceBlock, NewNode, NewTypeDecl}
+import io.shiftleft.codepropertygraph.generated.nodes.{NewMethod, NewNamespaceBlock, NewNode, NewTypeDecl}
 import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal
 import org.slf4j.LoggerFactory
 
@@ -14,7 +14,6 @@ class Scope extends X2CpgScope[String, NewNode, PhpScopeElement] {
 
   private val logger = LoggerFactory.getLogger(this.getClass)
 
-  private var localStack: List[mutable.ArrayBuffer[NewLocal]]     = Nil
   private var constAndStaticInits: List[mutable.ArrayBuffer[Ast]] = Nil
   private var fieldInits: List[mutable.ArrayBuffer[Ast]]          = Nil
   private val anonymousMethods                                    = mutable.ArrayBuffer[Ast]()
@@ -22,7 +21,6 @@ class Scope extends X2CpgScope[String, NewNode, PhpScopeElement] {
   def pushNewScope(scopeNode: NewNode): Unit = {
     scopeNode match {
       case method: NewMethod =>
-        localStack = mutable.ArrayBuffer[NewLocal]() :: localStack
         super.pushNewScope(PhpScopeElement(method))
 
       case typeDecl: NewTypeDecl =>
@@ -42,10 +40,6 @@ class Scope extends X2CpgScope[String, NewNode, PhpScopeElement] {
     val scopeNode = super.popScope()
 
     scopeNode.map(_.node) match {
-      case Some(_: NewMethod) =>
-        // TODO This is unsafe to catch errors for now.
-        localStack = localStack.tail
-
       case Some(_: NewTypeDecl) =>
         // TODO This is unsafe to catch errors for now
         constAndStaticInits = constAndStaticInits.tail
@@ -58,12 +52,6 @@ class Scope extends X2CpgScope[String, NewNode, PhpScopeElement] {
   }
 
   override def addToScope(identifier: String, variable: NewNode): PhpScopeElement = {
-    variable match {
-      case local: NewLocal =>
-        // TODO This is unsafe to catch errors for now.
-        localStack.head.addOne(local)
-      case _ => // Nothing to do here
-    }
     super.addToScope(identifier, variable)
   }
 
@@ -81,8 +69,6 @@ class Scope extends X2CpgScope[String, NewNode, PhpScopeElement] {
   def getEnclosingTypeDeclTypeName: Option[String] =
     stack.map(_.scopeNode.node).collectFirst { case td: NewTypeDecl => td }.map(_.name)
 
-  def getLocalsInScope: List[NewLocal] = localStack.headOption.map(_.toList).toList.flatten
-
   def addConstOrStaticInitToScope(ast: Ast): Unit = {
     addInitToScope(ast, constAndStaticInits)
   }
diff --git a/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/astcreation/AstCreator.scala b/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/astcreation/AstCreator.scala
index 4cbfa11fd..5bc9a8781 100644
--- a/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/astcreation/AstCreator.scala
+++ b/joern-cli/frontends/rubysrc2cpg/src/main/scala/io/joern/rubysrc2cpg/astcreation/AstCreator.scala
@@ -7,7 +7,6 @@ import io.joern.x2cpg.{Ast, AstCreatorBase, AstNodeBuilder}
 import io.shiftleft.codepropertygraph.generated.{
   ControlStructureTypes,
   DispatchTypes,
-  EdgeTypes,
   ModifierTypes,
   NodeTypes,
   Operators
@@ -16,12 +15,12 @@ import io.shiftleft.codepropertygraph.generated.nodes._
 import org.antlr.v4.runtime.tree.TerminalNode
 import org.antlr.v4.runtime.{CharStreams, CommonTokenStream, Token}
 import org.slf4j.LoggerFactory
-import overflowdb.{BatchedUpdate, Node}
+import overflowdb.{BatchedUpdate}
 
 import java.util
-import scala.collection.immutable.Seq
 import scala.collection.mutable
 import scala.collection.mutable.ListBuffer
+import scala.io.Source
 import scala.jdk.CollectionConverters._
 
 class AstCreator(filename: String, global: Global)
@@ -109,7 +108,7 @@ class AstCreator(filename: String, global: Global)
     val statementCtx = programCtx.compoundStatement().statements()
     pushScope()
     val statementAsts = if (statementCtx != null) {
-      astsForStatementsContext(statementCtx)
+      astForStatementsContext(statementCtx)
     } else {
       List[Ast](Ast())
     }
@@ -138,7 +137,7 @@ class AstCreator(filename: String, global: Global)
 
     val blockNode = NewBlock().typeFullName(Defines.Any)
     val programAst =
-      methodAst(programMethod, Seq(thisParamAst), blockAst(blockNode, statementAsts), methodRetNode)
+      methodAst(programMethod, Seq(thisParamAst), blockAst(blockNode, statementAsts.toList), methodRetNode)
 
     val fileNode       = NewFile().name(filename).order(1)
     val namespaceBlock = globalNamespaceBlock()
@@ -159,21 +158,21 @@ class AstCreator(filename: String, global: Global)
     }
     typ
   }
-  def astForVariableIdentifierContext(ctx: VariableIdentifierContext): Ast = {
+  def astForVariableIdentifierContext(ctx: VariableIdentifierContext): Seq[Ast] = {
     val terminalNode = ctx.children.asScala.map(_.asInstanceOf[TerminalNode]).head
     val token        = terminalNode.getSymbol
     val variableName = token.getText
     val node         = createIdentiferWithScope(terminalNode, variableName, variableName, Defines.Any, List[String]())
     setIdentiferInScope(node)
-    Ast(node)
+    Seq(Ast(node))
   }
 
-  def astForSingleLeftHandSideContext(ctx: SingleLeftHandSideContext): Ast = ctx match {
+  def astForSingleLeftHandSideContext(ctx: SingleLeftHandSideContext): Seq[Ast] = ctx match {
     case ctx: VariableIdentifierOnlySingleLeftHandSideContext =>
       astForVariableIdentifierContext(ctx.variableIdentifier())
     case ctx: PrimaryInsideBracketsSingleLeftHandSideContext =>
-      val primaryAst = astForPrimaryContext(ctx.primary())
-      val argsAst    = astForArgumentsContext(ctx.arguments())
+      val primaryAsts = astForPrimaryContext(ctx.primary())
+      val argsAsts    = astForArgumentsContext(ctx.arguments())
       val callNode = NewCall()
         .name(Operators.indexAccess)
         .code(Operators.indexAccess)
@@ -183,9 +182,9 @@ class AstCreator(filename: String, global: Global)
         .typeFullName(Defines.Any)
         .lineNumber(ctx.LBRACK().getSymbol.getLine)
         .columnNumber(ctx.LBRACK().getSymbol.getCharPositionInLine())
-      callAst(callNode, Seq(primaryAst, argsAst))
+      Seq(callAst(callNode, primaryAsts ++ argsAsts))
     case ctx: XdotySingleLeftHandSideContext =>
-      val xAst = astForPrimaryContext(ctx.primary())
+      val xAsts = astForPrimaryContext(ctx.primary())
       val localVar = {
         if (ctx.LOCAL_VARIABLE_IDENTIFIER() != null) {
           ctx.LOCAL_VARIABLE_IDENTIFIER()
@@ -209,39 +208,42 @@ class AstCreator(filename: String, global: Global)
         .typeFullName(Defines.Any)
         .lineNumber(localVar.getSymbol.getLine)
         .columnNumber(localVar.getSymbol.getCharPositionInLine())
-      callAst(callNode, Seq(xAst, yAst))
+      Seq(callAst(callNode, xAsts ++ Seq(yAst)))
     case ctx: ScopedConstantAccessSingleLeftHandSideContext =>
       // TODO to be implemented
-      Ast()
+      Seq(Ast())
     case _ =>
       logger.error("astForSingleLeftHandSideContext() All contexts mismatched.")
-      Ast()
+      Seq(Ast())
 
   }
 
   def astForExpressionOrCommandsContext(ctx: ExpressionOrCommandsContext): Seq[Ast] = {
-    ctx.expressionOrCommand().asScala.map(astForExpressionOrCommandContext).toSeq
+    ctx
+      .expressionOrCommand()
+      .asScala
+      .flatMap(ec => astForExpressionOrCommandContext(ec))
+      .toSeq
   }
 
-  def astForSplattingArgumentContext(ctx: SplattingArgumentContext): Ast = {
-    if (ctx == null) return Ast()
+  def astForSplattingArgumentContext(ctx: SplattingArgumentContext): Seq[Ast] = {
+    if (ctx == null) return Seq(Ast())
     astForExpressionOrCommandContext(ctx.expressionOrCommand())
   }
 
-  def astForMultipleRightHandSideContext(ctx: MultipleRightHandSideContext): Ast = {
-    if (ctx == null) return Ast()
+  def astForMultipleRightHandSideContext(ctx: MultipleRightHandSideContext): Seq[Ast] = {
+    if (ctx == null) return Seq(Ast())
 
     val exprAsts = astForExpressionOrCommandsContext(ctx.expressionOrCommands())
 
     val paramAsts = if (ctx.splattingArgument() != null) {
-      val splattingAst = astForSplattingArgumentContext(ctx.splattingArgument())
-      exprAsts ++ Seq(splattingAst)
+      val splattingAsts = astForSplattingArgumentContext(ctx.splattingArgument())
+      exprAsts ++ splattingAsts
     } else {
       exprAsts
     }
 
     if (paramAsts.size > 1) {
-
       val callNode = NewCall()
         .name(Operators.arrayInitializer)
         .code(ctx.getText)
@@ -249,13 +251,13 @@ class AstCreator(filename: String, global: Global)
         .signature("")
         .dispatchType(DispatchTypes.STATIC_DISPATCH)
         .typeFullName(Defines.Any)
-      callAst(callNode, paramAsts)
+      Seq(callAst(callNode, paramAsts))
     } else {
-      paramAsts.head
+      paramAsts
     }
   }
 
-  def astForSingleAssignmentExpressionContext(ctx: SingleAssignmentExpressionContext): Ast = {
+  def astForSingleAssignmentExpressionContext(ctx: SingleAssignmentExpressionContext): Seq[Ast] = {
     val rightAst = astForMultipleRightHandSideContext(ctx.multipleRightHandSide())
     val leftAst  = astForSingleLeftHandSideContext(ctx.singleLeftHandSide())
     val callNode = NewCall()
@@ -267,15 +269,15 @@ class AstCreator(filename: String, global: Global)
       .typeFullName(Defines.Any)
       .lineNumber(ctx.op.getLine())
       .columnNumber(ctx.op.getCharPositionInLine())
-    callAst(callNode, Seq(leftAst) ++ Seq(rightAst))
+    Seq(callAst(callNode, leftAst ++ rightAst))
   }
 
-  def astForStringInterpolationPrimaryContext(ctx: StringInterpolationPrimaryContext): Ast = {
+  def astForStringInterpolationPrimaryContext(ctx: StringInterpolationPrimaryContext): Seq[Ast] = {
     val varAsts = ctx
       .stringInterpolation()
       .interpolatedStringSequence()
       .asScala
-      .map(inter => {
+      .flatMap(inter => {
         astForStatementsContext(inter.compoundStatement().statements())
       })
       .toSeq
@@ -293,11 +295,10 @@ class AstCreator(filename: String, global: Global)
         }
       }
       .toSeq
-    val strAst = Ast(nodes)
-    Ast().withChildren(varAsts).merge(strAst)
+    varAsts ++ Seq(Ast(nodes))
   }
 
-  def astForPrimaryContext(ctx: PrimaryContext): Ast = ctx match {
+  def astForPrimaryContext(ctx: PrimaryContext): Seq[Ast] = ctx match {
     case ctx: ClassDefinitionPrimaryContext           => astForClassDefinitionPrimaryContext(ctx)
     case ctx: ModuleDefinitionPrimaryContext          => astForModuleDefinitionPrimaryContext(ctx)
     case ctx: MethodDefinitionPrimaryContext          => astForMethodDefinitionContext(ctx.methodDefinition())
@@ -331,10 +332,10 @@ class AstCreator(filename: String, global: Global)
       astForChainedInvocationWithoutArgumentsPrimaryContext(ctx)
     case _ =>
       logger.error("astForPrimaryContext() All contexts mismatched.")
-      Ast()
+      Seq(Ast())
   }
 
-  def astForExpressionContext(ctx: ExpressionContext): Ast = ctx match {
+  def astForExpressionContext(ctx: ExpressionContext): Seq[Ast] = ctx match {
     case ctx: PrimaryExpressionContext             => astForPrimaryContext(ctx.primary())
     case ctx: UnaryExpressionContext               => astForUnaryExpressionContext(ctx)
     case ctx: PowerExpressionContext               => astForPowerExpressionContext(ctx)
@@ -355,11 +356,11 @@ class AstCreator(filename: String, global: Global)
     case ctx: IsDefinedExpressionContext           => astForIsDefinedExpressionContext(ctx)
     case _ =>
       logger.error("astForExpressionContext() All contexts mismatched.")
-      Ast()
+      Seq(Ast())
   }
 
-  def astForExpressionOrCommandContext(ctx: ExpressionOrCommandContext): Ast = {
-    if (ctx == null) return Ast()
+  def astForExpressionOrCommandContext(ctx: ExpressionOrCommandContext): Seq[Ast] = {
+    if (ctx == null) return Seq(Ast())
 
     ctx match {
       case ctx: InvocationExpressionOrCommandContext => astForInvocationExpressionOrCommandContext(ctx)
@@ -368,29 +369,29 @@ class AstCreator(filename: String, global: Global)
       case ctx: ExpressionExpressionOrCommandContext => astForExpressionContext(ctx.expression())
       case _ =>
         logger.error("astForExpressionOrCommandContext() All contexts mismatched.")
-        Ast()
+        Seq(Ast())
     }
   }
 
-  def astForSymbolContext(ctx: SymbolContext): Ast = {
+  def astForSymbolContext(ctx: SymbolContext): Seq[Ast] = {
     val text =
       if (ctx.SYMBOL_LITERAL() != null) {
         ctx.getText
       } else if (ctx.SINGLE_QUOTED_STRING_LITERAL() != null) {
         ctx.getText
       } else {
-        return Ast()
+        return Seq(Ast())
       }
 
     val node = NewLiteral()
       .code(text)
       .typeFullName(Defines.String)
       .dynamicTypeHintFullName(List(Defines.String))
-    Ast(node)
+    Seq(Ast(node))
   }
 
-  def astForDefinedMethodNameOrSymbolContext(ctx: DefinedMethodNameOrSymbolContext): Ast = {
-    if (ctx == null) return Ast()
+  def astForDefinedMethodNameOrSymbolContext(ctx: DefinedMethodNameOrSymbolContext): Seq[Ast] = {
+    if (ctx == null) return Seq(Ast())
 
     if (ctx.definedMethodName() != null) {
       astForDefinedMethodNameContext(ctx.definedMethodName())
@@ -399,12 +400,12 @@ class AstCreator(filename: String, global: Global)
     }
   }
 
-  def astForAliasStatementContext(ctx: AliasStatementContext): Ast = {
+  def astForAliasStatementContext(ctx: AliasStatementContext): Seq[Ast] = {
     val aliasName = astForDefinedMethodNameOrSymbolContext(
       ctx
         .definedMethodNameOrSymbol()
         .get(0)
-    ).nodes.head
+    ).head.nodes.head
       .asInstanceOf[NewLiteral]
       .code
       .substring(1)
@@ -413,44 +414,44 @@ class AstCreator(filename: String, global: Global)
       ctx
         .definedMethodNameOrSymbol()
         .get(1)
-    ).nodes.head
+    ).head.nodes.head
       .asInstanceOf[NewLiteral]
       .code
       .substring(1)
 
     methodAliases.addOne(aliasName, methodName)
-    Ast()
+    Seq(Ast())
   }
 
-  def astForUndefStatementContext(ctx: UndefStatementContext): Ast = {
+  def astForUndefStatementContext(ctx: UndefStatementContext): Seq[Ast] = {
     // TODO to be implemented
-    Ast()
+    Seq(Ast())
   }
 
-  def astForBeginStatementContext(ctx: BeginStatementContext): Ast = {
-    val astStmts  = astsForStatementsContext(ctx.statements())
+  def astForBeginStatementContext(ctx: BeginStatementContext): Seq[Ast] = {
+    val astStmts  = astForStatementsContext(ctx.statements())
     val blockNode = NewBlock().typeFullName(Defines.Any)
-    blockAst(blockNode, astStmts)
+    Seq(blockAst(blockNode, astStmts.toList))
   }
 
-  def astForEndStatementContext(ctx: EndStatementContext): Ast = {
-    val astStmts  = astsForStatementsContext(ctx.statements())
+  def astForEndStatementContext(ctx: EndStatementContext): Seq[Ast] = {
+    val astStmts  = astForStatementsContext(ctx.statements())
     val blockNode = NewBlock().typeFullName(Defines.Any)
-    blockAst(blockNode, astStmts)
+    Seq(blockAst(blockNode, astStmts.toList))
   }
 
-  def astForModifierStatementContext(ctx: ModifierStatementContext): Ast = {
+  def astForModifierStatementContext(ctx: ModifierStatementContext): Seq[Ast] = {
     if (ctx.statement().size() != 2) {
       // unsupported or invalid modifier statement
-      return Ast()
+      return Seq(Ast())
     }
 
     val rightAst        = astForStatementContext(ctx.statement(1))
     val leftAst         = astForStatementContext(ctx.statement(0))
-    val ctrlStructNodes = leftAst.nodes.filter(node => node.isInstanceOf[NewControlStructure])
+    val ctrlStructNodes = leftAst.head.nodes.filter(node => node.isInstanceOf[NewControlStructure])
 
     if (ctrlStructNodes.size > 1) {
-      return Ast() // there cannot be multiple of these. some issue with the code or the parser
+      return Seq(Ast()) // there cannot be multiple of these. some issue with the code or the parser
     }
 
     if (ctrlStructNodes.size == 1) {
@@ -471,9 +472,11 @@ class AstCreator(filename: String, global: Global)
         .lineNumber(ctrlContinue.lineNumber)
         .columnNumber(ctrlContinue.columnNumber)
         .code(ctx.getText)
-      Ast(node)
-        .withConditionEdge(node, rightAst.nodes.head)
-        .withChild(rightAst)
+      Seq(
+        Ast(node)
+          .withConditionEdge(node, rightAst.head.nodes.head)
+          .withChildren(rightAst)
+      )
     } else {
       /*
        * This is <stmt> if/unless/while/until/rescue <stmt>
@@ -495,14 +498,16 @@ class AstCreator(filename: String, global: Global)
         .lineNumber(ctx.mod.getLine)
         .columnNumber(ctx.mod.getCharPositionInLine)
         .code(ctx.getText)
-      Ast(node)
-        .withConditionEdge(node, rightAst.nodes.head)
-        .withChild(rightAst)
-        .withChild(leftAst)
+      Seq(
+        Ast(node)
+          .withConditionEdge(node, rightAst.head.nodes.head)
+          .withChildren(rightAst)
+          .withChildren(leftAst)
+      )
     }
   }
 
-  def astForStatementContext(ctx: StatementContext): Ast = ctx match {
+  def astForStatementContext(ctx: StatementContext): Seq[Ast] = ctx match {
     case ctx: AliasStatementContext               => astForAliasStatementContext(ctx)
     case ctx: UndefStatementContext               => astForUndefStatementContext(ctx)
     case ctx: BeginStatementContext               => astForBeginStatementContext(ctx)
@@ -511,32 +516,26 @@ class AstCreator(filename: String, global: Global)
     case ctx: ExpressionOrCommandStatementContext => astForExpressionOrCommandContext(ctx.expressionOrCommand())
     case _ =>
       logger.error("astForStatementContext() All contexts mismatched.")
-      Ast()
+      Seq(Ast())
   }
 
-  def astForStatementsContext(ctx: StatementsContext): Ast = {
-    if (ctx == null) return Ast()
-
-    val blockNode = NewBlock().typeFullName(Defines.Any)
-    val asts      = astsForStatementsContext(ctx)
-    blockAst(blockNode, asts)
-  }
-
-  def astsForStatementsContext(ctx: StatementsContext): List[Ast] = {
+  def astForStatementsContext(ctx: StatementsContext): Seq[Ast] = {
+    if (ctx == null) return Seq(Ast())
     ctx
       .statement()
       .asScala
-      .map(st => {
-        astForStatementContext(st)
+      .flatMap(st => {
+        val asts = astForStatementContext(st)
+        asts
       })
-      .toList
+      .toSeq
   }
 
-  def astForAdditiveExpressionContext(ctx: AdditiveExpressionContext): Ast = {
+  def astForAdditiveExpressionContext(ctx: AdditiveExpressionContext): Seq[Ast] = {
     astForBinaryExpression(ctx.expression(0), ctx.expression(1), ctx.op)
   }
 
-  def astForIndexingArgumentsContext(ctx: IndexingArgumentsContext): Ast = ctx match {
+  def astForIndexingArgumentsContext(ctx: IndexingArgumentsContext): Seq[Ast] = ctx match {
     case ctx: RubyParser.CommandOnlyIndexingArgumentsContext =>
       astForCommandContext(ctx.command())
     case ctx: RubyParser.ExpressionsOnlyIndexingArgumentsContext =>
@@ -545,7 +544,7 @@ class AstCreator(filename: String, global: Global)
           .expressions()
           .expression()
           .asScala
-          .map(exp => {
+          .flatMap(exp => {
             astForExpressionContext(exp)
           })
           .toSeq
@@ -556,17 +555,17 @@ class AstCreator(filename: String, global: Global)
         .typeFullName(MethodFullNames.UnknownFullName)
         .dispatchType(DispatchTypes.STATIC_DISPATCH)
         .code(ctx.getText)
-      callAst(callNode, expAsts)
+      Seq(callAst(callNode, expAsts))
     case ctx: RubyParser.ExpressionsAndSplattingIndexingArgumentsContext =>
       val expAsts = ctx
         .expressions()
         .expression()
         .asScala
-        .map(exp => {
+        .flatMap(exp => {
           astForExpressionContext(exp)
         })
         .toSeq
-      val splatAst = astForSplattingArgumentContext(ctx.splattingArgument())
+      val splatAsts = astForSplattingArgumentContext(ctx.splattingArgument())
       val callNode = NewCall()
         .name(ctx.COMMA().getText)
         .methodFullName(Operators.arrayInitializer)
@@ -576,59 +575,59 @@ class AstCreator(filename: String, global: Global)
         .code(ctx.getText)
         .lineNumber(ctx.COMMA().getSymbol.getLine)
         .columnNumber(ctx.COMMA().getSymbol.getCharPositionInLine)
-      callAst(callNode, expAsts ++ Seq(splatAst))
+      Seq(callAst(callNode, expAsts ++ splatAsts))
     case ctx: AssociationsOnlyIndexingArgumentsContext =>
       astForAssociationsContext(ctx.associations())
     case ctx: RubyParser.SplattingOnlyIndexingArgumentsContext =>
       astForSplattingArgumentContext(ctx.splattingArgument())
     case _ =>
       logger.error("astForIndexingArgumentsContext() All contexts mismatched.")
-      Ast()
+      Seq(Ast())
   }
 
-  def astForArrayConstructorPrimaryContext(ctx: ArrayConstructorPrimaryContext): Ast = {
+  def astForArrayConstructorPrimaryContext(ctx: ArrayConstructorPrimaryContext): Seq[Ast] = {
     astForIndexingArgumentsContext(ctx.arrayConstructor().indexingArguments())
   }
 
-  def astForBeginExpressionPrimaryContext(ctx: BeginExpressionPrimaryContext): Ast = {
+  def astForBeginExpressionPrimaryContext(ctx: BeginExpressionPrimaryContext): Seq[Ast] = {
     astForBodyStatementContext(ctx.beginExpression().bodyStatement())
   }
 
-  def astForBitwiseAndExpressionContext(ctx: BitwiseAndExpressionContext): Ast = {
+  def astForBitwiseAndExpressionContext(ctx: BitwiseAndExpressionContext): Seq[Ast] = {
     astForBinaryExpression(ctx.expression(0), ctx.expression(1), ctx.op)
   }
 
-  def astForBitwiseOrExpressionContext(ctx: BitwiseOrExpressionContext): Ast = {
+  def astForBitwiseOrExpressionContext(ctx: BitwiseOrExpressionContext): Seq[Ast] = {
     astForBinaryExpression(ctx.expression(0), ctx.expression(1), ctx.op)
   }
 
-  def astForBitwiseShiftExpressionContext(ctx: BitwiseShiftExpressionContext): Ast = {
+  def astForBitwiseShiftExpressionContext(ctx: BitwiseShiftExpressionContext): Seq[Ast] = {
     astForBinaryExpression(ctx.expression(0), ctx.expression(1), ctx.op)
   }
 
-  def astForWhenArgumentContext(ctx: WhenArgumentContext): Ast = {
+  def astForWhenArgumentContext(ctx: WhenArgumentContext): Seq[Ast] = {
     val expAsts =
       ctx
         .expressions()
         .expression()
         .asScala
-        .map(exp => {
+        .flatMap(exp => {
           astForExpressionContext(exp)
         })
         .toList
 
     val asts =
       if (ctx.splattingArgument() != null) {
-        expAsts ++ List[Ast](astForSplattingArgumentContext(ctx.splattingArgument()))
+        expAsts ++ astForSplattingArgumentContext(ctx.splattingArgument())
       } else {
         expAsts
       }
 
     val blockNode = NewBlock().typeFullName(Defines.Any)
-    blockAst(blockNode, asts)
+    Seq(blockAst(blockNode, asts))
   }
 
-  def astForCaseExpressionPrimaryContext(ctx: CaseExpressionPrimaryContext): Ast = {
+  def astForCaseExpressionPrimaryContext(ctx: CaseExpressionPrimaryContext): Seq[Ast] = {
     val whenThenAstsList = ctx
       .caseExpression()
       .whenClause()
@@ -640,11 +639,11 @@ class AstCreator(filename: String, global: Global)
           .lineNumber(wh.WHEN().getSymbol.getLine)
           .columnNumber(wh.WHEN().getSymbol.getCharPositionInLine)
 
-        val whenACondAst = astForWhenArgumentContext(wh.whenArgument())
-        val thenAsts     = astForThenClauseContext(wh.thenClause())
+        val whenACondAsts = astForWhenArgumentContext(wh.whenArgument())
+        val thenAsts      = astForThenClauseContext(wh.thenClause())
         Ast(whenNode)
-          .withChild(whenACondAst)
-          .withConditionEdge(whenNode, whenACondAst.nodes.head)
+          .withChildren(whenACondAsts)
+          .withConditionEdge(whenNode, whenACondAsts.head.nodes.head)
           .withChildren(thenAsts)
       })
       .toList
@@ -660,10 +659,10 @@ class AstCreator(filename: String, global: Global)
 
     val condAst = {
       if (ctx.caseExpression().expressionOrCommand() != null) {
-        val caseExpressionAst = astForExpressionOrCommandContext(ctx.caseExpression().expressionOrCommand())
+        val caseExpressionAsts = astForExpressionOrCommandContext(ctx.caseExpression().expressionOrCommand())
         baseAst
-          .withChild(caseExpressionAst)
-          .withConditionEdge(caseNode, caseExpressionAst.nodes.head)
+          .withChildren(caseExpressionAsts)
+          .withConditionEdge(caseNode, caseExpressionAsts.head.nodes.head)
       } else {
         baseAst
       }
@@ -671,27 +670,28 @@ class AstCreator(filename: String, global: Global)
 
     if (ctx.caseExpression().elseClause() != null) {
       val elseAst = astForElseClauseContext(ctx.caseExpression().elseClause())
-      condAst.withChildren(whenThenAstsList).withChild(elseAst)
+      Seq(condAst.withChildren(whenThenAstsList).withChildren(elseAst))
     } else {
-      condAst.withChildren(whenThenAstsList)
+      Seq(condAst.withChildren(whenThenAstsList))
     }
   }
 
-  def astForChainedInvocationPrimaryContext(ctx: ChainedInvocationPrimaryContext): Ast = {
+  def astForChainedInvocationPrimaryContext(ctx: ChainedInvocationPrimaryContext): Seq[Ast] = {
     val methodNameAst = astForMethodNameContext(ctx.methodName())
 
     val argList = ListBuffer[Ast]()
-    argList.addOne(astForPrimaryContext(ctx.primary()))
+    argList.addAll(astForPrimaryContext(ctx.primary()))
 
     if (ctx.argumentsWithParentheses() != null) {
-      argList.addOne(astForArgumentsWithParenthesesContext(ctx.argumentsWithParentheses()))
+      argList.addAll(astForArgumentsWithParenthesesContext(ctx.argumentsWithParentheses()))
     }
 
     if (ctx.block() != null) {
-      argList.addOne(astForBlockContext(ctx.block()))
+      argList.addAll(astForBlockContext(ctx.block()))
     }
 
-    val identifierNodes = methodNameAst.nodes.filter(node => node.isInstanceOf[NewIdentifier])
+    val identifierNodes = methodNameAst.head.nodes
+      .filter(node => node.isInstanceOf[NewIdentifier])
     if (identifierNodes.size > 0) {
       // this is a object.member access. The methodNameAst contains the object whose member is being accessed
       val terminalNode = if (ctx.COLON2() != null) {
@@ -708,29 +708,29 @@ class AstCreator(filename: String, global: Global)
         .typeFullName(Defines.Any)
         .lineNumber(terminalNode.getSymbol().getLine())
         .columnNumber(terminalNode.getSymbol().getCharPositionInLine())
-      callAst(callNode, Seq(methodNameAst) ++ argList.toSeq)
+      Seq(callAst(callNode, methodNameAst ++ argList))
     } else {
       // this is a object.method(args) access
-      val callNode = methodNameAst.nodes.filter(node => node.isInstanceOf[NewCall]).head.asInstanceOf[NewCall]
-      callAst(callNode, argList.toSeq)
+      val callNode = methodNameAst.head.nodes.filter(node => node.isInstanceOf[NewCall]).head.asInstanceOf[NewCall]
+      Seq(callAst(callNode, argList.toSeq))
     }
   }
 
   def astForChainedInvocationWithoutArgumentsPrimaryContext(
     ctx: ChainedInvocationWithoutArgumentsPrimaryContext
-  ): Ast = {
+  ): Seq[Ast] = {
     val primaryAst    = astForPrimaryContext(ctx.primary())
     val methodNameAst = astForMethodNameContext(ctx.methodName())
-    val blockAst = if (ctx.block() != null) {
-      astForBlockContext(ctx.block())
+    if (ctx.block() != null) {
+      primaryAst ++ methodNameAst ++ astForBlockContext(ctx.block())
     } else {
-      Ast()
+      primaryAst ++ methodNameAst
     }
-
-    primaryAst.withChild(methodNameAst).withChild(blockAst)
   }
 
-  def astForChainedScopedConstantReferencePrimaryContext(ctx: ChainedScopedConstantReferencePrimaryContext): Ast = {
+  def astForChainedScopedConstantReferencePrimaryContext(
+    ctx: ChainedScopedConstantReferencePrimaryContext
+  ): Seq[Ast] = {
     val primaryAst = astForPrimaryContext(ctx.primary())
     val localVar   = ctx.CONSTANT_IDENTIFIER()
     val varSymbol  = localVar.getSymbol()
@@ -746,7 +746,7 @@ class AstCreator(filename: String, global: Global)
       .typeFullName(Defines.Any)
       .lineNumber(ctx.COLON2().getSymbol().getLine())
       .columnNumber(ctx.COLON2().getSymbol().getCharPositionInLine())
-    callAst(callNode, Seq(primaryAst, constAst))
+    Seq(callAst(callNode, primaryAst ++ Seq(constAst)))
   }
 
   private def getClassNameScopedConstantReferenceContext(ctx: ScopedConstantReferenceContext): String = {
@@ -754,7 +754,7 @@ class AstCreator(filename: String, global: Global)
 
     if (ctx.primary() != null) {
       val primaryAst = astForPrimaryContext(ctx.primary())
-      val moduleNameNode = primaryAst.nodes
+      val moduleNameNode = primaryAst.head.nodes
         .filter(node => node.isInstanceOf[NewIdentifier])
         .head
         .asInstanceOf[NewIdentifier]
@@ -768,7 +768,7 @@ class AstCreator(filename: String, global: Global)
   def astForClassOrModuleReferenceContext(
     ctx: ClassOrModuleReferenceContext,
     baseClassName: Option[String] = None
-  ): Ast = {
+  ): Seq[Ast] = {
     val className = if (ctx.scopedConstantReference() != null) {
       getClassNameScopedConstantReferenceContext(ctx.scopedConstantReference())
     } else if (ctx.CONSTANT_IDENTIFIER() != null) {
@@ -783,14 +783,14 @@ class AstCreator(filename: String, global: Global)
     if (className != Defines.Any) {
       classStack.push(className)
     }
-    Ast()
+    Seq(Ast())
   }
 
-  def astForClassDefinitionPrimaryContext(ctx: ClassDefinitionPrimaryContext): Ast = {
+  def astForClassDefinitionPrimaryContext(ctx: ClassDefinitionPrimaryContext): Seq[Ast] = {
     if (ctx.classDefinition().classOrModuleReference() != null) {
       val baseClassName = if (ctx.classDefinition().expressionOrCommand() != null) {
         val parentClassNameAst = astForExpressionOrCommandContext(ctx.classDefinition().expressionOrCommand())
-        val nameNode = parentClassNameAst.nodes
+        val nameNode = parentClassNameAst.head.nodes
           .filter(node => node.isInstanceOf[NewIdentifier])
           .head
           .asInstanceOf[NewIdentifier]
@@ -802,20 +802,34 @@ class AstCreator(filename: String, global: Global)
       val classOrModuleRefAst =
         astForClassOrModuleReferenceContext(ctx.classDefinition().classOrModuleReference(), baseClassName)
       val bodyAst = astForBodyStatementContext(ctx.classDefinition().bodyStatement())
+      val bodyAstSansModifiers = bodyAst
+        .filterNot(ast => {
+          val nodes = ast.nodes
+            .filter(_.isInstanceOf[NewIdentifier])
+
+          if (nodes.size == 1) {
+            val varName = nodes
+              .map(_.asInstanceOf[NewIdentifier].name)
+              .head
+            varName == "public" || varName == "protected" || varName == "private"
+          } else {
+            false
+          }
+        })
 
       if (classStack.size > 0) {
         classStack.pop()
       }
-      classOrModuleRefAst.withChild(bodyAst)
+      Seq(classOrModuleRefAst.head.withChildren(bodyAstSansModifiers))
     } else {
       // TODO test for this is pending due to lack of understanding to generate an example
       val astExprOfCommand = astForExpressionOrCommandContext(ctx.classDefinition().expressionOrCommand())
       val astBodyStatement = astForBodyStatementContext(ctx.classDefinition().bodyStatement())
-      Ast().withChildren(Seq(astExprOfCommand, astBodyStatement))
+      astExprOfCommand ++ astBodyStatement
     }
   }
 
-  def astForConditionalOperatorExpressionContext(ctx: ConditionalOperatorExpressionContext): Ast = {
+  def astForConditionalOperatorExpressionContext(ctx: ConditionalOperatorExpressionContext): Seq[Ast] = {
     val conditionAst = astForExpressionContext(ctx.expression().get(0))
     val thenAst      = astForExpressionContext(ctx.expression().get(1))
     val elseAst      = astForExpressionContext(ctx.expression().get(2))
@@ -826,31 +840,33 @@ class AstCreator(filename: String, global: Global)
       .lineNumber(ctx.QMARK().getSymbol.getLine)
       .columnNumber(ctx.QMARK().getSymbol.getCharPositionInLine)
 
-    Ast(ifNode)
-      .withChild(conditionAst)
-      .withConditionEdge(ifNode, conditionAst.nodes.head)
-      .withChild(thenAst)
-      .withChild(elseAst)
+    Seq(
+      Ast(ifNode)
+        .withChildren(conditionAst)
+        .withConditionEdge(ifNode, conditionAst.head.nodes.head)
+        .withChildren(thenAst)
+        .withChildren(elseAst)
+    )
   }
 
-  def astForEqualityExpressionContext(ctx: EqualityExpressionContext): Ast = {
+  def astForEqualityExpressionContext(ctx: EqualityExpressionContext): Seq[Ast] = {
     astForBinaryExpression(ctx.expression(0), ctx.expression(1), ctx.op)
   }
 
-  def astForGroupedLeftHandSideContext(ctx: GroupedLeftHandSideContext): Ast = {
+  def astForGroupedLeftHandSideContext(ctx: GroupedLeftHandSideContext): Seq[Ast] = {
     astForMultipleLeftHandSideContext(ctx.multipleLeftHandSide())
   }
 
-  def astForPackingLeftHandSideContext(ctx: PackingLeftHandSideContext): Ast = {
+  def astForPackingLeftHandSideContext(ctx: PackingLeftHandSideContext): Seq[Ast] = {
     astForSingleLeftHandSideContext(ctx.singleLeftHandSide())
   }
 
-  def astForMultipleLeftHandSideContext(ctx: MultipleLeftHandSideContext): Ast = ctx match {
+  def astForMultipleLeftHandSideContext(ctx: MultipleLeftHandSideContext): Seq[Ast] = ctx match {
     case ctx: MultipleLeftHandSideAndpackingLeftHandSideMultipleLeftHandSideContext =>
       val multipleLHSAsts = ctx
         .multipleLeftHandSideItem()
         .asScala
-        .map(item => {
+        .flatMap(item => {
           if (item.singleLeftHandSide() != null) {
             astForSingleLeftHandSideContext(item.singleLeftHandSide())
           } else {
@@ -862,7 +878,7 @@ class AstCreator(filename: String, global: Global)
       val paramAsts =
         if (ctx.packingLeftHandSide() != null) {
           val packingLHSAst = astForPackingLeftHandSideContext(ctx.packingLeftHandSide())
-          Seq(packingLHSAst) ++ multipleLHSAsts
+          packingLHSAst ++ multipleLHSAsts
         } else {
           multipleLHSAsts
         }
@@ -875,9 +891,9 @@ class AstCreator(filename: String, global: Global)
           .signature("")
           .dispatchType(DispatchTypes.STATIC_DISPATCH)
           .typeFullName(Defines.Any)
-        callAst(callNode, paramAsts)
+        Seq(callAst(callNode, paramAsts))
       } else {
-        paramAsts.head
+        paramAsts
       }
 
     case ctx: PackingLeftHandSideOnlyMultipleLeftHandSideContext =>
@@ -886,20 +902,20 @@ class AstCreator(filename: String, global: Global)
       astForGroupedLeftHandSideContext(ctx.groupedLeftHandSide())
     case _ =>
       logger.error("astForMultipleLeftHandSideContext() All contexts mismatched.")
-      Ast()
+      Seq(Ast())
   }
 
-  def astForForVariableContext(ctx: ForVariableContext): Ast = {
+  def astForForVariableContext(ctx: ForVariableContext): Seq[Ast] = {
     if (ctx.singleLeftHandSide() != null) {
       astForSingleLeftHandSideContext(ctx.singleLeftHandSide())
     } else if (ctx.multipleLeftHandSide() != null) {
       astForMultipleLeftHandSideContext(ctx.multipleLeftHandSide())
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
 
-  def astForForExpressionContext(ctx: ForExpressionContext): Ast = {
+  def astForForExpressionContext(ctx: ForExpressionContext): Seq[Ast] = {
     val forVarAst  = astForForVariableContext(ctx.forVariable())
     val forCondAst = astForExpressionOrCommandContext(ctx.expressionOrCommand())
 
@@ -910,24 +926,26 @@ class AstCreator(filename: String, global: Global)
       .columnNumber(ctx.FOR().getSymbol.getCharPositionInLine)
     val doClauseAst = astForDoClauseContext(ctx.doClause())
 
-    Ast(forNode)
-      .withChild(forVarAst)
-      .withChild(forCondAst)
-      .withConditionEdge(forNode, forCondAst.nodes.head)
-      .withChild(doClauseAst)
+    Seq(
+      Ast(forNode)
+        .withChildren(forVarAst)
+        .withChildren(forCondAst)
+        .withConditionEdge(forNode, forCondAst.head.nodes.head)
+        .withChildren(doClauseAst)
+    )
   }
 
-  def astForGroupingExpressionPrimaryContext(ctx: GroupingExpressionPrimaryContext): Ast = {
+  def astForGroupingExpressionPrimaryContext(ctx: GroupingExpressionPrimaryContext): Seq[Ast] = {
     astForStatementsContext(ctx.compoundStatement().statements())
   }
 
-  def astForHashConstructorPrimaryContext(ctx: HashConstructorPrimaryContext): Ast = {
-    if (ctx.hashConstructor().associations() == null) return Ast()
+  def astForHashConstructorPrimaryContext(ctx: HashConstructorPrimaryContext): Seq[Ast] = {
+    if (ctx.hashConstructor().associations() == null) return Seq(Ast())
     astForAssociationsContext(ctx.hashConstructor().associations())
   }
 
-  def astForThenClauseContext(ctx: ThenClauseContext): List[Ast] = {
-    astsForStatementsContext(ctx.compoundStatement().statements())
+  def astForThenClauseContext(ctx: ThenClauseContext): Seq[Ast] = {
+    astForStatementsContext(ctx.compoundStatement().statements())
   }
 
   def astForElsifClauseContext(ctx: util.List[ElsifClauseContext]): Seq[Ast] = {
@@ -944,30 +962,32 @@ class AstCreator(filename: String, global: Global)
         val conditionAst = astForExpressionOrCommandContext(elif.expressionOrCommand())
         val thenAsts     = astForThenClauseContext(elif.thenClause())
         Ast(elifNode)
-          .withChild(conditionAst)
-          .withConditionEdge(elifNode, conditionAst.nodes.head)
+          .withChildren(conditionAst)
+          .withConditionEdge(elifNode, conditionAst.head.nodes.head)
           .withChildren(thenAsts)
       })
       .toSeq
   }
 
-  def astForElseClauseContext(ctx: ElseClauseContext): Ast = {
-    if (ctx == null) return Ast()
+  def astForElseClauseContext(ctx: ElseClauseContext): Seq[Ast] = {
+    if (ctx == null) return Seq(Ast())
     val elseNode = NewControlStructure()
       .controlStructureType(ControlStructureTypes.ELSE)
       .code(ctx.getText())
       .lineNumber(ctx.ELSE().getSymbol.getLine)
       .columnNumber(ctx.ELSE().getSymbol.getCharPositionInLine)
-    val stmtsAsts = astsForStatementsContext(ctx.compoundStatement().statements())
-    Ast(elseNode)
-      .withChildren(stmtsAsts)
+    val stmtsAsts = astForStatementsContext(ctx.compoundStatement().statements())
+    Seq(
+      Ast(elseNode)
+        .withChildren(stmtsAsts)
+    )
   }
 
-  def astForIfExpressionContext(ctx: IfExpressionContext): Ast = {
-    val conditionAst = astForExpressionOrCommandContext(ctx.expressionOrCommand())
-    val thenAsts     = astForThenClauseContext(ctx.thenClause())
-    val elseifAsts   = astForElsifClauseContext(ctx.elsifClause())
-    val elseAst      = astForElseClauseContext(ctx.elseClause())
+  def astForIfExpressionContext(ctx: IfExpressionContext): Seq[Ast] = {
+    val conditionAsts = astForExpressionOrCommandContext(ctx.expressionOrCommand())
+    val thenAsts      = astForThenClauseContext(ctx.thenClause())
+    val elseifAsts    = astForElsifClauseContext(ctx.elsifClause())
+    val elseAst       = astForElseClauseContext(ctx.elseClause())
 
     val ifNode = NewControlStructure()
       .controlStructureType(ControlStructureTypes.IF)
@@ -975,19 +995,21 @@ class AstCreator(filename: String, global: Global)
       .lineNumber(ctx.IF().getSymbol.getLine)
       .columnNumber(ctx.IF().getSymbol.getCharPositionInLine)
 
-    Ast(ifNode)
-      .withChild(conditionAst)
-      .withConditionEdge(ifNode, conditionAst.nodes.head)
-      .withChildren(thenAsts)
-      .withChild(elseAst)
-      .withChildren(elseifAsts)
+    Seq(
+      Ast(ifNode)
+        .withChildren(conditionAsts)
+        .withConditionEdge(ifNode, conditionAsts.head.nodes.head)
+        .withChildren(thenAsts)
+        .withChildren(elseAst)
+        .withChildren(elseifAsts)
+    )
   }
 
-  def astForIfExpressionPrimaryContext(ctx: IfExpressionPrimaryContext): Ast = {
+  def astForIfExpressionPrimaryContext(ctx: IfExpressionPrimaryContext): Seq[Ast] = {
     astForIfExpressionContext(ctx.ifExpression())
   }
 
-  def astForIndexingExpressionPrimaryContext(ctx: IndexingExpressionPrimaryContext): Ast = {
+  def astForIndexingExpressionPrimaryContext(ctx: IndexingExpressionPrimaryContext): Seq[Ast] = {
     val lhsExpressionAst = astForPrimaryContext(ctx.primary())
     val rhsExpressionAst = astForIndexingArgumentsContext(ctx.indexingArguments())
     val callNode = NewCall()
@@ -999,13 +1021,13 @@ class AstCreator(filename: String, global: Global)
       .typeFullName(Defines.Any)
       .lineNumber(ctx.LBRACK().getSymbol.getLine())
       .columnNumber(ctx.LBRACK().getSymbol.getCharPositionInLine())
-    callAst(callNode, Seq(lhsExpressionAst, rhsExpressionAst))
+    Seq(callAst(callNode, lhsExpressionAst ++ rhsExpressionAst))
 
   }
 
-  def astForInvocationExpressionOrCommandContext(ctx: InvocationExpressionOrCommandContext): Ast = {
+  def astForInvocationExpressionOrCommandContext(ctx: InvocationExpressionOrCommandContext): Seq[Ast] = {
     if (ctx.EMARK() != null) {
-      val invocWOParenAst = astForInvocationWithoutParenthesesContext(ctx.invocationWithoutParentheses())
+      val invocWOParenAsts = astForInvocationWithoutParenthesesContext(ctx.invocationWithoutParentheses())
       val callNode = NewCall()
         .name(ctx.EMARK().getText)
         .code(ctx.EMARK().getText)
@@ -1015,13 +1037,13 @@ class AstCreator(filename: String, global: Global)
         .typeFullName(Defines.Any)
         .lineNumber(ctx.EMARK().getSymbol().getLine())
         .columnNumber(ctx.EMARK().getSymbol().getCharPositionInLine())
-      callAst(callNode, Seq(invocWOParenAst))
+      Seq(callAst(callNode, invocWOParenAsts))
     } else {
       astForInvocationWithoutParenthesesContext(ctx.invocationWithoutParentheses())
     }
   }
 
-  def astForInvocationWithoutParenthesesContext(ctx: InvocationWithoutParenthesesContext): Ast = ctx match {
+  def astForInvocationWithoutParenthesesContext(ctx: InvocationWithoutParenthesesContext): Seq[Ast] = ctx match {
     case ctx: SingleCommandOnlyInvocationWithoutParenthesesContext => astForCommandContext(ctx.command())
     case ctx: ChainedCommandDoBlockInvocationWithoutParenthesesContext =>
       astForChainedCommandWithDoBlockContext(ctx.chainedCommandWithDoBlock())
@@ -1029,22 +1051,24 @@ class AstCreator(filename: String, global: Global)
       val cmdDoBlockAst  = astForChainedCommandWithDoBlockContext(ctx.chainedCommandWithDoBlock())
       val methodNameAst  = astForMethodNameContext(ctx.methodName())
       val argsWOParenAst = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
-      cmdDoBlockAst.withChild(methodNameAst).withChild(argsWOParenAst)
+      cmdDoBlockAst ++ methodNameAst ++ argsWOParenAst
     case ctx: ReturnArgsInvocationWithoutParenthesesContext =>
       val retNode = NewReturn()
         .code(ctx.getText)
         .lineNumber(ctx.RETURN().getSymbol().getLine)
         .columnNumber(ctx.RETURN().getSymbol().getCharPositionInLine)
       val argAst = astForArgumentsContext(ctx.arguments())
-      returnAst(retNode, Seq(argAst))
+      Seq(returnAst(retNode, argAst))
     case ctx: BreakArgsInvocationWithoutParenthesesContext =>
       val node = NewControlStructure()
         .controlStructureType(ControlStructureTypes.BREAK)
         .lineNumber(ctx.BREAK().getSymbol.getLine)
         .columnNumber(ctx.BREAK().getSymbol.getCharPositionInLine)
         .code(ctx.getText)
-      Ast(node)
-        .withChild(astForArgumentsContext(ctx.arguments()))
+      Seq(
+        Ast(node)
+          .withChildren(astForArgumentsContext(ctx.arguments()))
+      )
     case ctx: NextArgsInvocationWithoutParenthesesContext =>
       astForArgumentsContext(ctx.arguments())
       val node = NewControlStructure()
@@ -1052,34 +1076,36 @@ class AstCreator(filename: String, global: Global)
         .lineNumber(ctx.NEXT().getSymbol.getLine)
         .columnNumber(ctx.NEXT().getSymbol.getCharPositionInLine)
         .code(Defines.ModifierNext)
-      Ast(node)
-        .withChild(astForArgumentsContext(ctx.arguments()))
+      Seq(
+        Ast(node)
+          .withChildren(astForArgumentsContext(ctx.arguments()))
+      )
     case _ =>
       logger.error("astForInvocationWithoutParenthesesContext() All contexts mismatched.")
-      Ast()
+      Seq(Ast())
   }
 
-  def astForInvocationWithBlockOnlyPrimaryContext(ctx: InvocationWithBlockOnlyPrimaryContext): Ast = {
+  def astForInvocationWithBlockOnlyPrimaryContext(ctx: InvocationWithBlockOnlyPrimaryContext): Seq[Ast] = {
     val methodIdAst = astForMethodIdentifierContext(ctx.methodIdentifier())
     val blockAst    = astForBlockContext(ctx.block())
-    blockAst.withChild(methodIdAst)
+    blockAst ++ methodIdAst
   }
 
-  def astForInvocationWithParenthesesPrimaryContext(ctx: InvocationWithParenthesesPrimaryContext): Ast = {
+  def astForInvocationWithParenthesesPrimaryContext(ctx: InvocationWithParenthesesPrimaryContext): Seq[Ast] = {
     val methodIdAst = astForMethodIdentifierContext(ctx.methodIdentifier())
     val parenAst    = astForArgumentsWithParenthesesContext(ctx.argumentsWithParentheses())
-    val callNode    = methodIdAst.nodes.filter(_.isInstanceOf[NewCall]).head.asInstanceOf[NewCall]
+    val callNode    = methodIdAst.head.nodes.filter(_.isInstanceOf[NewCall]).head.asInstanceOf[NewCall]
     callNode.name(getActualMethodName(callNode.name))
 
     if (ctx.block() != null) {
       val blockAst = astForBlockContext(ctx.block())
-      callAst(callNode, Seq(parenAst, blockAst))
+      Seq(callAst(callNode, parenAst ++ blockAst))
     } else {
-      callAst(callNode, Seq(parenAst))
+      Seq(callAst(callNode, parenAst))
     }
   }
 
-  def astForIsDefinedExpressionContext(ctx: IsDefinedExpressionContext): Ast = {
+  def astForIsDefinedExpressionContext(ctx: IsDefinedExpressionContext): Seq[Ast] = {
     val exprAst = astForExpressionContext(ctx.expression())
     val callNode = NewCall()
       .name(ctx.IS_DEFINED().getText)
@@ -1090,54 +1116,54 @@ class AstCreator(filename: String, global: Global)
       .typeFullName(Defines.Any)
       .lineNumber(ctx.IS_DEFINED().getSymbol().getLine())
       .columnNumber(ctx.IS_DEFINED().getSymbol().getCharPositionInLine())
-    callAst(callNode, Seq(exprAst))
+    Seq(callAst(callNode, exprAst))
   }
 
-  def astForIsDefinedPrimaryContext(ctx: IsDefinedPrimaryContext): Ast = {
+  def astForIsDefinedPrimaryContext(ctx: IsDefinedPrimaryContext): Seq[Ast] = {
     astForExpressionOrCommandContext(ctx.expressionOrCommand())
   }
 
-  def astForJumpExpressionPrimaryContext(ctx: JumpExpressionPrimaryContext): Ast = {
+  def astForJumpExpressionPrimaryContext(ctx: JumpExpressionPrimaryContext): Seq[Ast] = {
     if (ctx.jumpExpression().RETURN() != null) {
       val retNode = NewReturn()
         .code(ctx.getText)
         .lineNumber(ctx.jumpExpression().RETURN().getSymbol().getLine)
         .columnNumber(ctx.jumpExpression().RETURN().getSymbol().getCharPositionInLine)
-      returnAst(retNode, Seq[Ast]())
+      Seq(returnAst(retNode, Seq[Ast]()))
     } else if (ctx.jumpExpression().BREAK() != null) {
       val node = NewControlStructure()
         .controlStructureType(ControlStructureTypes.BREAK)
         .lineNumber(ctx.jumpExpression().BREAK().getSymbol.getLine)
         .columnNumber(ctx.jumpExpression().BREAK().getSymbol.getCharPositionInLine)
         .code(ctx.getText)
-      Ast(node)
+      Seq(Ast(node))
     } else if (ctx.jumpExpression().NEXT() != null) {
       val node = NewControlStructure()
         .controlStructureType(ControlStructureTypes.CONTINUE)
         .lineNumber(ctx.jumpExpression().NEXT().getSymbol.getLine)
         .columnNumber(ctx.jumpExpression().NEXT().getSymbol.getCharPositionInLine)
         .code(Defines.ModifierNext)
-      Ast(node)
+      Seq(Ast(node))
     } else if (ctx.jumpExpression().REDO() != null) {
       val node = NewControlStructure()
         .controlStructureType(ControlStructureTypes.CONTINUE)
         .lineNumber(ctx.jumpExpression().REDO().getSymbol.getLine)
         .columnNumber(ctx.jumpExpression().REDO().getSymbol.getCharPositionInLine)
         .code(Defines.ModifierRedo)
-      Ast(node)
+      Seq(Ast(node))
     } else if (ctx.jumpExpression().RETRY() != null) {
       val node = NewControlStructure()
         .controlStructureType(ControlStructureTypes.CONTINUE)
         .lineNumber(ctx.jumpExpression().RETRY().getSymbol.getLine)
         .columnNumber(ctx.jumpExpression().RETRY().getSymbol.getCharPositionInLine)
         .code(Defines.ModifierRetry)
-      Ast(node)
+      Seq(Ast(node))
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
 
-  def astForLiteralPrimaryContext(ctx: LiteralPrimaryContext): Ast = {
+  def astForLiteralPrimaryContext(ctx: LiteralPrimaryContext): Seq[Ast] = {
     if (ctx.literal().numericLiteral() != null) {
       val text = ctx.getText
       val node = NewLiteral()
@@ -1145,14 +1171,14 @@ class AstCreator(filename: String, global: Global)
         .typeFullName(Defines.Number)
         .dynamicTypeHintFullName(List(Defines.Number))
       registerType(Defines.Number)
-      Ast(node)
+      Seq(Ast(node))
     } else if (ctx.literal().SINGLE_QUOTED_STRING_LITERAL() != null) {
       val text = ctx.getText
       val node = NewLiteral()
         .code(text)
         .typeFullName(Defines.String)
         .dynamicTypeHintFullName(List(Defines.String))
-      Ast(node)
+      Seq(Ast(node))
     } else if (ctx.literal().DOUBLE_QUOTED_STRING_CHARACTER_SEQUENCE() != null) {
       val text = ctx.literal().DOUBLE_QUOTED_STRING_CHARACTER_SEQUENCE().getText
       val node = NewLiteral()
@@ -1160,19 +1186,19 @@ class AstCreator(filename: String, global: Global)
         .typeFullName(Defines.String)
         .dynamicTypeHintFullName(List(Defines.String))
       registerType(Defines.String)
-      Ast(node)
+      Seq(Ast(node))
     } else if (ctx.literal().symbol() != null) {
       astForSymbolContext(ctx.literal().symbol())
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
 
-  def astForSimpleMethodNamePartContext(ctx: SimpleMethodNamePartContext): Ast = {
+  def astForSimpleMethodNamePartContext(ctx: SimpleMethodNamePartContext): Seq[Ast] = {
     astForDefinedMethodNameContext(ctx.definedMethodName())
   }
 
-  def astForCallNode(localIdentifier: TerminalNode): Ast = {
+  def astForCallNode(localIdentifier: TerminalNode): Seq[Ast] = {
     val column = localIdentifier.getSymbol().getCharPositionInLine()
     val line   = localIdentifier.getSymbol().getLine()
     val callNode = NewCall()
@@ -1184,20 +1210,20 @@ class AstCreator(filename: String, global: Global)
       .code(localIdentifier.getText())
       .lineNumber(line)
       .columnNumber(column)
-    callAst(callNode)
+    Seq(callAst(callNode))
   }
 
-  def astForMethodOnlyIdentifier(ctx: MethodOnlyIdentifierContext): Ast = {
+  def astForMethodOnlyIdentifier(ctx: MethodOnlyIdentifierContext): Seq[Ast] = {
     if (ctx.LOCAL_VARIABLE_IDENTIFIER() != null) {
       astForCallNode(ctx.LOCAL_VARIABLE_IDENTIFIER())
     } else if (ctx.CONSTANT_IDENTIFIER() != null) {
       astForCallNode(ctx.CONSTANT_IDENTIFIER())
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
 
-  def astForMethodIdentifierContext(ctx: MethodIdentifierContext): Ast = {
+  def astForMethodIdentifierContext(ctx: MethodIdentifierContext): Seq[Ast] = {
     if (ctx.methodOnlyIdentifier() != null) {
       astForMethodOnlyIdentifier(ctx.methodOnlyIdentifier())
     } else if (ctx.LOCAL_VARIABLE_IDENTIFIER() != null) {
@@ -1206,7 +1232,7 @@ class AstCreator(filename: String, global: Global)
       if (lookupIdentiferInScope(varSymbol.getText)) {
         val node =
           createIdentiferWithScope(localVar, varSymbol.getText, varSymbol.getText, Defines.Any, List(Defines.Any))
-        Ast(node)
+        Seq(Ast(node))
       } else {
         astForCallNode(localVar)
       }
@@ -1216,16 +1242,16 @@ class AstCreator(filename: String, global: Global)
       if (lookupIdentiferInScope(varSymbol.getText)) {
         val node =
           createIdentiferWithScope(localVar, varSymbol.getText, varSymbol.getText, Defines.Any, List(Defines.Any))
-        Ast(node)
+        Seq(Ast(node))
       } else {
         astForCallNode(localVar)
       }
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
 
-  def astForOperatorMethodNameContext(ctx: OperatorMethodNameContext): Ast = {
+  def astForOperatorMethodNameContext(ctx: OperatorMethodNameContext): Seq[Ast] = {
 
     val terminalNode = ctx.children.asScala.head
       .asInstanceOf[TerminalNode]
@@ -1239,10 +1265,10 @@ class AstCreator(filename: String, global: Global)
       .typeFullName(Defines.Any)
       .lineNumber(terminalNode.getSymbol().getLine())
       .columnNumber(terminalNode.getSymbol().getCharPositionInLine())
-    callAst(callNode)
+    Seq(callAst(callNode))
   }
 
-  def astForMethodNameContext(ctx: MethodNameContext): Ast = {
+  def astForMethodNameContext(ctx: MethodNameContext): Seq[Ast] = {
     if (ctx.methodIdentifier() != null) {
       astForMethodIdentifierContext(ctx.methodIdentifier())
     } else if (ctx.operatorMethodName() != null) {
@@ -1263,65 +1289,65 @@ class AstCreator(filename: String, global: Global)
         .typeFullName(Defines.Any)
         .lineNumber(terminalNode.getSymbol().getLine())
         .columnNumber(terminalNode.getSymbol().getCharPositionInLine())
-      callAst(callNode)
+      Seq(callAst(callNode))
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
-  def astForAssignmentLikeMethodIdentifierContext(ctx: AssignmentLikeMethodIdentifierContext): Ast = {
-    if (ctx == null) return Ast()
+  def astForAssignmentLikeMethodIdentifierContext(ctx: AssignmentLikeMethodIdentifierContext): Seq[Ast] = {
+    if (ctx == null) return Seq(Ast())
 
     if (ctx.LOCAL_VARIABLE_IDENTIFIER() != null) {
       val localVar  = ctx.LOCAL_VARIABLE_IDENTIFIER()
       val varSymbol = localVar.getSymbol()
       val node =
         createIdentiferWithScope(localVar, varSymbol.getText, varSymbol.getText, Defines.Any, List(Defines.Any))
-      Ast(node)
+      Seq(Ast(node))
     } else if (ctx.CONSTANT_IDENTIFIER() != null) {
       val localVar  = ctx.CONSTANT_IDENTIFIER()
       val varSymbol = localVar.getSymbol()
       val node =
         createIdentiferWithScope(localVar, varSymbol.getText, varSymbol.getText, Defines.Any, List(Defines.Any))
-      Ast(node)
+      Seq(Ast(node))
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
 
-  def astForDefinedMethodNameContext(ctx: DefinedMethodNameContext): Ast = {
+  def astForDefinedMethodNameContext(ctx: DefinedMethodNameContext): Seq[Ast] = {
     val methodNameAst         = astForMethodNameContext(ctx.methodName())
     val assignLinkedMethodAst = astForAssignmentLikeMethodIdentifierContext(ctx.assignmentLikeMethodIdentifier())
-    Ast().withChildren(Seq(methodNameAst, assignLinkedMethodAst))
+    methodNameAst ++ assignLinkedMethodAst
   }
 
-  def astForSingletonObjextContext(ctx: SingletonObjectContext): Ast = {
+  def astForSingletonObjextContext(ctx: SingletonObjectContext): Seq[Ast] = {
     if (ctx.variableIdentifier() != null) {
       astForVariableIdentifierContext(ctx.variableIdentifier())
     } else if (ctx.pseudoVariableIdentifier() != null) {
-      Ast()
+      Seq(Ast())
     } else if (ctx.expressionOrCommand() != null) {
       astForExpressionOrCommandContext(ctx.expressionOrCommand())
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
 
-  def astForSingletonMethodNamePartContext(ctx: SingletonMethodNamePartContext): Ast = {
+  def astForSingletonMethodNamePartContext(ctx: SingletonMethodNamePartContext): Seq[Ast] = {
     val definedMethodNameAst = astForDefinedMethodNameContext(ctx.definedMethodName())
     val singletonObjAst      = astForSingletonObjextContext(ctx.singletonObject())
-    Ast().withChildren(Seq(definedMethodNameAst, singletonObjAst))
+    definedMethodNameAst ++ singletonObjAst
   }
 
-  def astForMethodNamePartContext(ctx: MethodNamePartContext): Ast = ctx match {
+  def astForMethodNamePartContext(ctx: MethodNamePartContext): Seq[Ast] = ctx match {
     case ctx: SimpleMethodNamePartContext    => astForSimpleMethodNamePartContext(ctx)
     case ctx: SingletonMethodNamePartContext => astForSingletonMethodNamePartContext(ctx)
     case _ =>
       logger.error("astForMethodNamePartContext() All contexts mismatched.")
-      Ast()
+      Seq(Ast())
   }
 
-  def astForMethodParameterPartContext(ctx: MethodParameterPartContext): Ast = {
-    if (ctx == null || ctx.parameters() == null) return Ast()
+  def astForMethodParameterPartContext(ctx: MethodParameterPartContext): Seq[Ast] = {
+    if (ctx == null || ctx.parameters() == null) return Seq(Ast())
     // NOT differentiating between the productions here since either way we get paramaters
     val mandatoryParameters = ctx.parameters().mandatoryParameters()
     val optionalParameters  = ctx.parameters().optionalParameters()
@@ -1353,32 +1379,30 @@ class AstCreator(filename: String, global: Global)
       localVarList.addOne(procParameter.LOCAL_VARIABLE_IDENTIFIER())
     }
 
-    val seqMethodPar = localVarList
+    localVarList
       .map(localVar => {
         val varSymbol = localVar.getSymbol()
         createIdentiferWithScope(localVar, varSymbol.getText, varSymbol.getText, Defines.Any, Seq[String](Defines.Any))
-        NewMethodParameterIn()
+        val param = NewMethodParameterIn()
           .name(varSymbol.getText)
           .code(varSymbol.getText)
           .lineNumber(varSymbol.getLine)
           .columnNumber(varSymbol.getCharPositionInLine)
+        Ast(param)
       })
       .toSeq
-
-    val ast = Ast(seqMethodPar)
-    Ast().withChildren(List[Ast](ast))
   }
 
-  def astForBodyStatementContext(ctx: BodyStatementContext): Ast = {
+  def astForBodyStatementContext(ctx: BodyStatementContext): Seq[Ast] = {
     astForStatementsContext(ctx.compoundStatement().statements())
     // TODO rescue else and ensure to be implemented
   }
 
-  def astForMethodDefinitionContext(ctx: MethodDefinitionContext): Ast = {
+  def astForMethodDefinitionContext(ctx: MethodDefinitionContext): Seq[Ast] = {
     pushScope()
     val astMethodParam = astForMethodParameterPartContext(ctx.methodParameterPart())
     val astMethodName  = astForMethodNamePartContext(ctx.methodNamePart())
-    val callNode       = astMethodName.nodes.filter(node => node.isInstanceOf[NewCall]).head.asInstanceOf[NewCall]
+    val callNode       = astMethodName.head.nodes.filter(node => node.isInstanceOf[NewCall]).head.asInstanceOf[NewCall]
     // there can be only one call node
     val astBody = astForBodyStatementContext(ctx.bodyStatement())
     popScope()
@@ -1404,30 +1428,40 @@ class AstCreator(filename: String, global: Global)
      * TODO find out how they should be used. Need to do this iff it adds any value
      */
 
-    val paramSeq = astMethodParam.nodes
+    val paramSeq = astMethodParam.head.nodes
       .map(node => {
         Ast(node)
       })
       .toSeq
-    methodAst(methodNode, paramSeq, astBody, methodRetNode, Seq[NewModifier](publicModifier))
-  }
 
-  def astForMethodOnlyIdentifierPrimaryContext(ctx: MethodOnlyIdentifierPrimaryContext): Ast = {
+    val blockNode = NewBlock().typeFullName(Defines.Any)
+    Seq(
+      methodAst(
+        methodNode,
+        paramSeq,
+        blockAst(blockNode, astBody.toList),
+        methodRetNode,
+        Seq[NewModifier](publicModifier)
+      )
+    )
+  }
+
+  def astForMethodOnlyIdentifierPrimaryContext(ctx: MethodOnlyIdentifierPrimaryContext): Seq[Ast] = {
     astForMethodOnlyIdentifier(ctx.methodOnlyIdentifier())
   }
 
-  def astForModuleDefinitionPrimaryContext(ctx: ModuleDefinitionPrimaryContext): Ast = {
-    val referenceAst = astForClassOrModuleReferenceContext(ctx.moduleDefinition().classOrModuleReference())
-    val bodyStmtAst  = astForBodyStatementContext(ctx.moduleDefinition().bodyStatement())
+  def astForModuleDefinitionPrimaryContext(ctx: ModuleDefinitionPrimaryContext): Seq[Ast] = {
+    val referenceAsts = astForClassOrModuleReferenceContext(ctx.moduleDefinition().classOrModuleReference())
+    val bodyStmtAsts  = astForBodyStatementContext(ctx.moduleDefinition().bodyStatement())
     if (classStack.size > 0) {
       classStack.pop()
     }
-    referenceAst.withChild(bodyStmtAst)
+    Seq(referenceAsts.head.withChildren(bodyStmtAsts))
   }
 
-  def astForMultipleAssignmentExpressionContext(ctx: MultipleAssignmentExpressionContext): Ast = {
-    val lhsAst = astForMultipleLeftHandSideContext(ctx.multipleLeftHandSide())
-    val rhsAst = astForMultipleRightHandSideContext(ctx.multipleRightHandSide())
+  def astForMultipleAssignmentExpressionContext(ctx: MultipleAssignmentExpressionContext): Seq[Ast] = {
+    val lhsAsts = astForMultipleLeftHandSideContext(ctx.multipleLeftHandSide())
+    val rhsAsts = astForMultipleRightHandSideContext(ctx.multipleRightHandSide())
     val callNode = NewCall()
       .name(ctx.EQ().getText)
       .code(ctx.getText)
@@ -1437,15 +1471,15 @@ class AstCreator(filename: String, global: Global)
       .typeFullName(Defines.Any)
       .lineNumber(ctx.EQ().getSymbol().getLine())
       .columnNumber(ctx.EQ().getSymbol().getCharPositionInLine())
-    callAst(callNode, Seq(lhsAst) ++ Seq(rhsAst))
+    Seq(callAst(callNode, lhsAsts ++ rhsAsts))
   }
 
-  def astForMultiplicativeExpressionContext(ctx: MultiplicativeExpressionContext): Ast = {
+  def astForMultiplicativeExpressionContext(ctx: MultiplicativeExpressionContext): Seq[Ast] = {
     astForBinaryExpression(ctx.expression(0), ctx.expression(1), ctx.op)
   }
 
-  def astForNotExpressionOrCommandContext(ctx: NotExpressionOrCommandContext): Ast = {
-    val expAst = astForExpressionOrCommandContext(ctx.expressionOrCommand())
+  def astForNotExpressionOrCommandContext(ctx: NotExpressionOrCommandContext): Seq[Ast] = {
+    val expAsts = astForExpressionOrCommandContext(ctx.expressionOrCommand())
     val callNode = NewCall()
       .name(ctx.NOT().getText)
       .code(ctx.NOT().getText)
@@ -1455,20 +1489,20 @@ class AstCreator(filename: String, global: Global)
       .typeFullName(Defines.Any)
       .lineNumber(ctx.NOT().getSymbol().getLine())
       .columnNumber(ctx.NOT().getSymbol().getCharPositionInLine())
-    callAst(callNode, Seq(expAst))
+    Seq(callAst(callNode, expAsts))
   }
 
-  def astForOperatorAndExpressionContext(ctx: OperatorAndExpressionContext): Ast = {
+  def astForOperatorAndExpressionContext(ctx: OperatorAndExpressionContext): Seq[Ast] = {
     astForBinaryExpression(ctx.expression(0), ctx.expression(1), ctx.op)
   }
 
-  def astForOperatorOrExpressionContext(ctx: OperatorOrExpressionContext): Ast = {
+  def astForOperatorOrExpressionContext(ctx: OperatorOrExpressionContext): Seq[Ast] = {
     astForBinaryExpression(ctx.expression(0), ctx.expression(1), ctx.op)
   }
 
-  def astForOrAndExpressionOrCommandContext(ctx: OrAndExpressionOrCommandContext): Ast = {
-    val lhsAst = astForExpressionOrCommandContext(ctx.expressionOrCommand().get(0))
-    val rhsAst = astForExpressionOrCommandContext(ctx.expressionOrCommand().get(1))
+  def astForOrAndExpressionOrCommandContext(ctx: OrAndExpressionOrCommandContext): Seq[Ast] = {
+    val lhsAsts = astForExpressionOrCommandContext(ctx.expressionOrCommand().get(0))
+    val rhsAsts = astForExpressionOrCommandContext(ctx.expressionOrCommand().get(1))
     val callNode = NewCall()
       .name(ctx.op.getText)
       .code(ctx.getText)
@@ -1478,13 +1512,13 @@ class AstCreator(filename: String, global: Global)
       .typeFullName(Defines.Any)
       .lineNumber(ctx.op.getLine())
       .columnNumber(ctx.op.getCharPositionInLine())
-    callAst(callNode, Seq(lhsAst, rhsAst))
+    Seq(callAst(callNode, lhsAsts ++ rhsAsts))
   }
 
-  def astForPowerExpressionContext(ctx: PowerExpressionContext): Ast = {
-    val expressions           = ctx.expression()
-    val baseExpressionAst     = astForExpressionContext(expressions.get(0))
-    val exponentExpressionAst = astForExpressionContext(expressions.get(1))
+  def astForPowerExpressionContext(ctx: PowerExpressionContext): Seq[Ast] = {
+    val expressions            = ctx.expression()
+    val baseExpressionAsts     = astForExpressionContext(expressions.get(0))
+    val exponentExpressionAsts = astForExpressionContext(expressions.get(1))
     val callNode = NewCall()
       .name(ctx.STAR2().getText)
       .code(ctx.STAR2().getText)
@@ -1494,24 +1528,24 @@ class AstCreator(filename: String, global: Global)
       .typeFullName(Defines.Any)
       .lineNumber(ctx.STAR2().getSymbol().getLine())
       .columnNumber(ctx.STAR2().getSymbol().getCharPositionInLine())
-    callAst(callNode, Seq(baseExpressionAst, exponentExpressionAst))
+    Seq(callAst(callNode, baseExpressionAsts ++ exponentExpressionAsts))
   }
 
-  def astForRangeExpressionContext(ctx: RangeExpressionContext): Ast = {
+  def astForRangeExpressionContext(ctx: RangeExpressionContext): Seq[Ast] = {
     if (ctx.expression().size() == 2) {
       astForBinaryExpression(ctx.expression(0), ctx.expression(1), ctx.op)
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
 
-  def astForRelationalExpressionContext(ctx: RelationalExpressionContext): Ast = {
+  def astForRelationalExpressionContext(ctx: RelationalExpressionContext): Seq[Ast] = {
     astForBinaryExpression(ctx.expression(0), ctx.expression(1), ctx.op)
   }
 
-  def astForBinaryExpression(lhs: ExpressionContext, rhs: ExpressionContext, operatorToken: Token): Ast = {
-    val lhsExpressionAst = astForExpressionContext(lhs)
-    val rhsExpressionAst = astForExpressionContext(rhs)
+  def astForBinaryExpression(lhs: ExpressionContext, rhs: ExpressionContext, operatorToken: Token): Seq[Ast] = {
+    val lhsExpressionAsts = astForExpressionContext(lhs)
+    val rhsExpressionAsts = astForExpressionContext(rhs)
     val callNode = NewCall()
       .name(operatorToken.getText)
       .code(operatorToken.getText)
@@ -1521,10 +1555,10 @@ class AstCreator(filename: String, global: Global)
       .typeFullName(Defines.Any)
       .lineNumber(operatorToken.getLine())
       .columnNumber(operatorToken.getCharPositionInLine())
-    callAst(callNode, Seq(lhsExpressionAst, rhsExpressionAst))
+    Seq(callAst(callNode, lhsExpressionAsts ++ rhsExpressionAsts))
   }
 
-  def astForSimpleScopedConstantReferencePrimaryContext(ctx: SimpleScopedConstantReferencePrimaryContext): Ast = {
+  def astForSimpleScopedConstantReferencePrimaryContext(ctx: SimpleScopedConstantReferencePrimaryContext): Seq[Ast] = {
     val localVar  = ctx.CONSTANT_IDENTIFIER()
     val varSymbol = localVar.getSymbol()
     val node = createIdentiferWithScope(localVar, varSymbol.getText, varSymbol.getText, Defines.Any, List(Defines.Any))
@@ -1539,122 +1573,122 @@ class AstCreator(filename: String, global: Global)
       .lineNumber(ctx.COLON2().getSymbol.getLine)
       .columnNumber(ctx.COLON2().getSymbol.getCharPositionInLine())
 
-    callAst(callNode, Seq(Ast(node)))
+    Seq(callAst(callNode, Seq(Ast(node))))
 
   }
 
-  def astForSuperExpressionPrimaryContext(ctx: SuperExpressionPrimaryContext): Ast = {
-    val argAst = astForArgumentsWithParenthesesContext(ctx.argumentsWithParentheses())
-    val blockAst = if (ctx.block() != null) {
-      astForBlockContext(ctx.block())
+  def astForSuperExpressionPrimaryContext(ctx: SuperExpressionPrimaryContext): Seq[Ast] = {
+    val argAsts = astForArgumentsWithParenthesesContext(ctx.argumentsWithParentheses())
+    if (ctx.block() != null) {
+      argAsts ++ astForBlockContext(ctx.block())
     } else {
-      Ast()
+      argAsts
     }
-    argAst.withChild(blockAst)
   }
 
-  def astForCommandWithDoBlockContext(ctx: CommandWithDoBlockContext): Ast = ctx match {
+  def astForCommandWithDoBlockContext(ctx: CommandWithDoBlockContext): Seq[Ast] = ctx match {
     case ctx: ArgsAndDoBlockCommandWithDoBlockContext =>
-      val argsAst    = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
+      val argsAsts   = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
       val doBlockAst = astForDoBlockContext(ctx.doBlock())
-      argsAst.withChild(doBlockAst)
+      argsAsts ++ doBlockAst
     case ctx: RubyParser.ArgsAndDoBlockAndMethodIdCommandWithDoBlockContext =>
-      val argsAst     = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
-      val doBlockAst  = astForDoBlockContext(ctx.doBlock())
-      val methodIdAst = astForMethodIdentifierContext(ctx.methodIdentifier())
-      methodIdAst.withChild(argsAst).withChild(doBlockAst)
+      val argsAsts     = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
+      val doBlockAsts  = astForDoBlockContext(ctx.doBlock())
+      val methodIdAsts = astForMethodIdentifierContext(ctx.methodIdentifier())
+      methodIdAsts ++ argsAsts ++ doBlockAsts
     case ctx: RubyParser.PrimaryMethodArgsDoBlockCommandWithDoBlockContext =>
-      val argsAst       = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
-      val doBlockAst    = astForDoBlockContext(ctx.doBlock())
-      val methodNameAst = astForMethodNameContext(ctx.methodName())
-      val primaryAst    = astForPrimaryContext(ctx.primary())
-      primaryAst.withChild(methodNameAst).withChild(argsAst).withChild(doBlockAst)
+      val argsAsts       = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
+      val doBlockAsts    = astForDoBlockContext(ctx.doBlock())
+      val methodNameAsts = astForMethodNameContext(ctx.methodName())
+      val primaryAsts    = astForPrimaryContext(ctx.primary())
+      primaryAsts ++ methodNameAsts ++ argsAsts ++ doBlockAsts
     case _ =>
       logger.error("astForCommandWithDoBlockContext() All contexts mismatched.")
-      Ast()
+      Seq(Ast())
   }
 
-  def astForChainedCommandWithDoBlockContext(ctx: ChainedCommandWithDoBlockContext): Ast = {
-    val cmdAst    = astForCommandWithDoBlockContext(ctx.commandWithDoBlock())
-    val mNameAsts = ctx.methodName().asScala.map(mName => astForMethodNameContext(mName)).toSeq
+  def astForChainedCommandWithDoBlockContext(ctx: ChainedCommandWithDoBlockContext): Seq[Ast] = {
+    val cmdAsts   = astForCommandWithDoBlockContext(ctx.commandWithDoBlock())
+    val mNameAsts = ctx.methodName().asScala.flatMap(mName => astForMethodNameContext(mName)).toSeq
     val apAsts = ctx
       .argumentsWithParentheses()
       .asScala
-      .map(ap => {
+      .flatMap(ap => {
         astForArgumentsWithParenthesesContext(ap)
       })
       .toSeq
-    cmdAst.withChildren(mNameAsts).withChildren(apAsts)
+    cmdAsts ++ mNameAsts ++ apAsts
   }
 
-  def astForArgumentsWithParenthesesContext(ctx: ArgumentsWithParenthesesContext): Ast = ctx match {
-    case ctx: BlankArgsArgumentsWithParenthesesContext => Ast()
+  def astForArgumentsWithParenthesesContext(ctx: ArgumentsWithParenthesesContext): Seq[Ast] = ctx match {
+    case ctx: BlankArgsArgumentsWithParenthesesContext => Seq(Ast())
     case ctx: ArgsOnlyArgumentsWithParenthesesContext  => astForArgumentsContext(ctx.arguments())
     case ctx: ExpressionsAndChainedCommandWithDoBlockArgumentsWithParenthesesContext =>
       val expAsts = ctx
         .expressions()
         .expression
         .asScala
-        .map(exp => {
+        .flatMap(exp => {
           astForExpressionContext(exp)
         })
         .toSeq
       val ccDoBlock = astForChainedCommandWithDoBlockContext(ctx.chainedCommandWithDoBlock())
-      Ast().withChildren(expAsts).merge(ccDoBlock)
+      expAsts ++ ccDoBlock
     case ctx: ChainedCommandWithDoBlockOnlyArgumentsWithParenthesesContext =>
       astForChainedCommandWithDoBlockContext(ctx.chainedCommandWithDoBlock())
     case _ =>
       logger.error("astForArgumentsWithParenthesesContext() All contexts mismatched.")
-      Ast()
+      Seq(Ast())
   }
 
-  def astForBlockParametersContext(ctx: BlockParametersContext): Ast = {
+  def astForBlockParametersContext(ctx: BlockParametersContext): Seq[Ast] = {
     if (ctx.singleLeftHandSide() != null) {
       astForSingleLeftHandSideContext(ctx.singleLeftHandSide())
     } else if (ctx.multipleLeftHandSide() != null) {
       astForMultipleLeftHandSideContext(ctx.multipleLeftHandSide())
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
 
-  def astForBlockParameterContext(ctx: BlockParameterContext): Ast = {
+  def astForBlockParameterContext(ctx: BlockParameterContext): Seq[Ast] = {
     if (ctx.blockParameters() != null) {
       astForBlockParametersContext(ctx.blockParameters())
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
 
-  def astForDoBlockContext(ctx: DoBlockContext): Ast = {
+  def astForDoBlockContext(ctx: DoBlockContext): Seq[Ast] = {
     astForBlock(ctx.compoundStatement().statements(), ctx.blockParameter())
   }
 
-  def astForBraceBlockContext(ctx: BraceBlockContext): Ast = {
+  def astForBraceBlockContext(ctx: BraceBlockContext): Seq[Ast] = {
     astForBlock(ctx.compoundStatement().statements(), ctx.blockParameter())
   }
 
-  def astForBlock(ctxStmt: StatementsContext, ctxParam: BlockParameterContext): Ast = {
-    val stmtAsts  = astsForStatementsContext(ctxStmt)
+  def astForBlock(ctxStmt: StatementsContext, ctxParam: BlockParameterContext): Seq[Ast] = {
+    val stmtAsts  = astForStatementsContext(ctxStmt)
     val blockNode = NewBlock().typeFullName(Defines.Any)
-    if (ctxParam != null) {
-      val bpAst = astForBlockParameterContext(ctxParam)
-      blockAst(blockNode, List[Ast](bpAst) ++ stmtAsts)
+    val retAst = if (ctxParam != null) {
+      val bpAsts = astForBlockParameterContext(ctxParam)
+      blockAst(blockNode, (bpAsts ++ stmtAsts).toList)
     } else {
-      blockAst(blockNode, stmtAsts)
+      blockAst(blockNode, stmtAsts.toList)
     }
+    Seq(retAst)
   }
 
-  def astForBlockContext(ctx: BlockContext): Ast = {
+  def astForBlockContext(ctx: BlockContext): Seq[Ast] = {
     if (ctx.doBlock() != null) {
       astForDoBlockContext(ctx.doBlock())
     } else if (ctx.braceBlock() != null) {
       astForBraceBlockContext(ctx.braceBlock())
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
-  def astForUnaryExpressionContext(ctx: UnaryExpressionContext): Ast = {
+  def astForUnaryExpressionContext(ctx: UnaryExpressionContext): Seq[Ast] = {
     val expressionAst = astForExpressionContext(ctx.expression())
     if (ctx.op.getText == "+" && methodNameAsIdentiferQ.size > 0) {
       /*
@@ -1671,7 +1705,7 @@ class AstCreator(filename: String, global: Global)
         .lineNumber(ctx.op.getLine())
         .columnNumber(ctx.op.getCharPositionInLine())
       val lhsAst = methodNameAsIdentiferQ.dequeue()
-      callAst(callNode, Seq(lhsAst, expressionAst))
+      Seq(callAst(callNode, Seq(lhsAst) ++ expressionAst))
     } else {
       val callNode = NewCall()
         .name(ctx.op.getText)
@@ -1682,11 +1716,11 @@ class AstCreator(filename: String, global: Global)
         .typeFullName(Defines.Any)
         .lineNumber(ctx.op.getLine())
         .columnNumber(ctx.op.getCharPositionInLine())
-      callAst(callNode, Seq(expressionAst))
+      Seq(callAst(callNode, expressionAst))
     }
   }
 
-  def astForUnaryMinusExpressionContext(ctx: UnaryMinusExpressionContext): Ast = {
+  def astForUnaryMinusExpressionContext(ctx: UnaryMinusExpressionContext): Seq[Ast] = {
     val expressionAst = astForExpressionContext(ctx.expression())
     if (methodNameAsIdentiferQ.size > 0) {
       /*
@@ -1703,7 +1737,7 @@ class AstCreator(filename: String, global: Global)
         .lineNumber(ctx.MINUS().getSymbol.getLine())
         .columnNumber(ctx.MINUS().getSymbol.getCharPositionInLine())
       val lhsAst = methodNameAsIdentiferQ.dequeue()
-      callAst(callNode, Seq(lhsAst, expressionAst))
+      Seq(callAst(callNode, Seq(lhsAst) ++ expressionAst))
     } else {
       val callNode = NewCall()
         .name(ctx.MINUS().getText)
@@ -1714,14 +1748,14 @@ class AstCreator(filename: String, global: Global)
         .typeFullName(Defines.Any)
         .lineNumber(ctx.MINUS().getSymbol().getLine())
         .columnNumber(ctx.MINUS().getSymbol().getCharPositionInLine())
-      callAst(callNode, Seq(expressionAst))
+      Seq(callAst(callNode, expressionAst))
     }
   }
 
-  def astForUnlessExpressionPrimaryContext(ctx: UnlessExpressionPrimaryContext): Ast = {
-    val conditionAst = astForExpressionOrCommandContext(ctx.unlessExpression().expressionOrCommand())
-    val thenAsts     = astForThenClauseContext(ctx.unlessExpression().thenClause())
-    val elseAst      = astForElseClauseContext(ctx.unlessExpression().elseClause())
+  def astForUnlessExpressionPrimaryContext(ctx: UnlessExpressionPrimaryContext): Seq[Ast] = {
+    val conditionAsts = astForExpressionOrCommandContext(ctx.unlessExpression().expressionOrCommand())
+    val thenAsts      = astForThenClauseContext(ctx.unlessExpression().thenClause())
+    val elseAst       = astForElseClauseContext(ctx.unlessExpression().elseClause())
 
     // unless will be modelled as IF since there is no difference from a static analysis POV
     val unlessNode = NewControlStructure()
@@ -1730,31 +1764,31 @@ class AstCreator(filename: String, global: Global)
       .lineNumber(ctx.unlessExpression().UNLESS().getSymbol.getLine)
       .columnNumber(ctx.unlessExpression().UNLESS().getSymbol.getCharPositionInLine)
 
-    Ast(unlessNode)
-      .withChild(conditionAst)
-      .withConditionEdge(unlessNode, conditionAst.nodes.head)
-      .withChildren(thenAsts)
-      .withChild(elseAst)
+    Seq(
+      Ast(unlessNode)
+        .withChild(conditionAsts.head)
+        .withConditionEdge(unlessNode, conditionAsts.head.nodes.head)
+        .withChildren(thenAsts)
+        .withChildren(elseAst)
+    )
   }
 
-  def astForUntilExpressionContext(ctx: UntilExpressionContext): Ast = {
+  def astForUntilExpressionContext(ctx: UntilExpressionContext): Seq[Ast] = {
     // until will be modelled as a while
-    val untilNode = NewControlStructure()
-      .controlStructureType(ControlStructureTypes.WHILE)
-      .code(ctx.UNTIL().getText)
-      .lineNumber(ctx.UNTIL().getSymbol.getLine)
-      .columnNumber(ctx.UNTIL().getSymbol.getCharPositionInLine)
+    val untilCondAst = astForExpressionOrCommandContext(ctx.expressionOrCommand()).headOption
+    val doClauseAsts = astForDoClauseContext(ctx.doClause())
 
-    val untilCondAst = astForExpressionOrCommandContext(ctx.expressionOrCommand())
-    val doClauseAst  = astForDoClauseContext(ctx.doClause())
-
-    Ast(untilNode)
-      .withChild(untilCondAst)
-      .withConditionEdge(untilNode, untilCondAst.nodes.head)
-      .withChild(doClauseAst)
+    val ast = whileAst(
+      untilCondAst,
+      doClauseAsts,
+      None,
+      Some(ctx.UNTIL().getSymbol.getLine),
+      Some(ctx.UNTIL().getSymbol.getCharPositionInLine)
+    )
+    Seq(ast)
   }
 
-  def astForPseudoVariableIdentifierContext(ctx: PseudoVariableIdentifierContext): Ast = {
+  def astForPseudoVariableIdentifierContext(ctx: PseudoVariableIdentifierContext): Seq[Ast] = {
     val node = {
       if (ctx.TRUE() != null) { ctx.TRUE() }
       else if (ctx.NIL() != null) { ctx.NIL() }
@@ -1763,14 +1797,14 @@ class AstCreator(filename: String, global: Global)
       else if (ctx.FILE__() != null) { ctx.FILE__() }
       else if (ctx.LINE__() != null) { ctx.LINE__() }
       else if (ctx.ENCODING__() != null) { ctx.ENCODING__() }
-      else return Ast()
+      else return Seq(Ast())
     }
 
     val astNode = createIdentiferWithScope(node, ctx.getText, ctx.getText, Defines.Any, List(Defines.Any))
-    Ast(astNode)
+    Seq(Ast(astNode))
   }
 
-  def astForVariableRefenceContext(ctx: RubyParser.VariableReferenceContext): Ast = {
+  def astForVariableRefenceContext(ctx: RubyParser.VariableReferenceContext): Seq[Ast] = {
     if (ctx.variableIdentifier() != null) {
       astForVariableIdentifierContext(ctx.variableIdentifier())
     } else {
@@ -1778,53 +1812,50 @@ class AstCreator(filename: String, global: Global)
     }
   }
 
-  def astForVariableReferencePrimaryContext(ctx: VariableReferencePrimaryContext): Ast = {
+  def astForVariableReferencePrimaryContext(ctx: VariableReferencePrimaryContext): Seq[Ast] = {
     astForVariableRefenceContext(ctx.variableReference())
   }
 
-  def astForDoClauseContext(ctx: DoClauseContext): Ast = {
+  def astForDoClauseContext(ctx: DoClauseContext): Seq[Ast] = {
     astForStatementsContext(ctx.compoundStatement().statements())
   }
 
-  def astForWhileExpressionContext(ctx: WhileExpressionContext): Ast = {
-    val whileNode = NewControlStructure()
-      .controlStructureType(ControlStructureTypes.WHILE)
-      .code(ctx.getText)
-      .lineNumber(ctx.WHILE().getSymbol.getLine)
-      .columnNumber(ctx.WHILE().getSymbol.getCharPositionInLine)
-
-    val whileCondAst = astForExpressionOrCommandContext(ctx.expressionOrCommand())
-    val doClauseAst  = astForDoClauseContext(ctx.doClause())
+  def astForWhileExpressionContext(ctx: WhileExpressionContext): Seq[Ast] = {
+    val whileCondAst = astForExpressionOrCommandContext(ctx.expressionOrCommand()).headOption
+    val doClauseAsts = astForDoClauseContext(ctx.doClause())
 
-    Ast(whileNode)
-      .withChild(whileCondAst)
-      .withConditionEdge(whileNode, whileCondAst.nodes.head)
-      .withChild(doClauseAst)
+    val ast = whileAst(
+      whileCondAst,
+      doClauseAsts,
+      None,
+      Some(ctx.WHILE().getSymbol.getLine),
+      Some(ctx.WHILE().getSymbol.getCharPositionInLine)
+    )
+    Seq(ast)
   }
 
-  def astForBlockArgumentContext(ctx: BlockArgumentContext): Ast = {
-    if (ctx == null) return Ast()
+  def astForBlockArgumentContext(ctx: BlockArgumentContext): Seq[Ast] = {
+    if (ctx == null) return Seq(Ast())
     astForExpressionContext(ctx.expression())
   }
 
-  def astForBlockArgumentTypeArgumentsContext(ctx: BlockArgumentTypeArgumentsContext): Ast = {
+  def astForBlockArgumentTypeArgumentsContext(ctx: BlockArgumentTypeArgumentsContext): Seq[Ast] = {
     astForBlockArgumentContext(ctx.blockArgument())
   }
 
-  def astForBlockSplattingTypeArgumentsContext(ctx: BlockSplattingTypeArgumentsContext): Ast = {
-    val blockNode = NewBlock().typeFullName(Defines.Any)
-    val splatAst  = astForSplattingArgumentContext(ctx.splattingArgument())
+  def astForBlockSplattingTypeArgumentsContext(ctx: BlockSplattingTypeArgumentsContext): Seq[Ast] = {
+    val splatAst = astForSplattingArgumentContext(ctx.splattingArgument())
     if (ctx.blockArgument() != null) {
       val blockArgAst = astForBlockArgumentContext(ctx.blockArgument())
-      blockAst(blockNode, List[Ast](blockArgAst, splatAst))
+      blockArgAst ++ splatAst
     } else {
-      blockAst(blockNode, List[Ast](splatAst))
+      splatAst
     }
   }
 
-  def astForAssociationContext(ctx: AssociationContext): Ast = {
-    val expr1Ast = astForExpressionContext(ctx.expression().get(0))
-    val expr2Ast = astForExpressionContext(ctx.expression().get(1))
+  def astForAssociationContext(ctx: AssociationContext): Seq[Ast] = {
+    val expr1Asts = astForExpressionContext(ctx.expression().get(0))
+    val expr2Asts = astForExpressionContext(ctx.expression().get(1))
 
     val terminalNode =
       if (ctx.COLON() != null) ctx.COLON()
@@ -1837,68 +1868,59 @@ class AstCreator(filename: String, global: Global)
       .signature("")
       .dispatchType(DispatchTypes.STATIC_DISPATCH)
       .typeFullName(Defines.Any)
-    callAst(callNode, Seq(expr1Ast, expr2Ast))
+    Seq(callAst(callNode, expr1Asts ++ expr2Asts))
   }
 
-  def astForAssociationsContext(ctx: AssociationsContext) = {
-    Ast().withChildren(astsForAssociationsContext(ctx))
-  }
-
-  def astsForAssociationsContext(ctx: AssociationsContext): Seq[Ast] = {
+  def astForAssociationsContext(ctx: AssociationsContext): Seq[Ast] = {
     ctx
       .association()
       .asScala
-      .map(assoc => {
+      .flatMap(assoc => {
         astForAssociationContext(assoc)
       })
       .toSeq
   }
 
-  def astForBlockSplattingExprAssocTypeArgumentsContext(ctx: BlockSplattingExprAssocTypeArgumentsContext): Ast = {
-    val blockArgAst      = astForBlockArgumentContext(ctx.blockArgument())
-    val splatAst         = astForSplattingArgumentContext(ctx.splattingArgument())
-    val associationsAsts = astsForAssociationsContext(ctx.associations())
-    val expAsts          = ctx.expressions().expression().asScala.map(exp => astForExpressionContext(exp)).toSeq
-    val asts             = Seq(blockArgAst, splatAst) ++ associationsAsts ++ expAsts
-    val blockNode        = NewBlock().typeFullName(Defines.Any)
-    blockAst(blockNode, asts.toList)
+  def astForBlockSplattingExprAssocTypeArgumentsContext(ctx: BlockSplattingExprAssocTypeArgumentsContext): Seq[Ast] = {
+    val blockArgAsts     = astForBlockArgumentContext(ctx.blockArgument())
+    val splatAsts        = astForSplattingArgumentContext(ctx.splattingArgument())
+    val associationsAsts = astForAssociationsContext(ctx.associations())
+    val expAsts          = ctx.expressions().expression().asScala.flatMap(exp => astForExpressionContext(exp)).toSeq
+    blockArgAsts ++ splatAsts ++ associationsAsts ++ expAsts
   }
 
-  def astForBlockExprAssocTypeArgumentsContext(ctx: BlockExprAssocTypeArgumentsContext): Ast = {
+  def astForBlockExprAssocTypeArgumentsContext(ctx: BlockExprAssocTypeArgumentsContext): Seq[Ast] = {
     val listAsts = ListBuffer[Ast]()
 
     if (ctx.blockArgument() != null) {
-      listAsts.addOne(astForBlockArgumentContext(ctx.blockArgument()))
+      listAsts.addAll(astForBlockArgumentContext(ctx.blockArgument()))
     }
 
     if (ctx.associations() != null) {
-      listAsts.addOne(astForAssociationsContext(ctx.associations()))
+      listAsts.addAll(astForAssociationsContext(ctx.associations()))
     } else {
-      val exprAsts = ctx.expressions().expression().asScala.map(exp => astForExpressionContext(exp)).toList
+      val exprAsts = ctx.expressions().expression().asScala.flatMap(exp => astForExpressionContext(exp))
       listAsts.addAll(exprAsts)
     }
 
-    val blockNode = NewBlock().typeFullName(Defines.Any)
-    blockAst(blockNode, listAsts.toList)
+    listAsts.toSeq
   }
 
-  def astForArgumentsWithoutParenthesesContext(ctx: ArgumentsWithoutParenthesesContext): Ast = {
+  def astForArgumentsWithoutParenthesesContext(ctx: ArgumentsWithoutParenthesesContext): Seq[Ast] = {
     astForArgumentsContext(ctx.arguments())
   }
 
-  def astForCommandContext(ctx: CommandContext): Ast = {
+  def astForCommandContext(ctx: CommandContext): Seq[Ast] = {
     if (ctx.SUPER() != null) {
-      val argumentsWithoutParenAst = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
-      Ast().withChild(argumentsWithoutParenAst)
+      astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
     } else if (ctx.YIELD() != null) {
-      val argumentsWithoutParenAst = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
-      Ast().withChild(argumentsWithoutParenAst)
+      astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
     } else if (ctx.methodIdentifier() != null) {
-      val methodIdentifierAst = astForMethodIdentifierContext(ctx.methodIdentifier())
-      methodNameAsIdentiferQ.enqueue(methodIdentifierAst)
-      val argsAst = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
+      val methodIdentifierAsts = astForMethodIdentifierContext(ctx.methodIdentifier())
+      methodNameAsIdentiferQ.enqueue(methodIdentifierAsts.head)
+      val argsAsts = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
 
-      val callNodes = methodIdentifierAst.nodes.filter(node => node.isInstanceOf[NewCall])
+      val callNodes = methodIdentifierAsts.head.nodes.filter(node => node.isInstanceOf[NewCall])
       if (callNodes.size == 1) {
         val callNode = callNodes.head.asInstanceOf[NewCall]
         if (
@@ -1907,18 +1929,23 @@ class AstCreator(filename: String, global: Global)
           callNode.name == "load"
         ) {
           val importedFile =
-            argsAst.nodes.filter(node => node.isInstanceOf[NewLiteral]).head.asInstanceOf[NewLiteral].code
+            argsAsts.head.nodes
+              .filter(node => node.isInstanceOf[NewLiteral])
+              .head
+              .asInstanceOf[NewLiteral]
+              .code
           println(s"AST to be created for imported file ${importedFile}")
 
         }
-        callAst(callNode, Seq(argsAst))
+        Seq(callAst(callNode, argsAsts))
       } else {
-        argsAst
+        argsAsts
       }
     } else if (ctx.primary() != null) {
-      val argsAst        = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
-      val primaryAst     = astForPrimaryContext(ctx.primary())
-      val methodCallNode = astForMethodNameContext(ctx.methodName()).nodes.head.asInstanceOf[NewCall]
+      val argsAst    = astForArgumentsWithoutParenthesesContext(ctx.argumentsWithoutParentheses())
+      val primaryAst = astForPrimaryContext(ctx.primary())
+      val methodCallNode = astForMethodNameContext(ctx.methodName()).head.nodes.head
+        .asInstanceOf[NewCall]
       val callNode = NewCall()
         .name(getActualMethodName(methodCallNode.name))
         .code(ctx.getText)
@@ -1928,17 +1955,17 @@ class AstCreator(filename: String, global: Global)
         .typeFullName(Defines.Any)
         .lineNumber(methodCallNode.lineNumber)
         .columnNumber(methodCallNode.columnNumber)
-      callAst(callNode, Seq(primaryAst, argsAst))
+      Seq(callAst(callNode, primaryAst ++ argsAst))
     } else {
-      Ast()
+      Seq(Ast())
     }
   }
 
-  def astForCommandTypeArgumentsContext(ctx: CommandTypeArgumentsContext): Ast = {
+  def astForCommandTypeArgumentsContext(ctx: CommandTypeArgumentsContext): Seq[Ast] = {
     astForCommandContext(ctx.command())
   }
 
-  def astForArgumentsContext(ctx: ArgumentsContext): Ast = ctx match {
+  def astForArgumentsContext(ctx: ArgumentsContext): Seq[Ast] = ctx match {
     case ctx: BlockArgumentTypeArgumentsContext           => astForBlockArgumentTypeArgumentsContext(ctx)
     case ctx: BlockSplattingTypeArgumentsContext          => astForBlockSplattingTypeArgumentsContext(ctx)
     case ctx: BlockSplattingExprAssocTypeArgumentsContext => astForBlockSplattingExprAssocTypeArgumentsContext(ctx)
@@ -1946,15 +1973,15 @@ class AstCreator(filename: String, global: Global)
     case ctx: CommandTypeArgumentsContext                 => astForCommandTypeArgumentsContext(ctx)
     case _ =>
       logger.error("astForArgumentsContext() All contexts mismatched.")
-      Ast()
+      Seq(Ast())
   }
 
-  def astForYieldWithOptionalArgumentContext(ctx: YieldWithOptionalArgumentContext): Ast = {
-    if (ctx.arguments() == null) return Ast()
+  def astForYieldWithOptionalArgumentContext(ctx: YieldWithOptionalArgumentContext): Seq[Ast] = {
+    if (ctx.arguments() == null) return Seq(Ast())
     astForArgumentsContext(ctx.arguments())
   }
 
-  def astForYieldWithOptionalArgumentPrimaryContext(ctx: YieldWithOptionalArgumentPrimaryContext): Ast = {
+  def astForYieldWithOptionalArgumentPrimaryContext(ctx: YieldWithOptionalArgumentPrimaryContext): Seq[Ast] = {
     astForYieldWithOptionalArgumentContext(ctx.yieldWithOptionalArgument())
   }
 }
diff --git a/project/Versions.scala b/project/Versions.scala
index 99c1e690d..fd7241c41 100644
--- a/project/Versions.scala
+++ b/project/Versions.scala
@@ -5,7 +5,7 @@ object Versions {
   // causes problems upstreams.
   val antlr         = "4.7"
   val scalatest     = "3.2.16"
-  val cats          = "3.4.11"
+  val cats          = "3.5.0"
   val json4s        = "4.0.6"
   val gradleTooling = "7.6.1"
   val circe         = "0.14.5"


Test patch:
diff --git a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ArrayTests.scala b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ArrayTests.scala
index 38e540b4e..4e17f84ad 100644
--- a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ArrayTests.scala
+++ b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ArrayTests.scala
@@ -70,21 +70,20 @@ class ArrayTests extends PhpCode2CpgFixture {
         |)
         |""".stripMargin)
 
-    inside(cpg.method.internal.body.astChildren.collectAll[Block].l) { case List(arrayBlock) =>
-      inside(arrayBlock.astChildren.l) {
-        case List(tmpLocal: Local, aAssign: Call, bAssign: Call, tmpIdent: Identifier) =>
-          tmpLocal.name shouldBe "tmp0"
-          tmpLocal.code shouldBe "$tmp0"
+    inside(cpg.method.internal.body.astChildren.l) { case List(tmpLocal: Local, arrayBlock: Block) =>
+      tmpLocal.name shouldBe "tmp0"
+      tmpLocal.code shouldBe "$tmp0"
 
-          aAssign.code shouldBe "$tmp0[\"A\"] = 1"
-          aAssign.lineNumber shouldBe Some(3)
+      inside(arrayBlock.astChildren.l) { case List(aAssign: Call, bAssign: Call, tmpIdent: Identifier) =>
+        aAssign.code shouldBe "$tmp0[\"A\"] = 1"
+        aAssign.lineNumber shouldBe Some(3)
 
-          bAssign.code shouldBe "$tmp0[\"B\"] = 2"
-          bAssign.lineNumber shouldBe Some(4)
+        bAssign.code shouldBe "$tmp0[\"B\"] = 2"
+        bAssign.lineNumber shouldBe Some(4)
 
-          tmpIdent.name shouldBe "tmp0"
-          tmpIdent.code shouldBe "$tmp0"
-          tmpIdent._localViaRefOut should contain(tmpLocal)
+        tmpIdent.name shouldBe "tmp0"
+        tmpIdent.code shouldBe "$tmp0"
+        tmpIdent._localViaRefOut should contain(tmpLocal)
       }
     }
   }
@@ -97,21 +96,20 @@ class ArrayTests extends PhpCode2CpgFixture {
         |)
         |""".stripMargin)
 
-    inside(cpg.method.internal.body.astChildren.collectAll[Block].l) { case List(arrayBlock) =>
-      inside(arrayBlock.astChildren.l) {
-        case List(tmpLocal: Local, aAssign: Call, bAssign: Call, tmpIdent: Identifier) =>
-          tmpLocal.name shouldBe "tmp0"
-          tmpLocal.code shouldBe "$tmp0"
+    inside(cpg.method.internal.body.astChildren.l) { case List(tmpLocal: Local, arrayBlock: Block) =>
+      tmpLocal.name shouldBe "tmp0"
+      tmpLocal.code shouldBe "$tmp0"
 
-          aAssign.code shouldBe "$tmp0[0] = \"A\""
-          aAssign.lineNumber shouldBe Some(3)
+      inside(arrayBlock.astChildren.l) { case List(aAssign: Call, bAssign: Call, tmpIdent: Identifier) =>
+        aAssign.code shouldBe "$tmp0[0] = \"A\""
+        aAssign.lineNumber shouldBe Some(3)
 
-          bAssign.code shouldBe "$tmp0[1] = \"B\""
-          bAssign.lineNumber shouldBe Some(4)
+        bAssign.code shouldBe "$tmp0[1] = \"B\""
+        bAssign.lineNumber shouldBe Some(4)
 
-          tmpIdent.name shouldBe "tmp0"
-          tmpIdent.code shouldBe "$tmp0"
-          tmpIdent._localViaRefOut should contain(tmpLocal)
+        tmpIdent.name shouldBe "tmp0"
+        tmpIdent.code shouldBe "$tmp0"
+        tmpIdent._localViaRefOut should contain(tmpLocal)
       }
     }
   }
@@ -123,11 +121,11 @@ class ArrayTests extends PhpCode2CpgFixture {
         |)
         |""".stripMargin)
 
-    inside(cpg.method.internal.body.astChildren.collectAll[Block].l) { case List(arrayBlock) =>
-      inside(arrayBlock.astChildren.l) { case List(tmpLocal: Local, assign: Call, tmpIdent: Identifier) =>
-        tmpLocal.name shouldBe "tmp0"
-        tmpLocal.code shouldBe "$tmp0"
+    inside(cpg.method.internal.body.astChildren.l) { case List(tmpLocal: Local, arrayBlock: Block) =>
+      tmpLocal.name shouldBe "tmp0"
+      tmpLocal.code shouldBe "$tmp0"
 
+      inside(arrayBlock.astChildren.l) { case List(assign: Call, tmpIdent: Identifier) =>
         assign.code shouldBe "$tmp0[2] = \"A\""
         inside(assign.argument.collectAll[Call].argument.l) { case List(array: Identifier, index: Literal) =>
           array.name shouldBe "tmp0"
@@ -157,10 +155,12 @@ class ArrayTests extends PhpCode2CpgFixture {
         |)
         |""".stripMargin)
 
-    inside(cpg.method.internal.body.astChildren.collectAll[Block].l) { case List(arrayBlock) =>
+    inside(cpg.method.internal.body.astChildren.l) { case List(tmpLocal: Local, arrayBlock: Block) =>
+      tmpLocal.name shouldBe "tmp0"
+      tmpLocal.code shouldBe "$tmp0"
+
       inside(arrayBlock.astChildren.l) {
         case List(
-              tmpLocal: Local,
               aAssign: Call,
               cAssign: Call,
               fourAssign: Call,
@@ -170,9 +170,6 @@ class ArrayTests extends PhpCode2CpgFixture {
               eightAssign: Call,
               tmpIdent: Identifier
             ) =>
-          tmpLocal.name shouldBe "tmp0"
-          tmpLocal.code shouldBe "$tmp0"
-
           aAssign.code shouldBe "$tmp0[\"A\"] = \"B\""
           cAssign.code shouldBe "$tmp0[0] = \"C\""
           fourAssign.code shouldBe "$tmp0[4] = \"D\""
diff --git a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/CallTests.scala b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/CallTests.scala
index ce5575536..4e067d90d 100644
--- a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/CallTests.scala
+++ b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/CallTests.scala
@@ -7,6 +7,7 @@ import io.joern.x2cpg.Defines
 import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators}
 import io.shiftleft.codepropertygraph.generated.nodes.{Call, Identifier}
 import io.shiftleft.semanticcpg.language._
+import io.shiftleft.codepropertygraph.generated.nodes.Block
 
 class CallTests extends PhpCode2CpgFixture {
   "halt_compiler calls should be created correctly" in {
@@ -49,22 +50,38 @@ class CallTests extends PhpCode2CpgFixture {
     }
   }
 
-  "static method calls with simple names should be correct" in {
+  "static method calls with simple names" should {
     val cpg = code("<?php\nFoo::foo($x);")
 
-    inside(cpg.call.l) { case List(fooCall) =>
-      fooCall.name shouldBe "foo"
-      fooCall.methodFullName shouldBe s"Foo.foo:${Defines.UnresolvedSignature}(1)"
-      fooCall.receiver.isEmpty shouldBe true
-      fooCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH
-      fooCall.lineNumber shouldBe Some(2)
-      fooCall.code shouldBe "Foo::foo($x)"
+    "have the correct method node defined" in {
+      inside(cpg.call.l) { case List(fooCall) =>
+        fooCall.name shouldBe "foo"
+        fooCall.methodFullName shouldBe s"Foo::foo"
+        fooCall.receiver.isEmpty shouldBe true
+        fooCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH
+        fooCall.lineNumber shouldBe Some(2)
+        fooCall.code shouldBe "Foo::foo($x)"
+      }
+    }
 
-      inside(fooCall.argument.l) { case List(xArg: Identifier) =>
+    "have the correct arguments" in {
+      inside(cpg.call.argument.l) { case List(xArg: Identifier) =>
         xArg.name shouldBe "x"
         xArg.code shouldBe "$x"
       }
     }
+
+    "have the correct child nodes" in {
+      inside(cpg.call.astChildren.l) { case List(arg: Identifier) =>
+        arg.name shouldBe "x"
+      }
+    }
+
+    "not create an identifier for the class target" in {
+      inside(cpg.identifier.l) { case List(xArg) =>
+        xArg.name shouldBe "x"
+      }
+    }
   }
 
   "method calls with simple names should be correct" in {
diff --git a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ClosureTests.scala b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ClosureTests.scala
index 92b2313dd..804d3be73 100644
--- a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ClosureTests.scala
+++ b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ClosureTests.scala
@@ -72,7 +72,7 @@ class ClosureTests extends PhpCode2CpgFixture {
       inside(closureMethod.body.astChildren.l) { case List(use1: Local, use2: Local, echoCall: Call) =>
         use1.name shouldBe "use1"
         use1.code shouldBe "$use1"
-        use1.closureBindingId.isEmpty shouldBe false
+        use1.closureBindingId shouldBe Some(s"foo.php:$expectedName:use1")
         inside(cpg.all.collectAll[ClosureBinding].filter(_.closureBindingId == use1.closureBindingId).l) {
           case List(closureBinding) =>
             closureBinding.closureOriginalName shouldBe Some("use1")
@@ -80,11 +80,20 @@ class ClosureTests extends PhpCode2CpgFixture {
 
         use2.name shouldBe "use2"
         use2.code shouldBe "&$use2"
+        use2.closureBindingId shouldBe Some(s"foo.php:$expectedName:use2")
 
         echoCall.code shouldBe "echo $value"
       }
     }
 
+    "have a ref edge from the closure binding for a use to the captured node" in {
+      inside(cpg.all.collectAll[ClosureBinding].filter(_.closureOriginalName.contains("use1")).l) {
+        case List(closureBinding) =>
+          val capturedNode = cpg.method.nameExact("<global>").local.name("use1").head
+          closureBinding.refOut.toList shouldBe List(capturedNode)
+      }
+    }
+
     "have a correct MethodRef added to the AST where the closure is defined" in {
       inside(cpg.assignment.code(".*closure.*").argument.l) { case List(_: Identifier, methodRef: MethodRef) =>
         val expectedName = s"foo.php:<global>->__closure0"
diff --git a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ControlStructureTests.scala b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ControlStructureTests.scala
index d1acd089e..51364cb35 100644
--- a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ControlStructureTests.scala
+++ b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/ControlStructureTests.scala
@@ -16,6 +16,9 @@ import io.shiftleft.codepropertygraph.generated.nodes.{
   Local
 }
 import io.shiftleft.semanticcpg.language._
+import io.shiftleft.codepropertygraph.generated.nodes.AstNode
+
+import scala.util.Try
 
 class ControlStructureTests extends PhpCode2CpgFixture {
   "switch statements" should {
@@ -1024,6 +1027,12 @@ class ControlStructureTests extends PhpCode2CpgFixture {
     }
   }
 
+  "foreach statements should not create parentless identifiers" in {
+    val cpg = code("""<?php foreach($GLOBALS as $x) {}""")
+
+    cpg.all.collectAll[Identifier].filter(node => Try(node.astParent).isFailure).toList shouldBe Nil
+  }
+
   "foreach statements with only simple values should be represented as a for" in {
     val cpg = code("""<?php
      |function foo($arr) {
diff --git a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/MethodTests.scala b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/MethodTests.scala
index 151dd16b5..77806ba5b 100644
--- a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/MethodTests.scala
+++ b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/MethodTests.scala
@@ -52,7 +52,7 @@ class MethodTests extends PhpCode2CpgFixture {
         |}
         |""".stripMargin)
 
-    inside(cpg.method.name("foo").body.astChildren.l) { case List(xLocal: Local, yLocal: Local, xAssign: Call) =>
+    inside(cpg.method.name("foo").body.astChildren.l) { case List(xLocal: Local, xAssign: Call, yLocal: Local) =>
       xLocal.name shouldBe "x"
       xLocal.code shouldBe "static $x"
       xLocal.lineNumber shouldBe Some(3)
diff --git a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/OperatorTests.scala b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/OperatorTests.scala
index 9876e4367..e7cbc6639 100644
--- a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/OperatorTests.scala
+++ b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/OperatorTests.scala
@@ -5,7 +5,7 @@ import io.joern.php2cpg.parser.Domain.{PhpDomainTypeConstants, PhpOperators}
 import io.joern.php2cpg.testfixtures.PhpCode2CpgFixture
 import io.joern.x2cpg.Defines
 import io.shiftleft.codepropertygraph.generated.{DispatchTypes, Operators}
-import io.shiftleft.codepropertygraph.generated.nodes.{Block, Call, Identifier, Literal, TypeRef}
+import io.shiftleft.codepropertygraph.generated.nodes.{Block, Call, Identifier, Literal, Local, TypeRef}
 import io.shiftleft.passes.IntervalKeyPool
 import io.shiftleft.semanticcpg.language._
 import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal
diff --git a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/TypeDeclTests.scala b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/TypeDeclTests.scala
index 8110f171b..52fce7634 100644
--- a/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/TypeDeclTests.scala
+++ b/joern-cli/frontends/php2cpg/src/test/scala/io/joern/php2cpg/querying/TypeDeclTests.scala
@@ -5,6 +5,7 @@ import io.joern.x2cpg.Defines
 import io.shiftleft.codepropertygraph.generated.{ModifierTypes, Operators}
 import io.shiftleft.codepropertygraph.generated.nodes.{Call, Identifier, Literal, Local, Member, Method}
 import io.shiftleft.semanticcpg.language._
+import io.shiftleft.codepropertygraph.generated.nodes.Block
 
 class TypeDeclTests extends PhpCode2CpgFixture {
 
@@ -55,38 +56,37 @@ class TypeDeclTests extends PhpCode2CpgFixture {
         |}
         |""".stripMargin)
 
-    inside(cpg.method.name("foo").body.astChildren.isBlock.l) { case List(constructorBlock) =>
+    inside(cpg.method.name("foo").body.astChildren.l) { case List(tmpLocal: Local, constructorBlock: Block) =>
+      tmpLocal.name shouldBe "tmp0"
+      tmpLocal.code shouldBe "$tmp0"
+
       constructorBlock.lineNumber shouldBe Some(3)
 
-      inside(constructorBlock.astChildren.l) {
-        case List(tmpLocal: Local, allocAssign: Call, initCall: Call, tmpVar: Identifier) =>
-          tmpLocal.name shouldBe "tmp0"
-          tmpLocal.code shouldBe "$tmp0"
-
-          allocAssign.methodFullName shouldBe Operators.assignment
-          inside(allocAssign.astChildren.l) { case List(tmpIdentifier: Identifier, allocCall: Call) =>
-            tmpIdentifier.name shouldBe "tmp0"
-            tmpIdentifier.code shouldBe "$tmp0"
-            tmpIdentifier._localViaRefOut should contain(tmpLocal)
-
-            allocCall.name shouldBe Operators.alloc
-            allocCall.methodFullName shouldBe Operators.alloc
-            allocCall.lineNumber shouldBe Some(3)
-            allocCall.code shouldBe "Foo.<alloc>()"
-          }
+      inside(constructorBlock.astChildren.l) { case List(allocAssign: Call, initCall: Call, tmpVar: Identifier) =>
+        allocAssign.methodFullName shouldBe Operators.assignment
+        inside(allocAssign.astChildren.l) { case List(tmpIdentifier: Identifier, allocCall: Call) =>
+          tmpIdentifier.name shouldBe "tmp0"
+          tmpIdentifier.code shouldBe "$tmp0"
+          tmpIdentifier._localViaRefOut should contain(tmpLocal)
+
+          allocCall.name shouldBe Operators.alloc
+          allocCall.methodFullName shouldBe Operators.alloc
+          allocCall.lineNumber shouldBe Some(3)
+          allocCall.code shouldBe "Foo.<alloc>()"
+        }
 
-          initCall.name shouldBe "__construct"
-          initCall.methodFullName shouldBe s"Foo->__construct"
-          initCall.signature shouldBe s"${Defines.UnresolvedSignature}(1)"
-          initCall.code shouldBe "Foo->__construct(42)"
-          inside(initCall.argument.l) { case List(tmpIdentifier: Identifier, literal: Literal) =>
-            tmpIdentifier.name shouldBe "tmp0"
-            tmpIdentifier.code shouldBe "$tmp0"
-            tmpIdentifier.argumentIndex shouldBe 0
-            tmpIdentifier._localViaRefOut should contain(tmpLocal)
-            literal.code shouldBe "42"
-            literal.argumentIndex shouldBe 1
-          }
+        initCall.name shouldBe "__construct"
+        initCall.methodFullName shouldBe s"Foo->__construct"
+        initCall.signature shouldBe s"${Defines.UnresolvedSignature}(1)"
+        initCall.code shouldBe "Foo->__construct(42)"
+        inside(initCall.argument.l) { case List(tmpIdentifier: Identifier, literal: Literal) =>
+          tmpIdentifier.name shouldBe "tmp0"
+          tmpIdentifier.code shouldBe "$tmp0"
+          tmpIdentifier.argumentIndex shouldBe 0
+          tmpIdentifier._localViaRefOut should contain(tmpLocal)
+          literal.code shouldBe "42"
+          literal.argumentIndex shouldBe 1
+        }
       }
     }
   }
diff --git a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/IdentifierTests.scala b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/IdentifierTests.scala
index 9f85dbeff..bfbac5fb4 100644
--- a/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/IdentifierTests.scala
+++ b/joern-cli/frontends/rubysrc2cpg/src/test/scala/io/joern/rubysrc2cpg/querying/IdentifierTests.scala
@@ -824,12 +824,6 @@ class IdentifierTests extends RubyCode2CpgFixture {
         .l
         .size shouldBe 1
     }
-    "recognise all call nodes" in {
-      cpg.method
-        .name("puts")
-        .l
-        .size shouldBe 1
-    }
 
     "successfully plot ASTs" in {
       cpg.method.name(":program").dotAst.l
@@ -867,9 +861,49 @@ class IdentifierTests extends RubyCode2CpgFixture {
         .size shouldBe 1
     }
     "successfully plot ASTs" in {
-      cpg.method.name(":some_method").dotAst.l
+      cpg.method.name("some_method").dotAst.l
       cpg.method.name(":program").dotAst.l
     }
   }
 
+  "CPG for code with private/protected/public" should {
+    val cpg = code("""
+        |class SomeClass
+        |  private
+        |  def method1
+        |  end
+        |
+        |  protected
+        |  def method2
+        |  end
+        |
+        |  public
+        |  def method3
+        |  end
+        |end
+        |
+        |""".stripMargin)
+
+    "recognise all method nodes" in {
+      cpg.method
+        .name("method1")
+        .l
+        .size shouldBe 1
+      cpg.method
+        .name("method1")
+        .l
+        .size shouldBe 1
+      cpg.method
+        .name("method3")
+        .l
+        .size shouldBe 1
+
+    }
+    "successfully plot ASTs" in {
+      cpg.method.name(":program").dotAst.l
+      cpg.method.name("method1").dotAst.l
+      cpg.method.name("method2").dotAst.l
+      cpg.method.name("method3").dotAst.l
+    }
+  }
 }