turbo_test_static_analysis 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/tests.yml +42 -0
- data/.gitignore +66 -0
- data/.rubocop.yml +44 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -0
- data/Gemfile.common +3 -0
- data/Gemfile.lock +54 -0
- data/GemfileCI +9 -0
- data/GemfileCI.lock +69 -0
- data/LICENSE.txt +21 -0
- data/Makefile +44 -0
- data/README.md +27 -0
- data/Rakefile +19 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/turbo_test_static_analysis.rb +6 -0
- data/lib/turbo_test_static_analysis/active_record_schema.rb +28 -0
- data/lib/turbo_test_static_analysis/active_record_schema/constructor.rb +51 -0
- data/lib/turbo_test_static_analysis/active_record_schema/diff.rb +16 -0
- data/lib/turbo_test_static_analysis/active_record_schema/diff_compute.rb +52 -0
- data/lib/turbo_test_static_analysis/active_record_schema/map.rb +15 -0
- data/lib/turbo_test_static_analysis/active_record_schema/sexp_builder/line_column_stack.rb +41 -0
- data/lib/turbo_test_static_analysis/active_record_schema/sexp_builder/line_stack_sexp_builder.rb +40 -0
- data/lib/turbo_test_static_analysis/active_record_schema/sexp_builder/sexp_builder.rb +129 -0
- data/lib/turbo_test_static_analysis/active_record_schema/snapshot.rb +15 -0
- data/lib/turbo_test_static_analysis/constants.rb +13 -0
- data/lib/turbo_test_static_analysis/constants/node.rb +66 -0
- data/lib/turbo_test_static_analysis/constants/node_maker.rb +118 -0
- data/lib/turbo_test_static_analysis/constants/node_processor.rb +115 -0
- data/lib/turbo_test_static_analysis/constants/sexp_builder.rb +109 -0
- data/lib/turbo_test_static_analysis/constants/token_matcher.rb +43 -0
- data/lib/turbo_test_static_analysis/version.rb +7 -0
- data/turbo_test_static_analysis.gemspec +30 -0
- metadata +120 -0
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TurboTest
         | 
| 4 | 
            +
              module StaticAnalysis
         | 
| 5 | 
            +
                module ActiveRecord
         | 
| 6 | 
            +
                  Snapshot = Struct.new(:extensions, :tables) do
         | 
| 7 | 
            +
                    def initialize(*)
         | 
| 8 | 
            +
                      super
         | 
| 9 | 
            +
                      self.extensions ||= {}
         | 
| 10 | 
            +
                      self.tables ||= {}
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TurboTest
         | 
| 4 | 
            +
              module StaticAnalysis
         | 
| 5 | 
            +
                module Constants
         | 
| 6 | 
            +
                  class Node
         | 
| 7 | 
            +
                    attr_reader :name, :start_pos, :parent, :children, :singleton
         | 
| 8 | 
            +
                    attr_accessor :end_pos, :top_level, :definition
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    def initialize(name:, start_pos:, end_pos:, singleton: false, definition: false)
         | 
| 11 | 
            +
                      @name = name
         | 
| 12 | 
            +
                      @children = []
         | 
| 13 | 
            +
                      @start_pos = start_pos
         | 
| 14 | 
            +
                      @end_pos = end_pos
         | 
| 15 | 
            +
                      @parent = nil
         | 
| 16 | 
            +
                      @singleton = singleton
         | 
| 17 | 
            +
                      @definition = definition
         | 
| 18 | 
            +
                      @top_level = false
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def add_child(child)
         | 
| 22 | 
            +
                      return if child.start_pos == start_pos && child.name == name
         | 
| 23 | 
            +
                      return unless contains?(child)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      @children << child
         | 
| 26 | 
            +
                      child.instance_variable_set(:@parent, self)
         | 
| 27 | 
            +
                      child.instance_variable_set(:@definition, definition)
         | 
| 28 | 
            +
                      child
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def contains?(node)
         | 
| 32 | 
            +
                      ((@start_pos <=> node.start_pos) == -1 && (@end_pos <=> node.end_pos) == 1) ||
         | 
| 33 | 
            +
                        ((@start_pos <=> node.start_pos).zero? &&
         | 
| 34 | 
            +
                          ((@end_pos <=> node.end_pos).zero? || (@end_pos <=> node.end_pos) == 1))
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    def full_name
         | 
| 38 | 
            +
                      return @name unless @parent
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                      [@parent.full_name, @name].compact.join("::")
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    def root
         | 
