cycromatic 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +28 -0
- data/README.md +57 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cycromatic.gemspec +27 -0
- data/exe/cycromatic +8 -0
- data/lib/cycromatic/calculator.rb +79 -0
- data/lib/cycromatic/cli.rb +50 -0
- data/lib/cycromatic/complexity.rb +13 -0
- data/lib/cycromatic/file_enumerator.rb +55 -0
- data/lib/cycromatic/json_formatter.rb +48 -0
- data/lib/cycromatic/text_formatter.rb +34 -0
- data/lib/cycromatic/version.rb +3 -0
- data/lib/cycromatic.rb +9 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 737d504d14a3c4223d39792e314668df88d39f8b
|
4
|
+
data.tar.gz: 628f94b6c7cf38e27ff1104fb083169a6e1f4cc6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8ee208f67d4f325f0cb5ee15d0820270f899700375471761ed818917696f3bef5df04999348b8ac75e8fed238def6b5788784ec1a7232a86edae8a1c433c799b
|
7
|
+
data.tar.gz: 9e8b3cbabf294d6620b218a67bd7aed7340ffafb69d41a364e6a66d8cc572f74655c120acd7d6a3c80c6c23a1cc3f2eb2331a45438796cb36512f9de49b43fef
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
cycromatic (0.1.0)
|
5
|
+
parser (~> 2.3)
|
6
|
+
rainbow (~> 2.1)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
ast (2.3.0)
|
12
|
+
minitest (5.9.0)
|
13
|
+
parser (2.3.1.2)
|
14
|
+
ast (~> 2.2)
|
15
|
+
rainbow (2.1.0)
|
16
|
+
rake (10.5.0)
|
17
|
+
|
18
|
+
PLATFORMS
|
19
|
+
ruby
|
20
|
+
|
21
|
+
DEPENDENCIES
|
22
|
+
bundler (~> 1.12)
|
23
|
+
cycromatic!
|
24
|
+
minitest (~> 5.0)
|
25
|
+
rake (~> 10.0)
|
26
|
+
|
27
|
+
BUNDLED WITH
|
28
|
+
1.12.5
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Cycromatic
|
2
|
+
|
3
|
+
Cycromatic calculates cyclomatic complexity of Ruby programs.
|
4
|
+
|
5
|
+
* Cyclomatic Complexity - https://en.wikipedia.org/wiki/Cyclomatic_complexity
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Install by `gem`:
|
10
|
+
|
11
|
+
$ gem install cycromatic
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Run `cycromatic` command to calculate complexity:
|
16
|
+
|
17
|
+
```
|
18
|
+
$ cycromatic ruby_program.rb # Specify paths to .rb files
|
19
|
+
$ cycromatic app config # Specify directories including .rb files
|
20
|
+
```
|
21
|
+
|
22
|
+
The output will be like the following:
|
23
|
+
|
24
|
+
```
|
25
|
+
$ cycromatic ../contror/lib/contror/anf/translator.rb
|
26
|
+
../contror/lib/contror/anf/translator.rb [toplevel]:1 1
|
27
|
+
../contror/lib/contror/anf/translator.rb initialize:8 1
|
28
|
+
../contror/lib/contror/anf/translator.rb translate:14 1
|
29
|
+
../contror/lib/contror/anf/translator.rb with_new_block:20 3
|
30
|
+
../contror/lib/contror/anf/translator.rb current_block:37 1
|
31
|
+
../contror/lib/contror/anf/translator.rb push_stmt:41 1
|
32
|
+
../contror/lib/contror/anf/translator.rb normalize_node:46 3
|
33
|
+
../contror/lib/contror/anf/translator.rb translate0:59 52
|
34
|
+
../contror/lib/contror/anf/translator.rb translate_arg:478 3
|
35
|
+
../contror/lib/contror/anf/translator.rb translate_call:491 2
|
36
|
+
../contror/lib/contror/anf/translator.rb translate_params:533 2
|
37
|
+
../contror/lib/contror/anf/translator.rb value_node?:547 7
|
38
|
+
../contror/lib/contror/anf/translator.rb fresh_var:566 1
|
39
|
+
../contror/lib/contror/anf/translator.rb translate_var:571 5
|
40
|
+
```
|
41
|
+
|
42
|
+
The tool accepts `--format=json` option to output in JSON format.
|
43
|
+
|
44
|
+
## Calculation
|
45
|
+
|
46
|
+
It calculates complexities as the following:
|
47
|
+
|
48
|
+
* Basic block have complexity of 1 (base case)
|
49
|
+
* Branching construct have complexity of 1
|
50
|
+
* Loop construct have complexity of 1
|
51
|
+
* `&&` and `||` have complexity of 1
|
52
|
+
* Safe navigation operator have complexity of 1
|
53
|
+
* Passing iterator block does not introduce complexity
|
54
|
+
|
55
|
+
## Contributing
|
56
|
+
|
57
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/cycromatic.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "cycromatic"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/cycromatic.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cycromatic/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cycromatic"
|
8
|
+
spec.version = Cycromatic::VERSION
|
9
|
+
spec.authors = ["Soutaro Matsumoto"]
|
10
|
+
spec.email = ["matsumoto@soutaro.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Cyclomatic Complexity}
|
13
|
+
spec.description = %q{Compute Cyclomatic Complexity of Ruby programs}
|
14
|
+
spec.homepage = "https://github.com/cycromatic"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
24
|
+
|
25
|
+
spec.add_dependency "rainbow", "~> 2.1"
|
26
|
+
spec.add_dependency "parser", "~> 2.3"
|
27
|
+
end
|
data/exe/cycromatic
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
module Cycromatic
|
2
|
+
class Calculator
|
3
|
+
attr_reader :node
|
4
|
+
|
5
|
+
def initialize(node:)
|
6
|
+
@node = node
|
7
|
+
end
|
8
|
+
|
9
|
+
def each_complexity(&block)
|
10
|
+
if block_given?
|
11
|
+
calculate_toplevel node, &block
|
12
|
+
else
|
13
|
+
enum_for :each_complexity
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
COMPLEX_NODE_TYPES = Set.new([:if, :while, :while_post, :when, :resbody, :csend, :for,
|
20
|
+
:and, :or,
|
21
|
+
:optarg, :kwoptarg])
|
22
|
+
|
23
|
+
def calculate_toplevel(node, &block)
|
24
|
+
value = calculate_node(node, &block)
|
25
|
+
yield Complexity.new(type: :toplevel, node: node, value: value + 1)
|
26
|
+
end
|
27
|
+
|
28
|
+
def calculate_def(node, &block)
|
29
|
+
value = 0
|
30
|
+
|
31
|
+
case node.type
|
32
|
+
when :def
|
33
|
+
args = node.children[1]
|
34
|
+
body = node.children[2]
|
35
|
+
when :defs
|
36
|
+
args = node.children[2]
|
37
|
+
body = node.children[3]
|
38
|
+
end
|
39
|
+
|
40
|
+
value += calculate_node(args, &block)
|
41
|
+
value += calculate_node(body, &block) if body
|
42
|
+
|
43
|
+
yield Complexity.new(type: :method, node: node, value: value + 1)
|
44
|
+
end
|
45
|
+
|
46
|
+
def calculate_node(node, &block)
|
47
|
+
count = 0
|
48
|
+
|
49
|
+
case node.type
|
50
|
+
when :def
|
51
|
+
calculate_def node, &block
|
52
|
+
return 0
|
53
|
+
when :defs
|
54
|
+
calculate_def node, &block
|
55
|
+
return calculate_node(node.children[0], &block)
|
56
|
+
when :case
|
57
|
+
if node.children.last
|
58
|
+
count = 1
|
59
|
+
end
|
60
|
+
when :rescue
|
61
|
+
if node.children.last
|
62
|
+
count = 1
|
63
|
+
end
|
64
|
+
else
|
65
|
+
if COMPLEX_NODE_TYPES.include?(node.type)
|
66
|
+
count = 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
count + node.children.flat_map do |child|
|
71
|
+
if child.is_a? Parser::AST::Node
|
72
|
+
[calculate_node(child, &block)]
|
73
|
+
else
|
74
|
+
[]
|
75
|
+
end
|
76
|
+
end.inject(0) {|x, y| x + y }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'rainbow'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Cycromatic
|
6
|
+
class CLI
|
7
|
+
attr_reader :args
|
8
|
+
|
9
|
+
def self.start(args = ARGV)
|
10
|
+
self.new(args).run
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(args)
|
14
|
+
@args = args
|
15
|
+
|
16
|
+
OptionParser.new do |opts|
|
17
|
+
opts.on("--format FORMAT") {|fmt| @format = fmt }
|
18
|
+
end.parse!(args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
formatter = @format == 'json' ? JSONFormatter.new(io: STDOUT) : TextFormatter.new(io: STDOUT)
|
23
|
+
|
24
|
+
FileEnumerator.new(paths: paths).each do |path|
|
25
|
+
formatter.started path: path
|
26
|
+
begin
|
27
|
+
node = Parser::CurrentRuby.parse(path.read, path.to_s)
|
28
|
+
if node
|
29
|
+
Calculator.new(node: node).each_complexity do |complexity|
|
30
|
+
formatter.calculated(path: path, complexity: complexity)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
rescue => exn
|
34
|
+
require 'pp'
|
35
|
+
p exn
|
36
|
+
pp exn.backtrace
|
37
|
+
formatter.error(path: path, exception: exn)
|
38
|
+
ensure
|
39
|
+
formatter.finished path: path
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
formatter.completed
|
44
|
+
end
|
45
|
+
|
46
|
+
def paths
|
47
|
+
args.map {|arg| Pathname(arg) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Cycromatic
|
2
|
+
class FileEnumerator
|
3
|
+
attr_reader :paths
|
4
|
+
|
5
|
+
def initialize(paths:)
|
6
|
+
@paths = paths
|
7
|
+
end
|
8
|
+
|
9
|
+
def each(&block)
|
10
|
+
if block_given?
|
11
|
+
paths.each do |path|
|
12
|
+
case
|
13
|
+
when path.file?
|
14
|
+
yield path
|
15
|
+
when path.directory?
|
16
|
+
enumerate_files_in_dir(path, &block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
else
|
20
|
+
self.enum_for :each
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def enumerate_files_in_dir(path, &block)
|
27
|
+
if path.basename.to_s =~ /\A\.[^\.]+/
|
28
|
+
# skip hidden paths
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
case
|
33
|
+
when path.directory?
|
34
|
+
path.children.each do |child|
|
35
|
+
enumerate_files_in_dir child, &block
|
36
|
+
end
|
37
|
+
when path.file?
|
38
|
+
if is_ruby_file?(path)
|
39
|
+
yield path
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def is_ruby_file?(path)
|
45
|
+
case
|
46
|
+
when path.extname == ".rb"
|
47
|
+
true
|
48
|
+
when path.extname == ".gemspec"
|
49
|
+
true
|
50
|
+
when path.basename.to_s == "Rakefile"
|
51
|
+
true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Cycromatic
|
2
|
+
class JSONFormatter
|
3
|
+
attr_reader :io
|
4
|
+
|
5
|
+
def initialize(io:)
|
6
|
+
@io = io
|
7
|
+
@results = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def started(path:)
|
11
|
+
@path_results = []
|
12
|
+
@results[path.to_s] = { results: @path_results }
|
13
|
+
end
|
14
|
+
|
15
|
+
def finished(path:)
|
16
|
+
@path_results = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def completed
|
20
|
+
@io.puts @results.to_json
|
21
|
+
end
|
22
|
+
|
23
|
+
def error(path:, exception:)
|
24
|
+
@results[path.to_s] = {
|
25
|
+
error: {
|
26
|
+
message: exception.to_s,
|
27
|
+
trace: exception.backtrace
|
28
|
+
}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def calculated(path:, complexity:)
|
33
|
+
loc = complexity.node.loc
|
34
|
+
name = case complexity.type
|
35
|
+
when :toplevel
|
36
|
+
"[toplevel]"
|
37
|
+
when :method
|
38
|
+
complexity.node.children.find {|c| c.is_a? Symbol }.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
@path_results << {
|
42
|
+
method: name,
|
43
|
+
line: [loc.first_line, loc.last_line],
|
44
|
+
complexity: complexity.value
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Cycromatic
|
2
|
+
class TextFormatter
|
3
|
+
attr_reader :io
|
4
|
+
|
5
|
+
def initialize(io:)
|
6
|
+
@io = io
|
7
|
+
end
|
8
|
+
|
9
|
+
def started(path:)
|
10
|
+
end
|
11
|
+
|
12
|
+
def finished(path:)
|
13
|
+
end
|
14
|
+
|
15
|
+
def completed
|
16
|
+
end
|
17
|
+
|
18
|
+
def error(path:, exception:)
|
19
|
+
io.puts "#{path}\t(error)"
|
20
|
+
end
|
21
|
+
|
22
|
+
def calculated(path:, complexity:)
|
23
|
+
loc = complexity.node.loc
|
24
|
+
name = case complexity.type
|
25
|
+
when :toplevel
|
26
|
+
"[toplevel]"
|
27
|
+
when :method
|
28
|
+
complexity.node.children.find {|c| c.is_a? Symbol }.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
io.puts "#{path}\t#{name}:#{loc.first_line}\t#{complexity.value}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/cycromatic.rb
ADDED
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cycromatic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Soutaro Matsumoto
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rainbow
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: parser
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.3'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.3'
|
83
|
+
description: Compute Cyclomatic Complexity of Ruby programs
|
84
|
+
email:
|
85
|
+
- matsumoto@soutaro.com
|
86
|
+
executables:
|
87
|
+
- cycromatic
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- Gemfile.lock
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/console
|
98
|
+
- bin/setup
|
99
|
+
- cycromatic.gemspec
|
100
|
+
- exe/cycromatic
|
101
|
+
- lib/cycromatic.rb
|
102
|
+
- lib/cycromatic/calculator.rb
|
103
|
+
- lib/cycromatic/cli.rb
|
104
|
+
- lib/cycromatic/complexity.rb
|
105
|
+
- lib/cycromatic/file_enumerator.rb
|
106
|
+
- lib/cycromatic/json_formatter.rb
|
107
|
+
- lib/cycromatic/text_formatter.rb
|
108
|
+
- lib/cycromatic/version.rb
|
109
|
+
homepage: https://github.com/cycromatic
|
110
|
+
licenses: []
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 2.5.1
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: Cyclomatic Complexity
|
132
|
+
test_files: []
|