From f56f81f55537fb431f3c26f33b244ec9f4fb6faf Mon Sep 17 00:00:00 2001 From: Nick Rolfe Date: Fri, 12 Feb 2021 17:36:28 +0000 Subject: [PATCH] Add and expand AST classes for literals --- Cargo.lock | 2 +- extractor/Cargo.toml | 2 +- generator/Cargo.toml | 2 +- ql/src/codeql_ruby/AST.qll | 1 + ql/src/codeql_ruby/ast/Expr.qll | 157 ++-- ql/src/codeql_ruby/ast/Literal.qll | 410 ++++++++++ ql/src/codeql_ruby/ast/internal/AST.qll | 24 - ql/src/codeql_ruby/ast/internal/Expr.qll | 199 +---- ql/src/codeql_ruby/ast/internal/Literal.qll | 373 +++++++++ .../codeql_ruby/ast/internal/TreeSitter.qll | 20 +- ql/src/codeql_ruby/ast/internal/Variable.qll | 4 +- ql/src/ruby.dbscheme | 20 +- .../ast/literals/literals.expected | 726 ++++++++++++++++++ .../library-tests/ast/literals/literals.ql | 110 +++ .../library-tests/ast/literals/literals.rb | 142 ++++ 15 files changed, 1858 insertions(+), 334 deletions(-) create mode 100644 ql/src/codeql_ruby/ast/Literal.qll create mode 100644 ql/src/codeql_ruby/ast/internal/Literal.qll create mode 100644 ql/test/library-tests/ast/literals/literals.expected create mode 100644 ql/test/library-tests/ast/literals/literals.ql create mode 100644 ql/test/library-tests/ast/literals/literals.rb diff --git a/Cargo.lock b/Cargo.lock index c311de0b5fa..c6a1b7fdf6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,7 +599,7 @@ dependencies = [ [[package]] name = "tree-sitter-ruby" version = "0.17.0" -source = "git+https://github.com/tree-sitter/tree-sitter-ruby.git?rev=add8cb36d5fc0a00d4499ba2e8eedc04a38a2488#add8cb36d5fc0a00d4499ba2e8eedc04a38a2488" +source = "git+https://github.com/nickrolfe/tree-sitter-ruby.git?branch=range_fields#ef0fb075c77dbd3cd16f69cf3eff44f4edd55381" dependencies = [ "cc", "tree-sitter", diff --git a/extractor/Cargo.toml b/extractor/Cargo.toml index dedc2732551..c49b1a69ba6 100644 --- a/extractor/Cargo.toml +++ b/extractor/Cargo.toml @@ -11,7 +11,7 @@ flate2 = "1.0" node-types = { path = "../node-types" } tree-sitter = "0.17" tree-sitter-embedded-template = { git = "https://github.com/aibaars/tree-sitter-embedded-template", rev = "d4aac29c08aa7c596633d00b5ec2dd2d247eafe4" } -tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "add8cb36d5fc0a00d4499ba2e8eedc04a38a2488" } +tree-sitter-ruby = { git = "https://github.com/nickrolfe/tree-sitter-ruby.git", branch = "range_fields" } clap = "2.33" tracing = "0.1" tracing-subscriber = { version = "0.2", features = ["env-filter"] } diff --git a/generator/Cargo.toml b/generator/Cargo.toml index 9dec281ff61..3550b2a4ac7 100644 --- a/generator/Cargo.toml +++ b/generator/Cargo.toml @@ -10,4 +10,4 @@ edition = "2018" node-types = { path = "../node-types" } tracing = "0.1" tracing-subscriber = { version = "0.2", features = ["env-filter"] } -tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "add8cb36d5fc0a00d4499ba2e8eedc04a38a2488" } +tree-sitter-ruby = { git = "https://github.com/nickrolfe/tree-sitter-ruby.git", branch = "range_fields" } diff --git a/ql/src/codeql_ruby/AST.qll b/ql/src/codeql_ruby/AST.qll index 3af26170184..11c41330aa4 100644 --- a/ql/src/codeql_ruby/AST.qll +++ b/ql/src/codeql_ruby/AST.qll @@ -3,6 +3,7 @@ import ast.Call import ast.Control import ast.Constant import ast.Expr +import ast.Literal import ast.Method import ast.Module import ast.Parameter diff --git a/ql/src/codeql_ruby/ast/Expr.qll b/ql/src/codeql_ruby/ast/Expr.qll index ef38e6d18c3..7d2a1270925 100644 --- a/ql/src/codeql_ruby/ast/Expr.qll +++ b/ql/src/codeql_ruby/ast/Expr.qll @@ -24,115 +24,7 @@ class Self extends Expr, @token_self { final override string getAPrimaryQlClass() { result = "Self" } } -/** - * A literal. - * - * This is the QL root class for all literals. - */ -class Literal extends Expr { - override Literal::Range range; - /** Gets the source text for this literal, if it is constant. */ - final string getValueText() { result = range.getValueText() } -} - -/** - * An integer literal. - * ```rb - * x = 123 - * y = 0xff - * ``` - */ -class IntegerLiteral extends Literal, @token_integer { - final override IntegerLiteral::Range range; - - final override string getAPrimaryQlClass() { result = "IntegerLiteral" } -} - -/** A `nil` literal. */ -class NilLiteral extends Literal, @token_nil { - final override NilLiteral::Range range; - - final override string getAPrimaryQlClass() { result = "NilLiteral" } -} - -/** - * A Boolean literal. - * ```rb - * true - * false - * TRUE - * FALSE - * ``` - */ -class BooleanLiteral extends Literal, BooleanLiteral::DbUnion { - final override BooleanLiteral::Range range; - - final override string getAPrimaryQlClass() { result = "BooleanLiteral" } - - /** Holds if the Boolean literal is `true` or `TRUE`. */ - predicate isTrue() { range.isTrue() } - - /** Holds if the Boolean literal is `false` or `FALSE`. */ - predicate isFalse() { range.isFalse() } -} - -// TODO: expand this. It's a minimal placeholder so we can test `=~` and `!~`. -class RegexLiteral extends Literal, @regex { - final override RegexLiteral::Range range; - - final override string getAPrimaryQlClass() { result = "RegexLiteral" } -} - -/** - * A string literal. - * ```rb - * 'hello' - * "hello, #{name}" - * ``` - * TODO: expand this minimal placeholder. - */ -class StringLiteral extends Literal, @string__ { - final override StringLiteral::Range range; - - final override string getAPrimaryQlClass() { result = "StringLiteral" } -} - -/** - * A symbol literal. - * ```rb - * :foo - * :"foo bar" - * :"foo bar #{baz}" - * ``` - * TODO: expand this minimal placeholder. - */ -class SymbolLiteral extends Literal { - final override SymbolLiteral::Range range; - - SymbolLiteral() { - not any(UndefStmt u).getAMethodName() = this and - not any(AliasStmt a).getNewName() = this and - not any(AliasStmt a).getOldName() = this - } - - final override string getAPrimaryQlClass() { result = "SymbolLiteral" } -} - -/** - * A method name literal. For example: - * ```rb - * - method_name # a normal name - * - + # an operator - * - :method_name # a symbol - * - :"eval_#{name}" # a complex symbol - * ``` - */ -class MethodName extends Literal { - final override MethodName::Range range; - - final override string getAPrimaryQlClass() { result = "MethodName" } -} /** A sequence of expressions. */ class StmtSequence extends Expr { @@ -322,3 +214,52 @@ class RescueModifierExpr extends Expr, @rescue_modifier { */ final Stmt getHandler() { result = range.getHandler() } } + +/** + * A concatenation of string literals. + * + * ```rb + * "foo" "bar" "baz" + * ``` + */ +class StringConcatenation extends Expr, @chained_string { + final override StringConcatenation::Range range; + + final override string getAPrimaryQlClass() { result = "StringConcatenation" } + + /** Gets the `n`th string literal in this concatenation. */ + final StringLiteral getString(int n) { result = range.getString(n) } + + /** Gets a string literal in this concatenation. */ + final StringLiteral getAString() { result = this.getString(_) } + + /** Gets the number of string literals in this concatenation. */ + final int getNumberOfStrings() { result = count(this.getString(_)) } + + /** + * Gets the result of concatenating all the string literals, if and only if + * they do not contain any interpolations. + * + * For the following example, the result is `"foobar"`: + * + * ```rb + * "foo" 'bar' + * ``` + * + * And for the following example, where one of the string literals includes + * an interpolation, there is no result: + * + * ```rb + * "foo" "bar#{ n }" + * ``` + */ + final string getConcatenatedValueText() { + forall(StringLiteral c | c = this.getString(_) | exists(c.getValueText())) and + result = + concat(string valueText, int i | + valueText = this.getString(i).getValueText() + | + valueText order by i + ) + } +} diff --git a/ql/src/codeql_ruby/ast/Literal.qll b/ql/src/codeql_ruby/ast/Literal.qll new file mode 100644 index 00000000000..30c9d0f51c9 --- /dev/null +++ b/ql/src/codeql_ruby/ast/Literal.qll @@ -0,0 +1,410 @@ +private import codeql_ruby.AST +private import internal.Literal + +/** + * A literal. + * + * This is the QL root class for all literals. + */ +class Literal extends Expr { + override Literal::Range range; + + /** + * Gets the source text for this literal, if this is a simple literal. + * + * For complex literals, such as arrays, hashes, and strings with + * interpolations, this predicate has no result. + */ + final string getValueText() { result = range.getValueText() } +} + +/** + * A numeric literal, i.e. an integer, floating-point, rational, or complex + * value. + * + * ```rb + * 123 + * 0xff + * 3.14159 + * 1.0E2 + * 7r + * 1i + * ``` + */ +class NumericLiteral extends Literal { + override NumericLiteral::Range range; +} + +/** + * An integer literal. + * + * ```rb + * 123 + * 0xff + * ``` + */ +class IntegerLiteral extends NumericLiteral, @token_integer { + final override IntegerLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "IntegerLiteral" } +} + +/** + * A floating-point literal. + * + * ```rb + * 1.3 + * 2.7e+5 + * ``` + */ +class FloatLiteral extends NumericLiteral, @token_float { + final override FloatLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "FloatLiteral" } +} + +/** + * A rational literal. + * + * ```rb + * 123r + * ``` + */ +class RationalLiteral extends NumericLiteral, @rational { + final override RationalLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "RationalLiteral" } +} + +/** + * A complex literal. + * + * ```rb + * 1i + * ``` + */ +class ComplexLiteral extends NumericLiteral, @token_complex { + final override ComplexLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "ComplexLiteral" } +} + +/** A `nil` literal. */ +class NilLiteral extends Literal, @token_nil { + final override NilLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "NilLiteral" } +} + +/** + * A Boolean literal. + * ```rb + * true + * false + * TRUE + * FALSE + * ``` + */ +class BooleanLiteral extends Literal, BooleanLiteral::DbUnion { + final override BooleanLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "BooleanLiteral" } + + /** Holds if the Boolean literal is `true` or `TRUE`. */ + predicate isTrue() { range.isTrue() } + + /** Holds if the Boolean literal is `false` or `FALSE`. */ + predicate isFalse() { range.isFalse() } +} + +/** + * The base class for a component of a string: `StringTextComponent`, + * `StringEscapeSequenceComponent`, or `StringInterpolationComponent`. + */ +class StringComponent extends AstNode { + override StringComponent::Range range; + + /** + * Gets the source text for this string component. Has no result if this is + * a `StringInterpolationComponent`. + */ + final string getValueText() { result = range.getValueText() } +} + +/** + * A component of a string (or string-like) literal that is simply text. + * + * For example, the following string literals all contain `StringTextComponent` + * components whose `getValueText()` returns `"foo"`: + * + * ```rb + * 'foo' + * "#{ bar() }foo" + * "foo#{ bar() } baz" + * ``` + */ +class StringTextComponent extends StringComponent, @token_string_content { + final override StringTextComponent::Range range; + + final override string getAPrimaryQlClass() { result = "StringTextComponent" } +} + +/** + * An escape sequence component of a string or string-like literal. + */ +class StringEscapeSequenceComponent extends StringComponent, @token_escape_sequence { + final override StringEscapeSequenceComponent::Range range; + + final override string getAPrimaryQlClass() { result = "StringEscapeSequenceComponent" } +} + +/** + * An interpolation expression component of a string or string-like literal. + */ +class StringInterpolationComponent extends StringComponent, StmtSequence, @interpolation { + final override StringInterpolationComponent::Range range; + + final override string getAPrimaryQlClass() { result = "StringInterpolationComponent" } +} + +/** + * A string, symbol, regex, or subshell literal. + */ +class StringlikeLiteral extends Literal { + override StringlikeLiteral::Range range; + + /** + * Gets the `n`th component of this string or string-like literal. The result + * will be one of `StringTextComponent`, `StringInterpolationComponent`, and + * `StringEscapeSequenceComponent`. + * + * In the following example, the result for `n = 0` is the + * `StringTextComponent` for `foo_`, and the result for `n = 1` is the + * `StringInterpolationComponent` for `Time.now`. + * + * ```rb + * "foo_#{ Time.now }" + * ``` + */ + final StringComponent getComponent(int n) { result = range.getComponent(n) } + + /** + * Gets the number of components in this string or string-like literal. + * + * For the empty string `""`, the result is 0. + * + * For the string `"foo"`, the result is 1: there is a single + * `StringTextComponent`. + * + * For the following example, the result is 3: there is a + * `StringTextComponent` for the substring `"foo_"`; a + * `StringEscapeSequenceComponent` for the escaped quote; and a + * `StringInterpolationComponent` for the interpolation. + * + * ```rb + * "foo\"#{bar}" + * ``` + */ + final int getNumberOfComponents() { result = count(this.getComponent(_)) } +} + +/** + * A string literal. + * + * ```rb + * 'hello' + * "hello, #{name}" + * ``` + */ +class StringLiteral extends StringlikeLiteral { + final override StringLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "StringLiteral" } +} + +/** + * A regular expression literal. + * + * ```rb + * /[a-z]+/ + * ``` + */ +class RegexLiteral extends StringlikeLiteral, @regex { + final override RegexLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "RegexLiteral" } + + /** + * Gets the regex flags as a string. + * + * ```rb + * /foo/ # => "" + * /foo/i # => "i" + * /foo/imxo # => "imxo" + */ + final string getFlagString() { result = range.getFlagString() } + + /** + * Holds if the regex was specified using the `i` flag to indicate case + * insensitivity, as in the following example: + * + * ```rb + * /foo/i + * ``` + */ + final predicate hasCaseInsensitiveFlag() { this.getFlagString().charAt(_) = "i" } +} + +/** + * A symbol literal. + * + * ```rb + * :foo + * :"foo bar" + * :"foo bar #{baz}" + * ``` + */ +class SymbolLiteral extends StringlikeLiteral { + final override SymbolLiteral::Range range; + + SymbolLiteral() { + not any(UndefStmt u).getAMethodName() = this and + not any(AliasStmt a).getNewName() = this and + not any(AliasStmt a).getOldName() = this + } + + final override string getAPrimaryQlClass() { result = "SymbolLiteral" } +} + +/** + * A subshell literal. + * + * ```rb + * `ls -l` + * %x(/bin/sh foo.sh) + * ``` + */ +class SubshellLiteral extends StringlikeLiteral, @subshell { + final override SubshellLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "SubshellLiteral" } +} + +/** + * A character literal. + * + * ```rb + * ?a + * ?\u{61} + * ``` + */ +class CharacterLiteral extends Literal, @token_character { + final override CharacterLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "CharacterLiteral" } +} + +/** + * An array literal. + * + * ```rb + * [123, 'foo', bar()] + * %w(foo bar) + * %i(foo bar) + * ``` + */ +class ArrayLiteral extends Literal { + final override ArrayLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "ArrayLiteral" } + + /** Gets the `n`th element in this array literal. */ + final Expr getElement(int n) { result = range.getElement(n) } + + /** Gets an element in this array literal. */ + final Expr getAnElement() { result = range.getElement(_) } + + /** Gets the number of elements in this array literal. */ + final int getNumberOfElements() { result = count(range.getElement(_)) } +} + +/** + * A hash literal. + * + * ```rb + * { foo: 123, bar: 456 } + * ``` + */ +class HashLiteral extends Literal, @hash { + final override HashLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "HashLiteral" } + + /** + * Gets the `n`th element in this array literal. + * + * In the following example, the 0th element is a `Pair`, and the 1st element + * is a `HashSplatArgument`. + * + * ```rb + * { foo: 123, **bar } + * ``` + */ + final Expr getElement(int n) { result = range.getElement(n) } + + /** Gets an element in this array literal. */ + final Expr getAnElement() { result = range.getElement(_) } + + /** Gets a key-value `Pair` in this hash literal. */ + final Pair getAKeyValuePair() { result = this.getAnElement() } + + /** Gets the number of elements in this hash literal. */ + final int getNumberOfElements() { result = count(range.getElement(_)) } +} + +/** + * A range literal. + * + * ```rb + * (1..10) + * (1024...2048) + * ``` + */ +class RangeLiteral extends Literal, @range { + final override RangeLiteral::Range range; + + final override string getAPrimaryQlClass() { result = "RangeLiteral" } + + /** Gets the begin expression of this range, if any. */ + final Expr getBegin() { result = range.getBegin() } + + /** Gets the end expression of this range, if any. */ + final Expr getEnd() { result = range.getEnd() } + + /** + * Holds if the range is inclusive of the end value, i.e. uses the `..` + * operator. + */ + final predicate isInclusive() { range.isInclusive() } + + /** + * Holds if the range is exclusive of the end value, i.e. uses the `...` + * operator. + */ + final predicate isExclusive() { range.isExclusive() } +} + +/** + * A method name literal. For example: + * ```rb + * method_name # a normal name + * + # an operator + * :method_name # a symbol + * :"eval_#{name}" # a complex symbol + * ``` + */ +class MethodName extends Literal { + final override MethodName::Range range; + + final override string getAPrimaryQlClass() { result = "MethodName" } +} diff --git a/ql/src/codeql_ruby/ast/internal/AST.qll b/ql/src/codeql_ruby/ast/internal/AST.qll index ab62aadbfc8..d49beb8f081 100644 --- a/ql/src/codeql_ruby/ast/internal/AST.qll +++ b/ql/src/codeql_ruby/ast/internal/AST.qll @@ -31,37 +31,13 @@ module AstNode { or this = any(Generated::RestAssignment ra).getChild() or - this instanceof Generated::SymbolArray - or - this instanceof Generated::Interpolation - or - this instanceof Generated::StringArray - or - this instanceof Generated::BareString - or - this instanceof Generated::Float - or this instanceof Generated::Superclass or - this instanceof Generated::Hash - or - this instanceof Generated::Array - or - this instanceof Generated::Complex - or - this instanceof Generated::Character - or this instanceof Generated::HeredocBody or this instanceof Generated::HeredocBeginning or this instanceof Generated::HeredocEnd - or - this instanceof Generated::Range - or - this instanceof Generated::Rational - or - this instanceof Generated::Subshell } override string toString() { result = "AstNode" } diff --git a/ql/src/codeql_ruby/ast/internal/Expr.qll b/ql/src/codeql_ruby/ast/internal/Expr.qll index c395511e43f..bb02c50ae06 100644 --- a/ql/src/codeql_ruby/ast/internal/Expr.qll +++ b/ql/src/codeql_ruby/ast/internal/Expr.qll @@ -1,4 +1,5 @@ private import codeql_ruby.AST +private import codeql_ruby.ast.internal.Literal private import codeql_ruby.ast.internal.Statement private import codeql_ruby.ast.internal.TreeSitter @@ -14,194 +15,6 @@ module Self { } } -module Literal { - abstract class Range extends Expr::Range { - abstract string getValueText(); - - override string toString() { result = this.getValueText() } - } -} - -module IntegerLiteral { - class Range extends Literal::Range, @token_integer { - final override Generated::Integer generated; - - final override string getValueText() { result = generated.getValue() } - - final override string toString() { result = this.getValueText() } - } -} - -module NilLiteral { - class Range extends Literal::Range, @token_nil { - final override Generated::Nil generated; - - final override string getValueText() { result = generated.getValue() } - - final override string toString() { result = this.getValueText() } - } -} - -module BooleanLiteral { - class DbUnion = @token_true or @token_false; - - class Range extends Literal::Range, DbUnion { - final override Generated::Token generated; - - final override string getValueText() { result = generated.getValue() } - - final override string toString() { result = this.getValueText() } - - predicate isTrue() { this instanceof @token_true } - - predicate isFalse() { this instanceof @token_false } - } -} - -// TODO: expand this. It's a minimal placeholder so we can test `=~` and `!~`. -module RegexLiteral { - class Range extends Literal::Range, @regex { - final override Generated::Regex generated; - - final override string getValueText() { - forall(AstNode n | n = generated.getChild(_) | n instanceof Generated::Token) and - result = - concat(int i, string s | - s = generated.getChild(i).(Generated::Token).getValue() - | - s order by i - ) - } - - final override string toString() { - result = - concat(Generated::AstNode c, int i, string s | - c = generated.getChild(i) and - if c instanceof Generated::Token - then s = c.(Generated::Token).getValue() - else s = "#{...}" - | - s order by i - ) - } - } -} - -// TODO: expand this minimal placeholder. -module StringLiteral { - class Range extends Literal::Range, @string__ { - final override Generated::String generated; - - final override string getValueText() { - strictcount(generated.getChild(_)) = 1 and - result = generated.getChild(0).(Generated::Token).getValue() - } - - final override string toString() { - result = - concat(Generated::AstNode c, int i, string s | - c = generated.getChild(i) and - if c instanceof Generated::Token - then s = c.(Generated::Token).getValue() - else s = "#{...}" - | - s order by i - ) - } - } -} - -// TODO: expand this minimal placeholder. -module SymbolLiteral { - abstract class Range extends Literal::Range { } - - class SimpleSymbolRange extends SymbolLiteral::Range { - final override Generated::SimpleSymbol generated; - - // Tree-sitter gives us value text including the colon, which we skip. - final override string getValueText() { result = generated.getValue().suffix(1) } - - final override string toString() { result = generated.getValue() } - } - - abstract private class ComplexSymbolRange extends SymbolLiteral::Range { - abstract Generated::AstNode getChild(int i); - - final override string getValueText() { - strictcount(this.getChild(_)) = 1 and - result = this.getChild(0).(Generated::Token).getValue() - } - - private string summaryString() { - result = - concat(Generated::AstNode c, int i, string s | - c = this.getChild(i) and - if c instanceof Generated::Token - then s = c.(Generated::Token).getValue() - else s = "#{...}" - | - s order by i - ) - } - - final override string toString() { - if summaryString().regexpMatch("[a-zA-z_][a-zA-Z_0-9]*") - then result = ":" + summaryString() - else result = ":\"" + summaryString() + "\"" - } - } - - class DelimitedSymbolRange extends ComplexSymbolRange, @delimited_symbol { - final override Generated::DelimitedSymbol generated; - - final override Generated::AstNode getChild(int i) { result = generated.getChild(i) } - } - - class BareSymbolRange extends ComplexSymbolRange, @bare_symbol { - final override Generated::BareSymbol generated; - - final override Generated::AstNode getChild(int i) { result = generated.getChild(i) } - } - - class HashKeySymbolRange extends SymbolLiteral::Range, @token_hash_key_symbol { - final override Generated::HashKeySymbol generated; - - final override string getValueText() { result = generated.getValue() } - - final override string toString() { result = ":" + this.getValueText() } - } -} - -module MethodName { - private class TokenTypes = - @setter or @token_class_variable or @token_constant or @token_global_variable or - @token_identifier or @token_instance_variable or @token_operator; - - abstract class Range extends Literal::Range, @underscore_method_name { - Range() { - exists(Generated::Undef u | u.getChild(_) = generated) - or - exists(Generated::Alias a | a.getName() = generated or a.getAlias() = generated) - } - } - - private class TokenMethodName extends MethodName::Range, TokenTypes { - final override Generated::UnderscoreMethodName generated; - - final override string getValueText() { - result = generated.(Generated::Token).getValue() - or - result = generated.(Generated::Setter).getName().getValue() + "=" - } - } - - private class SimpleSymbolMethodName extends MethodName::Range, SymbolLiteral::SimpleSymbolRange, - @token_simple_symbol { } - - private class DelimitedSymbolMethodName extends MethodName::Range, - SymbolLiteral::DelimitedSymbolRange, @delimited_symbol { } -} - module StmtSequence { abstract class Range extends Expr::Range { abstract Stmt getStmt(int n); @@ -343,3 +156,13 @@ module Pair { final override string toString() { result = "Pair" } } } + +module StringConcatenation { + class Range extends Expr::Range, @chained_string { + final override Generated::ChainedString generated; + + final StringLiteral::Range getString(int i) { result = generated.getChild(i) } + + final override string toString() { result = "\"...\" \"...\"" } + } +} diff --git a/ql/src/codeql_ruby/ast/internal/Literal.qll b/ql/src/codeql_ruby/ast/internal/Literal.qll new file mode 100644 index 00000000000..6c37beca1bb --- /dev/null +++ b/ql/src/codeql_ruby/ast/internal/Literal.qll @@ -0,0 +1,373 @@ +private import codeql_ruby.AST +private import codeql_ruby.ast.internal.AST +private import codeql_ruby.ast.internal.Expr +private import codeql_ruby.ast.internal.TreeSitter + +module Literal { + abstract class Range extends Expr::Range { + abstract string getValueText(); + + override string toString() { result = this.getValueText() } + } +} + +module NumericLiteral { + abstract class Range extends Literal::Range { } +} + +module IntegerLiteral { + class Range extends NumericLiteral::Range, @token_integer { + final override Generated::Integer generated; + + Range() { not any(Generated::Rational r).getChild() = this } + + final override string getValueText() { result = generated.getValue() } + + final override string toString() { result = this.getValueText() } + } +} + +module FloatLiteral { + class Range extends NumericLiteral::Range, @token_float { + final override Generated::Float generated; + + Range() { not any(Generated::Rational r).getChild() = this } + + final override string getValueText() { result = generated.getValue() } + + final override string toString() { result = this.getValueText() } + } +} + +module RationalLiteral { + class Range extends NumericLiteral::Range, @rational { + final override Generated::Rational generated; + + final override string getValueText() { + result = generated.getChild().(Generated::Token).getValue() + } + + final override string toString() { result = this.getValueText() + "r" } + } +} + +module ComplexLiteral { + class Range extends NumericLiteral::Range, @token_complex { + final override Generated::Complex generated; + + final override string getValueText() { result = generated.getValue() } + + final override string toString() { result = this.getValueText() } + } +} + +module NilLiteral { + class Range extends Literal::Range, @token_nil { + final override Generated::Nil generated; + + final override string getValueText() { result = generated.getValue() } + + final override string toString() { result = this.getValueText() } + } +} + +module BooleanLiteral { + class DbUnion = @token_true or @token_false; + + class Range extends Literal::Range, DbUnion { + final override Generated::Token generated; + + final override string getValueText() { result = generated.getValue() } + + final override string toString() { result = this.getValueText() } + + predicate isTrue() { this instanceof @token_true } + + predicate isFalse() { this instanceof @token_false } + } +} + +module StringComponent { + abstract class Range extends AstNode::Range { + abstract string getValueText(); + } +} + +module StringTextComponent { + class Range extends StringComponent::Range, @token_string_content { + final override Generated::StringContent generated; + + final override string toString() { result = generated.getValue() } + + final override string getValueText() { result = generated.getValue() } + } +} + +module StringEscapeSequenceComponent { + class Range extends StringComponent::Range, @token_escape_sequence { + final override Generated::EscapeSequence generated; + + final override string toString() { result = generated.getValue() } + + final override string getValueText() { result = generated.getValue() } + } +} + +module StringInterpolationComponent { + class Range extends StringComponent::Range, StmtSequence::Range, @interpolation { + final override Generated::Interpolation generated; + + final override string toString() { result = "#{...}" } + + final override Expr getStmt(int n) { + // TODO: fix grammar to properly handle a sequence of more than one expr, + // e.g. #{ foo; bar } + n = 0 and + result = generated.getChild() + } + + final override string getValueText() { none() } + } +} + +module StringlikeLiteral { + abstract class Range extends Literal::Range { + abstract StringComponent::Range getComponent(int i); + + string getStartDelimiter() { result = "" } + + string getEndDelimiter() { result = "" } + + final predicate isSimple() { count(this.getComponent(_)) <= 1 } + + override string getValueText() { + // 0 components should result in the empty string + // if there are any interpolations, there should be no result + // otherwise, concatenate all the components + forall(StringComponent c | c = this.getComponent(_) | + not c instanceof StringInterpolationComponent::Range + ) and + result = + concat(StringComponent::Range c, int i | + c = this.getComponent(i) + | + c.getValueText() order by i + ) + } + + override string toString() { + result = + this.getStartDelimiter() + + concat(StringComponent::Range c, int i, string s | + c = this.getComponent(i) and + if c instanceof Generated::Token + then s = c.(Generated::Token).getValue() + else s = "#{...}" + | + s order by i + ) + this.getEndDelimiter() + } + } +} + +module StringLiteral { + abstract class Range extends StringlikeLiteral::Range { + final override string getStartDelimiter() { result = "\"" } + + final override string getEndDelimiter() { result = "\"" } + } + + private class RegularStringRange extends StringLiteral::Range, @string__ { + final override Generated::String generated; + + final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) } + } + + private class BareStringRange extends StringLiteral::Range, @bare_string { + final override Generated::BareString generated; + + final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) } + } +} + +module RegexLiteral { + class Range extends StringlikeLiteral::Range, @regex { + final override Generated::Regex generated; + + final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) } + + final override string getStartDelimiter() { result = "/" } + + final override string getEndDelimiter() { result = "/" } + + final string getFlagString() { + // For `/foo/i`, there should be an `/i` token in the database with `this` + // as its parents. Strip the delimiter, which can vary. + result = + max(Generated::Token t | + t.getParent() = this + | + t.getValue().suffix(1) order by t.getParentIndex() + ) + } + } +} + +module SymbolLiteral { + abstract class Range extends StringlikeLiteral::Range { } + + class SimpleSymbolRange extends SymbolLiteral::Range { + final override Generated::SimpleSymbol generated; + + final override StringComponent::Range getComponent(int i) { none() } + + final override string getStartDelimiter() { result = ":" } + + // Tree-sitter gives us value text including the colon, which we skip. + final override string getValueText() { result = generated.getValue().suffix(1) } + + final override string toString() { result = generated.getValue() } + } + + abstract private class ComplexSymbolRange extends SymbolLiteral::Range { + final override string getStartDelimiter() { result = ":\"" } + + final override string getEndDelimiter() { result = "\"" } + } + + class DelimitedSymbolRange extends ComplexSymbolRange, @delimited_symbol { + final override Generated::DelimitedSymbol generated; + + final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) } + } + + class BareSymbolRange extends ComplexSymbolRange, @bare_symbol { + final override Generated::BareSymbol generated; + + final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) } + } + + class HashKeySymbolRange extends SymbolLiteral::Range, @token_hash_key_symbol { + final override Generated::HashKeySymbol generated; + + final override StringComponent::Range getComponent(int i) { none() } + + final override string getValueText() { result = generated.getValue() } + + final override string toString() { result = ":" + this.getValueText() } + } +} + +module SubshellLiteral { + class Range extends StringlikeLiteral::Range, @subshell { + final override Generated::Subshell generated; + + final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) } + + final override string getStartDelimiter() { result = "`" } + + final override string getEndDelimiter() { result = "`" } + } +} + +module CharacterLiteral { + class Range extends Literal::Range, @token_character { + final override Generated::Character generated; + + final override string getValueText() { result = generated.getValue() } + + final override string toString() { result = generated.getValue() } + } +} + +module ArrayLiteral { + abstract class Range extends Literal::Range { + final override string getValueText() { none() } + + abstract Expr getElement(int i); + } + + private class RegularArrayRange extends ArrayLiteral::Range, @array { + final override Generated::Array generated; + + final override Expr getElement(int i) { result = generated.getChild(i) } + + final override string toString() { result = "[...]" } + } + + private class StringArrayRange extends ArrayLiteral::Range, @string_array { + final override Generated::StringArray generated; + + final override Expr getElement(int i) { result = generated.getChild(i) } + + final override string toString() { result = "%w(...)" } + } + + private class SymbolArrayRange extends ArrayLiteral::Range, @symbol_array { + final override Generated::SymbolArray generated; + + final override Expr getElement(int i) { result = generated.getChild(i) } + + final override string toString() { result = "%i(...)" } + } +} + +module HashLiteral { + class Range extends Literal::Range, @hash { + final override Generated::Hash generated; + + final override string getValueText() { none() } + + final Expr getElement(int i) { result = generated.getChild(i) } + + final override string toString() { result = "{...}" } + } +} + +module RangeLiteral { + class Range extends Literal::Range, @range { + final override Generated::Range generated; + + final override string getValueText() { none() } + + final override string toString() { result = "_ " + generated.getOperator() + " _" } + + final Expr getBegin() { result = generated.getBegin() } + + final Expr getEnd() { result = generated.getEnd() } + + final predicate isInclusive() { this instanceof @range_dotdot } + + final predicate isExclusive() { this instanceof @range_dotdotdot } + } +} + +module MethodName { + private class TokenTypes = + @setter or @token_class_variable or @token_constant or @token_global_variable or + @token_identifier or @token_instance_variable or @token_operator; + + abstract class Range extends Literal::Range, @underscore_method_name { + Range() { + exists(Generated::Undef u | u.getChild(_) = generated) + or + exists(Generated::Alias a | a.getName() = generated or a.getAlias() = generated) + } + } + + private class TokenMethodName extends MethodName::Range, TokenTypes { + final override Generated::UnderscoreMethodName generated; + + final override string getValueText() { + result = generated.(Generated::Token).getValue() + or + result = generated.(Generated::Setter).getName().getValue() + "=" + } + } + + private class SimpleSymbolMethodName extends MethodName::Range, SymbolLiteral::SimpleSymbolRange, + @token_simple_symbol { } + + private class DelimitedSymbolMethodName extends MethodName::Range, + SymbolLiteral::DelimitedSymbolRange, @delimited_symbol { } +} diff --git a/ql/src/codeql_ruby/ast/internal/TreeSitter.qll b/ql/src/codeql_ruby/ast/internal/TreeSitter.qll index 170e9c25e6a..b33abbdbdc3 100644 --- a/ql/src/codeql_ruby/ast/internal/TreeSitter.qll +++ b/ql/src/codeql_ruby/ast/internal/TreeSitter.qll @@ -1083,15 +1083,25 @@ module Generated { class Range extends @range, AstNode { override string getAPrimaryQlClass() { result = "Range" } - override Location getLocation() { range_def(this, _, _, result) } + override Location getLocation() { range_def(this, _, _, _, result) } - UnderscoreArg getChild(int i) { range_child(this, i, result) } + UnderscoreArg getBegin() { range_begin(this, result) } - override AstNode getParent() { range_def(this, result, _, _) } + UnderscoreArg getEnd() { range_end(this, result) } - override int getParentIndex() { range_def(this, _, result, _) } + string getOperator() { + exists(int value | range_def(this, _, _, value, _) | + result = ".." and value = 0 + or + result = "..." and value = 1 + ) + } - override AstNode getAFieldOrChild() { range_child(this, _, result) } + override AstNode getParent() { range_def(this, result, _, _, _) } + + override int getParentIndex() { range_def(this, _, result, _, _) } + + override AstNode getAFieldOrChild() { range_begin(this, result) or range_end(this, result) } } class Rational extends @rational, AstNode { diff --git a/ql/src/codeql_ruby/ast/internal/Variable.qll b/ql/src/codeql_ruby/ast/internal/Variable.qll index da2066b7024..a3d5391cf1f 100644 --- a/ql/src/codeql_ruby/ast/internal/Variable.qll +++ b/ql/src/codeql_ruby/ast/internal/Variable.qll @@ -264,7 +264,9 @@ private module Cached { or i = any(Generated::Program x).getChild(_) or - i = any(Generated::Range x).getChild(_) + i = any(Generated::Range x).getBegin() + or + i = any(Generated::Range x).getEnd() or i = any(Generated::RescueModifier x).getBody() or diff --git a/ql/src/ruby.dbscheme b/ql/src/ruby.dbscheme index 880faf3235f..932cb23dbc1 100644 --- a/ql/src/ruby.dbscheme +++ b/ql/src/ruby.dbscheme @@ -965,18 +965,28 @@ program_def( int loc: @location ref ); -#keyset[range, index] -range_child( - int range: @range ref, - int index: int ref, - unique int child: @underscore_arg ref +range_begin( + unique int range: @range ref, + unique int begin: @underscore_arg ref ); +range_end( + unique int range: @range ref, + unique int end: @underscore_arg ref +); + +case @range.operator of + 0 = @range_dotdot +| 1 = @range_dotdotdot +; + + #keyset[parent, parent_index] range_def( unique int id: @range, int parent: @ast_node_parent ref, int parent_index: int ref, + int operator: int ref, int loc: @location ref ); diff --git a/ql/test/library-tests/ast/literals/literals.expected b/ql/test/library-tests/ast/literals/literals.expected new file mode 100644 index 00000000000..c95298af6ef --- /dev/null +++ b/ql/test/library-tests/ast/literals/literals.expected @@ -0,0 +1,726 @@ +allLiterals +| literals.rb:2:1:2:3 | nil | NilLiteral | nil | +| literals.rb:3:1:3:3 | NIL | NilLiteral | NIL | +| literals.rb:4:1:4:5 | false | BooleanLiteral | false | +| literals.rb:5:1:5:5 | FALSE | BooleanLiteral | FALSE | +| literals.rb:6:1:6:4 | true | BooleanLiteral | true | +| literals.rb:7:1:7:4 | TRUE | BooleanLiteral | TRUE | +| literals.rb:10:1:10:4 | 1234 | IntegerLiteral | 1234 | +| literals.rb:11:1:11:5 | 5_678 | IntegerLiteral | 5_678 | +| literals.rb:12:1:12:1 | 0 | IntegerLiteral | 0 | +| literals.rb:13:1:13:5 | 0d900 | IntegerLiteral | 0d900 | +| literals.rb:16:1:16:6 | 0x1234 | IntegerLiteral | 0x1234 | +| literals.rb:17:1:17:10 | 0xdeadbeef | IntegerLiteral | 0xdeadbeef | +| literals.rb:18:1:18:11 | 0xF00D_face | IntegerLiteral | 0xF00D_face | +| literals.rb:21:1:21:4 | 0123 | IntegerLiteral | 0123 | +| literals.rb:22:1:22:5 | 0o234 | IntegerLiteral | 0o234 | +| literals.rb:23:1:23:6 | 0O45_6 | IntegerLiteral | 0O45_6 | +| literals.rb:26:1:26:10 | 0b10010100 | IntegerLiteral | 0b10010100 | +| literals.rb:27:1:27:11 | 0B011_01101 | IntegerLiteral | 0B011_01101 | +| literals.rb:30:1:30:5 | 12.34 | FloatLiteral | 12.34 | +| literals.rb:31:1:31:7 | 1234e-2 | FloatLiteral | 1234e-2 | +| literals.rb:32:1:32:7 | 1.234E1 | FloatLiteral | 1.234E1 | +| literals.rb:35:1:35:3 | 23r | RationalLiteral | 23 | +| literals.rb:36:1:36:5 | 9.85r | RationalLiteral | 9.85 | +| literals.rb:39:1:39:2 | 2i | ComplexLiteral | 2i | +| literals.rb:46:1:46:2 | "" | StringLiteral | | +| literals.rb:47:1:47:2 | "" | StringLiteral | | +| literals.rb:48:1:48:7 | "hello" | StringLiteral | hello | +| literals.rb:49:1:49:9 | "goodbye" | StringLiteral | goodbye | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | StringLiteral | string with escaped \\" quote | +| literals.rb:51:1:51:21 | "string with " quote" | StringLiteral | string with " quote | +| literals.rb:52:1:52:14 | "foo bar baz" | StringLiteral | foo bar baz | +| literals.rb:53:1:53:15 | "foo bar baz" | StringLiteral | foo bar baz | +| literals.rb:54:1:54:20 | "foo ' bar " baz'" | StringLiteral | foo ' bar " baz' | +| literals.rb:55:1:55:20 | "FOO ' BAR " BAZ'" | StringLiteral | FOO ' BAR " BAZ' | +| literals.rb:56:1:56:12 | "foo\\ bar" | StringLiteral | foo\\ bar | +| literals.rb:57:1:57:12 | "foo\\ bar" | StringLiteral | foo\\ bar | +| literals.rb:58:1:58:20 | "2 + 2 = #{...}" | StringLiteral | | +| literals.rb:58:13:58:13 | 2 | IntegerLiteral | 2 | +| literals.rb:58:17:58:17 | 2 | IntegerLiteral | 2 | +| literals.rb:59:1:59:22 | "3 + 4 = #{...}" | StringLiteral | | +| literals.rb:59:15:59:15 | 3 | IntegerLiteral | 3 | +| literals.rb:59:19:59:19 | 4 | IntegerLiteral | 4 | +| literals.rb:60:1:60:20 | "2 + 2 = #{ 2 + 2 }" | StringLiteral | 2 + 2 = #{ 2 + 2 } | +| literals.rb:61:1:61:22 | "3 + 4 = #{ 3 + 4 }" | StringLiteral | 3 + 4 = #{ 3 + 4 } | +| literals.rb:62:1:62:5 | "foo" | StringLiteral | foo | +| literals.rb:62:7:62:11 | "bar" | StringLiteral | bar | +| literals.rb:62:13:62:17 | "baz" | StringLiteral | baz | +| literals.rb:63:1:63:7 | "foo" | StringLiteral | foo | +| literals.rb:63:9:63:13 | "bar" | StringLiteral | bar | +| literals.rb:63:15:63:19 | "baz" | StringLiteral | baz | +| literals.rb:64:1:64:5 | "foo" | StringLiteral | foo | +| literals.rb:64:7:64:21 | "bar#{...}" | StringLiteral | | +| literals.rb:64:14:64:14 | 1 | IntegerLiteral | 1 | +| literals.rb:64:18:64:18 | 1 | IntegerLiteral | 1 | +| literals.rb:64:23:64:27 | "baz" | StringLiteral | baz | +| literals.rb:65:1:65:35 | "foo #{...} qux" | StringLiteral | | +| literals.rb:65:9:65:28 | "bar #{...} baz" | StringLiteral | | +| literals.rb:65:17:65:17 | 2 | IntegerLiteral | 2 | +| literals.rb:65:21:65:21 | 3 | IntegerLiteral | 3 | +| literals.rb:68:1:68:2 | ?x | CharacterLiteral | ?x | +| literals.rb:69:1:69:3 | ?\\n | CharacterLiteral | ?\\n | +| literals.rb:70:1:70:3 | ?\\s | CharacterLiteral | ?\\s | +| literals.rb:71:1:71:3 | ?\\\\ | CharacterLiteral | ?\\\\ | +| literals.rb:72:1:72:7 | ?\\u{58} | CharacterLiteral | ?\\u{58} | +| literals.rb:73:1:73:5 | ?\\C-a | CharacterLiteral | ?\\C-a | +| literals.rb:74:1:74:5 | ?\\M-a | CharacterLiteral | ?\\M-a | +| literals.rb:75:1:75:8 | ?\\M-\\C-a | CharacterLiteral | ?\\M-\\C-a | +| literals.rb:76:1:76:8 | ?\\C-\\M-a | CharacterLiteral | ?\\C-\\M-a | +| literals.rb:79:1:79:3 | :"" | SymbolLiteral | | +| literals.rb:80:1:80:6 | :hello | SymbolLiteral | hello | +| literals.rb:81:1:81:10 | :"foo bar" | SymbolLiteral | foo bar | +| literals.rb:82:1:82:10 | :"bar baz" | SymbolLiteral | bar baz | +| literals.rb:83:1:83:14 | {...} | HashLiteral | | +| literals.rb:83:3:83:5 | :foo | SymbolLiteral | foo | +| literals.rb:83:8:83:12 | "bar" | StringLiteral | bar | +| literals.rb:84:1:84:10 | :"wibble" | SymbolLiteral | wibble | +| literals.rb:85:1:85:17 | :"wibble wobble" | SymbolLiteral | wibble wobble | +| literals.rb:86:1:86:16 | :"foo_#{...}" | SymbolLiteral | | +| literals.rb:86:10:86:10 | 2 | IntegerLiteral | 2 | +| literals.rb:86:14:86:14 | 2 | IntegerLiteral | 2 | +| literals.rb:87:1:87:17 | :"foo_#{ 1 + 1 }" | SymbolLiteral | foo_#{ 1 + 1 } | +| literals.rb:88:1:88:18 | :"foo_#{ 3 - 2 }" | SymbolLiteral | foo_#{ 3 - 2 } | +| literals.rb:91:1:91:2 | [...] | ArrayLiteral | | +| literals.rb:92:1:92:9 | [...] | ArrayLiteral | | +| literals.rb:92:2:92:2 | 1 | IntegerLiteral | 1 | +| literals.rb:92:5:92:5 | 2 | IntegerLiteral | 2 | +| literals.rb:92:8:92:8 | 3 | IntegerLiteral | 3 | +| literals.rb:93:1:93:14 | [...] | ArrayLiteral | | +| literals.rb:93:2:93:2 | 4 | IntegerLiteral | 4 | +| literals.rb:93:5:93:5 | 5 | IntegerLiteral | 5 | +| literals.rb:93:8:93:9 | 12 | IntegerLiteral | 12 | +| literals.rb:93:13:93:13 | 2 | IntegerLiteral | 2 | +| literals.rb:94:1:94:11 | [...] | ArrayLiteral | | +| literals.rb:94:2:94:2 | 7 | IntegerLiteral | 7 | +| literals.rb:94:5:94:10 | [...] | ArrayLiteral | | +| literals.rb:94:6:94:6 | 8 | IntegerLiteral | 8 | +| literals.rb:94:9:94:9 | 9 | IntegerLiteral | 9 | +| literals.rb:97:1:97:4 | %w(...) | ArrayLiteral | | +| literals.rb:98:1:98:15 | %w(...) | ArrayLiteral | | +| literals.rb:98:4:98:6 | "foo" | StringLiteral | foo | +| literals.rb:98:8:98:10 | "bar" | StringLiteral | bar | +| literals.rb:98:12:98:14 | "baz" | StringLiteral | baz | +| literals.rb:99:1:99:15 | %w(...) | ArrayLiteral | | +| literals.rb:99:4:99:6 | "foo" | StringLiteral | foo | +| literals.rb:99:8:99:10 | "bar" | StringLiteral | bar | +| literals.rb:99:12:99:14 | "baz" | StringLiteral | baz | +| literals.rb:100:1:100:21 | %w(...) | ArrayLiteral | | +| literals.rb:100:4:100:6 | "foo" | StringLiteral | foo | +| literals.rb:100:8:100:16 | "bar#{...}" | StringLiteral | | +| literals.rb:100:13:100:13 | 1 | IntegerLiteral | 1 | +| literals.rb:100:15:100:15 | 1 | IntegerLiteral | 1 | +| literals.rb:100:18:100:20 | "baz" | StringLiteral | baz | +| literals.rb:101:1:101:21 | %w(...) | ArrayLiteral | | +| literals.rb:101:4:101:6 | "foo" | StringLiteral | foo | +| literals.rb:101:8:101:16 | "bar#{1+1}" | StringLiteral | bar#{1+1} | +| literals.rb:101:18:101:20 | "baz" | StringLiteral | baz | +| literals.rb:104:1:104:4 | %i(...) | ArrayLiteral | | +| literals.rb:105:1:105:15 | %i(...) | ArrayLiteral | | +| literals.rb:105:4:105:6 | :"foo" | SymbolLiteral | foo | +| literals.rb:105:8:105:10 | :"bar" | SymbolLiteral | bar | +| literals.rb:105:12:105:14 | :"baz" | SymbolLiteral | baz | +| literals.rb:106:1:106:15 | %i(...) | ArrayLiteral | | +| literals.rb:106:4:106:6 | :"foo" | SymbolLiteral | foo | +| literals.rb:106:8:106:10 | :"bar" | SymbolLiteral | bar | +| literals.rb:106:12:106:14 | :"baz" | SymbolLiteral | baz | +| literals.rb:107:1:107:25 | %i(...) | ArrayLiteral | | +| literals.rb:107:4:107:6 | :"foo" | SymbolLiteral | foo | +| literals.rb:107:8:107:20 | :"bar#{...}" | SymbolLiteral | | +| literals.rb:107:14:107:14 | 2 | IntegerLiteral | 2 | +| literals.rb:107:18:107:18 | 4 | IntegerLiteral | 4 | +| literals.rb:107:22:107:24 | :"baz" | SymbolLiteral | baz | +| literals.rb:108:1:108:25 | %i(...) | ArrayLiteral | | +| literals.rb:108:4:108:6 | :"foo" | SymbolLiteral | foo | +| literals.rb:108:8:108:12 | :"bar#{" | SymbolLiteral | bar#{ | +| literals.rb:108:14:108:14 | :"2" | SymbolLiteral | 2 | +| literals.rb:108:16:108:16 | :"+" | SymbolLiteral | + | +| literals.rb:108:18:108:18 | :"4" | SymbolLiteral | 4 | +| literals.rb:108:20:108:20 | :"}" | SymbolLiteral | } | +| literals.rb:108:22:108:24 | :"baz" | SymbolLiteral | baz | +| literals.rb:111:1:111:2 | {...} | HashLiteral | | +| literals.rb:112:1:112:33 | {...} | HashLiteral | | +| literals.rb:112:3:112:5 | :foo | SymbolLiteral | foo | +| literals.rb:112:8:112:8 | 1 | IntegerLiteral | 1 | +| literals.rb:112:11:112:14 | :bar | SymbolLiteral | bar | +| literals.rb:112:19:112:19 | 2 | IntegerLiteral | 2 | +| literals.rb:112:22:112:26 | "baz" | StringLiteral | baz | +| literals.rb:112:31:112:31 | 3 | IntegerLiteral | 3 | +| literals.rb:113:1:113:17 | {...} | HashLiteral | | +| literals.rb:113:3:113:5 | :foo | SymbolLiteral | foo | +| literals.rb:113:8:113:8 | 7 | IntegerLiteral | 7 | +| literals.rb:116:2:116:2 | 1 | IntegerLiteral | 1 | +| literals.rb:116:2:116:6 | _ .. _ | RangeLiteral | | +| literals.rb:116:5:116:6 | 10 | IntegerLiteral | 10 | +| literals.rb:117:2:117:2 | 1 | IntegerLiteral | 1 | +| literals.rb:117:2:117:7 | _ ... _ | RangeLiteral | | +| literals.rb:117:6:117:7 | 10 | IntegerLiteral | 10 | +| literals.rb:118:2:118:2 | 1 | IntegerLiteral | 1 | +| literals.rb:118:2:118:7 | _ .. _ | RangeLiteral | | +| literals.rb:118:7:118:7 | 0 | IntegerLiteral | 0 | +| literals.rb:119:2:119:11 | _ .. _ | RangeLiteral | | +| literals.rb:119:9:119:9 | 2 | IntegerLiteral | 2 | +| literals.rb:119:11:119:11 | 3 | IntegerLiteral | 3 | +| literals.rb:120:2:120:2 | 1 | IntegerLiteral | 1 | +| literals.rb:120:2:120:4 | _ .. _ | RangeLiteral | | +| literals.rb:121:2:121:4 | _ .. _ | RangeLiteral | | +| literals.rb:121:4:121:4 | 1 | IntegerLiteral | 1 | +| literals.rb:122:2:122:2 | 0 | IntegerLiteral | 0 | +| literals.rb:122:2:122:4 | _ .. _ | RangeLiteral | | +| literals.rb:122:6:122:6 | 1 | IntegerLiteral | 1 | +| literals.rb:125:1:125:7 | `ls -l` | SubshellLiteral | ls -l | +| literals.rb:126:1:126:9 | `ls -l` | SubshellLiteral | ls -l | +| literals.rb:127:1:127:18 | `du -d #{...}` | SubshellLiteral | | +| literals.rb:127:11:127:11 | 1 | IntegerLiteral | 1 | +| literals.rb:127:15:127:15 | 1 | IntegerLiteral | 1 | +| literals.rb:128:1:128:20 | `du -d #{...}` | SubshellLiteral | | +| literals.rb:128:13:128:13 | 5 | IntegerLiteral | 5 | +| literals.rb:128:17:128:17 | 4 | IntegerLiteral | 4 | +| literals.rb:131:1:131:2 | // | RegexLiteral | | +| literals.rb:132:1:132:5 | /foo/ | RegexLiteral | foo | +| literals.rb:133:1:133:6 | /foo/ | RegexLiteral | foo | +| literals.rb:134:1:134:13 | /foo+\\sbar\\S/ | RegexLiteral | foo+\\sbar\\S | +| literals.rb:135:1:135:18 | /foo#{...}bar/ | RegexLiteral | | +| literals.rb:135:8:135:8 | 1 | IntegerLiteral | 1 | +| literals.rb:135:12:135:12 | 1 | IntegerLiteral | 1 | +| literals.rb:136:1:136:8 | /foo/ | RegexLiteral | foo | +| literals.rb:137:1:137:4 | // | RegexLiteral | | +| literals.rb:138:1:138:7 | /foo/ | RegexLiteral | foo | +| literals.rb:139:1:139:8 | /foo/ | RegexLiteral | foo | +| literals.rb:140:1:140:15 | /foo+\\sbar\\S/ | RegexLiteral | foo+\\sbar\\S | +| literals.rb:141:1:141:20 | /foo#{...}bar/ | RegexLiteral | | +| literals.rb:141:10:141:10 | 1 | IntegerLiteral | 1 | +| literals.rb:141:14:141:14 | 1 | IntegerLiteral | 1 | +| literals.rb:142:1:142:10 | /foo/ | RegexLiteral | foo | +stringlikeLiterals +| literals.rb:46:1:46:2 | "" | | +| literals.rb:47:1:47:2 | "" | | +| literals.rb:48:1:48:7 | "hello" | hello | +| literals.rb:49:1:49:9 | "goodbye" | goodbye | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | string with escaped \\" quote | +| literals.rb:51:1:51:21 | "string with " quote" | string with " quote | +| literals.rb:52:1:52:14 | "foo bar baz" | foo bar baz | +| literals.rb:53:1:53:15 | "foo bar baz" | foo bar baz | +| literals.rb:54:1:54:20 | "foo ' bar " baz'" | foo ' bar " baz' | +| literals.rb:55:1:55:20 | "FOO ' BAR " BAZ'" | FOO ' BAR " BAZ' | +| literals.rb:56:1:56:12 | "foo\\ bar" | foo\\ bar | +| literals.rb:57:1:57:12 | "foo\\ bar" | foo\\ bar | +| literals.rb:58:1:58:20 | "2 + 2 = #{...}" | | +| literals.rb:59:1:59:22 | "3 + 4 = #{...}" | | +| literals.rb:60:1:60:20 | "2 + 2 = #{ 2 + 2 }" | 2 + 2 = #{ 2 + 2 } | +| literals.rb:61:1:61:22 | "3 + 4 = #{ 3 + 4 }" | 3 + 4 = #{ 3 + 4 } | +| literals.rb:62:1:62:5 | "foo" | foo | +| literals.rb:62:7:62:11 | "bar" | bar | +| literals.rb:62:13:62:17 | "baz" | baz | +| literals.rb:63:1:63:7 | "foo" | foo | +| literals.rb:63:9:63:13 | "bar" | bar | +| literals.rb:63:15:63:19 | "baz" | baz | +| literals.rb:64:1:64:5 | "foo" | foo | +| literals.rb:64:7:64:21 | "bar#{...}" | | +| literals.rb:64:23:64:27 | "baz" | baz | +| literals.rb:65:1:65:35 | "foo #{...} qux" | | +| literals.rb:65:9:65:28 | "bar #{...} baz" | | +| literals.rb:79:1:79:3 | :"" | | +| literals.rb:80:1:80:6 | :hello | hello | +| literals.rb:81:1:81:10 | :"foo bar" | foo bar | +| literals.rb:82:1:82:10 | :"bar baz" | bar baz | +| literals.rb:83:3:83:5 | :foo | foo | +| literals.rb:83:8:83:12 | "bar" | bar | +| literals.rb:84:1:84:10 | :"wibble" | wibble | +| literals.rb:85:1:85:17 | :"wibble wobble" | wibble wobble | +| literals.rb:86:1:86:16 | :"foo_#{...}" | | +| literals.rb:87:1:87:17 | :"foo_#{ 1 + 1 }" | foo_#{ 1 + 1 } | +| literals.rb:88:1:88:18 | :"foo_#{ 3 - 2 }" | foo_#{ 3 - 2 } | +| literals.rb:98:4:98:6 | "foo" | foo | +| literals.rb:98:8:98:10 | "bar" | bar | +| literals.rb:98:12:98:14 | "baz" | baz | +| literals.rb:99:4:99:6 | "foo" | foo | +| literals.rb:99:8:99:10 | "bar" | bar | +| literals.rb:99:12:99:14 | "baz" | baz | +| literals.rb:100:4:100:6 | "foo" | foo | +| literals.rb:100:8:100:16 | "bar#{...}" | | +| literals.rb:100:18:100:20 | "baz" | baz | +| literals.rb:101:4:101:6 | "foo" | foo | +| literals.rb:101:8:101:16 | "bar#{1+1}" | bar#{1+1} | +| literals.rb:101:18:101:20 | "baz" | baz | +| literals.rb:105:4:105:6 | :"foo" | foo | +| literals.rb:105:8:105:10 | :"bar" | bar | +| literals.rb:105:12:105:14 | :"baz" | baz | +| literals.rb:106:4:106:6 | :"foo" | foo | +| literals.rb:106:8:106:10 | :"bar" | bar | +| literals.rb:106:12:106:14 | :"baz" | baz | +| literals.rb:107:4:107:6 | :"foo" | foo | +| literals.rb:107:8:107:20 | :"bar#{...}" | | +| literals.rb:107:22:107:24 | :"baz" | baz | +| literals.rb:108:4:108:6 | :"foo" | foo | +| literals.rb:108:8:108:12 | :"bar#{" | bar#{ | +| literals.rb:108:14:108:14 | :"2" | 2 | +| literals.rb:108:16:108:16 | :"+" | + | +| literals.rb:108:18:108:18 | :"4" | 4 | +| literals.rb:108:20:108:20 | :"}" | } | +| literals.rb:108:22:108:24 | :"baz" | baz | +| literals.rb:112:3:112:5 | :foo | foo | +| literals.rb:112:11:112:14 | :bar | bar | +| literals.rb:112:22:112:26 | "baz" | baz | +| literals.rb:113:3:113:5 | :foo | foo | +| literals.rb:125:1:125:7 | `ls -l` | ls -l | +| literals.rb:126:1:126:9 | `ls -l` | ls -l | +| literals.rb:127:1:127:18 | `du -d #{...}` | | +| literals.rb:128:1:128:20 | `du -d #{...}` | | +| literals.rb:131:1:131:2 | // | | +| literals.rb:132:1:132:5 | /foo/ | foo | +| literals.rb:133:1:133:6 | /foo/ | foo | +| literals.rb:134:1:134:13 | /foo+\\sbar\\S/ | foo+\\sbar\\S | +| literals.rb:135:1:135:18 | /foo#{...}bar/ | | +| literals.rb:136:1:136:8 | /foo/ | foo | +| literals.rb:137:1:137:4 | // | | +| literals.rb:138:1:138:7 | /foo/ | foo | +| literals.rb:139:1:139:8 | /foo/ | foo | +| literals.rb:140:1:140:15 | /foo+\\sbar\\S/ | foo+\\sbar\\S | +| literals.rb:141:1:141:20 | /foo#{...}bar/ | | +| literals.rb:142:1:142:10 | /foo/ | foo | +stringLiterals +| literals.rb:46:1:46:2 | "" | | +| literals.rb:47:1:47:2 | "" | | +| literals.rb:48:1:48:7 | "hello" | hello | +| literals.rb:49:1:49:9 | "goodbye" | goodbye | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | string with escaped \\" quote | +| literals.rb:51:1:51:21 | "string with " quote" | string with " quote | +| literals.rb:52:1:52:14 | "foo bar baz" | foo bar baz | +| literals.rb:53:1:53:15 | "foo bar baz" | foo bar baz | +| literals.rb:54:1:54:20 | "foo ' bar " baz'" | foo ' bar " baz' | +| literals.rb:55:1:55:20 | "FOO ' BAR " BAZ'" | FOO ' BAR " BAZ' | +| literals.rb:56:1:56:12 | "foo\\ bar" | foo\\ bar | +| literals.rb:57:1:57:12 | "foo\\ bar" | foo\\ bar | +| literals.rb:58:1:58:20 | "2 + 2 = #{...}" | | +| literals.rb:59:1:59:22 | "3 + 4 = #{...}" | | +| literals.rb:60:1:60:20 | "2 + 2 = #{ 2 + 2 }" | 2 + 2 = #{ 2 + 2 } | +| literals.rb:61:1:61:22 | "3 + 4 = #{ 3 + 4 }" | 3 + 4 = #{ 3 + 4 } | +| literals.rb:62:1:62:5 | "foo" | foo | +| literals.rb:62:7:62:11 | "bar" | bar | +| literals.rb:62:13:62:17 | "baz" | baz | +| literals.rb:63:1:63:7 | "foo" | foo | +| literals.rb:63:9:63:13 | "bar" | bar | +| literals.rb:63:15:63:19 | "baz" | baz | +| literals.rb:64:1:64:5 | "foo" | foo | +| literals.rb:64:7:64:21 | "bar#{...}" | | +| literals.rb:64:23:64:27 | "baz" | baz | +| literals.rb:65:1:65:35 | "foo #{...} qux" | | +| literals.rb:65:9:65:28 | "bar #{...} baz" | | +| literals.rb:83:8:83:12 | "bar" | bar | +| literals.rb:98:4:98:6 | "foo" | foo | +| literals.rb:98:8:98:10 | "bar" | bar | +| literals.rb:98:12:98:14 | "baz" | baz | +| literals.rb:99:4:99:6 | "foo" | foo | +| literals.rb:99:8:99:10 | "bar" | bar | +| literals.rb:99:12:99:14 | "baz" | baz | +| literals.rb:100:4:100:6 | "foo" | foo | +| literals.rb:100:8:100:16 | "bar#{...}" | | +| literals.rb:100:18:100:20 | "baz" | baz | +| literals.rb:101:4:101:6 | "foo" | foo | +| literals.rb:101:8:101:16 | "bar#{1+1}" | bar#{1+1} | +| literals.rb:101:18:101:20 | "baz" | baz | +| literals.rb:112:22:112:26 | "baz" | baz | +regexLiterals +| literals.rb:131:1:131:2 | // | | | +| literals.rb:132:1:132:5 | /foo/ | foo | | +| literals.rb:133:1:133:6 | /foo/ | foo | i | +| literals.rb:134:1:134:13 | /foo+\\sbar\\S/ | foo+\\sbar\\S | | +| literals.rb:135:1:135:18 | /foo#{...}bar/ | | | +| literals.rb:136:1:136:8 | /foo/ | foo | oxm | +| literals.rb:137:1:137:4 | // | | | +| literals.rb:138:1:138:7 | /foo/ | foo | | +| literals.rb:139:1:139:8 | /foo/ | foo | i | +| literals.rb:140:1:140:15 | /foo+\\sbar\\S/ | foo+\\sbar\\S | | +| literals.rb:141:1:141:20 | /foo#{...}bar/ | | | +| literals.rb:142:1:142:10 | /foo/ | foo | mxo | +symbolLiterals +| literals.rb:79:1:79:3 | :"" | | +| literals.rb:80:1:80:6 | :hello | hello | +| literals.rb:81:1:81:10 | :"foo bar" | foo bar | +| literals.rb:82:1:82:10 | :"bar baz" | bar baz | +| literals.rb:83:3:83:5 | :foo | foo | +| literals.rb:84:1:84:10 | :"wibble" | wibble | +| literals.rb:85:1:85:17 | :"wibble wobble" | wibble wobble | +| literals.rb:86:1:86:16 | :"foo_#{...}" | | +| literals.rb:87:1:87:17 | :"foo_#{ 1 + 1 }" | foo_#{ 1 + 1 } | +| literals.rb:88:1:88:18 | :"foo_#{ 3 - 2 }" | foo_#{ 3 - 2 } | +| literals.rb:105:4:105:6 | :"foo" | foo | +| literals.rb:105:8:105:10 | :"bar" | bar | +| literals.rb:105:12:105:14 | :"baz" | baz | +| literals.rb:106:4:106:6 | :"foo" | foo | +| literals.rb:106:8:106:10 | :"bar" | bar | +| literals.rb:106:12:106:14 | :"baz" | baz | +| literals.rb:107:4:107:6 | :"foo" | foo | +| literals.rb:107:8:107:20 | :"bar#{...}" | | +| literals.rb:107:22:107:24 | :"baz" | baz | +| literals.rb:108:4:108:6 | :"foo" | foo | +| literals.rb:108:8:108:12 | :"bar#{" | bar#{ | +| literals.rb:108:14:108:14 | :"2" | 2 | +| literals.rb:108:16:108:16 | :"+" | + | +| literals.rb:108:18:108:18 | :"4" | 4 | +| literals.rb:108:20:108:20 | :"}" | } | +| literals.rb:108:22:108:24 | :"baz" | baz | +| literals.rb:112:3:112:5 | :foo | foo | +| literals.rb:112:11:112:14 | :bar | bar | +| literals.rb:113:3:113:5 | :foo | foo | +subshellLiterals +| literals.rb:125:1:125:7 | `ls -l` | ls -l | +| literals.rb:126:1:126:9 | `ls -l` | ls -l | +| literals.rb:127:1:127:18 | `du -d #{...}` | | +| literals.rb:128:1:128:20 | `du -d #{...}` | | +stringComponents +| literals.rb:48:1:48:7 | "hello" | StringLiteral | 0 | literals.rb:48:2:48:6 | hello | StringTextComponent | +| literals.rb:49:1:49:9 | "goodbye" | StringLiteral | 0 | literals.rb:49:2:49:8 | goodbye | StringTextComponent | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | StringLiteral | 0 | literals.rb:50:2:50:21 | string with escaped | StringTextComponent | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | StringLiteral | 1 | literals.rb:50:22:50:23 | \\" | StringEscapeSequenceComponent | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | StringLiteral | 2 | literals.rb:50:24:50:29 | quote | StringTextComponent | +| literals.rb:51:1:51:21 | "string with " quote" | StringLiteral | 0 | literals.rb:51:2:51:20 | string with " quote | StringTextComponent | +| literals.rb:52:1:52:14 | "foo bar baz" | StringLiteral | 0 | literals.rb:52:3:52:13 | foo bar baz | StringTextComponent | +| literals.rb:53:1:53:15 | "foo bar baz" | StringLiteral | 0 | literals.rb:53:4:53:14 | foo bar baz | StringTextComponent | +| literals.rb:54:1:54:20 | "foo ' bar " baz'" | StringLiteral | 0 | literals.rb:54:4:54:19 | foo ' bar " baz' | StringTextComponent | +| literals.rb:55:1:55:20 | "FOO ' BAR " BAZ'" | StringLiteral | 0 | literals.rb:55:4:55:19 | FOO ' BAR " BAZ' | StringTextComponent | +| literals.rb:56:1:56:12 | "foo\\ bar" | StringLiteral | 0 | literals.rb:56:4:56:11 | foo\\ bar | StringTextComponent | +| literals.rb:57:1:57:12 | "foo\\ bar" | StringLiteral | 0 | literals.rb:57:4:57:6 | foo | StringTextComponent | +| literals.rb:57:1:57:12 | "foo\\ bar" | StringLiteral | 1 | literals.rb:57:7:57:8 | \\ | StringEscapeSequenceComponent | +| literals.rb:57:1:57:12 | "foo\\ bar" | StringLiteral | 2 | literals.rb:57:9:57:11 | bar | StringTextComponent | +| literals.rb:58:1:58:20 | "2 + 2 = #{...}" | StringLiteral | 0 | literals.rb:58:2:58:9 | 2 + 2 = | StringTextComponent | +| literals.rb:58:1:58:20 | "2 + 2 = #{...}" | StringLiteral | 1 | literals.rb:58:10:58:19 | #{...} | StringInterpolationComponent | +| literals.rb:59:1:59:22 | "3 + 4 = #{...}" | StringLiteral | 0 | literals.rb:59:4:59:11 | 3 + 4 = | StringTextComponent | +| literals.rb:59:1:59:22 | "3 + 4 = #{...}" | StringLiteral | 1 | literals.rb:59:12:59:21 | #{...} | StringInterpolationComponent | +| literals.rb:60:1:60:20 | "2 + 2 = #{ 2 + 2 }" | StringLiteral | 0 | literals.rb:60:2:60:19 | 2 + 2 = #{ 2 + 2 } | StringTextComponent | +| literals.rb:61:1:61:22 | "3 + 4 = #{ 3 + 4 }" | StringLiteral | 0 | literals.rb:61:4:61:21 | 3 + 4 = #{ 3 + 4 } | StringTextComponent | +| literals.rb:62:1:62:5 | "foo" | StringLiteral | 0 | literals.rb:62:2:62:4 | foo | StringTextComponent | +| literals.rb:62:7:62:11 | "bar" | StringLiteral | 0 | literals.rb:62:8:62:10 | bar | StringTextComponent | +| literals.rb:62:13:62:17 | "baz" | StringLiteral | 0 | literals.rb:62:14:62:16 | baz | StringTextComponent | +| literals.rb:63:1:63:7 | "foo" | StringLiteral | 0 | literals.rb:63:4:63:6 | foo | StringTextComponent | +| literals.rb:63:9:63:13 | "bar" | StringLiteral | 0 | literals.rb:63:10:63:12 | bar | StringTextComponent | +| literals.rb:63:15:63:19 | "baz" | StringLiteral | 0 | literals.rb:63:16:63:18 | baz | StringTextComponent | +| literals.rb:64:1:64:5 | "foo" | StringLiteral | 0 | literals.rb:64:2:64:4 | foo | StringTextComponent | +| literals.rb:64:7:64:21 | "bar#{...}" | StringLiteral | 0 | literals.rb:64:8:64:10 | bar | StringTextComponent | +| literals.rb:64:7:64:21 | "bar#{...}" | StringLiteral | 1 | literals.rb:64:11:64:20 | #{...} | StringInterpolationComponent | +| literals.rb:64:23:64:27 | "baz" | StringLiteral | 0 | literals.rb:64:24:64:26 | baz | StringTextComponent | +| literals.rb:65:1:65:35 | "foo #{...} qux" | StringLiteral | 0 | literals.rb:65:2:65:5 | foo | StringTextComponent | +| literals.rb:65:1:65:35 | "foo #{...} qux" | StringLiteral | 1 | literals.rb:65:6:65:30 | #{...} | StringInterpolationComponent | +| literals.rb:65:1:65:35 | "foo #{...} qux" | StringLiteral | 2 | literals.rb:65:31:65:34 | qux | StringTextComponent | +| literals.rb:65:9:65:28 | "bar #{...} baz" | StringLiteral | 0 | literals.rb:65:10:65:13 | bar | StringTextComponent | +| literals.rb:65:9:65:28 | "bar #{...} baz" | StringLiteral | 1 | literals.rb:65:14:65:23 | #{...} | StringInterpolationComponent | +| literals.rb:65:9:65:28 | "bar #{...} baz" | StringLiteral | 2 | literals.rb:65:24:65:27 | baz | StringTextComponent | +| literals.rb:81:1:81:10 | :"foo bar" | SymbolLiteral | 0 | literals.rb:81:3:81:9 | foo bar | StringTextComponent | +| literals.rb:82:1:82:10 | :"bar baz" | SymbolLiteral | 0 | literals.rb:82:3:82:9 | bar baz | StringTextComponent | +| literals.rb:83:8:83:12 | "bar" | StringLiteral | 0 | literals.rb:83:9:83:11 | bar | StringTextComponent | +| literals.rb:84:1:84:10 | :"wibble" | SymbolLiteral | 0 | literals.rb:84:4:84:9 | wibble | StringTextComponent | +| literals.rb:85:1:85:17 | :"wibble wobble" | SymbolLiteral | 0 | literals.rb:85:4:85:16 | wibble wobble | StringTextComponent | +| literals.rb:86:1:86:16 | :"foo_#{...}" | SymbolLiteral | 0 | literals.rb:86:3:86:6 | foo_ | StringTextComponent | +| literals.rb:86:1:86:16 | :"foo_#{...}" | SymbolLiteral | 1 | literals.rb:86:7:86:15 | #{...} | StringInterpolationComponent | +| literals.rb:87:1:87:17 | :"foo_#{ 1 + 1 }" | SymbolLiteral | 0 | literals.rb:87:3:87:16 | foo_#{ 1 + 1 } | StringTextComponent | +| literals.rb:88:1:88:18 | :"foo_#{ 3 - 2 }" | SymbolLiteral | 0 | literals.rb:88:4:88:17 | foo_#{ 3 - 2 } | StringTextComponent | +| literals.rb:98:4:98:6 | "foo" | StringLiteral | 0 | literals.rb:98:4:98:6 | foo | StringTextComponent | +| literals.rb:98:8:98:10 | "bar" | StringLiteral | 0 | literals.rb:98:8:98:10 | bar | StringTextComponent | +| literals.rb:98:12:98:14 | "baz" | StringLiteral | 0 | literals.rb:98:12:98:14 | baz | StringTextComponent | +| literals.rb:99:4:99:6 | "foo" | StringLiteral | 0 | literals.rb:99:4:99:6 | foo | StringTextComponent | +| literals.rb:99:8:99:10 | "bar" | StringLiteral | 0 | literals.rb:99:8:99:10 | bar | StringTextComponent | +| literals.rb:99:12:99:14 | "baz" | StringLiteral | 0 | literals.rb:99:12:99:14 | baz | StringTextComponent | +| literals.rb:100:4:100:6 | "foo" | StringLiteral | 0 | literals.rb:100:4:100:6 | foo | StringTextComponent | +| literals.rb:100:8:100:16 | "bar#{...}" | StringLiteral | 0 | literals.rb:100:8:100:10 | bar | StringTextComponent | +| literals.rb:100:8:100:16 | "bar#{...}" | StringLiteral | 1 | literals.rb:100:11:100:16 | #{...} | StringInterpolationComponent | +| literals.rb:100:18:100:20 | "baz" | StringLiteral | 0 | literals.rb:100:18:100:20 | baz | StringTextComponent | +| literals.rb:101:4:101:6 | "foo" | StringLiteral | 0 | literals.rb:101:4:101:6 | foo | StringTextComponent | +| literals.rb:101:8:101:16 | "bar#{1+1}" | StringLiteral | 0 | literals.rb:101:8:101:16 | bar#{1+1} | StringTextComponent | +| literals.rb:101:18:101:20 | "baz" | StringLiteral | 0 | literals.rb:101:18:101:20 | baz | StringTextComponent | +| literals.rb:105:4:105:6 | :"foo" | SymbolLiteral | 0 | literals.rb:105:4:105:6 | foo | StringTextComponent | +| literals.rb:105:8:105:10 | :"bar" | SymbolLiteral | 0 | literals.rb:105:8:105:10 | bar | StringTextComponent | +| literals.rb:105:12:105:14 | :"baz" | SymbolLiteral | 0 | literals.rb:105:12:105:14 | baz | StringTextComponent | +| literals.rb:106:4:106:6 | :"foo" | SymbolLiteral | 0 | literals.rb:106:4:106:6 | foo | StringTextComponent | +| literals.rb:106:8:106:10 | :"bar" | SymbolLiteral | 0 | literals.rb:106:8:106:10 | bar | StringTextComponent | +| literals.rb:106:12:106:14 | :"baz" | SymbolLiteral | 0 | literals.rb:106:12:106:14 | baz | StringTextComponent | +| literals.rb:107:4:107:6 | :"foo" | SymbolLiteral | 0 | literals.rb:107:4:107:6 | foo | StringTextComponent | +| literals.rb:107:8:107:20 | :"bar#{...}" | SymbolLiteral | 0 | literals.rb:107:8:107:10 | bar | StringTextComponent | +| literals.rb:107:8:107:20 | :"bar#{...}" | SymbolLiteral | 1 | literals.rb:107:11:107:20 | #{...} | StringInterpolationComponent | +| literals.rb:107:22:107:24 | :"baz" | SymbolLiteral | 0 | literals.rb:107:22:107:24 | baz | StringTextComponent | +| literals.rb:108:4:108:6 | :"foo" | SymbolLiteral | 0 | literals.rb:108:4:108:6 | foo | StringTextComponent | +| literals.rb:108:8:108:12 | :"bar#{" | SymbolLiteral | 0 | literals.rb:108:8:108:12 | bar#{ | StringTextComponent | +| literals.rb:108:14:108:14 | :"2" | SymbolLiteral | 0 | literals.rb:108:14:108:14 | 2 | StringTextComponent | +| literals.rb:108:16:108:16 | :"+" | SymbolLiteral | 0 | literals.rb:108:16:108:16 | + | StringTextComponent | +| literals.rb:108:18:108:18 | :"4" | SymbolLiteral | 0 | literals.rb:108:18:108:18 | 4 | StringTextComponent | +| literals.rb:108:20:108:20 | :"}" | SymbolLiteral | 0 | literals.rb:108:20:108:20 | } | StringTextComponent | +| literals.rb:108:22:108:24 | :"baz" | SymbolLiteral | 0 | literals.rb:108:22:108:24 | baz | StringTextComponent | +| literals.rb:112:22:112:26 | "baz" | StringLiteral | 0 | literals.rb:112:23:112:25 | baz | StringTextComponent | +| literals.rb:125:1:125:7 | `ls -l` | SubshellLiteral | 0 | literals.rb:125:2:125:6 | ls -l | StringTextComponent | +| literals.rb:126:1:126:9 | `ls -l` | SubshellLiteral | 0 | literals.rb:126:4:126:8 | ls -l | StringTextComponent | +| literals.rb:127:1:127:18 | `du -d #{...}` | SubshellLiteral | 0 | literals.rb:127:2:127:7 | du -d | StringTextComponent | +| literals.rb:127:1:127:18 | `du -d #{...}` | SubshellLiteral | 1 | literals.rb:127:8:127:17 | #{...} | StringInterpolationComponent | +| literals.rb:128:1:128:20 | `du -d #{...}` | SubshellLiteral | 0 | literals.rb:128:4:128:9 | du -d | StringTextComponent | +| literals.rb:128:1:128:20 | `du -d #{...}` | SubshellLiteral | 1 | literals.rb:128:10:128:19 | #{...} | StringInterpolationComponent | +| literals.rb:132:1:132:5 | /foo/ | RegexLiteral | 0 | literals.rb:132:2:132:4 | foo | StringTextComponent | +| literals.rb:133:1:133:6 | /foo/ | RegexLiteral | 0 | literals.rb:133:2:133:4 | foo | StringTextComponent | +| literals.rb:134:1:134:13 | /foo+\\sbar\\S/ | RegexLiteral | 0 | literals.rb:134:2:134:5 | foo+ | StringTextComponent | +| literals.rb:134:1:134:13 | /foo+\\sbar\\S/ | RegexLiteral | 1 | literals.rb:134:6:134:7 | \\s | StringEscapeSequenceComponent | +| literals.rb:134:1:134:13 | /foo+\\sbar\\S/ | RegexLiteral | 2 | literals.rb:134:8:134:10 | bar | StringTextComponent | +| literals.rb:134:1:134:13 | /foo+\\sbar\\S/ | RegexLiteral | 3 | literals.rb:134:11:134:12 | \\S | StringEscapeSequenceComponent | +| literals.rb:135:1:135:18 | /foo#{...}bar/ | RegexLiteral | 0 | literals.rb:135:2:135:4 | foo | StringTextComponent | +| literals.rb:135:1:135:18 | /foo#{...}bar/ | RegexLiteral | 1 | literals.rb:135:5:135:14 | #{...} | StringInterpolationComponent | +| literals.rb:135:1:135:18 | /foo#{...}bar/ | RegexLiteral | 2 | literals.rb:135:15:135:17 | bar | StringTextComponent | +| literals.rb:136:1:136:8 | /foo/ | RegexLiteral | 0 | literals.rb:136:2:136:4 | foo | StringTextComponent | +| literals.rb:138:1:138:7 | /foo/ | RegexLiteral | 0 | literals.rb:138:4:138:6 | foo | StringTextComponent | +| literals.rb:139:1:139:8 | /foo/ | RegexLiteral | 0 | literals.rb:139:4:139:6 | foo | StringTextComponent | +| literals.rb:140:1:140:15 | /foo+\\sbar\\S/ | RegexLiteral | 0 | literals.rb:140:4:140:7 | foo+ | StringTextComponent | +| literals.rb:140:1:140:15 | /foo+\\sbar\\S/ | RegexLiteral | 1 | literals.rb:140:8:140:9 | \\s | StringEscapeSequenceComponent | +| literals.rb:140:1:140:15 | /foo+\\sbar\\S/ | RegexLiteral | 2 | literals.rb:140:10:140:12 | bar | StringTextComponent | +| literals.rb:140:1:140:15 | /foo+\\sbar\\S/ | RegexLiteral | 3 | literals.rb:140:13:140:14 | \\S | StringEscapeSequenceComponent | +| literals.rb:141:1:141:20 | /foo#{...}bar/ | RegexLiteral | 0 | literals.rb:141:4:141:6 | foo | StringTextComponent | +| literals.rb:141:1:141:20 | /foo#{...}bar/ | RegexLiteral | 1 | literals.rb:141:7:141:16 | #{...} | StringInterpolationComponent | +| literals.rb:141:1:141:20 | /foo#{...}bar/ | RegexLiteral | 2 | literals.rb:141:17:141:19 | bar | StringTextComponent | +| literals.rb:142:1:142:10 | /foo/ | RegexLiteral | 0 | literals.rb:142:4:142:6 | foo | StringTextComponent | +stringInterpolations +| literals.rb:58:10:58:19 | #{...} | 0 | literals.rb:58:13:58:17 | ... + ... | AddExpr | +| literals.rb:59:12:59:21 | #{...} | 0 | literals.rb:59:15:59:19 | ... + ... | AddExpr | +| literals.rb:64:11:64:20 | #{...} | 0 | literals.rb:64:14:64:18 | ... * ... | MulExpr | +| literals.rb:65:6:65:30 | #{...} | 0 | literals.rb:65:9:65:28 | "bar #{...} baz" | StringLiteral | +| literals.rb:65:14:65:23 | #{...} | 0 | literals.rb:65:17:65:21 | ... + ... | AddExpr | +| literals.rb:86:7:86:15 | #{...} | 0 | literals.rb:86:10:86:14 | ... + ... | AddExpr | +| literals.rb:100:11:100:16 | #{...} | 0 | literals.rb:100:13:100:15 | ... + ... | AddExpr | +| literals.rb:107:11:107:20 | #{...} | 0 | literals.rb:107:14:107:18 | ... + ... | AddExpr | +| literals.rb:127:8:127:17 | #{...} | 0 | literals.rb:127:11:127:15 | ... + ... | AddExpr | +| literals.rb:128:10:128:19 | #{...} | 0 | literals.rb:128:13:128:17 | ... - ... | SubExpr | +| literals.rb:135:5:135:14 | #{...} | 0 | literals.rb:135:8:135:12 | ... + ... | AddExpr | +| literals.rb:141:7:141:16 | #{...} | 0 | literals.rb:141:10:141:14 | ... + ... | AddExpr | +concatenatedStrings +| literals.rb:62:1:62:17 | "..." "..." | foobarbaz | 0 | literals.rb:62:1:62:5 | "foo" | +| literals.rb:62:1:62:17 | "..." "..." | foobarbaz | 1 | literals.rb:62:7:62:11 | "bar" | +| literals.rb:62:1:62:17 | "..." "..." | foobarbaz | 2 | literals.rb:62:13:62:17 | "baz" | +| literals.rb:63:1:63:19 | "..." "..." | foobarbaz | 0 | literals.rb:63:1:63:7 | "foo" | +| literals.rb:63:1:63:19 | "..." "..." | foobarbaz | 1 | literals.rb:63:9:63:13 | "bar" | +| literals.rb:63:1:63:19 | "..." "..." | foobarbaz | 2 | literals.rb:63:15:63:19 | "baz" | +| literals.rb:64:1:64:27 | "..." "..." | | 0 | literals.rb:64:1:64:5 | "foo" | +| literals.rb:64:1:64:27 | "..." "..." | | 1 | literals.rb:64:7:64:21 | "bar#{...}" | +| literals.rb:64:1:64:27 | "..." "..." | | 2 | literals.rb:64:23:64:27 | "baz" | +arrayLiterals +| literals.rb:91:1:91:2 | [...] | 0 | +| literals.rb:92:1:92:9 | [...] | 3 | +| literals.rb:93:1:93:14 | [...] | 3 | +| literals.rb:94:1:94:11 | [...] | 2 | +| literals.rb:94:5:94:10 | [...] | 2 | +| literals.rb:97:1:97:4 | %w(...) | 0 | +| literals.rb:98:1:98:15 | %w(...) | 3 | +| literals.rb:99:1:99:15 | %w(...) | 3 | +| literals.rb:100:1:100:21 | %w(...) | 3 | +| literals.rb:101:1:101:21 | %w(...) | 3 | +| literals.rb:104:1:104:4 | %i(...) | 0 | +| literals.rb:105:1:105:15 | %i(...) | 3 | +| literals.rb:106:1:106:15 | %i(...) | 3 | +| literals.rb:107:1:107:25 | %i(...) | 3 | +| literals.rb:108:1:108:25 | %i(...) | 7 | +arrayLiteralElements +| literals.rb:92:1:92:9 | [...] | 0 | literals.rb:92:2:92:2 | 1 | IntegerLiteral | +| literals.rb:92:1:92:9 | [...] | 1 | literals.rb:92:5:92:5 | 2 | IntegerLiteral | +| literals.rb:92:1:92:9 | [...] | 2 | literals.rb:92:8:92:8 | 3 | IntegerLiteral | +| literals.rb:93:1:93:14 | [...] | 0 | literals.rb:93:2:93:2 | 4 | IntegerLiteral | +| literals.rb:93:1:93:14 | [...] | 1 | literals.rb:93:5:93:5 | 5 | IntegerLiteral | +| literals.rb:93:1:93:14 | [...] | 2 | literals.rb:93:8:93:13 | ... / ... | DivExpr | +| literals.rb:94:1:94:11 | [...] | 0 | literals.rb:94:2:94:2 | 7 | IntegerLiteral | +| literals.rb:94:1:94:11 | [...] | 1 | literals.rb:94:5:94:10 | [...] | ArrayLiteral | +| literals.rb:94:5:94:10 | [...] | 0 | literals.rb:94:6:94:6 | 8 | IntegerLiteral | +| literals.rb:94:5:94:10 | [...] | 1 | literals.rb:94:9:94:9 | 9 | IntegerLiteral | +| literals.rb:98:1:98:15 | %w(...) | 0 | literals.rb:98:4:98:6 | "foo" | StringLiteral | +| literals.rb:98:1:98:15 | %w(...) | 1 | literals.rb:98:8:98:10 | "bar" | StringLiteral | +| literals.rb:98:1:98:15 | %w(...) | 2 | literals.rb:98:12:98:14 | "baz" | StringLiteral | +| literals.rb:99:1:99:15 | %w(...) | 0 | literals.rb:99:4:99:6 | "foo" | StringLiteral | +| literals.rb:99:1:99:15 | %w(...) | 1 | literals.rb:99:8:99:10 | "bar" | StringLiteral | +| literals.rb:99:1:99:15 | %w(...) | 2 | literals.rb:99:12:99:14 | "baz" | StringLiteral | +| literals.rb:100:1:100:21 | %w(...) | 0 | literals.rb:100:4:100:6 | "foo" | StringLiteral | +| literals.rb:100:1:100:21 | %w(...) | 1 | literals.rb:100:8:100:16 | "bar#{...}" | StringLiteral | +| literals.rb:100:1:100:21 | %w(...) | 2 | literals.rb:100:18:100:20 | "baz" | StringLiteral | +| literals.rb:101:1:101:21 | %w(...) | 0 | literals.rb:101:4:101:6 | "foo" | StringLiteral | +| literals.rb:101:1:101:21 | %w(...) | 1 | literals.rb:101:8:101:16 | "bar#{1+1}" | StringLiteral | +| literals.rb:101:1:101:21 | %w(...) | 2 | literals.rb:101:18:101:20 | "baz" | StringLiteral | +| literals.rb:105:1:105:15 | %i(...) | 0 | literals.rb:105:4:105:6 | :"foo" | SymbolLiteral | +| literals.rb:105:1:105:15 | %i(...) | 1 | literals.rb:105:8:105:10 | :"bar" | SymbolLiteral | +| literals.rb:105:1:105:15 | %i(...) | 2 | literals.rb:105:12:105:14 | :"baz" | SymbolLiteral | +| literals.rb:106:1:106:15 | %i(...) | 0 | literals.rb:106:4:106:6 | :"foo" | SymbolLiteral | +| literals.rb:106:1:106:15 | %i(...) | 1 | literals.rb:106:8:106:10 | :"bar" | SymbolLiteral | +| literals.rb:106:1:106:15 | %i(...) | 2 | literals.rb:106:12:106:14 | :"baz" | SymbolLiteral | +| literals.rb:107:1:107:25 | %i(...) | 0 | literals.rb:107:4:107:6 | :"foo" | SymbolLiteral | +| literals.rb:107:1:107:25 | %i(...) | 1 | literals.rb:107:8:107:20 | :"bar#{...}" | SymbolLiteral | +| literals.rb:107:1:107:25 | %i(...) | 2 | literals.rb:107:22:107:24 | :"baz" | SymbolLiteral | +| literals.rb:108:1:108:25 | %i(...) | 0 | literals.rb:108:4:108:6 | :"foo" | SymbolLiteral | +| literals.rb:108:1:108:25 | %i(...) | 1 | literals.rb:108:8:108:12 | :"bar#{" | SymbolLiteral | +| literals.rb:108:1:108:25 | %i(...) | 2 | literals.rb:108:14:108:14 | :"2" | SymbolLiteral | +| literals.rb:108:1:108:25 | %i(...) | 3 | literals.rb:108:16:108:16 | :"+" | SymbolLiteral | +| literals.rb:108:1:108:25 | %i(...) | 4 | literals.rb:108:18:108:18 | :"4" | SymbolLiteral | +| literals.rb:108:1:108:25 | %i(...) | 5 | literals.rb:108:20:108:20 | :"}" | SymbolLiteral | +| literals.rb:108:1:108:25 | %i(...) | 6 | literals.rb:108:22:108:24 | :"baz" | SymbolLiteral | +hashLiterals +| literals.rb:83:1:83:14 | {...} | 1 | +| literals.rb:111:1:111:2 | {...} | 0 | +| literals.rb:112:1:112:33 | {...} | 3 | +| literals.rb:113:1:113:17 | {...} | 2 | +hashLiteralElements +| literals.rb:83:1:83:14 | {...} | 0 | literals.rb:83:3:83:12 | Pair | Pair | +| literals.rb:112:1:112:33 | {...} | 0 | literals.rb:112:3:112:8 | Pair | Pair | +| literals.rb:112:1:112:33 | {...} | 1 | literals.rb:112:11:112:19 | Pair | Pair | +| literals.rb:112:1:112:33 | {...} | 2 | literals.rb:112:22:112:31 | Pair | Pair | +| literals.rb:113:1:113:17 | {...} | 0 | literals.rb:113:3:113:8 | Pair | Pair | +| literals.rb:113:1:113:17 | {...} | 1 | literals.rb:113:11:113:15 | **... | HashSplatArgument | +hashLiteralKeyValuePairs +| literals.rb:83:1:83:14 | {...} | literals.rb:83:3:83:12 | Pair | literals.rb:83:3:83:5 | :foo | literals.rb:83:8:83:12 | "bar" | +| literals.rb:112:1:112:33 | {...} | literals.rb:112:3:112:8 | Pair | literals.rb:112:3:112:5 | :foo | literals.rb:112:8:112:8 | 1 | +| literals.rb:112:1:112:33 | {...} | literals.rb:112:11:112:19 | Pair | literals.rb:112:11:112:14 | :bar | literals.rb:112:19:112:19 | 2 | +| literals.rb:112:1:112:33 | {...} | literals.rb:112:22:112:31 | Pair | literals.rb:112:22:112:26 | "baz" | literals.rb:112:31:112:31 | 3 | +| literals.rb:113:1:113:17 | {...} | literals.rb:113:3:113:8 | Pair | literals.rb:113:3:113:5 | :foo | literals.rb:113:8:113:8 | 7 | +finiteRangeLiterals +| literals.rb:116:2:116:6 | _ .. _ | literals.rb:116:2:116:2 | 1 | literals.rb:116:5:116:6 | 10 | +| literals.rb:117:2:117:7 | _ ... _ | literals.rb:117:2:117:2 | 1 | literals.rb:117:6:117:7 | 10 | +| literals.rb:118:2:118:7 | _ .. _ | literals.rb:118:2:118:2 | 1 | literals.rb:118:7:118:7 | 0 | +| literals.rb:119:2:119:11 | _ .. _ | literals.rb:119:2:119:6 | call to start | literals.rb:119:9:119:11 | ... + ... | +beginlessRangeLiterals +| literals.rb:121:2:121:4 | _ .. _ | literals.rb:121:4:121:4 | 1 | +endlessRangeLiterals +| literals.rb:120:2:120:4 | _ .. _ | literals.rb:120:2:120:2 | 1 | +| literals.rb:122:2:122:4 | _ .. _ | literals.rb:122:2:122:2 | 0 | +inclusiveRangeLiterals +| literals.rb:116:2:116:6 | _ .. _ | +| literals.rb:118:2:118:7 | _ .. _ | +| literals.rb:119:2:119:11 | _ .. _ | +| literals.rb:120:2:120:4 | _ .. _ | +| literals.rb:121:2:121:4 | _ .. _ | +| literals.rb:122:2:122:4 | _ .. _ | +exclusiveRangeLiterals +| literals.rb:117:2:117:7 | _ ... _ | +numericLiterals +| literals.rb:10:1:10:4 | 1234 | IntegerLiteral | 1234 | +| literals.rb:11:1:11:5 | 5_678 | IntegerLiteral | 5_678 | +| literals.rb:12:1:12:1 | 0 | IntegerLiteral | 0 | +| literals.rb:13:1:13:5 | 0d900 | IntegerLiteral | 0d900 | +| literals.rb:16:1:16:6 | 0x1234 | IntegerLiteral | 0x1234 | +| literals.rb:17:1:17:10 | 0xdeadbeef | IntegerLiteral | 0xdeadbeef | +| literals.rb:18:1:18:11 | 0xF00D_face | IntegerLiteral | 0xF00D_face | +| literals.rb:21:1:21:4 | 0123 | IntegerLiteral | 0123 | +| literals.rb:22:1:22:5 | 0o234 | IntegerLiteral | 0o234 | +| literals.rb:23:1:23:6 | 0O45_6 | IntegerLiteral | 0O45_6 | +| literals.rb:26:1:26:10 | 0b10010100 | IntegerLiteral | 0b10010100 | +| literals.rb:27:1:27:11 | 0B011_01101 | IntegerLiteral | 0B011_01101 | +| literals.rb:30:1:30:5 | 12.34 | FloatLiteral | 12.34 | +| literals.rb:31:1:31:7 | 1234e-2 | FloatLiteral | 1234e-2 | +| literals.rb:32:1:32:7 | 1.234E1 | FloatLiteral | 1.234E1 | +| literals.rb:35:1:35:3 | 23r | RationalLiteral | 23 | +| literals.rb:36:1:36:5 | 9.85r | RationalLiteral | 9.85 | +| literals.rb:39:1:39:2 | 2i | ComplexLiteral | 2i | +| literals.rb:58:13:58:13 | 2 | IntegerLiteral | 2 | +| literals.rb:58:17:58:17 | 2 | IntegerLiteral | 2 | +| literals.rb:59:15:59:15 | 3 | IntegerLiteral | 3 | +| literals.rb:59:19:59:19 | 4 | IntegerLiteral | 4 | +| literals.rb:64:14:64:14 | 1 | IntegerLiteral | 1 | +| literals.rb:64:18:64:18 | 1 | IntegerLiteral | 1 | +| literals.rb:65:17:65:17 | 2 | IntegerLiteral | 2 | +| literals.rb:65:21:65:21 | 3 | IntegerLiteral | 3 | +| literals.rb:86:10:86:10 | 2 | IntegerLiteral | 2 | +| literals.rb:86:14:86:14 | 2 | IntegerLiteral | 2 | +| literals.rb:92:2:92:2 | 1 | IntegerLiteral | 1 | +| literals.rb:92:5:92:5 | 2 | IntegerLiteral | 2 | +| literals.rb:92:8:92:8 | 3 | IntegerLiteral | 3 | +| literals.rb:93:2:93:2 | 4 | IntegerLiteral | 4 | +| literals.rb:93:5:93:5 | 5 | IntegerLiteral | 5 | +| literals.rb:93:8:93:9 | 12 | IntegerLiteral | 12 | +| literals.rb:93:13:93:13 | 2 | IntegerLiteral | 2 | +| literals.rb:94:2:94:2 | 7 | IntegerLiteral | 7 | +| literals.rb:94:6:94:6 | 8 | IntegerLiteral | 8 | +| literals.rb:94:9:94:9 | 9 | IntegerLiteral | 9 | +| literals.rb:100:13:100:13 | 1 | IntegerLiteral | 1 | +| literals.rb:100:15:100:15 | 1 | IntegerLiteral | 1 | +| literals.rb:107:14:107:14 | 2 | IntegerLiteral | 2 | +| literals.rb:107:18:107:18 | 4 | IntegerLiteral | 4 | +| literals.rb:112:8:112:8 | 1 | IntegerLiteral | 1 | +| literals.rb:112:19:112:19 | 2 | IntegerLiteral | 2 | +| literals.rb:112:31:112:31 | 3 | IntegerLiteral | 3 | +| literals.rb:113:8:113:8 | 7 | IntegerLiteral | 7 | +| literals.rb:116:2:116:2 | 1 | IntegerLiteral | 1 | +| literals.rb:116:5:116:6 | 10 | IntegerLiteral | 10 | +| literals.rb:117:2:117:2 | 1 | IntegerLiteral | 1 | +| literals.rb:117:6:117:7 | 10 | IntegerLiteral | 10 | +| literals.rb:118:2:118:2 | 1 | IntegerLiteral | 1 | +| literals.rb:118:7:118:7 | 0 | IntegerLiteral | 0 | +| literals.rb:119:9:119:9 | 2 | IntegerLiteral | 2 | +| literals.rb:119:11:119:11 | 3 | IntegerLiteral | 3 | +| literals.rb:120:2:120:2 | 1 | IntegerLiteral | 1 | +| literals.rb:121:4:121:4 | 1 | IntegerLiteral | 1 | +| literals.rb:122:2:122:2 | 0 | IntegerLiteral | 0 | +| literals.rb:122:6:122:6 | 1 | IntegerLiteral | 1 | +| literals.rb:127:11:127:11 | 1 | IntegerLiteral | 1 | +| literals.rb:127:15:127:15 | 1 | IntegerLiteral | 1 | +| literals.rb:128:13:128:13 | 5 | IntegerLiteral | 5 | +| literals.rb:128:17:128:17 | 4 | IntegerLiteral | 4 | +| literals.rb:135:8:135:8 | 1 | IntegerLiteral | 1 | +| literals.rb:135:12:135:12 | 1 | IntegerLiteral | 1 | +| literals.rb:141:10:141:10 | 1 | IntegerLiteral | 1 | +| literals.rb:141:14:141:14 | 1 | IntegerLiteral | 1 | +integerLiterals +| literals.rb:10:1:10:4 | 1234 | IntegerLiteral | 1234 | +| literals.rb:11:1:11:5 | 5_678 | IntegerLiteral | 5_678 | +| literals.rb:12:1:12:1 | 0 | IntegerLiteral | 0 | +| literals.rb:13:1:13:5 | 0d900 | IntegerLiteral | 0d900 | +| literals.rb:16:1:16:6 | 0x1234 | IntegerLiteral | 0x1234 | +| literals.rb:17:1:17:10 | 0xdeadbeef | IntegerLiteral | 0xdeadbeef | +| literals.rb:18:1:18:11 | 0xF00D_face | IntegerLiteral | 0xF00D_face | +| literals.rb:21:1:21:4 | 0123 | IntegerLiteral | 0123 | +| literals.rb:22:1:22:5 | 0o234 | IntegerLiteral | 0o234 | +| literals.rb:23:1:23:6 | 0O45_6 | IntegerLiteral | 0O45_6 | +| literals.rb:26:1:26:10 | 0b10010100 | IntegerLiteral | 0b10010100 | +| literals.rb:27:1:27:11 | 0B011_01101 | IntegerLiteral | 0B011_01101 | +| literals.rb:58:13:58:13 | 2 | IntegerLiteral | 2 | +| literals.rb:58:17:58:17 | 2 | IntegerLiteral | 2 | +| literals.rb:59:15:59:15 | 3 | IntegerLiteral | 3 | +| literals.rb:59:19:59:19 | 4 | IntegerLiteral | 4 | +| literals.rb:64:14:64:14 | 1 | IntegerLiteral | 1 | +| literals.rb:64:18:64:18 | 1 | IntegerLiteral | 1 | +| literals.rb:65:17:65:17 | 2 | IntegerLiteral | 2 | +| literals.rb:65:21:65:21 | 3 | IntegerLiteral | 3 | +| literals.rb:86:10:86:10 | 2 | IntegerLiteral | 2 | +| literals.rb:86:14:86:14 | 2 | IntegerLiteral | 2 | +| literals.rb:92:2:92:2 | 1 | IntegerLiteral | 1 | +| literals.rb:92:5:92:5 | 2 | IntegerLiteral | 2 | +| literals.rb:92:8:92:8 | 3 | IntegerLiteral | 3 | +| literals.rb:93:2:93:2 | 4 | IntegerLiteral | 4 | +| literals.rb:93:5:93:5 | 5 | IntegerLiteral | 5 | +| literals.rb:93:8:93:9 | 12 | IntegerLiteral | 12 | +| literals.rb:93:13:93:13 | 2 | IntegerLiteral | 2 | +| literals.rb:94:2:94:2 | 7 | IntegerLiteral | 7 | +| literals.rb:94:6:94:6 | 8 | IntegerLiteral | 8 | +| literals.rb:94:9:94:9 | 9 | IntegerLiteral | 9 | +| literals.rb:100:13:100:13 | 1 | IntegerLiteral | 1 | +| literals.rb:100:15:100:15 | 1 | IntegerLiteral | 1 | +| literals.rb:107:14:107:14 | 2 | IntegerLiteral | 2 | +| literals.rb:107:18:107:18 | 4 | IntegerLiteral | 4 | +| literals.rb:112:8:112:8 | 1 | IntegerLiteral | 1 | +| literals.rb:112:19:112:19 | 2 | IntegerLiteral | 2 | +| literals.rb:112:31:112:31 | 3 | IntegerLiteral | 3 | +| literals.rb:113:8:113:8 | 7 | IntegerLiteral | 7 | +| literals.rb:116:2:116:2 | 1 | IntegerLiteral | 1 | +| literals.rb:116:5:116:6 | 10 | IntegerLiteral | 10 | +| literals.rb:117:2:117:2 | 1 | IntegerLiteral | 1 | +| literals.rb:117:6:117:7 | 10 | IntegerLiteral | 10 | +| literals.rb:118:2:118:2 | 1 | IntegerLiteral | 1 | +| literals.rb:118:7:118:7 | 0 | IntegerLiteral | 0 | +| literals.rb:119:9:119:9 | 2 | IntegerLiteral | 2 | +| literals.rb:119:11:119:11 | 3 | IntegerLiteral | 3 | +| literals.rb:120:2:120:2 | 1 | IntegerLiteral | 1 | +| literals.rb:121:4:121:4 | 1 | IntegerLiteral | 1 | +| literals.rb:122:2:122:2 | 0 | IntegerLiteral | 0 | +| literals.rb:122:6:122:6 | 1 | IntegerLiteral | 1 | +| literals.rb:127:11:127:11 | 1 | IntegerLiteral | 1 | +| literals.rb:127:15:127:15 | 1 | IntegerLiteral | 1 | +| literals.rb:128:13:128:13 | 5 | IntegerLiteral | 5 | +| literals.rb:128:17:128:17 | 4 | IntegerLiteral | 4 | +| literals.rb:135:8:135:8 | 1 | IntegerLiteral | 1 | +| literals.rb:135:12:135:12 | 1 | IntegerLiteral | 1 | +| literals.rb:141:10:141:10 | 1 | IntegerLiteral | 1 | +| literals.rb:141:14:141:14 | 1 | IntegerLiteral | 1 | +floatLiterals +| literals.rb:30:1:30:5 | 12.34 | FloatLiteral | 12.34 | +| literals.rb:31:1:31:7 | 1234e-2 | FloatLiteral | 1234e-2 | +| literals.rb:32:1:32:7 | 1.234E1 | FloatLiteral | 1.234E1 | +rationalLiterals +| literals.rb:35:1:35:3 | 23r | RationalLiteral | 23 | +| literals.rb:36:1:36:5 | 9.85r | RationalLiteral | 9.85 | +complexLiterals +| literals.rb:39:1:39:2 | 2i | ComplexLiteral | 2i | diff --git a/ql/test/library-tests/ast/literals/literals.ql b/ql/test/library-tests/ast/literals/literals.ql new file mode 100644 index 00000000000..23e9cb05fc0 --- /dev/null +++ b/ql/test/library-tests/ast/literals/literals.ql @@ -0,0 +1,110 @@ +import ruby + +query predicate allLiterals(Literal l, string pClass, string valueText) { + pClass = l.getAPrimaryQlClass() and + ( + valueText = l.getValueText() + or + not exists(l.getValueText()) and valueText = "" + ) +} + +query predicate stringlikeLiterals(StringlikeLiteral l, string valueText) { + valueText = l.getValueText() + or + not exists(l.getValueText()) and valueText = "" +} + +query predicate stringLiterals(StringLiteral l, string valueText) { + stringlikeLiterals(l, valueText) +} + +query predicate regexLiterals(RegexLiteral l, string valueText, string flags) { + stringlikeLiterals(l, valueText) and flags = l.getFlagString() +} + +query predicate symbolLiterals(SymbolLiteral l, string valueText) { + stringlikeLiterals(l, valueText) +} + +query predicate subshellLiterals(SubshellLiteral l, string valueText) { + stringlikeLiterals(l, valueText) +} + +query predicate stringComponents( + StringlikeLiteral l, string lClass, int i, StringComponent c, string cClass +) { + lClass = l.getAPrimaryQlClass() and + c = l.getComponent(i) and + cClass = c.getAPrimaryQlClass() +} + +query predicate stringInterpolations(StringInterpolationComponent c, int i, Expr e, string eClass) { + e = c.getStmt(i) and eClass = e.getAPrimaryQlClass() +} + +query predicate concatenatedStrings(StringConcatenation sc, string valueText, int n, StringLiteral l) { + l = sc.getString(n) and + ( + valueText = sc.getConcatenatedValueText() + or + not exists(sc.getConcatenatedValueText()) and valueText = "" + ) +} + +query predicate arrayLiterals(ArrayLiteral l, int numElements) { + numElements = l.getNumberOfElements() +} + +query predicate arrayLiteralElements(ArrayLiteral l, int n, Expr elementN, string pClass) { + elementN = l.getElement(n) and pClass = elementN.getAPrimaryQlClass() +} + +query predicate hashLiterals(HashLiteral l, int numElements) { + numElements = l.getNumberOfElements() +} + +query predicate hashLiteralElements(HashLiteral l, int n, Expr elementN, string pClass) { + elementN = l.getElement(n) and pClass = elementN.getAPrimaryQlClass() +} + +query predicate hashLiteralKeyValuePairs(HashLiteral l, Pair p, Expr k, Expr v) { + p = l.getAKeyValuePair() and k = p.getKey() and v = p.getValue() +} + +query predicate finiteRangeLiterals(RangeLiteral l, Expr begin, Expr end) { + begin = l.getBegin() and + end = l.getEnd() +} + +query predicate beginlessRangeLiterals(RangeLiteral l, Expr end) { + not exists(l.getBegin()) and end = l.getEnd() +} + +query predicate endlessRangeLiterals(RangeLiteral l, Expr begin) { + begin = l.getBegin() and not exists(l.getEnd()) +} + +query predicate inclusiveRangeLiterals(RangeLiteral l) { l.isInclusive() } + +query predicate exclusiveRangeLiterals(RangeLiteral l) { l.isExclusive() } + +query predicate numericLiterals(NumericLiteral l, string pClass, string valueText) { + allLiterals(l, pClass, valueText) +} + +query predicate integerLiterals(IntegerLiteral l, string pClass, string valueText) { + allLiterals(l, pClass, valueText) +} + +query predicate floatLiterals(FloatLiteral l, string pClass, string valueText) { + allLiterals(l, pClass, valueText) +} + +query predicate rationalLiterals(RationalLiteral l, string pClass, string valueText) { + allLiterals(l, pClass, valueText) +} + +query predicate complexLiterals(ComplexLiteral l, string pClass, string valueText) { + allLiterals(l, pClass, valueText) +} diff --git a/ql/test/library-tests/ast/literals/literals.rb b/ql/test/library-tests/ast/literals/literals.rb new file mode 100644 index 00000000000..9fb06a84129 --- /dev/null +++ b/ql/test/library-tests/ast/literals/literals.rb @@ -0,0 +1,142 @@ +# boolean values and nil +nil +NIL +false +FALSE +true +TRUE + +# decimal integers +1234 +5_678 +0 +0d900 + +# hexadecimal integers +0x1234 +0xdeadbeef +0xF00D_face + +# octal integers +0123 +0o234 +0O45_6 + +# binary integers +0b10010100 +0B011_01101 + +# floating-point numbers +12.34 +1234e-2 +1.234E1 + +# rational numbers +23r +9.85r + +# imaginary/complex numbers +2i +#3.14i # BAD: parse error + +# imaginary & rational +#1.2ri # BAD: parse error + +# strings +"" +'' +"hello" +'goodbye' +"string with escaped \" quote" +'string with " quote' +%(foo bar baz) +%q +%q(foo ' bar " baz') +%Q(FOO ' BAR " BAZ') +%q(foo\ bar) # "foo\\ bar" +%Q(foo\ bar) # "foo bar" +"2 + 2 = #{ 2 + 2 }" # interpolation +%Q(3 + 4 = #{ 3 + 4 }) # interpolation +'2 + 2 = #{ 2 + 2 }' # no interpolation +%q(3 + 4 = #{ 3 + 4 }) # no interpolation +"foo" 'bar' "baz" # concatenated +%q{foo} "bar" 'baz' # concatenated +"foo" "bar#{ 1 * 1 }" 'baz' # concatenated, interpolation +"foo #{ "bar #{ 2 + 3 } baz" } qux" # interpolation containing string containing interpolation + +# characters +?x +?\n +?\s +?\\ +?\u{58} +?\C-a +?\M-a +?\M-\C-a +?\C-\M-a + +# symbols +:"" +:hello +:"foo bar" +:'bar baz' +{ foo: "bar" } +%s(wibble) +%s[wibble wobble] +:"foo_#{ 2 + 2}" # interpolation +:'foo_#{ 1 + 1 }' # no interpolation +%s(foo_#{ 3 - 2 }) # no interpolation + +# arrays +[] +[1, 2, 3] +[4, 5, 12 / 2] +[7, [8, 9]] + +# arrays of strings +%w() +%w(foo bar baz) +%w!foo bar baz! +%W[foo bar#{1+1} baz] # interpolation +%w[foo bar#{1+1} baz] # no interpolation + +# arrays of symbols +%i() +%i(foo bar baz) +%i@foo bar baz@ +%I(foo bar#{ 2 + 4 } baz) # interpolation +%i(foo bar#{ 2 + 4 } baz) # no interpolation + +# hashes +{} +{ foo: 1, :bar => 2, 'baz' => 3 } +{ foo: 7, **bar } # hash-splat argument + +# ranges +(1..10) +(1...10) +(1 .. 0) +(start..2+3) +(1..) # 1 to infinity +(..1) # -infinity to 1 +(0..-1) # BAD: parsed as binary with minus endless range on the LHS + +# subshell +`ls -l` +%x(ls -l) +`du -d #{ 1 + 1 }` # interpolation +%x@du -d #{ 5 - 4 }@ # interpolation + +# regular expressions +// +/foo/ +/foo/i +/foo+\sbar\S/ +/foo#{ 1 + 1 }bar/ # interpolation +/foo/oxm +%r[] +%r(foo) +%r:foo:i +%r{foo+\sbar\S} +%r{foo#{ 1 + 1 }bar} # interpolation +%r:foo:mxo \ No newline at end of file