| 44 | 
            +
                      return self if parent.nil?
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                      parent.root
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    def parent_is_named_singleton?
         | 
| 50 | 
            +
                      return false unless parent
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                      parent.singleton && parent.name != "singleton_class"
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    def named_singleton_with_parent?
         | 
| 56 | 
            +
                      return false unless parent
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                      singleton && name != "singleton_class"
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    class << self
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| @@ -0,0 +1,118 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TurboTest
         | 
| 4 | 
            +
              module StaticAnalysis
         | 
| 5 | 
            +
                module Constants
         | 
| 6 | 
            +
                  module NodeMaker
         | 
| 7 | 
            +
                    private
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    def create_top_singleton_class_node(token)
         | 
| 10 | 
            +
                      parent = new_node_from_token(token[1], [lineno, column])
         | 
| 11 | 
            +
                      @class_nodes.push parent
         | 
| 12 | 
            +
                      node = Node.new(
         | 
| 13 | 
            +
                        name: "singleton_class",
         | 
| 14 | 
            +
                        start_pos: node_start_pos(token),
         | 
| 15 | 
            +
                        end_pos: [lineno, column]
         | 
| 16 | 
            +
                      )
         | 
| 17 | 
            +
                      parent.add_child node
         | 
| 18 | 
            +
                      node
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def create_var_ref_singleton_class_node(token)
         | 
| 22 | 
            +
                      node = new_node_from_token(token[1], [lineno, column], singleton_class: true)
         | 
| 23 | 
            +
                      @class_nodes.push node
         | 
| 24 | 
            +
                      child = Node.new(
         | 
| 25 | 
            +
                        name: "singleton_class",
         | 
| 26 | 
            +
                        start_pos: node_start_pos(token),
         | 
| 27 | 
            +
                        end_pos: [lineno, column]
         | 
| 28 | 
            +
                      )
         | 
| 29 | 
            +
                      node.add_child child
         | 
| 30 | 
            +
                      node
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    def create_singleton_class_node(token)
         | 
| 34 | 
            +
                      new_node_from_token(token[1], [lineno, column], singleton_class: true)
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    def create_singleton_method_definition_with_self_node(token)
         | 
| 38 | 
            +
                      node = Node.new(
         | 
| 39 | 
            +
                        name: "self",
         | 
| 40 | 
            +
                        start_pos: node_start_pos(token),
         | 
| 41 | 
            +
                        end_pos: [lineno, column], definition: true
         | 
| 42 | 
            +
                      )
         | 
| 43 | 
            +
                      @class_nodes.push node
         | 
| 44 | 
            +
                      @all_nodes.unshift(node)
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    def create_singleton_method_definition_with_constant_node(token)
         | 
| 48 | 
            +
                      Node.new(
         | 
| 49 | 
            +
                        name: token[1][1],
         | 
| 50 | 
            +
                        start_pos: node_start_pos(token),
         | 
| 51 | 
            +
                        end_pos: [lineno, column],
         | 
| 52 | 
            +
                        definition: true
         | 
| 53 | 
            +
                      )
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    def add_block_node(token)
         | 
| 57 | 
            +
                      return unless (start_pos = token[1][3]&.last)
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                      node = Node.new(
         | 
| 60 | 
            +
                        name: "block",
         | 
| 61 | 
            +
                        start_pos: start_pos,
         | 
| 62 | 
            +
                        end_pos: [lineno, column]
         | 
| 63 | 
            +
                      )
         | 
| 64 | 
            +
                      @block_nodes << node
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    def find_or_new_node_for_const_class_or_module_eval(token)
         | 
| 68 | 
            +
                      start_pos = token[1][1][1]
         | 
| 69 | 
            +
                      end_pos = token[1][1][2]
         | 
| 70 | 
            +
                      find_and_update_node([nil, [nil, start_pos, end_pos]]).tap do |node|
         | 
| 71 | 
            +
                        break const_class_or_module_eval_node(start_pos, end_pos) if node.nil?
         | 
| 72 | 
            +
                      end
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    def const_class_or_module_eval_node(start_pos, end_pos)
         | 
| 76 | 
            +
                      Node.new(
         | 
| 77 | 
            +
                        name: start_pos,
         | 
| 78 | 
            +
                        start_pos: end_pos,
         | 
| 79 | 
            +
                        end_pos: [lineno, column],
         | 
| 80 | 
            +
                        definition: true
         | 
| 81 | 
            +
                      )
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    def ident_class_or_module_eval_node(token)
         | 
