Merge pull request #249 from github/erb-lib

Add codeql_ruby.ast.Erb library
This commit is contained in:
Alex Ford
2021-09-02 16:26:52 +01:00
committed by GitHub
13 changed files with 543 additions and 24 deletions

View File

@@ -50,4 +50,12 @@ class Location extends @location {
filepath = f.getAbsolutePath()
)
}
/** Holds if this location starts strictly before the specified location. */
pragma[inline]
predicate strictlyBefore(Location other) {
this.getStartLine() < other.getStartLine()
or
this.getStartLine() = other.getStartLine() and this.getStartColumn() < other.getStartColumn()
}
}

View File

@@ -2,6 +2,7 @@ import codeql.Locations
import ast.Call
import ast.Control
import ast.Constant
import ast.Erb
import ast.Expr
import ast.Literal
import ast.Method

View File

@@ -0,0 +1,267 @@
private import codeql.Locations
private import codeql.ruby.AST
private import internal.Erb
private import internal.TreeSitter
/**
* A node in the ERB abstract syntax tree. This class is the base class for all
* ERB elements.
*/
class ErbAstNode extends TAstNode {
/** Gets a textual representation of this node. */
cached
string toString() { none() }
/** Gets the location of this node. */
Location getLocation() { result = getLocation(this) }
/**
* Gets the name of a primary CodeQL class to which this node belongs.
*
* This predicate always has a result. If no primary class can be
* determined, the result is `"???"`. If multiple primary classes match,
* this predicate can have multiple results.
*/
string getAPrimaryQlClass() { result = "???" }
}
/**
* An ERB template. This can contain multiple directives to be executed when
* the template is compiled.
*/
class ErbTemplate extends TTemplate, ErbAstNode {
private Erb::Template g;
ErbTemplate() { this = TTemplate(g) }
override string toString() { result = "erb template" }
final override string getAPrimaryQlClass() { result = "ErbTemplate" }
ErbAstNode getAChildNode() { toGenerated(result) = g.getChild(_) }
}
// Truncate the token string value to 32 char max
bindingset[val]
private string displayToken(string val) {
val.length() <= 32 and result = val
or
val.length() > 32 and result = val.prefix(29) + "..."
}
/**
* An ERB token. This could be embedded code, a comment, or arbitrary text.
*/
class ErbToken extends TTokenNode, ErbAstNode {
override string toString() { result = displayToken(this.getValue()) }
/** Gets the string value of this token. */
string getValue() { exists(Erb::Token g | this = fromGenerated(g) | result = g.getValue()) }
override string getAPrimaryQlClass() { result = "ErbToken" }
}
/**
* An ERB token appearing within a comment directive.
*/
class ErbComment extends ErbToken {
private Erb::Comment g;
ErbComment() { this = TComment(g) }
override string getValue() { result = g.getValue() }
final override string getAPrimaryQlClass() { result = "ErbComment" }
}
/**
* An ERB token appearing within a code directive. This will typically be
* interpreted as Ruby code or a GraphQL query, depending on context.
*/
class ErbCode extends ErbToken {
private Erb::Code g;
ErbCode() { this = TCode(g) }
override string getValue() { result = g.getValue() }
final override string getAPrimaryQlClass() { result = "ErbCode" }
}
bindingset[line, col]
private predicate locationIncludesPosition(Location loc, int line, int col) {
// position between start and end line, exclusive
line > loc.getStartLine() and
line < loc.getEndLine()
or
// position on start line, multi line location
line = loc.getStartLine() and
not loc.getStartLine() = loc.getEndLine() and
col >= loc.getStartColumn()
or
// position on end line, multi line location
line = loc.getEndLine() and
not loc.getStartLine() = loc.getEndLine() and
col <= loc.getEndColumn()
or
// single line location, position between start and end column
line = loc.getStartLine() and
loc.getStartLine() = loc.getEndLine() and
col >= loc.getStartColumn() and
col <= loc.getEndColumn()
}
/**
* A directive in an ERB template.
*/
class ErbDirective extends TDirectiveNode, ErbAstNode {
private predicate containsStartOf(Location loc) {
loc.getFile() = this.getLocation().getFile() and
locationIncludesPosition(this.getLocation(), loc.getStartLine(), loc.getStartColumn())
}
private predicate containsStmtStart(Stmt s) {
this.containsStartOf(s.getLocation()) and
// `Toplevel` statements are not contained within individual directives,
// though their start location may appear within a directive location
not s instanceof Toplevel
}
/**
* Gets a statement that starts in directive that is not a child of any other
* statement starting in this directive.
*/
Stmt getAChildStmt() {
this.containsStmtStart(result) and
not this.containsStmtStart(result.getParent())
}
/**
* Gets the last child statement in this directive.
* See `getAChildStmt` for more details.
*/
Stmt getTerminalStmt() {
result = this.getAChildStmt() and
forall(Stmt s | s = this.getAChildStmt() and not s = result |
s.getLocation().strictlyBefore(result.getLocation())
)
}
/** Gets the child token of this directive. */
ErbToken getToken() {
exists(Erb::Directive g | this = fromGenerated(g) | toGenerated(result) = g.getChild())
}
override string toString() { result = "erb directive" }
override string getAPrimaryQlClass() { result = "ErbDirective" }
}
/**
* A comment directive in an ERB template.
* ```erb
* <%#= 2 + 2 %>
* <%# for x in xs do %>
* ```
*/
class ErbCommentDirective extends ErbDirective {
private Erb::CommentDirective g;
ErbCommentDirective() { this = TCommentDirective(g) }
override ErbComment getToken() { toGenerated(result) = g.getChild() }
final override string toString() { result = "<%#" + this.getToken().toString() + "%>" }
final override string getAPrimaryQlClass() { result = "ErbCommentDirective" }
}
/**
* A GraphQL directive in an ERB template.
* ```erb
* <%graphql
* fragment Foo on Bar {
* some {
* queryText
* moreProperties
* }
* }
* %>
* ```
*/
class ErbGraphqlDirective extends ErbDirective {
private Erb::GraphqlDirective g;
ErbGraphqlDirective() { this = TGraphqlDirective(g) }
override ErbCode getToken() { toGenerated(result) = g.getChild() }
final override string toString() { result = "<%graphql" + this.getToken().toString() + "%>" }
final override string getAPrimaryQlClass() { result = "ErbGraphqlDirective" }
}
/**
* An output directive in an ERB template.
* ```erb
* <%=
* fragment Foo on Bar {
* some {
* queryText
* moreProperties
* }
* }
* %>
* ```
*/
class ErbOutputDirective extends ErbDirective {
private Erb::OutputDirective g;
ErbOutputDirective() { this = TOutputDirective(g) }
override ErbCode getToken() { toGenerated(result) = g.getChild() }
final override string toString() { result = "<%=" + this.getToken().toString() + "%>" }
final override string getAPrimaryQlClass() { result = "ErbOutputDirective" }
}
/**
* An execution directive in an ERB template.
* This code will be executed as Ruby, but not rendered.
* ```erb
* <% books = author.books
* for book in books do %>
* ```
*/
class ErbExecutionDirective extends ErbDirective {
private Erb::Directive g;
ErbExecutionDirective() { this = TDirective(g) }
final override string toString() { result = "<%" + this.getToken().toString() + "%>" }
final override string getAPrimaryQlClass() { result = "ErbExecutionDirective" }
}
/**
* A `File` containing an Embedded Ruby template.
* This is typically a file containing snippets of Ruby code that can be
* evaluated to create a compiled version of the file.
*/
class ErbFile extends File {
private ErbTemplate template;
ErbFile() { this = template.getLocation().getFile() }
/**
* Holds if the file represents a partial to be rendered in the context of
* another template.
*/
predicate isPartial() { this.getStem().charAt(0) = "_" }
/**
* Gets the erb template contained within this file.
*/
ErbTemplate getTemplate() { result = template }
}

