class AstBuilder::Builder

Constants

ALPHA

NodePattern won't allow numbers in meta-method calls, so we need to have alpha characters instead.

Attributes

meta_methods[R]

Public Class Methods

new(s = nil, &fn) click to toggle source

It can either work on a literal string or on a block

@param s = nil [String]

String to convert

@param &fn [Proc]

`instance_eval`'d function to allow for some nice Sexp-like
tokens to be used in construction

@return [AstBuilder]

# File lib/ast_builder/builder.rb, line 25
def initialize(s = nil, &fn)
  @meta_methods = {}
  @ast = s ? parse(s) : instance_eval(&fn)
end

Public Instance Methods

assigns(variable, value) click to toggle source

Wraps a variable assignment for shorthand usage. It will try and tell the difference between instance

@param variable [Symbol]

Name of the variable

@param value [Any]

Value of the variable. Could be a NodePattern literal

@return [AST::Node]

# File lib/ast_builder/builder.rb, line 139
def assigns(variable, value)
  # Constant assignment if we got a node
  return s(:casgn, nil, variable, value) unless variable.respond_to?(:to_sym)

  variable_name = variable.to_sym

  case variable.to_s
  when /^@@/
    s(:cvasgn, variable_name, value)
  when /^@/
    s(:ivasgn, variable_name, value)
  when /^\$/
    s(:gvasgn, variable_name, value)
  when /^[[:upper:]]/
    s(:casgn, nil, variable_name, value)
  else
    s(:lvasgn, variable_name, value)
  end
end
c(string)
Alias for: capture
capture(string) click to toggle source

Prepends a `$` to represent a captured node for matchers.

@param string [String]

String or AST (yeah yeah, names) to "capture"

@return [LiteralToken]

# File lib/ast_builder/builder.rb, line 83
def capture(string)
  literal("$#{string}")
end
Also aliased as: c
capture_children() click to toggle source

Captures the children of a node. Convenience function combining a capture and a literal.

@return [String]

# File lib/ast_builder/builder.rb, line 93
def capture_children
  capture literal '(...)'
end
capture_matching(value = nil, &function) click to toggle source

This method will both use anonymous functions or values to match against and then capture the output.

@see matching