| 85 | 
            +
                      Node.new(
         | 
| 86 | 
            +
                        name: token[1][1][1],
         | 
| 87 | 
            +
                        start_pos: token[1][1][2],
         | 
| 88 | 
            +
                        end_pos: [lineno, column],
         | 
| 89 | 
            +
                        definition: true
         | 
| 90 | 
            +
                      )
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                    def new_node_from_token(token, end_pos = [0, 0], singleton_class: false)
         | 
| 94 | 
            +
                      name = node_name(token)
         | 
| 95 | 
            +
                      start_pos = node_start_pos(token)
         | 
| 96 | 
            +
                      Node.new(name: name, start_pos: start_pos, end_pos: end_pos, singleton: singleton_class)
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    def node_name(token)
         | 
| 100 | 
            +
                      case token[0]
         | 
| 101 | 
            +
                      when :@kw
         | 
| 102 | 
            +
                        "singleton_class"
         | 
| 103 | 
            +
                      when :@const
         | 
| 104 | 
            +
                        token[1]
         | 
| 105 | 
            +
                      else
         | 
| 106 | 
            +
                        "unknown_ref"
         | 
| 107 | 
            +
                      end
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                    def node_start_pos(token)
         | 
| 111 | 
            +
                      start_pos = token.last
         | 
| 112 | 
            +
                      start_pos = start_pos.last while start_pos.last.is_a?(Array)
         | 
| 113 | 
            +
                      start_pos
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
              end
         | 
| 118 | 
            +
            end
         | 
| @@ -0,0 +1,115 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TurboTest
         | 
| 4 | 
            +
              module StaticAnalysis
         | 
| 5 | 
            +
                module Constants
         | 
| 6 | 
            +
                  module NodeProcessor
         | 
| 7 | 
            +
                    private
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    def node_key(token)
         | 
| 10 | 
            +
                      "#{token[1]}-#{token[2].join('-')}"
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def process_module(node)
         | 
| 14 | 
            +
                      @var_refs.reject! do |var_ref|
         | 
| 15 | 
            +
                        (var_ref.name == node.name && var_ref.start_pos == node.start_pos) ||
         | 
| 16 | 
            +
                          node.contains?(var_ref).tap do |condition|
         | 
| 17 | 
            +
                            @referenced_constants[var_ref.name] = true if condition
         | 
| 18 | 
            +
                          end
         | 
| 19 | 
            +
                      end
         | 
| 20 | 
            +
                      @class_nodes.push node
         | 
| 21 | 
            +
                      assign_children(node)
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def process_class_nodes
         | 
| 25 | 
            +
                      @class_nodes.reject! do |class_node|
         | 
| 26 | 
            +
                        next process_definition_class_node(class_node) if class_node.root.definition
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                        @referenced_constants[class_node.name] = true if class_node.named_singleton_with_parent?
         | 
| 29 | 
            +
                        class_node.parent_is_named_singleton? || class_node.named_singleton_with_parent?
         | 
| 30 | 
            +
                      end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                      @defined_classes = class_nodes_names_without_singletons_and_unknowns
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def process_definition_class_node(node)
         | 
| 36 | 
            +
                      container_node = @class_nodes.find do |class_node|
         | 
| 37 | 
            +
                        class_node != node.root && class_node.contains?(node.root)
         | 
| 38 | 
            +
                      end
         | 
| 39 | 
            +
                      return false if container_node.nil?
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                      @referenced_constants[node.full_name] = true if node.name != "self"
         | 
| 42 | 
            +
                      true
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    def process_var_refs
         | 
| 46 | 
            +
                      @var_refs.each do |var_ref|
         | 
| 47 | 
            +
                        if @block_nodes.find { |node| node.contains?(var_ref) }
         | 
| 48 | 
            +
                          @referenced_constants[var_ref.name] = true
         | 
| 49 | 
            +
                        else
         | 
| 50 | 
            +
                          @referenced_top_constants[var_ref.name] = true
         | 
| 51 | 
            +
                        end
         | 
| 52 | 
            +
                      end
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    def class_nodes_names_without_singletons_and_unknowns
         | 
| 56 | 
            +
                      @class_nodes.map(&:full_name).reject do |full_name|
         | 
| 57 | 
            +
                        full_name.end_with?("singleton_class") || full_name.include?("unknown_ref")
         | 
| 58 | 
            +
                      end.uniq
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    def reject_unknown_constants
         | 
| 62 | 
            +
                      @referenced_top_constants.reject! do |key, _value|
         | 
