diff --git a/lib/cc/engine/rubocop.rb b/lib/cc/engine/rubocop.rb index 49d92d5a..49173168 100644 --- a/lib/cc/engine/rubocop.rb +++ b/lib/cc/engine/rubocop.rb @@ -1,5 +1,6 @@ require "json" require "pathname" +require "rubocop/cop/method_complexity_patch" require "rubocop" require "cc/engine/category_parser" diff --git a/lib/rubocop/cop/method_complexity_patch.rb b/lib/rubocop/cop/method_complexity_patch.rb new file mode 100644 index 00000000..e16a345a --- /dev/null +++ b/lib/rubocop/cop/method_complexity_patch.rb @@ -0,0 +1,18 @@ +# Monkey patching! Here be dragons. +module RuboCop + module Cop + module MethodComplexity + # RuboCop's default implementation of `add_offense` in `Cop` only gets the + # location of the keyword associated with the problem which, for things + # like complexity checkers, is just the method def line. This isn't very + # useful for checkers where the entire method body is relevant. Fetching + # this information from an `Offense` instance is difficult, since the + # original AST is no longer available. So it's easier to monkey-path + # this method on complexity checkers to send the location of the entire + # method to the created `Offense`. + def add_offense(node, loc, message = nil, severity = nil) + super(node, node.loc, message, severity) + end + end + end +end diff --git a/spec/cc/engine/rubocop_spec.rb b/spec/cc/engine/rubocop_spec.rb index 145ef39f..d5449fc7 100644 --- a/spec/cc/engine/rubocop_spec.rb +++ b/spec/cc/engine/rubocop_spec.rb @@ -161,6 +161,46 @@ def method assert_equal location, result["location"] end + it "includes complete method body for cyclomatic complexity issue" do + create_source_file("my_script", <<-EORUBY) + #!/usr/bin/env ruby + + def method(a,b,c,d,e,f,g) + r = 1 + if a + if !b + if c + if !d + if e + if !f + (1..g).each do |n| + r = (r * n) - n + end + end + end + end + end + end + end + r + end + EORUBY + output = run_engine + assert includes_check?(output, "Metrics/CyclomaticComplexity") + + json = JSON.parse('[' + output.split("\u0000").join(',') + ']') + + result = json.select { |i| i && i["check_name"] =~ /Metrics\/CyclomaticComplexity/ }.first + location = { + "path" => "my_script", + "positions" => { + "begin" => { "column"=>11, "line"=>3 }, + "end" => { "column"=>14, "line"=>21 } + } + } + assert_equal location, result["location"] + end + def includes_check?(output, cop_name) issues = output.split("\0").map { |x| JSON.parse(x) }