@param value = nil [#===]

Any value that responds to `===`, used to build off of the flexibility of the Ruby `case`
expression.

@param &function [Proc]

A function used to match against. Note that this function _must_ have the proper arity or NodePattern will
reject it.

@return [LiteralToken]

# File lib/ast_builder/builder.rb, line 212
def capture_matching(value = nil, &function)
  capture(matching(value, &function))
end
Also aliased as: cm
cm(value = nil, &function)
Alias for: capture_matching
e(*strings)
Alias for: expand
expand(*strings) click to toggle source

Expands a token by parsing it instead of manually having to nest the thing 3-4 layers deep for constants and the like

@param string [String]

String to expand into AST Nodes

@return [AST::Node]

# File lib/ast_builder/builder.rb, line 69
def expand(*strings)
  strings
    .map { |s| s.is_a?(String) ? parse(s) : s }
    .yield_self { |node, *children| node.concat(children) }
end
Also aliased as: e
l(string)
Alias for: literal
literal(string) click to toggle source

A literal token. Think any of the node matchers from Rubocop's NodePattern:

rubocop.readthedocs.io/en/latest/node_pattern/

@param string [String]

String to use as a literal token

@return [LiteralToken]

# File lib/ast_builder/builder.rb, line 56
def literal(string)
  LiteralToken.new(string)
end
Also aliased as: l
m(name, *sexp)
Alias for: method_send
match(other) click to toggle source

Coerces the builder into a RuboCop NodePattern and attempts to match another value against it.

@param other [String, AST]

Either plaintext code or another AST to match against

@return [nil]

There was no match

@return [String]

The matched portion of the code
# File lib/ast_builder/builder.rb, line 228
def match(other)
  ast = other.is_a?(String) ? self.class.new(other).to_ast : other
  self.to_cop.match(ast)
end
matching(value = nil, &function) click to toggle source

Checks to see if a given value matches a meta-method.

In a normal NodePattern, this is a method which exists in the parent context or on the NodePattern itself. As these methods are rarely used outside of this context, they can be defined instead as anonymous functions using the additional flexibility of AstBuilder's builder syntax:

“`ruby assigns(:value, s(:str, matching(/abc/))) “`

Now instead of having to specify these checks in an actual handler, or defining a method on the parent context, we can do so inline.

These meta methods are then stored and defined on the generated NodePattern upon match time to ensure they're within scope.

@param value = nil [#===]

Any value that responds to `===`, used to build off of the flexibility of the Ruby `case`
expression.

@param &function [Proc]

A function used to match against. Note that this function _must_ have the proper arity or NodePattern will
reject it.

@return [LiteralToken]

This returns a literal token instead of a string, as NodePattern expects it to be a bare word.
# File lib/ast_builder/builder.rb, line 185
def matching(value = nil, &function)
  called_function = function ? function : -> x { value === x }

  # NodePattern will not accept numbers, so we have to use letters instead.
  meta_name = "_meta_method_#{ALPHA[@meta_methods.size]}"

  @meta_methods[meta_name] = called_function

  # These macros start with the `#` symbol, making this intentional
  literal("##{meta_name}")
end
Also aliased as: mm
method_send(name, *sexp) click to toggle source

Regular method send for any level, normally used for things like constants and otherwise.

@param name [String]

Name of the method

@param *sexp [[Array[String, AST::Node, LiteralToken]]

Anything that looks vaguely like a Sexp

@return [AST::Node]

# File lib/ast_builder/builder.rb, line 123
def method_send(name, *sexp)
  s(:send, name, *sexp)
end
Also aliased as: m
mm(value = nil, &function)
Alias for: matching
s(type, *children)
Alias for: s_expression
s_expression(type, *children) click to toggle source

Stand-in for the s-expression given from `AST::Sexp` to give us some of the `Node` level features that RuboCop's variant has.

@param type [String, Symbol]

Type of the node

@param *children [Array[Node, respond_to?(:inspect)]]

RuboCop compatible nodes, or meta-tokens defining
`inspect` to allow for `NodePattern` interpolation

@return [RuboCop::AST::Node]

# File lib/ast_builder/builder.rb, line 42
def s_expression(type, *children)
  RuboCop::AST::Node.new(type, children)
end
Also aliased as: s
t(name, *sexp)
Alias for: top_method_send
to_ast() click to toggle source

Returns the internal AST representation as-is

@return [AST::Node]

# File lib/ast_builder/builder.rb, line 250
def to_ast
  @ast
end
to_cop() click to toggle source

Because RuboCop has… interesting …formatting rules we have to hack around nil a bit and add a question mark.

@return [RuboCop::NodePattern]

RuboCop compatible Sexp
# File lib/ast_builder/builder.rb, line 238
def to_cop
  RuboCop::NodePattern.new(self.to_s.gsub(/\bnil\b/, 'nil?')).tap do |node_pattern|
    # If there are any meta methods defined we bind them to the node pattern to match against
    @meta_methods.each do |name, fn|
      node_pattern.define_singleton_method(name, &fn)
    end
  end
end
to_s() click to toggle source

String version of the AST

@return [String]

# File lib/ast_builder/builder.rb, line 257
def to_s
  @ast.to_s
end
top_method_send(name, *sexp) click to toggle source

Top level method send for sexps that avoids having to type out the entire `(send nil? name (…))` bit.

@param name [String]

Name of the top level keyword

@param *sexp [Array[String, AST::Node, LiteralToken]]

Anything that looks vaguely like a Sexp

@return [AST::Node]

# File lib/ast_builder/builder.rb, line 107
def top_method_send(name, *sexp)
  s(:send, nil, name, *sexp)
end
Also aliased as: t

Private Instance Methods

parse(string) click to toggle source

Parses a String to a Ruby AST

@param string [String]

String to convert

@return [AST::Node]

# File lib/ast_builder/builder.rb, line 267
        def parse(string)
  ast_results = RuboCop::ProcessedSource.new(string, RUBY_VERSION.to_f).ast

  raise InvalidCode, "The following node is invalid: \n  '#{string}'" unless ast_results

  ast_results
end