| 63 | 
            +
                        key.include?("unknown_ref")
         | 
| 64 | 
            +
                      end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                      @referenced_constants.reject! do |key, _value|
         | 
| 67 | 
            +
                        key.include?("unknown_ref")
         | 
| 68 | 
            +
                      end
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    def assign_children(node)
         | 
| 72 | 
            +
                      if @referenced_top_constants.delete(node.name)
         | 
| 73 | 
            +
                        @all_nodes.unshift(node)
         | 
| 74 | 
            +
                        add_children(node)
         | 
| 75 | 
            +
                      else
         | 
| 76 | 
            +
                        add_children(node)
         | 
| 77 | 
            +
                        @all_nodes.unshift(node)
         | 
| 78 | 
            +
                      end
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    def add_children(node)
         | 
| 82 | 
            +
                      @all_nodes.reject! do |children_candidate|
         | 
| 83 | 
            +
                        node.contains?(children_candidate).tap do |child|
         | 
| 84 | 
            +
                          node.add_child(children_candidate) if child && children_candidate.definition == false
         | 
| 85 | 
            +
                        end
         | 
| 86 | 
            +
                      end
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    def add_const_ref(token, top_level: false)
         | 
| 90 | 
            +
                      node = new_node_from_token(token)
         | 
| 91 | 
            +
                      node.top_level = top_level
         | 
| 92 | 
            +
                      @const_refs[node_key(token)] = node
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                    def add_referenced_top_constant(name)
         | 
| 96 | 
            +
                      @referenced_top_constants[name] = true
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    def find_and_update_node(token)
         | 
| 100 | 
            +
                      @const_refs.delete(node_key(token[1])).tap do |node|
         | 
| 101 | 
            +
                        node.end_pos = [lineno, column] if node
         | 
| 102 | 
            +
                      end
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    def reject_children_in_class_nodes(node)
         | 
| 106 | 
            +
                      @class_nodes.reject! do |class_node|
         | 
| 107 | 
            +
                        node.contains?(class_node).tap do |child|
         | 
| 108 | 
            +
                          @referenced_constants[class_node.full_name] = true if child
         | 
| 109 | 
            +
                        end
         | 
| 110 | 
            +
                      end
         | 
| 111 | 
            +
                    end
         | 
| 112 | 
            +
                  end
         | 
| 113 | 
            +
                end
         | 
| 114 | 
            +
              end
         | 
| 115 | 
            +
            end
         | 
| @@ -0,0 +1,109 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "ripper"
         | 
| 4 | 
            +
            require_relative "node"
         | 
| 5 | 
            +
            require_relative "token_matcher"
         | 
| 6 | 
            +
            require_relative "node_maker"
         | 
| 7 | 
            +
            require_relative "node_processor"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            module TurboTest
         | 
| 10 | 
            +
              module StaticAnalysis
         | 
| 11 | 
            +
                module Constants
         | 
| 12 | 
            +
                  class SexpBuilder < Ripper::SexpBuilder
         | 
| 13 | 
            +
                    attr_reader :defined_classes, :referenced_top_constants, :referenced_constants
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    include TokenMatcher
         | 
| 16 | 
            +
                    include NodeMaker
         | 
| 17 | 
            +
                    include NodeProcessor
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    def initialize(path, filename = "-", lineno = 1)
         | 
| 20 | 
            +
                      @const_refs = {}
         | 
| 21 | 
            +
                      @referenced_top_constants = {}
         | 
| 22 | 
            +
                      @referenced_constants = {}
         | 
| 23 | 
            +
                      @class_nodes = []
         | 
| 24 | 
            +
                      @defined_classes = []
         | 
| 25 | 
            +
                      @all_nodes = []
         | 
| 26 | 
            +
                      @var_refs = []
         | 
| 27 | 
            +
                      @block_nodes = []
         | 
| 28 | 
            +
                      super
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def on_sclass(token_one, token_two)
         | 
| 32 | 
            +
                      node = if top_const_ref?(token_one)
         | 
| 33 | 
            +
                               create_top_singleton_class_node(token_one)
         | 
| 34 | 
            +
                             elsif var_ref?(token_one)
         | 
| 35 | 
            +
                               create_var_ref_singleton_class_node(token_one)
         | 
| 36 | 
            +
                             else
         | 
| 37 | 
            +
                               create_singleton_class_node(token_one)
         | 
| 38 | 
            +
                             end
         | 
| 39 | 
            +
                      process_module(node)
         | 
