-2

I am looking for a function or gem that gives me the cyclomatic complexity of a function.

For example, using rubocop, If I write

def my_func(foo)
  foo.details['errors'].each do |attr, message|
    case attr
    when 1 then foo.errors.add(:err1, :format)
    when 2 then foo.errors.add(:err3, :format)
    when 3 then foo.errors.add(:err5, :format)
    when 4 then foo.errors.add(:err7, :format)
    when 5 then foo.errors.add(:err9, :format)
    when 6 then foo.errors.add(:err11, :format)
    when 7 then foo.errors.add(:err13, :format)
    else foo.errors.add(:base, message)
    end
  end
  return foo
end

When I run rubocop, then I got the error:

Metrics/CyclomaticComplexity: Cyclomatic complexity for my_func is too high. [9/7]                                                                                                    
    def my_func(foo) ..."

And I know my cylcomatic complexity is [9/7].

If I change my function and no error is raised by rubocop, how to get the function cyclomatic complexity ? A code snippet with an example would be great ! (I am not looking for a manual computation).

Bonus: Provide a solution for JavaScript functions also.

Thanks!

  • The code for [`cop/metrics/cyclomatic_complexity.rb`](https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/metrics/cyclomatic_complexity.rb) looks surprisingly simple as it just counts nodes. The cylcomatic complexity of your method starts at 1 plus 1 for `each` and 7 for the `when`'s. – Stefan Dec 01 '22 at 10:51

1 Answers1

2

From Rubocop::Cop::Metrics::CyclomaticComplexity itself;

# Checks that the cyclomatic complexity of methods is not higher
# than the configured maximum. The cyclomatic complexity is the number of
# linearly independent paths through a method. The algorithm counts
# decision points and adds one.
#
# An if statement (or unless or ?:) increases the complexity by one. An
# else branch does not, since it doesn't add a decision point. The &&
# operator (or keyword and) can be converted to a nested if statement,
# and ||/or is shorthand for a sequence of ifs, so they also add one.
# Loops can be said to have an exit condition, so they add one.
# Blocks that are calls to builtin iteration methods
# (e.g. `ary.map{...}) also add one, others are ignored.
#
#   def each_child_node(*types)               # count begins: 1
#     unless block_given?                     # unless: +1
#       return to_enum(__method__, *types)
#
#     children.each do |child|                # each{}: +1
#       next unless child.is_a?(Node)         # unless: +1
#
#       yield child if types.empty? ||        # if: +1, ||: +1
#                      types.include?(child.type)
#     end
#
#     self
#   end                                       # total: 6

So, going part by part in your code, you get an initial count of 1, plus 1 for the each loop and plus 1 for each when branch;

def my_func(foo)                                   # count begins: 1
  foo.details['errors'].each do |attr, message|    # each{}: +1
    case attr
    when 1 then foo.errors.add(:err1, :format)     # when: +1
    when 2 then foo.errors.add(:err3, :format)     # when: +1
    when 3 then foo.errors.add(:err5, :format)     # when: +1
    when 4 then foo.errors.add(:err7, :format)     # when: +1
    when 5 then foo.errors.add(:err9, :format)     # when: +1
    when 6 then foo.errors.add(:err11, :format)    # when: +1
    when 7 then foo.errors.add(:err13, :format)    # when: +1
    else foo.errors.add(:base, message)
    end
  end
  return foo
end                                                # total: 9

You could check the class examples to get a better idea as well.

Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
  • 1
    Its a true shame that the asker chose a low quality link only answer over this. But then again its also a low quality off topic software recommendation question. – max Dec 13 '22 at 17:36