View File

@@ -0,0 +1,43 @@
import codeql.Locations
private import TreeSitter
private import codeql.ruby.ast.Erb
cached
private module Cached {
cached
newtype TAstNode =
TCommentDirective(Erb::CommentDirective g) or
TDirective(Erb::Directive g) or
TGraphqlDirective(Erb::GraphqlDirective g) or
TOutputDirective(Erb::OutputDirective g) or
TTemplate(Erb::Template g) or
TToken(Erb::Token g) or
TComment(Erb::Comment g) or
TCode(Erb::Code g)
/**
* Gets the underlying TreeSitter entity for a given erb AST node.
*/
cached
Erb::AstNode toGenerated(ErbAstNode n) {
n = TCommentDirective(result) or
n = TDirective(result) or
n = TGraphqlDirective(result) or
n = TOutputDirective(result) or
n = TTemplate(result) or
n = TToken(result) or
n = TComment(result) or
n = TCode(result)
}
cached
Location getLocation(ErbAstNode n) { result = toGenerated(n).getLocation() }
}
import Cached
TAstNode fromGenerated(Erb::AstNode n) { n = toGenerated(result) }
class TDirectiveNode = TCommentDirective or TDirective or TGraphqlDirective or TOutputDirective;
class TTokenNode = TToken or TComment or TCode;

