Class: ScoutApm::AutoInstrument::ParserImplementation::Rewriter
- Inherits:
-
Parser::TreeRewriter
- Object
- Parser::TreeRewriter
- ScoutApm::AutoInstrument::ParserImplementation::Rewriter
- Defined in:
- lib/scout_apm/auto_instrument/parser.rb
Instance Method Summary collapse
-
#initialize ⇒ Rewriter
constructor
A new instance of Rewriter.
- #instrument(source, file_name, line) ⇒ Object
- #on_and_asgn(node) ⇒ Object
- #on_block(node) ⇒ Object
- #on_hash(node) ⇒ Object
- #on_mlhs(node) ⇒ Object
- #on_op_asgn(node) ⇒ Object
- #on_or_asgn(node) ⇒ Object
-
#on_send(node) ⇒ Object
Handle the method call AST node.
-
#parent_type?(type, up = 1) ⇒ Boolean
Look up 1 or more nodes to check if the parent exists and matches the given type.
-
#process(node) ⇒ Object
Invoked for every AST node as it is processed top to bottom.
Constructor Details
#initialize ⇒ Rewriter
23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/scout_apm/auto_instrument/parser.rb', line 23 def initialize super # Keeps track of the parent - child relationship between nodes: @nesting = [] # The stack of method nodes (type :def): @method = [] # The stack of class nodes: @scope = [] @cache = Cache.new end |
Instance Method Details
#instrument(source, file_name, line) ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/scout_apm/auto_instrument/parser.rb', line 38 def instrument(source, file_name, line) # Don't log huge chunks of code... just the first line: if lines = source.lines and lines.count > 1 source = lines.first.chomp + "..." end method_name = @method.last.children[0] bt = ["#{file_name}:#{line}:in `#{method_name}'"] return [ "::ScoutApm::AutoInstrument("+ source.dump + ",#{bt}){", "}" ] end |
#on_and_asgn(node) ⇒ Object
85 86 87 |
# File 'lib/scout_apm/auto_instrument/parser.rb', line 85 def on_and_asgn(node) process(node.children[1]) end |
#on_block(node) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/scout_apm/auto_instrument/parser.rb', line 60 def on_block(node) # If we are not in a method, don't do any instrumentation: return if @method.empty? line = node.location.line || 'line?' column = node.location.column || 'column?' # not used method_name = node.children[0].children[1] || '*unknown*' # not used file_name = @source_rewriter.source_buffer.name wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line)) end |
#on_hash(node) ⇒ Object
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/scout_apm/auto_instrument/parser.rb', line 111 def on_hash(node) node.children.each do |pair| # Skip `pair` if we're sure it's not using the hash shorthand syntax next if pair.type != :pair key_node, value_node = pair.children next unless key_node.type == :sym && value_node.type == :send key = key_node.children[0] next unless value_node.children.size == 2 && value_node.children[0].nil? && key == value_node.children[1] # Extract useful metadata for instrumentation: line = pair.location.line || 'line?' # column = pair.location.column || 'column?' # not used # method_name = key || '*unknown*' # not used file_name = @source_rewriter.source_buffer.name instrument_before, instrument_after = instrument(pair.location.expression.source, file_name, line) replace(pair.loc.expression, "#{key}: #{instrument_before}#{key}#{instrument_after}") end super end |
#on_mlhs(node) ⇒ Object
72 73 74 75 |
# File 'lib/scout_apm/auto_instrument/parser.rb', line 72 def on_mlhs(node) # Ignore / don't instrument multiple assignment (LHS). return end |
#on_op_asgn(node) ⇒ Object
77 78 79 |
# File 'lib/scout_apm/auto_instrument/parser.rb', line 77 def on_op_asgn(node) process(node.children[2]) end |
#on_or_asgn(node) ⇒ Object
81 82 83 |
# File 'lib/scout_apm/auto_instrument/parser.rb', line 81 def on_or_asgn(node) process(node.children[1]) end |
#on_send(node) ⇒ Object
Handle the method call AST node. If this method doesn’t call ‘super`, no futher rewriting is applied to children.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/scout_apm/auto_instrument/parser.rb', line 90 def on_send(node) # We aren't interested in top level function calls: return if @method.empty? if @cache.local_assignments?(node) return super end # This ignores both initial block method invocation `*x*{}`, and subsequent nested invocations `x{*y*}`: return if parent_type?(:block) # Extract useful metadata for instrumentation: line = node.location.line || 'line?' column = node.location.column || 'column?' # not used method_name = node.children[1] || '*unknown*' # not used file_name = @source_rewriter.source_buffer.name # Wrap the expression with instrumentation: wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line)) end |
#parent_type?(type, up = 1) ⇒ Boolean
Look up 1 or more nodes to check if the parent exists and matches the given type.
56 57 58 |
# File 'lib/scout_apm/auto_instrument/parser.rb', line 56 def parent_type?(type, up = 1) parent = @nesting[@nesting.size - up - 1] and parent.type == type end |
#process(node) ⇒ Object
Invoked for every AST node as it is processed top to bottom.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/scout_apm/auto_instrument/parser.rb', line 133 def process(node) # We are nesting inside this node: @nesting.push(node) if node and node.type == :def # If the node is a method, push it on the method stack as well: @method.push(node) super @method.pop elsif node and node.type == :class @scope.push(node.children[0]) super @scope.pop else super end @nesting.pop end |