lib/reek/context_builder.rb in reek-3.8.3 vs lib/reek/context_builder.rb in reek-3.9.0

- old
+ new

@@ -1,12 +1,14 @@ +require_relative 'context/attribute_context' +require_relative 'context/class_context' +require_relative 'context/ghost_context' require_relative 'context/method_context' require_relative 'context/module_context' require_relative 'context/root_context' -require_relative 'context/singleton_method_context' -require_relative 'context/attribute_context' require_relative 'context/send_context' -require_relative 'context/class_context' +require_relative 'context/singleton_attribute_context' +require_relative 'context/singleton_method_context' require_relative 'ast/node' module Reek # # Traverses an abstract syntax tree and fires events whenever it encounters @@ -14,20 +16,20 @@ # # TODO: This class is responsible for statements and reference # counting. Ideally `ContextBuilder` would only build up the context tree and leave the # statement and reference counting to the contexts. # - # :reek:TooManyMethods: { max_methods: 27 } + # :reek:TooManyMethods: { max_methods: 30 } # :reek:UnusedPrivateMethod: { exclude: [ !ruby/regexp /process_/ ] } class ContextBuilder attr_reader :context_tree - private_attr_accessor :element + private_attr_accessor :current_context private_attr_reader :exp def initialize(syntax_tree) @exp = syntax_tree - @element = Context::RootContext.new(exp) + @current_context = Context::RootContext.new(exp) @context_tree = build(exp) end private @@ -58,11 +60,11 @@ if context_processor_exists?(context_processor) send(context_processor, exp) else process exp end - element + current_context end # Handles every node for which we have no context_processor. # def process(exp) @@ -77,10 +79,23 @@ end end alias_method :process_class, :process_module + # Handles `sclass` nodes + # + # An input example that would trigger this method would be: + # + # class << self + # end + # + def process_sclass(exp) + inside_new_context(Context::GhostContext, exp) do + process(exp) + end + end + # Handles `casgn` ("class assign") nodes. # # An input example that would trigger this method would be: # # Foo = Class.new Bar @@ -100,11 +115,11 @@ # def call_me; foo = 2; bar = 5; end # # Given the above example we would count 2 statements overall. # def process_def(exp) - inside_new_context(Context::MethodContext, exp) do + inside_new_context(current_context.method_context_class, exp) do increase_statement_count_by(exp.body) process(exp) end end @@ -131,24 +146,17 @@ # # Besides checking if it's a visibility modifier or an attribute writer # we also record to what the method call is referring to # which we later use for smell detectors like FeatureEnvy. # - # :reek:TooManyStatements: { max_statements: 7 } - # :reek:FeatureEnvy def process_send(exp) - method_name = exp.method_name - if exp.visibility_modifier? - element.track_visibility(method_name, exp.arg_names) - elsif exp.attribute_writer? - exp.args.each do |arg| - append_new_context(Context::AttributeContext, arg, exp) - end - else - append_new_context(Context::SendContext, exp, method_name) + case current_context + when Context::ModuleContext + handle_send_for_modules exp + when Context::MethodContext + handle_send_for_methods exp end - element.record_call_to(exp) process(exp) end # Handles `op_asgn` nodes a.k.a. Ruby's assignment operators. # @@ -161,11 +169,11 @@ # x *= 3 # # We record one reference to `x` given the example above. # def process_op_asgn(exp) - element.record_call_to(exp) + current_context.record_call_to(exp) process(exp) end # Handles `ivasgn` and `ivar` nodes a.k.a. nodes related to instance variables. # @@ -180,11 +188,11 @@ # for just using instance variables (`ivar`). # # We record one reference to `self`. # def process_ivar(exp) - element.record_use_of_self + current_context.record_use_of_self process(exp) end alias_method :process_ivasgn, :process_ivar @@ -193,11 +201,11 @@ # An input example that would trigger this method would be: # # def self.foo; end # def process_self(_) - element.record_use_of_self + current_context.record_use_of_self end # Handles `zsuper` nodes a.k.a. calls to `super` without any arguments but a block possibly. # # An input example that would trigger this method would be: @@ -213,11 +221,11 @@ # def call_me; super(); end # # We record one reference to `self`. # def process_zsuper(_) - element.record_use_of_self + current_context.record_use_of_self end # Handles `block` nodes. # # An input example that would trigger this method would be: @@ -433,15 +441,15 @@ self.class.private_method_defined?(name) end # :reek:ControlParameter def increase_statement_count_by(sexp) - element.statement_counter.increase_by sexp + current_context.statement_counter.increase_by sexp end def decrease_statement_count - element.statement_counter.decrease_by 1 + current_context.statement_counter.decrease_by 1 end # Stores a reference to the current context, creates a nested new one, # yields to the given block and then restores the previous context. # @@ -450,24 +458,46 @@ # @yield block # def inside_new_context(klass, exp) new_context = append_new_context(klass, exp) - orig, self.element = element, new_context + orig, self.current_context = current_context, new_context yield - self.element = orig + self.current_context = orig end - # Append a new child context to the current element. + # Append a new child context to the current context but does not change the + # current context. # # @param klass [Context::*Context] - context class # @param args - arguments for the class initializer # # @return [Context::*Context] - the context that was appended # def append_new_context(klass, *args) - klass.new(element, *args).tap do |new_context| - element.append_child_context new_context + klass.new(current_context, *args).tap do |new_context| + new_context.register_with_parent(current_context) + end + end + + def handle_send_for_modules(exp) + method_name = exp.name + arg_names = exp.arg_names + current_context.track_visibility(method_name, arg_names) + current_context.track_singleton_visibility(method_name, arg_names) + register_attributes(exp) + end + + def handle_send_for_methods(exp) + append_new_context(Context::SendContext, exp, exp.name) + current_context.record_call_to(exp) + end + + def register_attributes(exp) + return unless exp.attribute_writer? + klass = current_context.attribute_context_class + exp.args.each do |arg| + append_new_context(klass, arg, exp) end end end end