View File

@@ -100,14 +100,6 @@ private predicate scopeAssigns(Scope::Range scope, string name, Ruby::Identifier
scope = scopeOf(i)
}
/** Holds if location `one` starts strictly before location `two` */
pragma[inline]
private predicate strictlyBefore(Location one, Location two) {
one.getStartLine() < two.getStartLine()
or
one.getStartLine() = two.getStartLine() and one.getStartColumn() < two.getStartColumn()
}
cached
private module Cached {
cached
@@ -296,7 +288,7 @@ private module Cached {
name = access.getValue()
|
variable.getDeclaringScope() = scopeOf(access) and
not strictlyBefore(access.getLocation(), variable.getLocation()) and
not access.getLocation().strictlyBefore(variable.getLocation()) and
// In case of overlapping parameter names, later parameters should not
// be considered accesses to the first parameter
if parameterAssignment(_, _, access)
@@ -366,7 +358,7 @@ private predicate inherits(Scope::Range scope, string name, Scope::Range outer)
or
exists(Ruby::Identifier i |
scopeAssigns(outer, name, i) and
strictlyBefore(i.getLocation(), scope.getLocation())
i.getLocation().strictlyBefore(scope.getLocation())
)
)
or

View File

@@ -68,8 +68,7 @@ class ActionControllerActionMethod extends Method, HTTP::Server::RequestHandler:
* corresponding template file at
* `<sourcePrefix>app/views/<subpath>/<method_name>.html.erb`.
*/
// TODO: result should be `ErbFile`
File getDefaultTemplateFile() {
ErbFile getDefaultTemplateFile() {
controllerTemplatesFolder(this.getControllerClass(), result.getParentContainer()) and
result.getBaseName() = this.getName() + ".html.erb"
}
@@ -194,8 +193,7 @@ class ActionControllerHelperMethod extends Method {
* mapped to a controller class in `app/controllers/foo/bar/baz_controller.rb`,
* if such a controller class exists.
*/
// TODO: parameter should be `ErbFile`
ActionControllerControllerClass getAssociatedControllerClass(File f) {
ActionControllerControllerClass getAssociatedControllerClass(ErbFile f) {
controllerTemplatesFolder(result, f.getParentContainer())
}

View File

@@ -8,8 +8,7 @@ private import ActionController
predicate inActionViewContext(AstNode n) {
// Within a template
// TODO: n.getLocation().getFile() instanceof ErbFile
n.getLocation().getFile().getExtension() = "erb"
n.getLocation().getFile() instanceof ErbFile
}
/**
@@ -33,10 +32,7 @@ private class ActionViewContextCall extends MethodCall {
inActionViewContext(this)
}
predicate isInErbFile() {
// TODO: this.getLocation().getFile() instanceof ErbFile
this.getLocation().getFile().getExtension() = "erb"
}
predicate isInErbFile() { this.getLocation().getFile() instanceof ErbFile }
}
/** A call to the `raw` method to output a value without HTML escaping. */
@@ -95,8 +91,7 @@ abstract class RenderCall extends MethodCall {
/**
* Get the template file to be rendered by this call, if any.
*/
// TODO: parameter should be `ErbFile`
File getTemplateFile() { result.getAbsolutePath().matches(this.getTemplatePathPatterns()) }
ErbFile getTemplateFile() { result.getAbsolutePath().matches(this.getTemplatePathPatterns()) }
/**
* Get the local variables passed as context to the renderer

View File

@@ -2058,6 +2058,28 @@ params/params.rb:
# 70| getAnOperand/getLeftOperand: [LocalVariableAccess] a
# 70| getAnOperand/getRightOperand: [LocalVariableAccess] b
# 70| getAnOperand/getRightOperand: [LocalVariableAccess] c
erb/template.html.erb:
# 19| [Toplevel] template.html.erb
# 19| getStmt: [StringLiteral] "hello world"
# 19| getComponent: [StringTextComponent] hello world
# 25| getStmt: [AssignExpr] ... = ...
# 25| getAnOperand/getLeftOperand: [LocalVariableAccess] xs
# 25| getAnOperand/getRightOperand: [StringLiteral] ""
# 27| getStmt: [ForExpr] for ... in ...
# 27| getPattern: [LocalVariableAccess] x
# 27| <in>: [???] In
# 27| getValue: [ArrayLiteral] [...]
# 27| getElement: [StringLiteral] "foo"
# 27| getComponent: [StringTextComponent] foo
# 27| getElement: [StringLiteral] "bar"
# 27| getComponent: [StringTextComponent] bar
# 27| getElement: [StringLiteral] "baz"
# 27| getComponent: [StringTextComponent] baz
# 27| getBody: [StmtSequence] do ...
# 28| getStmt: [AssignAddExpr] ... += ...
# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] xs
# 28| getAnOperand/getRightOperand: [LocalVariableAccess] x
# 29| getStmt: [LocalVariableAccess] xs
modules/toplevel.rb:
# 1| [Toplevel] toplevel.rb
# 1| getStmt: [MethodCall] call to puts

View File

@@ -381,3 +381,10 @@ operations/operations.rb:
# 96| getAnOperand/getRightOperand: [MulExpr] ... * ...
# 96| getAnOperand/getLeftOperand: [GlobalVariableAccess] $global_var
# 96| getAnOperand/getRightOperand: [IntegerLiteral] 6
erb/template.html.erb:
# 28| [AssignAddExpr] ... += ...
# 28| getDesugared: [AssignExpr] ... = ...
# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] xs
# 28| getAnOperand/getRightOperand: [AddExpr] ... + ...
# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] xs
# 28| getAnOperand/getRightOperand: [LocalVariableAccess] x

View File

@@ -0,0 +1,130 @@
erbFiles
| template.html.erb:0:0:0:0 | template.html.erb |
erbAstNodes
| template.html.erb:1:1:1:9 | <%graphql |
| template.html.erb:1:1:17:2 | <%graphql\n fragment Foo on Bar {\n ...%> |
| template.html.erb:1:1:32:6 | erb template |
| template.html.erb:1:10:16:4 | \n fragment Foo on Bar {\n ... |
| template.html.erb:1:10:16:4 | \n fragment Foo on Bar {\n ... |
| template.html.erb:17:1:17:2 | %> |
| template.html.erb:17:3:19:3 | \n\n<%= |
| template.html.erb:17:3:19:20 | <%= "hello world" %> |
| template.html.erb:19:4:19:18 | "hello world" |
| template.html.erb:19:4:19:18 | "hello world" |
| template.html.erb:19:19:19:20 | %> |
| template.html.erb:19:21:21:3 | \n\n<%# |
| template.html.erb:19:21:21:31 | <%#= "this is commented out" %> |
| template.html.erb:21:4:21:29 | = "this is commented out" |
| template.html.erb:21:4:21:29 | = "this is commented out" |
| template.html.erb:21:30:21:31 | %> |
| template.html.erb:21:32:23:3 | \n\n<%# |
| template.html.erb:21:32:23:35 | <%# "this is also commented out" %> |
| template.html.erb:23:4:23:33 | "this is also commented out" |
| template.html.erb:23:4:23:33 | "this is also commented out" |
| template.html.erb:23:34:23:35 | %> |
| template.html.erb:23:36:25:2 | \n\n<% |
| template.html.erb:23:36:25:13 | <% xs = "" %> |
| template.html.erb:25:3:25:11 | xs = "" |
| template.html.erb:25:3:25:11 | xs = "" |
| template.html.erb:25:12:25:13 | %> |
| template.html.erb:25:14:27:2 | \n<ul>\n |
| template.html.erb:27:3:27:4 | <% |
| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> |
| template.html.erb:27:5:27:39 | for x in ["foo", "bar", "baz... |
| template.html.erb:27:5:27:39 | for x in ["foo", "bar", "baz... |
| template.html.erb:27:40:27:41 | %> |
| template.html.erb:27:42:28:6 | \n <li> |
| template.html.erb:28:7:28:9 | <%= |
| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> |
| template.html.erb:28:10:30:10 | xs += x\n xs\n |
| template.html.erb:28:10:30:10 | xs += x\n xs\n |
| template.html.erb:30:11:30:12 | %> |
| template.html.erb:30:13:31:2 | </li>\n |
| template.html.erb:31:3:31:4 | <% |
| template.html.erb:31:3:31:11 | <% end %> |
| template.html.erb:31:5:31:9 | end |
| template.html.erb:31:5:31:9 | end |
| template.html.erb:31:10:31:11 | %> |
| template.html.erb:31:12:32:6 | \n</ul>\n |
erbTemplates
| template.html.erb:1:1:32:6 | erb template |
erbDirectives
| template.html.erb:1:1:17:2 | <%graphql\n fragment Foo on Bar {\n ...%> |
| template.html.erb:17:3:19:20 | <%= "hello world" %> |
| template.html.erb:19:21:21:31 | <%#= "this is commented out" %> |
| template.html.erb:21:32:23:35 | <%# "this is also commented out" %> |
| template.html.erb:23:36:25:13 | <% xs = "" %> |
| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> |
| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> |
| template.html.erb:31:3:31:11 | <% end %> |
erbCommentDirectives
| template.html.erb:19:21:21:31 | <%#= "this is commented out" %> |
| template.html.erb:21:32:23:35 | <%# "this is also commented out" %> |
erbGraphqlDirectives
| template.html.erb:1:1:17:2 | <%graphql\n fragment Foo on Bar {\n ...%> |
erbOutputDirectives
| template.html.erb:17:3:19:20 | <%= "hello world" %> |
| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> |
erbExecutionDirectives
| template.html.erb:23:36:25:13 | <% xs = "" %> |
| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> |
| template.html.erb:31:3:31:11 | <% end %> |
childStmts
| template.html.erb:17:3:19:20 | <%= "hello world" %> | template.html.erb:19:5:19:17 | "hello world" |
| template.html.erb:23:36:25:13 | <% xs = "" %> | template.html.erb:25:4:25:10 | ... = ... |
| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> | template.html.erb:27:6:31:8 | for ... in ... |
| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> | template.html.erb:28:11:28:17 | ... += ... |
| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> | template.html.erb:29:11:29:12 | xs |
terminalStatements
| template.html.erb:17:3:19:20 | <%= "hello world" %> | template.html.erb:19:5:19:17 | "hello world" |
| template.html.erb:23:36:25:13 | <% xs = "" %> | template.html.erb:25:4:25:10 | ... = ... |
| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> | template.html.erb:27:6:31:8 | for ... in ... |
| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> | template.html.erb:29:11:29:12 | xs |
primaryQlClasses
| template.html.erb:1:1:1:9 | <%graphql | ErbToken |
| template.html.erb:1:1:17:2 | <%graphql\n fragment Foo on Bar {\n ...%> | ErbGraphqlDirective |
| template.html.erb:1:1:32:6 | erb template | ErbTemplate |
| template.html.erb:1:10:16:4 | \n fragment Foo on Bar {\n ... | ErbCode |
| template.html.erb:1:10:16:4 | \n fragment Foo on Bar {\n ... | ErbToken |
| template.html.erb:17:1:17:2 | %> | ErbToken |
| template.html.erb:17:3:19:3 | \n\n<%= | ErbToken |
| template.html.erb:17:3:19:20 | <%= "hello world" %> | ErbOutputDirective |
| template.html.erb:19:4:19:18 | "hello world" | ErbCode |
| template.html.erb:19:4:19:18 | "hello world" | ErbToken |
| template.html.erb:19:19:19:20 | %> | ErbToken |
| template.html.erb:19:21:21:3 | \n\n<%# | ErbToken |
| template.html.erb:19:21:21:31 | <%#= "this is commented out" %> | ErbCommentDirective |
| template.html.erb:21:4:21:29 | = "this is commented out" | ErbComment |
| template.html.erb:21:4:21:29 | = "this is commented out" | ErbToken |
| template.html.erb:21:30:21:31 | %> | ErbToken |
| template.html.erb:21:32:23:3 | \n\n<%# | ErbToken |
| template.html.erb:21:32:23:35 | <%# "this is also commented out" %> | ErbCommentDirective |
| template.html.erb:23:4:23:33 | "this is also commented out" | ErbComment |
| template.html.erb:23:4:23:33 | "this is also commented out" | ErbToken |
| template.html.erb:23:34:23:35 | %> | ErbToken |
| template.html.erb:23:36:25:2 | \n\n<% | ErbToken |
| template.html.erb:23:36:25:13 | <% xs = "" %> | ErbExecutionDirective |
| template.html.erb:25:3:25:11 | xs = "" | ErbCode |
| template.html.erb:25:3:25:11 | xs = "" | ErbToken |
| template.html.erb:25:12:25:13 | %> | ErbToken |
| template.html.erb:25:14:27:2 | \n<ul>\n | ErbToken |
| template.html.erb:27:3:27:4 | <% | ErbToken |
| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> | ErbExecutionDirective |
| template.html.erb:27:5:27:39 | for x in ["foo", "bar", "baz... | ErbCode |
| template.html.erb:27:5:27:39 | for x in ["foo", "bar", "baz... | ErbToken |
| template.html.erb:27:40:27:41 | %> | ErbToken |
| template.html.erb:27:42:28:6 | \n <li> | ErbToken |
| template.html.erb:28:7:28:9 | <%= | ErbToken |
| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> | ErbOutputDirective |
| template.html.erb:28:10:30:10 | xs += x\n xs\n | ErbCode |
| template.html.erb:28:10:30:10 | xs += x\n xs\n | ErbToken |
| template.html.erb:30:11:30:12 | %> | ErbToken |
| template.html.erb:30:13:31:2 | </li>\n | ErbToken |
| template.html.erb:31:3:31:4 | <% | ErbToken |
| template.html.erb:31:3:31:11 | <% end %> | ErbExecutionDirective |
| template.html.erb:31:5:31:9 | end | ErbCode |
| template.html.erb:31:5:31:9 | end | ErbToken |
| template.html.erb:31:10:31:11 | %> | ErbToken |
| template.html.erb:31:12:32:6 | \n</ul>\n | ErbToken |
erbFileTemplates
| template.html.erb:0:0:0:0 | template.html.erb | template.html.erb:1:1:32:6 | erb template |

View File

@@ -0,0 +1,25 @@
import ruby
query predicate erbFiles(ErbFile f) { any() }
query predicate erbAstNodes(ErbAstNode n) { any() }
query predicate erbTemplates(ErbTemplate t) { any() }
query predicate erbDirectives(ErbDirective d) { any() }
query predicate erbCommentDirectives(ErbCommentDirective d) { any() }
query predicate erbGraphqlDirectives(ErbGraphqlDirective d) { any() }
query predicate erbOutputDirectives(ErbOutputDirective d) { any() }
query predicate erbExecutionDirectives(ErbExecutionDirective d) { any() }
query predicate childStmts(ErbDirective d, Stmt s) { s = d.getAChildStmt() }
query predicate terminalStatements(ErbDirective d, Stmt s) { s = d.getTerminalStmt() }
query predicate primaryQlClasses(ErbAstNode n, string cls) { cls = n.getAPrimaryQlClass() }
query predicate erbFileTemplates(ErbFile f, ErbTemplate t) { t = f.getTemplate() }

View File

@@ -0,0 +1,32 @@
<%graphql
fragment Foo on Bar {
some {
queryText
moreProperties
}
}
fragment AnotherFragment on SomethingElse {
other {
things
}
here {
etc
}
}
%>
<%= "hello world" %>
<%#= "this is commented out" %>
<%# "this is also commented out" %>
<% xs = "" %>
<ul>
<% for x in ["foo", "bar", "baz"] do %>
<li><%= xs += x
xs
%></li>
<% end %>
</ul>

View File

@@ -14,8 +14,7 @@ query predicate redirectToCalls(RedirectToCall c) { any() }
query predicate actionControllerHelperMethods(ActionControllerHelperMethod m) { any() }
// TODO: second parameter should be `ErbFile`
query predicate getAssociatedControllerClasses(ActionControllerControllerClass cls, File f) {
query predicate getAssociatedControllerClasses(ActionControllerControllerClass cls, ErbFile f) {
cls = getAssociatedControllerClass(f)
}