File: debug.rb

package info (click to toggle)
ruby-rubocop-ast 1.49.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,768 kB
  • sloc: ruby: 17,017; yacc: 90; makefile: 9
file content (163 lines) | stat: -rw-r--r-- 4,896 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# frozen_string_literal: true

require 'rainbow'

module RuboCop
  module AST
    class NodePattern
      class Compiler
        # Variant of the Compiler with tracing information for nodes
        class Debug < Compiler
          # Compiled node pattern requires a named parameter `trace`,
          # which should be an instance of this class
          class Trace
            def initialize
              @visit = {}
            end

            # rubocop:disable Naming/PredicateMethod
            def enter(node_id)
              @visit[node_id] = false
              true
            end
            # rubocop:enable Naming/PredicateMethod

            def success(node_id)
              @visit[node_id] = true
            end

            # return nil (not visited), false (not matched) or true (matched)
            def matched?(node_id)
              @visit[node_id]
            end
          end

          attr_reader :node_ids

          # @api private
          class Colorizer
            COLOR_SCHEME = {
              not_visitable: :lightseagreen,
              nil => :yellow,
              false => :red,
              true => :green
            }.freeze

            # Result of a NodePattern run against a particular AST
            # Consider constructor is private
            Result = Struct.new(:colorizer, :trace, :returned, :ruby_ast) do
              # @return [String] a Rainbow colorized version of ruby
              def colorize(color_scheme = COLOR_SCHEME)
                map = color_map(color_scheme)
                ast.source_range.source_buffer.source.chars.map.with_index do |char, i|
                  Rainbow(char).color(map[i])
                end.join
              end

              # @return [Hash] a map for {character_position => color}
              def color_map(color_scheme = COLOR_SCHEME)
                @color_map ||=
                  match_map
                  .transform_values { |matched| color_scheme.fetch(matched) }
                  .map { |node, color| color_map_for(node, color) }
                  .inject(:merge)
                  .tap { |h| h.default = color_scheme.fetch(:not_visitable) }
              end

              # @return [Hash] a map for {node => matched?}, depth-first
              def match_map
                @match_map ||=
                  ast
                  .each_node
                  .to_h { |node| [node, matched?(node)] }
              end

              # @return a value of `Trace#matched?` or `:not_visitable`
              def matched?(node)
                id = colorizer.compiler.node_ids.fetch(node) { return :not_visitable }
                trace.matched?(id)
              end

              private

              def color_map_for(node, color)
                return {} unless (range = node.loc&.expression)

                range.to_a.to_h { |char| [char, color] }
              end

              def ast
                colorizer.node_pattern.ast
              end
            end

            Compiler = Debug

            attr_reader :pattern, :compiler, :node_pattern

            def initialize(pattern, compiler: self.class::Compiler.new)
              @pattern = pattern
              @compiler = compiler
              @node_pattern = ::RuboCop::AST::NodePattern.new(pattern, compiler: @compiler)
            end

            # @return [Node] the Ruby AST
            def test(ruby, trace: self.class::Compiler::Trace.new)
              ruby = ruby_ast(ruby) if ruby.is_a?(String)
              returned = @node_pattern.as_lambda.call(ruby, trace: trace)
              self.class::Result.new(self, trace, returned, ruby)
            end

            private

            def ruby_ast(ruby)
              ProcessedSource.new(ruby, RUBY_VERSION.to_f, '(ruby)').ast
            end
          end

          def initialize
            super
            @node_ids = Hash.new { |h, k| h[k] = h.size }.compare_by_identity
          end

          def named_parameters
            super << :trace
          end

          def parser
            @parser ||= Parser::WithMeta.new
          end

          def_delegators :parser, :comments, :tokens

          # @api private
          module InstrumentationSubcompiler
            def do_compile
              "#{tracer(:enter)} && #{super} && #{tracer(:success)}"
            end

            private

            def tracer(kind)
              "trace.#{kind}(#{node_id})"
            end

            def node_id
              compiler.node_ids[node]
            end
          end

          # @api private
          class NodePatternSubcompiler < Compiler::NodePatternSubcompiler
            include InstrumentationSubcompiler
          end

          # @api private
          class SequenceSubcompiler < Compiler::SequenceSubcompiler
            include InstrumentationSubcompiler
          end
        end
      end
    end
  end
end