| 40 | 
            +
                      @all_nodes.shift if top_const_ref?(token_one)
         | 
| 41 | 
            +
                      super
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    def on_module(token_one, token_two, token_three = nil)
         | 
| 45 | 
            +
                      process_module find_and_update_node(token_one)
         | 
| 46 | 
            +
                      super
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
                    alias on_class on_module
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    def on_defs(token_one, token_two, token_three, token_four, token_five)
         | 
| 51 | 
            +
                      if singleton_method_definition_with_self?(token_one)
         | 
| 52 | 
            +
                        create_singleton_method_definition_with_self_node(token_one)
         | 
| 53 | 
            +
                      elsif singleton_method_definition_with_constant?(token_one)
         | 
| 54 | 
            +
                        node = create_singleton_method_definition_with_constant_node(token_one)
         | 
| 55 | 
            +
                        process_module(node)
         | 
| 56 | 
            +
                      end
         | 
| 57 | 
            +
                      super
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    def on_top_const_ref(token)
         | 
| 61 | 
            +
                      add_const_ref token, top_level: true
         | 
| 62 | 
            +
                      add_referenced_top_constant token[1]
         | 
| 63 | 
            +
                      super
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    def on_const_ref(token)
         | 
| 67 | 
            +
                      add_const_ref token
         | 
| 68 | 
            +
                      super
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    def on_var_ref(token)
         | 
| 72 | 
            +
                      @var_refs << new_node_from_token(token, [lineno, column]) if token[0] == :@const
         | 
| 73 | 
            +
                      super
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
                    alias on_var_field on_var_ref
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    def on_top_const_field(token)
         | 
| 78 | 
            +
                      add_referenced_top_constant token[1]
         | 
| 79 | 
            +
                      super
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    def on_def(token_one, token_two, token_three)
         | 
| 83 | 
            +
                      super
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    def on_method_add_block(token_one, token_two)
         | 
| 87 | 
            +
                      add_block_node(token_one)
         | 
| 88 | 
            +
                      if const_class_or_module_eval?(token_one)
         | 
| 89 | 
            +
                        node = find_or_new_node_for_const_class_or_module_eval(token_one)
         | 
| 90 | 
            +
                        process_module node
         | 
| 91 | 
            +
                      elsif ident_class_or_module_eval?(token_one)
         | 
| 92 | 
            +
                        node = ident_class_or_module_eval_node(token_one)
         | 
| 93 | 
            +
                        reject_children_in_class_nodes(node)
         | 
| 94 | 
            +
                      end
         | 
| 95 | 
            +
                      super
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    def on_program(token_one)
         | 
| 99 | 
            +
                      process_class_nodes
         | 
| 100 | 
            +
                      process_var_refs
         | 
| 101 | 
            +
                      reject_unknown_constants
         | 
| 102 | 
            +
                      super
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
              end
         | 
| 107 | 
            +
            end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            # Check https://fili.pp.ru/leaky-constants.html
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TurboTest
         | 
| 4 | 
            +
              module StaticAnalysis
         | 
| 5 | 
            +
                module Constants
         | 
| 6 | 
            +
                  module TokenMatcher
         | 
| 7 | 
            +
                    private
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    def top_const_ref?(token)
         | 
| 10 | 
            +
                      token[0] == :top_const_ref
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def var_ref?(token)
         | 
| 14 | 
            +
                      token[0] == :var_ref
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def const_class_or_module_eval?(token)
         | 
| 18 | 
            +
                      token.last[0] == :@ident &&
         | 
| 19 | 
            +
                        %w[class_eval module_eval].include?(token.last[1]) &&
         | 
| 20 | 
            +
                        token[1][1][0] == :@const
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def ident_class_or_module_eval?(token)
         | 
| 24 | 
            +
                      token.last[0] == :@ident &&
         | 
| 25 | 
            +
                        %w[class_eval module_eval].include?(token.last[1]) &&
         | 
| 26 | 
            +
                        token[1][1][0] == :@ident
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def singleton_method_definition_with_self?(token)
         | 
| 30 | 
            +
                      token[1] &&
         | 
| 31 | 
            +
                        token[1][0] == :@kw &&
         | 
| 32 | 
            +
                        token[1][1] == "self"
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def singleton_method_definition_with_constant?(token)
         | 
| 36 | 
            +
                      token[0] == :var_ref &&
         | 
| 37 | 
            +
                        token[1] &&
         | 
| 38 | 
            +
                        token[1][0] == :@const
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         |