lib/spoom/deadcode/indexer.rb in spoom-1.2.4 vs lib/spoom/deadcode/indexer.rb in spoom-1.3.0

- old
+ new

@@ -1,15 +1,15 @@ # typed: strict # frozen_string_literal: true module Spoom module Deadcode - class Indexer < SyntaxTree::Visitor + class Indexer < Visitor extend T::Sig sig { returns(String) } - attr_reader :path, :file_name + attr_reader :path sig { returns(Index) } attr_reader :index sig { params(path: String, source: String, index: Index, plugins: T::Array[Plugins::Base]).void } @@ -19,267 +19,308 @@ @path = path @file_name = T.let(File.basename(path), String) @source = source @index = index @plugins = plugins - @previous_node = T.let(nil, T.nilable(SyntaxTree::Node)) + @previous_node = T.let(nil, T.nilable(Prism::Node)) @names_nesting = T.let([], T::Array[String]) - @nodes_nesting = T.let([], T::Array[SyntaxTree::Node]) + @nodes_nesting = T.let([], T::Array[Prism::Node]) @in_const_field = T.let(false, T::Boolean) @in_opassign = T.let(false, T::Boolean) @in_symbol_literal = T.let(false, T::Boolean) end # Visit - sig { override.params(node: T.nilable(SyntaxTree::Node)).void } + sig { override.params(node: T.nilable(Prism::Node)).void } def visit(node) return unless node @nodes_nesting << node super @nodes_nesting.pop @previous_node = node end - sig { override.params(node: SyntaxTree::AliasNode).void } - def visit_alias(node) - reference_method(node_string(node.right), node) + sig { override.params(node: Prism::AliasMethodNode).void } + def visit_alias_method_node(node) + reference_method(node.old_name.slice, node) end - sig { override.params(node: SyntaxTree::ARef).void } - def visit_aref(node) + sig { override.params(node: Prism::AndNode).void } + def visit_and_node(node) + reference_method(node.operator_loc.slice, node) super + end - reference_method("[]", node) + sig { override.params(node: Prism::BlockArgumentNode).void } + def visit_block_argument_node(node) + expression = node.expression + case expression + when Prism::SymbolNode + reference_method(expression.unescaped, expression) + else + visit(expression) + end end - sig { override.params(node: SyntaxTree::ARefField).void } - def visit_aref_field(node) - super - - reference_method("[]=", node) + sig { override.params(node: Prism::CallAndWriteNode).void } + def visit_call_and_write_node(node) + visit(node.receiver) + reference_method(node.read_name.to_s, node) + reference_method(node.write_name.to_s, node) + visit(node.value) end - sig { override.params(node: SyntaxTree::ArgBlock).void } - def visit_arg_block(node) - value = node.value - - case value - when SyntaxTree::SymbolLiteral - # If the block call is something like `x.select(&:foo)`, we need to reference the `foo` method - reference_method(symbol_string(value), node) - when SyntaxTree::VCall - # If the block call is something like `x.select { ... }`, we need to visit the block - super - end + sig { override.params(node: Prism::CallOperatorWriteNode).void } + def visit_call_operator_write_node(node) + visit(node.receiver) + reference_method(node.read_name.to_s, node) + reference_method(node.write_name.to_s, node) + visit(node.value) end - sig { override.params(node: SyntaxTree::Binary).void } - def visit_binary(node) - super - - op = node.operator - - # Reference the operator itself - reference_method(op.to_s, node) - - case op - when :<, :>, :<=, :>= - # For comparison operators, we also reference the `<=>` method - reference_method("<=>", node) - end + sig { override.params(node: Prism::CallOrWriteNode).void } + def visit_call_or_write_node(node) + visit(node.receiver) + reference_method(node.read_name.to_s, node) + reference_method(node.write_name.to_s, node) + visit(node.value) end - sig { override.params(node: SyntaxTree::CallNode).void } - def visit_call(node) + sig { override.params(node: Prism::CallNode).void } + def visit_call_node(node) visit_send( Send.new( node: node, - name: node_string(node.message), + name: node.name.to_s, recv: node.receiver, - args: call_args(node.arguments), + args: node.arguments&.arguments || [], + block: node.block, ), ) end - sig { override.params(node: SyntaxTree::ClassDeclaration).void } - def visit_class(node) - const_name = node_string(node.constant) - @names_nesting << const_name - define_class(T.must(const_name.split("::").last), @names_nesting.join("::"), node) + sig { override.params(node: Prism::ClassNode).void } + def visit_class_node(node) + constant_path = node.constant_path.slice - # We do not call `super` here because we don't want to visit the `constant` again - visit(node.superclass) if node.superclass - visit(node.bodystmt) + if constant_path.start_with?("::") + full_name = constant_path.delete_prefix("::") - @names_nesting.pop + # We found a top level definition such as `class ::A; end`, we need to reset the name nesting + old_nesting = @names_nesting.dup + @names_nesting.clear + @names_nesting << full_name + + define_class(T.must(constant_path.split("::").last), full_name, node) + + # We do not call `super` here because we don't want to visit the `constant` again + visit(node.superclass) if node.superclass + visit(node.body) + + # Restore the name nesting once we finished visited the class + @names_nesting.clear + @names_nesting = old_nesting + else + @names_nesting << constant_path + define_class(T.must(constant_path.split("::").last), @names_nesting.join("::"), node) + + # We do not call `super` here because we don't want to visit the `constant` again + visit(node.superclass) if node.superclass + visit(node.body) + + @names_nesting.pop + end end - sig { override.params(node: SyntaxTree::Command).void } - def visit_command(node) - visit_send( - Send.new( - node: node, - name: node_string(node.message), - args: call_args(node.arguments), - block: node.block, - ), - ) + sig { override.params(node: Prism::ConstantAndWriteNode).void } + def visit_constant_and_write_node(node) + reference_constant(node.name.to_s, node) + visit(node.value) end - sig { override.params(node: SyntaxTree::CommandCall).void } - def visit_command_call(node) - visit_send( - Send.new( - node: node, - name: node_string(node.message), - recv: node.receiver, - args: call_args(node.arguments), - block: node.block, - ), - ) + sig { override.params(node: Prism::ConstantOperatorWriteNode).void } + def visit_constant_operator_write_node(node) + reference_constant(node.name.to_s, node) + visit(node.value) end - sig { override.params(node: SyntaxTree::Const).void } - def visit_const(node) - reference_constant(node.value, node) unless @in_symbol_literal + sig { override.params(node: Prism::ConstantOrWriteNode).void } + def visit_constant_or_write_node(node) + reference_constant(node.name.to_s, node) + visit(node.value) end - sig { override.params(node: SyntaxTree::ConstPathField).void } - def visit_const_path_field(node) - # We do not call `super` here because we don't want to visit the `constant` again - visit(node.parent) + sig { override.params(node: Prism::ConstantPathWriteNode).void } + def visit_constant_path_write_node(node) + parent = node.target.parent + name = node.target.child.slice - name = node.constant.value - full_name = [*@names_nesting, node_string(node.parent), name].join("::") + if parent + visit(parent) + + parent_name = parent.slice + full_name = [*@names_nesting, parent_name, name].compact.join("::") + define_constant(name, full_name, node) + else + define_constant(name, name, node) + end + + visit(node.value) + end + + sig { override.params(node: Prism::ConstantReadNode).void } + def visit_constant_read_node(node) + reference_constant(node.name.to_s, node) + end + + sig { override.params(node: Prism::ConstantWriteNode).void } + def visit_constant_write_node(node) + name = node.name.to_s + full_name = [*@names_nesting, name].join("::") define_constant(name, full_name, node) + visit(node.value) end - sig { override.params(node: SyntaxTree::DefNode).void } - def visit_def(node) - name = node_string(node.name) + sig { override.params(node: Prism::DefNode).void } + def visit_def_node(node) + name = node.name.to_s define_method(name, [*@names_nesting, name].join("::"), node) super end - sig { override.params(node: SyntaxTree::Field).void } - def visit_field(node) - visit(node.parent) + sig { override.params(node: Prism::LocalVariableAndWriteNode).void } + def visit_local_variable_and_write_node(node) + name = node.name.to_s + reference_method(name, node) + reference_method("#{name}=", node) + visit(node.value) + end - name = node.name - case name - when SyntaxTree::Const - name = name.value - full_name = [*@names_nesting, node_string(node.parent), name].join("::") - define_constant(name, full_name, node) - when SyntaxTree::Ident - reference_method(name.value, node) if @in_opassign - reference_method("#{name.value}=", node) - end + sig { override.params(node: Prism::LocalVariableOperatorWriteNode).void } + def visit_local_variable_operator_write_node(node) + name = node.name.to_s + reference_method(name, node) + reference_method("#{name}=", node) + visit(node.value) end - sig { override.params(node: SyntaxTree::ModuleDeclaration).void } - def visit_module(node) - const_name = node_string(node.constant) - @names_nesting << const_name - define_module(T.must(const_name.split("::").last), @names_nesting.join("::"), node) + sig { override.params(node: Prism::LocalVariableOrWriteNode).void } + def visit_local_variable_or_write_node(node) + name = node.name.to_s + reference_method(name, node) + reference_method("#{name}=", node) + visit(node.value) + end - # We do not call `super` here because we don't want to visit the `constant` again - visit(node.bodystmt) + sig { override.params(node: Prism::LocalVariableWriteNode).void } + def visit_local_variable_write_node(node) + visit(node.value) + reference_method("#{node.name}=", node) + end - @names_nesting.pop + sig { override.params(node: Prism::ModuleNode).void } + def visit_module_node(node) + constant_path = node.constant_path.slice + + if constant_path.start_with?("::") + full_name = constant_path.delete_prefix("::") + + # We found a top level definition such as `class ::A; end`, we need to reset the name nesting + old_nesting = @names_nesting.dup + @names_nesting.clear + @names_nesting << full_name + + define_module(T.must(constant_path.split("::").last), full_name, node) + + visit(node.body) + + # Restore the name nesting once we finished visited the class + @names_nesting.clear + @names_nesting = old_nesting + else + @names_nesting << constant_path + define_module(T.must(constant_path.split("::").last), @names_nesting.join("::"), node) + + # We do not call `super` here because we don't want to visit the `constant` again + visit(node.body) + + @names_nesting.pop + end end - sig { override.params(node: SyntaxTree::OpAssign).void } - def visit_opassign(node) - # Both `FOO = x` and `FOO += x` yield a VarField node, but the former is a constant definition and the latter is - # a constant reference. We need to distinguish between the two cases. - @in_opassign = true + sig { override.params(node: Prism::MultiWriteNode).void } + def visit_multi_write_node(node) + node.lefts.each do |const| + case const + when Prism::ConstantTargetNode, Prism::ConstantPathTargetNode + name = const.slice + define_constant(T.must(name.split("::").last), [*@names_nesting, name].join("::"), const) + when Prism::LocalVariableTargetNode + reference_method("#{const.name}=", node) + end + end + visit(node.value) + end + + sig { override.params(node: Prism::OrNode).void } + def visit_or_node(node) + reference_method(node.operator_loc.slice, node) super - @in_opassign = false end sig { params(send: Send).void } def visit_send(send) visit(send.recv) case send.name when "attr_reader" send.args.each do |arg| - next unless arg.is_a?(SyntaxTree::SymbolLiteral) + next unless arg.is_a?(Prism::SymbolNode) - name = symbol_string(arg) + name = arg.unescaped define_attr_reader(name, [*@names_nesting, name].join("::"), arg) end when "attr_writer" send.args.each do |arg| - next unless arg.is_a?(SyntaxTree::SymbolLiteral) + next unless arg.is_a?(Prism::SymbolNode) - name = symbol_string(arg) + name = arg.unescaped define_attr_writer("#{name}=", "#{[*@names_nesting, name].join("::")}=", arg) end when "attr_accessor" send.args.each do |arg| - next unless arg.is_a?(SyntaxTree::SymbolLiteral) + next unless arg.is_a?(Prism::SymbolNode) - name = symbol_string(arg) + name = arg.unescaped full_name = [*@names_nesting, name].join("::") define_attr_reader(name, full_name, arg) define_attr_writer("#{name}=", "#{full_name}=", arg) end else @plugins.each do |plugin| plugin.internal_on_send(self, send) end reference_method(send.name, send.node) + + case send.name + when "<", ">", "<=", ">=" + # For comparison operators, we also reference the `<=>` method + reference_method("<=>", send.node) + end + visit_all(send.args) visit(send.block) end end - sig { override.params(node: SyntaxTree::SymbolLiteral).void } - def visit_symbol_literal(node) - # Something like `:FOO` will yield a Const node but we do not want to treat it as a constant reference. - # So we need to distinguish between the two cases. - @in_symbol_literal = true - super - @in_symbol_literal = false - end - - sig { override.params(node: SyntaxTree::TopConstField).void } - def visit_top_const_field(node) - define_constant(node.constant.value, node.constant.value, node) - end - - sig { override.params(node: SyntaxTree::VarField).void } - def visit_var_field(node) - value = node.value - case value - when SyntaxTree::Const - if @in_opassign - reference_constant(value.value, node) - else - name = value.value - define_constant(name, [*@names_nesting, name].join("::"), node) - end - when SyntaxTree::Ident - reference_method(value.value, node) if @in_opassign - reference_method("#{value.value}=", node) - end - end - - sig { override.params(node: SyntaxTree::VCall).void } - def visit_vcall(node) - visit_send(Send.new(node: node, name: node_string(node.value))) - end - # Definition indexing - sig { params(name: String, full_name: String, node: SyntaxTree::Node).void } + sig { params(name: String, full_name: String, node: Prism::Node).void } def define_attr_reader(name, full_name, node) definition = Definition.new( kind: Definition::Kind::AttrReader, name: name, full_name: full_name, @@ -287,11 +328,11 @@ ) @index.define(definition) @plugins.each { |plugin| plugin.internal_on_define_accessor(self, definition) } end - sig { params(name: String, full_name: String, node: SyntaxTree::Node).void } + sig { params(name: String, full_name: String, node: Prism::Node).void } def define_attr_writer(name, full_name, node) definition = Definition.new( kind: Definition::Kind::AttrWriter, name: name, full_name: full_name, @@ -299,11 +340,11 @@ ) @index.define(definition) @plugins.each { |plugin| plugin.internal_on_define_accessor(self, definition) } end - sig { params(name: String, full_name: String, node: SyntaxTree::Node).void } + sig { params(name: String, full_name: String, node: Prism::Node).void } def define_class(name, full_name, node) definition = Definition.new( kind: Definition::Kind::Class, name: name, full_name: full_name, @@ -311,11 +352,11 @@ ) @index.define(definition) @plugins.each { |plugin| plugin.internal_on_define_class(self, definition) } end - sig { params(name: String, full_name: String, node: SyntaxTree::Node).void } + sig { params(name: String, full_name: String, node: Prism::Node).void } def define_constant(name, full_name, node) definition = Definition.new( kind: Definition::Kind::Constant, name: name, full_name: full_name, @@ -323,11 +364,11 @@ ) @index.define(definition) @plugins.each { |plugin| plugin.internal_on_define_constant(self, definition) } end - sig { params(name: String, full_name: String, node: SyntaxTree::Node).void } + sig { params(name: String, full_name: String, node: Prism::Node).void } def define_method(name, full_name, node) definition = Definition.new( kind: Definition::Kind::Method, name: name, full_name: full_name, @@ -335,11 +376,11 @@ ) @index.define(definition) @plugins.each { |plugin| plugin.internal_on_define_method(self, definition) } end - sig { params(name: String, full_name: String, node: SyntaxTree::Node).void } + sig { params(name: String, full_name: String, node: Prism::Node).void } def define_module(name, full_name, node) definition = Definition.new( kind: Definition::Kind::Module, name: name, full_name: full_name, @@ -349,23 +390,23 @@ @plugins.each { |plugin| plugin.internal_on_define_module(self, definition) } end # Reference indexing - sig { params(name: String, node: SyntaxTree::Node).void } + sig { params(name: String, node: Prism::Node).void } def reference_constant(name, node) @index.reference(Reference.new(name: name, kind: Reference::Kind::Constant, location: node_location(node))) end - sig { params(name: String, node: SyntaxTree::Node).void } + sig { params(name: String, node: Prism::Node).void } def reference_method(name, node) @index.reference(Reference.new(name: name, kind: Reference::Kind::Method, location: node_location(node))) end # Context - sig { returns(SyntaxTree::Node) } + sig { returns(Prism::Node) } def current_node T.must(@nodes_nesting.last) end sig { type_parameters(:N).params(type: T::Class[T.type_parameter(:N)]).returns(T.nilable(T.type_parameter(:N))) } @@ -375,97 +416,54 @@ end nil end - sig { returns(T.nilable(SyntaxTree::ClassDeclaration)) } + sig { returns(T.nilable(Prism::ClassNode)) } def nesting_class - nesting_node(SyntaxTree::ClassDeclaration) + nesting_node(Prism::ClassNode) end - sig { returns(T.nilable(SyntaxTree::BlockNode)) } + sig { returns(T.nilable(Prism::BlockNode)) } def nesting_block - nesting_node(SyntaxTree::BlockNode) + nesting_node(Prism::BlockNode) end - sig { returns(T.nilable(SyntaxTree::MethodAddBlock)) } - def nesting_block_call - nesting_node(SyntaxTree::MethodAddBlock) + sig { returns(T.nilable(Prism::CallNode)) } + def nesting_call + nesting_node(Prism::CallNode) end sig { returns(T.nilable(String)) } - def nesting_block_call_name - block = nesting_block_call - return unless block.is_a?(SyntaxTree::MethodAddBlock) - - call = block.call - case call - when SyntaxTree::ARef - node_string(call.collection) - when SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall - node_string(call.message) - end - end - - sig { returns(T.nilable(String)) } def nesting_class_name nesting_class = self.nesting_class return unless nesting_class - node_string(nesting_class.constant) + nesting_class.name.to_s end sig { returns(T.nilable(String)) } def nesting_class_superclass_name nesting_class_superclass = nesting_class&.superclass return unless nesting_class_superclass - node_string(nesting_class_superclass).delete_prefix("::") + nesting_class_superclass.slice.delete_prefix("::") end sig { returns(T.nilable(String)) } def last_sig - return unless @previous_node.is_a?(SyntaxTree::MethodAddBlock) + previous_call = @previous_node + return unless previous_call.is_a?(Prism::CallNode) + return unless previous_call.name == :sig - node_string(@previous_node) + previous_call.slice end # Node utils - sig { params(node: T.any(Symbol, SyntaxTree::Node)).returns(String) } - def node_string(node) - case node - when Symbol - node.to_s - else - T.must(@source[node.location.start_char...node.location.end_char]) - end - end - - sig { params(node: SyntaxTree::Node).returns(Location) } + sig { params(node: Prism::Node).returns(Location) } def node_location(node) - Location.from_syntax_tree(@path, node.location) - end - - sig { params(node: SyntaxTree::Node).returns(String) } - def symbol_string(node) - node_string(node).delete_prefix(":") - end - - sig do - params( - node: T.any(SyntaxTree::Args, SyntaxTree::ArgParen, SyntaxTree::ArgsForward, NilClass), - ).returns(T::Array[SyntaxTree::Node]) - end - def call_args(node) - case node - when SyntaxTree::ArgParen - call_args(node.arguments) - when SyntaxTree::Args - node.parts - else - [] - end + Location.from_prism(@path, node.location) end end end end