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