Skip to content

Commit

Permalink
Handle anonymous class definitions correctly in ABC output.
Browse files Browse the repository at this point in the history
Fixes #20.
  • Loading branch information
xaviershay committed Aug 18, 2012
1 parent f6bab2c commit 1c3c2e6
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 2 deletions.
34 changes: 32 additions & 2 deletions lib/cane/abc_check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ def violations
# Wrapper object around sexps returned from ripper.
class RubyAst < Struct.new(:file_name, :max_allowed_complexity,
:sexps, :exclusions)

def initialize(*args)
super
self.anon_method_add = true
end

def violations
process_ast(sexps).
select { |nesting, complexity| complexity > max_allowed_complexity }.
Expand All @@ -46,6 +52,10 @@ def violations

protected

# Stateful flag used to determine whether we are currently parsing an
# anonymous class. See #container_label.
attr_accessor :anon_method_add

# Recursive function to process an AST. The `complexity` variable mutates,
# which is a bit confusing. `nesting` does not.
def process_ast(node, complexity = {}, nesting = [])
Expand All @@ -54,8 +64,7 @@ def process_ast(node, complexity = {}, nesting = [])
unless excluded?(node, *nesting)
complexity[nesting.join(" > ")] = calculate_abc(node)
end
elsif container_nodes.include?(node[0])
parent = node[1][-1][1]
elsif parent = container_label(node)
nesting = nesting + [parent]
end

Expand All @@ -73,6 +82,27 @@ def calculate_abc(method_node)
abc
end

def container_label(node)
if container_nodes.include?(node[0])
# def foo, def self.foo
node[1][-1][1]
elsif node[0] == :method_add_block
if anon_method_add
# Class.new do ...
"(anon)"
else
# MyClass = Class.new do ...
# parent already added when processing a parent node
anon_method_add = true
nil
end
elsif node[0] == :assign && node[2][0] == :method_add_block
# MyClass = Class.new do ...
self.anon_method_add = false
node[1][-1][1]
end
end

def label_for(node)
# A default case is deliberately omitted since I know of no way this
# could fail and want it to fail fast.
Expand Down
28 changes: 28 additions & 0 deletions spec/abc_check_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,34 @@ def other_meth
columns.should == [file_name, "Harness > Nested > other_meth", 1]
end

it "creates an AbcMaxViolation for method in assigned anonymous class" do
file_name = make_file(<<-RUBY)
MyClass = Struct.new(:foo) do
def test_method(a)
b = a
return b if b > 3
end
end
RUBY

violations = described_class.new(files: file_name, max: 1).violations
violations[0].detail.should == "MyClass > test_method"
end

it "creates an AbcMaxViolation for method in anonymous class" do
file_name = make_file(<<-RUBY)
Class.new do
def test_method(a)
b = a
return b if b > 3
end
end
RUBY

violations = described_class.new(files: file_name, max: 1).violations
violations[0].detail.should == "(anon) > test_method"
end

def self.it_should_extract_method_name(method_name, label=method_name)
it "creates an AbcMaxViolation for #{method_name}" do
file_name = make_file(<<-RUBY)
Expand Down

0 comments on commit 1c3c2e6

Please sign in to comment.