Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
6608084
[ruby] Add support for basic ERB files (#5404)
AndreiDreyer Apr 17, 2025
545f74d
[ruby] rework erb support. Added templateOutRaw and templateOutEscape…
AndreiDreyer Apr 23, 2025
e1d651b
[ruby] Add .html.erb files as config files for ruby
AndreiDreyer Apr 23, 2025
82a37a2
review comments
AndreiDreyer Apr 23, 2025
e9401dc
Fixed ERB lowering implementation. Updated test expectations
AndreiDreyer May 14, 2025
e8813ca
Updated comments
AndreiDreyer May 14, 2025
03c79d9
remove test
AndreiDreyer May 15, 2025
0db1370
upgrade ruby-ast-gen version
AndreiDreyer Jun 2, 2025
e63a443
upgrade ruby-ast-gen version, remove unnecessary whitespace
AndreiDreyer Jun 2, 2025
799c7a6
[ruby] update code string and tests for joern__buffer changes to self…
AndreiDreyer Jul 17, 2025
903ea4d
[ruby] update ERB tests to reflect no line numbers being set from rub…
AndreiDreyer Jul 17, 2025
f9227ef
[ruby] remove self parameter from lambda, capture from outside instead
AndreiDreyer Jul 18, 2025
bf9c788
[ruby] Add empty expression for if statement with no expressions in b…
AndreiDreyer Jul 28, 2025
415d890
[ruby] Only look for the closest self in the outerscope in closure body
AndreiDreyer Jul 29, 2025
0b1983e
[ruby] capture self variable
AndreiDreyer Jul 30, 2025
7b74888
fix: resolve multiple self identifier refOuts bug
Aug 20, 2025
78d6684
chore: add return type
Aug 20, 2025
ee4a305
refactor: scalafmt run
Aug 20, 2025
f22bd19
fix: resolve incorrect closure binding refOut bug
Sep 11, 2025
9c6ae12
feat: use joern__buffer_append
Oct 9, 2025
4923261
chore: scalafmt
Oct 9, 2025
b75c092
fix: recognise joern_inner_buffer appends as erb calls
Oct 14, 2025
73101ee
fix: retain joern__buffer_append args' receiver ast
Oct 14, 2025
9c9bed7
improvement: turn lookupSelfInOuterScope into wrapper
Oct 17, 2025
721e174
improvement: override span instead of using 2 parameter lists
Oct 17, 2025
fe6f43d
refactor: remove debug lines in test
Oct 17, 2025
cc6f13b
improvement: move typeFullName to outer scope
Oct 17, 2025
926e851
improvement: use buffer_append constant
Oct 17, 2025
504464f
improvement: use Option.when
Oct 17, 2025
1650c2f
improvement: remove case for self
Oct 17, 2025
45a684f
improvement: remove ERB call special case
Oct 17, 2025
30fc06a
improvement: use camelCase for ERB buffer symbols and methods
Oct 17, 2025
1778915
refactor: scalafmt
Oct 17, 2025
dd5db48
cleanup: remove unused variable
Oct 17, 2025
f19c5ab
improvement: rename isErbCall
Oct 20, 2025
de1453a
improvement: avoid unnecessary call
Oct 20, 2025
5319cc8
refactor: refactor block
Oct 20, 2025
39edae8
fix: don't generate receiver AST if isStatic
Oct 27, 2025
f0fdf28
fix: add tmp assignments to tests
Oct 27, 2025
0b8bd4d
Revert "fix: add tmp assignments to tests"
Oct 29, 2025
d524e45
Revert "fix: don't generate receiver AST if isStatic"
Oct 29, 2025
5c2a7a4
chore: use Option.unless
Oct 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
rubysrc2cpg {
ruby_ast_gen_version: "0.33.0"
ruby_ast_gen_version: "0.58.0"
joern_type_stubs_version: "0.6.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class RubySrc2Cpg extends X2CpgFrontend {
}
.filter { x =>
if x.fileContent.isBlank then logger.info(s"File content empty, skipping - ${x.fileName}")
!x.fileContent.isBlank
!x.fileContent.isBlank || x.fileName.endsWith(".html.erb")
}

val internalProgramSummary = ConcurrentTaskUtil
Expand Down Expand Up @@ -122,7 +122,7 @@ object RubySrc2Cpg {
): Iterator[() => AstCreator] = {
astFiles.map { fileName => () =>
val parserResult = RubyJsonParser.readFile(Paths.get(fileName))
val rubyProgram = new RubyJsonToNodeCreator().visitProgram(parserResult.json)
val rubyProgram = new RubyJsonToNodeCreator(fileName = fileName).visitProgram(parserResult.json)
val sourceFileName = parserResult.fullPath
val fileContent = new String(Files.readAllBytes(Paths.get(sourceFileName)), Charset.defaultCharset())
new AstCreator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import io.joern.rubysrc2cpg.astcreation.RubyIntermediateAst.{
}
import io.joern.rubysrc2cpg.datastructures.{BlockScope, FieldDecl}
import io.joern.rubysrc2cpg.passes.Defines
import io.joern.rubysrc2cpg.passes.Defines.RubyOperators
import io.joern.rubysrc2cpg.passes.GlobalTypes
import io.joern.rubysrc2cpg.passes.GlobalTypes.{kernelFunctions, kernelPrefix}
import io.joern.x2cpg.frontendspecific.rubysrc2cpg.Constants
import io.joern.x2cpg.{Ast, ValidationMode}
import io.shiftleft.codepropertygraph.generated.nodes.*
import io.shiftleft.codepropertygraph.generated.{DispatchTypes, EdgeTypes, Operators}
Expand Down Expand Up @@ -252,6 +254,14 @@ trait AstCreatorHelper(implicit withSchemaValidation: ValidationMode) { this: As
StatementList(tmpAssignment :: ifStmt :: Nil)(originSpan)
}

protected def isErbCall(callName: String): Boolean = ErbTemplateCallNames.contains(callName)

protected val ErbTemplateCallNames: Map[String, String] = Map(
Constants.joernErbTemplateOutRawName -> RubyOperators.templateOutRaw,
Constants.joernErbTemplateOutEscapeName -> RubyOperators.templateOutEscape,
Constants.joernErbBufferAppend -> RubyOperators.bufferAppend
)

protected val UnaryOperatorNames: Map[String, String] = Map(
"!" -> Operators.logicalNot,
"not" -> Operators.logicalNot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,12 @@ package io.joern.rubysrc2cpg.astcreation

import io.joern.rubysrc2cpg.astcreation.RubyIntermediateAst.{Unknown, Block as RubyBlock, *}
import io.joern.rubysrc2cpg.datastructures.BlockScope
import io.joern.rubysrc2cpg.parser.RubyJsonHelpers
import io.joern.rubysrc2cpg.passes.Defines
import io.joern.rubysrc2cpg.passes.GlobalTypes
import io.joern.rubysrc2cpg.passes.Defines.{RubyOperators, prefixAsKernelDefined}
import io.joern.rubysrc2cpg.passes.Defines.RubyOperators
import io.joern.rubysrc2cpg.passes.{Defines, GlobalTypes}
import io.joern.x2cpg.frontendspecific.rubysrc2cpg.Constants
import io.joern.x2cpg.{Ast, ValidationMode, Defines as XDefines}
import io.shiftleft.codepropertygraph.generated.nodes.*
import io.shiftleft.codepropertygraph.generated.{
ControlStructureTypes,
DispatchTypes,
EdgeTypes,
NodeTypes,
Operators,
PropertyNames
}
import io.shiftleft.codepropertygraph.generated.{ControlStructureTypes, DispatchTypes, EdgeTypes, NodeTypes, Operators}

import scala.collection.mutable

Expand All @@ -42,6 +34,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
case node: TypeIdentifier => astForTypeIdentifier(node)
case node: RubyIdentifier => astForSimpleIdentifier(node)
case node: SimpleCall => astForSimpleCall(node)
case node: ErbTemplateCall => astForErbTemplateCall(node)
case node: RequireCall => astForRequireCall(node)
case node: IncludeCall => astForIncludeCall(node)
case node: RaiseCall => astForRaiseCall(node)
Expand All @@ -64,6 +57,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
case node: ArrayPattern => astForArrayPattern(node)
case node: DummyNode => Ast(node.node)
case node: DummyAst => node.ast
case node: EmptyExpression => Ast()
case node: Unknown => astForUnknown(node)
case x =>
logger.warn(s"Unhandled expression of type ${x.getClass.getSimpleName}")
Expand Down Expand Up @@ -162,7 +156,12 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
case None => x
}
astForFieldAccess(node.copy(target = newTarget)(node.span))
case _ => astForFieldAccess(node)
case _ =>
if (Constants.joernErbBuffers.contains(node.memberName)) {
astForFieldAccess(node, typeFullName = Constants.stringPrefix)
} else {
astForFieldAccess(node)
}
}
}

Expand Down Expand Up @@ -191,26 +190,35 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
protected def astForMemberCall(node: RubyCallWithBase, isStatic: Boolean = false): Ast = {

def createMemberCall(n: RubyCallWithBase): Ast = {
val receiverAst = astForFieldAccess(MemberAccess(n.target, ".", n.methodName)(n.span), stripLeadingAt = true)
val isErbBufferAppendCall = n.methodName == Constants.joernErbBufferAppend

val receiverAstOpt = Option.unless(isErbBufferAppendCall)(
astForFieldAccess(MemberAccess(n.target, ".", n.methodName)(n.span), stripLeadingAt = true)
)

val (baseAst, baseCode) = astForMemberAccessTarget(n.target)
val builtinType = n.target match {
case MemberAccess(_: SelfIdentifier, _, memberName) if isBundledClass(memberName) =>
Option(prefixAsCoreType(memberName))
case x: TypeIdentifier if x.isBuiltin => Option(x.typeFullName)
case _ => None
}
val methodFullName = receiverAst.nodes
.collectFirst {
case _ if builtinType.isDefined => s"${builtinType.get}.${n.methodName}"
case x: NewMethodRef => x.methodFullName
case _ =>
(n.target match {
case ma: MemberAccess => scope.tryResolveTypeReference(ma.memberName).map(_.name)
case _ => typeFromCallTarget(n.target)
}).map(x => s"$x.${n.methodName}")
.getOrElse(XDefines.DynamicCallUnknownFullName)
val methodFullName = if (isErbBufferAppendCall) {
RubyOperators.bufferAppend
} else {
val fullNameOpt = receiverAstOpt.flatMap { ast =>
ast.nodes.headOption.flatMap {
case _ if builtinType.isDefined => builtinType.map(t => s"$t.${n.methodName}")
case x: NewMethodRef => Some(x.methodFullName)
case _ =>
(n.target match {
case ma: MemberAccess => scope.tryResolveTypeReference(ma.memberName).map(_.name)
case _ => typeFromCallTarget(n.target)
}).map(x => s"$x.${n.methodName}")
}
}
.getOrElse(XDefines.DynamicCallUnknownFullName)
fullNameOpt.getOrElse(XDefines.DynamicCallUnknownFullName)
}
val argumentAsts = n.arguments.map(astForMethodCallArgument)
val dispatchType = if (isStatic) DispatchTypes.STATIC_DISPATCH else DispatchTypes.DYNAMIC_DISPATCH

Expand All @@ -223,12 +231,21 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
code(n)
}

val call = callNode(n, callCode, n.methodName, XDefines.DynamicCallUnknownFullName, dispatchType)
if methodFullName != XDefines.DynamicCallUnknownFullName then call.possibleTypes(Seq(methodFullName))
if (isStatic) {
callAst(call, argumentAsts, base = Option(baseAst)).copy(receiverEdges = Nil)
val call = if (isErbBufferAppendCall) {
operatorCallNode(n, callCode, methodFullName, Some(Constants.stringPrefix))
} else {
val call = callNode(n, callCode, n.methodName, XDefines.DynamicCallUnknownFullName, dispatchType)
if methodFullName != XDefines.DynamicCallUnknownFullName then call.possibleTypes(Seq(methodFullName))
call
}
if (isErbBufferAppendCall) {
callAst(call, argumentAsts)
} else {
callAst(call, argumentAsts, base = Option(baseAst), receiver = Option(receiverAst))
if (isStatic) {
callAst(call, argumentAsts, base = Option(baseAst)).copy(receiverEdges = Nil)
} else {
callAst(call, argumentAsts, base = Option(baseAst), receiver = receiverAstOpt)
}
Comment on lines +241 to +248
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this whole if-cascade should also simplify to a single call callAst(call, argumentAsts, base = Option(baseAst), receiver = receiverAstOpt) - after adjusting the condition used for creating receiverAstOpt

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that before and found that .copy(receiverEdges = Nil) was done to remove the receiver edges coming from the baseAst.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we do the same as with the receiver? Conditionally create it only in case we want to actually make use of it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the creation of the receiver stores some state somewhere that's then used in the creation of baseAst. I'm getting a couple of failed tests if I don't create the receiver when isStatic is true. The difference I spotted is that baseAst has more nodes in it when astForFieldAccess is not called for the receiver. I'm not 100% sure what state it is that's at play here but I'm busy debugging to try figure that out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might have something to do with this TODO ->

private def handleTmpGen(target: RubyExpression, rhs: Ast): (Ast, String) = {
// Check cache
val createAssignmentToTmp = !baseAstCache.contains(target)
val tmpName = baseAstCache
.updateWith(target) {
case Some(tmpName) =>
// TODO: Type ref nodes are automatically committed on creation, so if we have found a suitable cached AST,
// we want to clean this creation up.
Option(tmpName)
case None =>
val tmpName = scope.getNewVarTmp
val tmpGenLocal = NewLocal().name(tmpName).code(tmpName).typeFullName(Defines.Any)
scope.addToScope(tmpName, tmpGenLocal) match {
case BlockScope(block) => diffGraph.addEdge(block, tmpGenLocal, EdgeTypes.AST)
case _ =>
}
Option(tmpName)
}
.get

handleTmpGen gets called during the creation of the receiver (astForFieldAccess) and triggers this flow. It's then also triggered when the baseAst is created. I don't understand enough yet to know why these side-effects exist yet the result of astForFieldAccess isn't used when isStatic is true :/.

I'll see if I can figure out how to make this better. I have a call with @ml86 later and will probably ask for some guidance on this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ml86 on closer inspection, I think this implementation is okay. The way the method createMemberCall was doing its operations was actually incorrect. It was resulting in some tmp assignments being omitted. You can have a look at my diff here to double check if what I did makes sense: https://github.com/joernio/joern/pull/5447/files/5319cc841d17e36954830f09782ecbf2438c99f7..f0fdf28d92ae5f751eedb93d76379c9d078e159b

}
}

Expand Down Expand Up @@ -278,7 +295,11 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
}
}

protected def astForFieldAccess(node: MemberAccess, stripLeadingAt: Boolean = false): Ast = {
protected def astForFieldAccess(
node: MemberAccess,
stripLeadingAt: Boolean = false,
typeFullName: String = Defines.Any
): Ast = {
val (memberName, memberCode) = node.target match {
case _ if node.memberName == Defines.Initialize => Defines.Initialize -> Defines.Initialize
case _ if stripLeadingAt => node.memberName -> node.memberName.stripPrefix("@")
Expand Down Expand Up @@ -307,7 +328,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
Operators.fieldAccess,
DispatchTypes.STATIC_DISPATCH,
signature = None,
typeFullName = Option(Defines.Any)
typeFullName = Option(typeFullName)
).possibleTypes(IndexedSeq(memberType.get))
callAst(fieldAccess, Seq(targetAst, fieldIdentifierAst))
}
Expand Down Expand Up @@ -662,6 +683,13 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
astForUnknown(targetNode)
}

protected def astForErbTemplateCall(node: ErbTemplateCall): Ast = {
val argAsts = node.arguments.map(astForExpression)
val opName = ErbTemplateCallNames(node.target.text)
val opNode = callNode(node, node.span.text, opName, opName, DispatchTypes.STATIC_DISPATCH)
callAst(opNode, argAsts)
}

protected def astForRequireCall(node: RequireCall): Ast = {
val pathOpt = node.argument match {
case arg: StaticLiteral if arg.isString => Option(arg.innerText)
Expand Down Expand Up @@ -970,7 +998,8 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
}

private def astForMethodCallWithoutBlock(node: SimpleCall, methodIdentifier: SimpleIdentifier): Ast = {
val methodName = methodIdentifier.text
val methodName =
if (isErbCall(methodIdentifier.text)) ErbTemplateCallNames(methodIdentifier.text) else methodIdentifier.text
lazy val defaultResult = Defines.Any -> XDefines.DynamicCallUnknownFullName

val (receiverType, methodFullNameHint) =
Expand Down Expand Up @@ -1087,4 +1116,6 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
private def getUnaryOperatorName(op: String): Option[String] = UnaryOperatorNames.get(op)

private def getAssignmentOperatorName(op: String): Option[String] = AssignmentOperatorNames.get(op)

private def isLineFeed(text: String): Boolean = text == "\n" || text == "\r" || text == "\r\n"
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,26 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
if (isConstructor) scope.pushNewScope(ConstructorScope(fullName, scope.getNewProcParam))
else scope.pushNewScope(MethodScope(fullName, scope.getNewProcParam))

val thisParameterNode = parameterInNode(
node,
name = Defines.Self,
code = Defines.Self,
index = 0,
isVariadic = false,
typeFullName = Option(scope.surroundingTypeFullName.getOrElse(Defines.Any)),
evaluationStrategy = EvaluationStrategies.BY_SHARING
)
val thisParameterAst = Ast(thisParameterNode)
scope.addToScope(Defines.Self, thisParameterNode)
val parameterAsts = thisParameterAst :: astForParameters(node.parameters)
val parameterAsts =
if (isClosure) {
astForParameters(node.parameters)
} else {
val thisParameterNode = parameterInNode(
node,
name = Defines.Self,
code = Defines.Self,
index = 0,
isVariadic = false,
typeFullName = Option(scope.surroundingTypeFullName.getOrElse(Defines.Any)),
evaluationStrategy = EvaluationStrategies.BY_SHARING
)

val optionalStatementList = statementListForOptionalParams(node.parameters)
val thisParameterAst = Ast(thisParameterNode)
scope.addToScope(Defines.Self, thisParameterNode)
thisParameterAst :: astForParameters(node.parameters)
}

val methodReturn = methodReturnNode(node, Defines.Any)
val optionalStatementList = statementListForOptionalParams(node.parameters)

val refs = {
val typeRef =
Expand All @@ -92,8 +96,21 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
List(typeRef, methodRefNode(node, methodName, fullName, fullName)).map(Ast.apply)
}

val methodReturn = methodReturnNode(node, Defines.Any)

// Consider which variables are captured from the outer scope
val stmtBlockAst = if (isClosure || isSingletonObjectMethod) {
// create closure `self` local used for capturing
scope.lookupSelfInOuterScope
.collect {
case local: NewLocal => local.name
case param: NewMethodParameterIn => param.name
}
.foreach { name =>
val capturingLocal =
localNode(node.body, name, name, Defines.Any, closureBindingId = Option(s"$fullName.$name"))
scope.addToScope(capturingLocal.name, capturingLocal)
}
val baseStmtBlockAst = astForMethodBody(node.body, optionalStatementList)
transformAsClosureBody(node.body, refs, baseStmtBlockAst)
} else {
Expand Down Expand Up @@ -205,22 +222,27 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
methodAst
}

private def transformAsClosureBody(originNode: RubyExpression, refs: List[Ast], baseStmtBlockAst: Ast) = {
private def transformAsClosureBody(originNode: RubyExpression, refs: List[Ast], baseStmtBlockAst: Ast): Ast = {
// Determine which locals are captured
val capturedLocalNodes = baseStmtBlockAst.nodes
.collect { case x: NewIdentifier if x.name != Defines.Self => x } // Self identifiers are handled separately
.collect { case x: NewIdentifier if x.name != Defines.Self => x }
.distinctBy(_.name)
.map(i => scope.lookupVariableInOuterScope(i.name))
.filter(_.nonEmpty)
.filter(_.iterator.nonEmpty)
.flatten
.toSet

val selfLocal = scope.lookupSelfInOuterScope.toSet
val capturedNodes = capturedLocalNodes ++ selfLocal

val capturedIdentifiers = baseStmtBlockAst.nodes.collect {
case i: NewIdentifier if capturedLocalNodes.map(_.name).contains(i.name) => i
case i: NewIdentifier if capturedNodes.map(_.name).contains(i.name) => i
}
// Copy AST block detaching the REF nodes between parent locals/params and identifiers, with the closures' one
val capturedBlockAst = baseStmtBlockAst.copy(refEdges = baseStmtBlockAst.refEdges.filterNot {
case AstEdge(_: NewIdentifier, dst: DeclarationNew) => capturedLocalNodes.contains(dst)
case AstEdge(_: NewIdentifier, dst: NewLocal) if dst.name == Defines.Self =>
capturedNodes.map(_.name).contains(dst.name)
case AstEdge(_: NewIdentifier, dst: DeclarationNew) => capturedNodes.contains(dst)
case _ => false
})

Expand All @@ -229,18 +251,13 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
val astChildren = mutable.Buffer.empty[NewNode]
val refEdges = mutable.Buffer.empty[(NewNode, NewNode)]
val captureEdges = mutable.Buffer.empty[(NewNode, NewNode)]
capturedLocalNodes
.collect {
case local: NewLocal =>
val closureBindingId = scope.variableScopeFullName(local.name).map(x => s"$x.${local.name}")
(local, local.name, local.code, closureBindingId)
case param: NewMethodParameterIn =>
val closureBindingId = scope.variableScopeFullName(param.name).map(x => s"$x.${param.name}")
(param, param.name, param.code, closureBindingId)
}

createClosureBindingInformation(capturedNodes)
.collect { case (capturedLocal, name, code, Some(closureBindingId)) =>
val capturingLocal =
val selfCapturingLocal = Option.when(name == Defines.Self)(scope.lookupSelfInCurrentScope).flatten
val capturingLocal = selfCapturingLocal.getOrElse(
localNode(originNode, name, name, Defines.Any, closureBindingId = Option(closureBindingId))
)

val closureBinding = closureBindingNode(
closureBindingId = closureBindingId,
Expand Down Expand Up @@ -614,4 +631,18 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) { th
case _ => false
}
}

private def createClosureBindingInformation(
capturedNodes: Set[DeclarationNew]
): Set[(DeclarationNew, String, String, Option[String])] = {
capturedNodes
.collect {
case local: NewLocal =>
val closureBindingId = scope.variableScopeFullName(local.name).map(x => s"$x.${local.name}")
(local, local.name, local.code, closureBindingId)
case param: NewMethodParameterIn =>
val closureBindingId = scope.variableScopeFullName(param.name).map(x => s"$x.${param.name}")
(param, param.name, param.code, closureBindingId)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,10 @@ object RubyIntermediateAst {
extends RubyExpression(span)
with RubyCall

final case class ErbTemplateCall(target: RubyExpression, arguments: List[RubyExpression])(span: TextSpan)
extends RubyExpression(span)
with RubyCall

sealed trait AccessModifier extends AllowedTypeDeclarationChild {
def toSimpleIdentifier: SimpleIdentifier
}
Expand Down Expand Up @@ -685,4 +689,5 @@ object RubyIntermediateAst {
extends RubyExpression(span)
with AllowedTypeDeclarationChild

final case class EmptyExpression(override val span: TextSpan) extends RubyExpression(span)
}
Loading
Loading