code_keeper 0.1.0 → 0.3.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 +4 -4
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +13 -0
- data/README.md +1 -1
- data/lib/code_keeper/config.rb +1 -1
- data/lib/code_keeper/finder.rb +0 -6
- data/lib/code_keeper/formatter.rb +7 -1
- data/lib/code_keeper/metrics/abc_metric.rb +31 -0
- data/lib/code_keeper/metrics/class_length.rb +74 -0
- data/lib/code_keeper/metrics/cyclomatic_complexity.rb +4 -2
- data/lib/code_keeper/metrics.rb +7 -1
- data/lib/code_keeper/result.rb +2 -2
- data/lib/code_keeper/scorer.rb +13 -6
- data/lib/code_keeper/version.rb +1 -1
- data/lib/code_keeper.rb +2 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d30fec72da618c4ce29c0c819fb0a912cb968d829182143337b537ec3a69dbc
|
4
|
+
data.tar.gz: f24e1bc9e08780f4e6c2f1943b4620671da06d1ff71026d8e81a00300137b352
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef7a1991ea72b93c1c47d6d07376008ca0d0e9b0da1156bdcedee4cd7efa96b9896912680c0520d8516a9a8486052d753b799fb836610787a794bc0b5001e5fe
|
7
|
+
data.tar.gz: 1c66dd84f996a705b93513bcd45b342cf1d3fba28d4773b84c882489b860c86024293096a0285580e38bde529e4dc53feb84b5cb719afe4ec0c8735ff078b7d5
|
data/.rubocop.yml
CHANGED
@@ -44,3 +44,16 @@ Style/Documentation:
|
|
44
44
|
|
45
45
|
Metrics/AbcSize:
|
46
46
|
Max: 25
|
47
|
+
Exclude:
|
48
|
+
# It's hard to control.
|
49
|
+
- lib/code_keeper/metrics/class_length.rb
|
50
|
+
|
51
|
+
Metrics/CyclomaticComplexity:
|
52
|
+
Exclude:
|
53
|
+
# It's hard to control.
|
54
|
+
- lib/code_keeper/metrics/class_length.rb
|
55
|
+
|
56
|
+
Metrics/PerceivedComplexity:
|
57
|
+
Exclude:
|
58
|
+
# It's hard to control.
|
59
|
+
- lib/code_keeper/metrics/class_length.rb
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Change log
|
2
|
+
|
3
|
+
## Unrelease
|
4
|
+
|
5
|
+
## 0.3.0 (2021-09-15)
|
6
|
+
|
7
|
+
### New features
|
8
|
+
- [#26](https://github.com/ebihara99999/code_keeper/pull/26): Support an ABC software metric.
|
9
|
+
- [#27](https://github.com/ebihara99999/code_keeper/pull/27): Support a class length metric.
|
10
|
+
### Bug fixes
|
11
|
+
|
12
|
+
### Changes
|
13
|
+
- [#25](https://github.com/ebihara99999/code_keeper/pull/25): Remove redundant codes.
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@ The CodeKeeper measures metrics especially about complexity and size of Ruby fil
|
|
3
3
|
|
4
4
|
Mesuring metrics leads to keep codebase simple and clean, and I name the gem CodeKeeper.
|
5
5
|
|
6
|
-
Now CodeKeeper supports the cyclomatic complexity. The scores are output to stdout.
|
6
|
+
Now CodeKeeper supports the cyclomatic complexity of a file, the ABC software metric of a file, and class length. The scores are output to stdout.
|
7
7
|
|
8
8
|
## Installation
|
9
9
|
|
data/lib/code_keeper/config.rb
CHANGED
data/lib/code_keeper/finder.rb
CHANGED
@@ -16,13 +16,7 @@ module CodeKeeper
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def search_recursively(file_or_dir_paths)
|
19
|
-
checked = {}
|
20
|
-
|
21
19
|
file_or_dir_paths.each do |edge|
|
22
|
-
next if checked[:"#{edge}"]
|
23
|
-
|
24
|
-
checked[:"#{edge}"] = true
|
25
|
-
|
26
20
|
if FileTest.file?(edge)
|
27
21
|
file_or_dir_paths << edge unless file_or_dir_paths.include?(edge)
|
28
22
|
else
|
@@ -10,10 +10,16 @@ module CodeKeeper
|
|
10
10
|
|
11
11
|
result.scores.each_key do |metric|
|
12
12
|
result.scores[metric].each do |k, v|
|
13
|
+
klass_or_file_key = if metric == :class_length
|
14
|
+
'Class'
|
15
|
+
else
|
16
|
+
'Filename'
|
17
|
+
end
|
18
|
+
|
13
19
|
formatted_result.concat(
|
14
20
|
<<~EOS
|
15
21
|
Metric: #{metric}
|
16
|
-
|
22
|
+
#{klass_or_file_key}: #{k}
|
17
23
|
Score: #{v}
|
18
24
|
---
|
19
25
|
EOS
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CodeKeeper
|
4
|
+
module Metrics
|
5
|
+
# Caluculate cyclomatic complexity
|
6
|
+
class AbcMetric
|
7
|
+
include ::RuboCop::Cop::Metrics::Utils::IteratingBlock
|
8
|
+
include ::RuboCop::Cop::Metrics::Utils::RepeatedCsendDiscount
|
9
|
+
|
10
|
+
def initialize(file_path)
|
11
|
+
ps = Parser.parse(file_path)
|
12
|
+
@path = file_path
|
13
|
+
@body = ps.ast
|
14
|
+
@assignments = 0
|
15
|
+
@branches = 0
|
16
|
+
@conditionals = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def score
|
20
|
+
caluculator = ::RuboCop::Cop::Metrics::Utils::AbcSizeCalculator.new(@body)
|
21
|
+
caluculator.calculate
|
22
|
+
@assignments = caluculator.instance_variable_get('@assignment')
|
23
|
+
@conditionals = caluculator.instance_variable_get('@condition')
|
24
|
+
@branches = caluculator.instance_variable_get('@branch')
|
25
|
+
|
26
|
+
value = Math.sqrt(@assignments**2 + @branches**2 + @conditionals**2).round(4)
|
27
|
+
{ "#{@path}": value }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CodeKeeper
|
4
|
+
module Metrics
|
5
|
+
# Caluculate Class Length.
|
6
|
+
class ClassLength
|
7
|
+
def initialize(file_path)
|
8
|
+
@ps = Parser.parse(file_path)
|
9
|
+
@body = @ps.ast
|
10
|
+
@score_hash = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# NOTE: This doesn't exclude foldale sources like Array, Hash and Heredoc.
|
14
|
+
def score
|
15
|
+
@body.each_node(:class, :casgn) do |node|
|
16
|
+
if node.class_type?
|
17
|
+
@score_hash.store(node.loc.name.source.to_sym, calculate(node))
|
18
|
+
elsif node.casgn_type?
|
19
|
+
parent = node.parent
|
20
|
+
|
21
|
+
if parent&.assignment?
|
22
|
+
block_node = node.children[2]
|
23
|
+
elsif parent&.parent&.masgn_type?
|
24
|
+
# In the case where `A, B = Struct.new(:a, :b)`,
|
25
|
+
# B is always nil.
|
26
|
+
assigned = parent.loc.expression.source.split(',').first
|
27
|
+
next unless node.loc.name.source == assigned
|
28
|
+
|
29
|
+
block_node = parent.parent.children[1]
|
30
|
+
else
|
31
|
+
_scope, klass, block_node = *node
|
32
|
+
end
|
33
|
+
|
34
|
+
# This is not to raise error on dynamic assignments like `X = Y = Z = Class.new`.
|
35
|
+
# the block node is as follows if node is X:
|
36
|
+
# `s(:casgn, nil, :Y, s(:casgn, nil, :Z, s(:block, ...`
|
37
|
+
# Similarly the block node is `:X` as follows if node is Y.
|
38
|
+
next unless block_node.respond_to?(:class_definition?) && block_node.class_definition?
|
39
|
+
|
40
|
+
# if the parent is an assignment_type or the parent of the parent is a masgn_type,
|
41
|
+
klass ||= node.loc.name.source.to_sym
|
42
|
+
|
43
|
+
@score_hash.store(klass, calculate(block_node))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
@score_hash
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def calculate(node)
|
52
|
+
# node.body.line_count doesn't include comments after definition of a class.
|
53
|
+
count = node.nonempty_line_count - 2
|
54
|
+
count - line_count_of_inner_nodes(node) - comment_line_count(node)
|
55
|
+
end
|
56
|
+
|
57
|
+
def line_count_of_inner_nodes(node)
|
58
|
+
count = 0
|
59
|
+
node.each_descendant(:class, :module) do |klass_or_module_node|
|
60
|
+
count += klass_or_module_node.nonempty_line_count
|
61
|
+
end
|
62
|
+
count
|
63
|
+
end
|
64
|
+
|
65
|
+
def comment_line_count(node)
|
66
|
+
count = 0
|
67
|
+
@ps.comments.each do |comment|
|
68
|
+
count += 1 if (node.first_line...node.last_line).include? comment.loc.line
|
69
|
+
end
|
70
|
+
count
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -10,18 +10,20 @@ module CodeKeeper
|
|
10
10
|
CONSIDERED_NODES = %i[if while until for csend block block_pass rescue when and or or_asgnand_asgn].freeze
|
11
11
|
|
12
12
|
def initialize(file_path)
|
13
|
-
|
13
|
+
@path = file_path
|
14
|
+
ps = Parser.parse(@path)
|
14
15
|
@body = ps.ast
|
15
16
|
end
|
16
17
|
|
17
18
|
# returns score of cyclomatic complexity
|
18
19
|
def score
|
19
|
-
@body.each_node(:lvasgn, *CONSIDERED_NODES).reduce(1) do |score, node|
|
20
|
+
final_score = @body.each_node(:lvasgn, *CONSIDERED_NODES).reduce(1) do |score, node|
|
20
21
|
next score if !iterating_block?(node) || node.lvasgn_type?
|
21
22
|
next score if node.csend_type? && discount_for_repeated_csend?(node)
|
22
23
|
|
23
24
|
next 1 + score
|
24
25
|
end
|
26
|
+
{ "#{@path}": final_score }
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
data/lib/code_keeper/metrics.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'code_keeper/metrics/abc_metric'
|
3
4
|
require 'code_keeper/metrics/cyclomatic_complexity'
|
5
|
+
require 'code_keeper/metrics/class_length'
|
4
6
|
|
5
7
|
module CodeKeeper
|
6
8
|
# Manage config values of metrics and the correspond classes
|
7
9
|
module Metrics
|
8
|
-
MAPPINGS = {
|
10
|
+
MAPPINGS = {
|
11
|
+
abc_metric: CodeKeeper::Metrics::AbcMetric,
|
12
|
+
cyclomatic_complexity: CodeKeeper::Metrics::CyclomaticComplexity,
|
13
|
+
class_length: CodeKeeper::Metrics::ClassLength
|
14
|
+
}.freeze
|
9
15
|
end
|
10
16
|
end
|
data/lib/code_keeper/result.rb
CHANGED
@@ -9,8 +9,8 @@ module CodeKeeper
|
|
9
9
|
@scores = CodeKeeper.config.metrics.map { |key| [key, {}] }.to_h
|
10
10
|
end
|
11
11
|
|
12
|
-
def add(metric,
|
13
|
-
scores[:"#{metric}"].store(
|
12
|
+
def add(metric, klass_or_path, score)
|
13
|
+
scores[:"#{metric}"].store(klass_or_path, score)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
data/lib/code_keeper/scorer.rb
CHANGED
@@ -14,20 +14,27 @@ module CodeKeeper
|
|
14
14
|
# `in_threads: 1` makes 2 threads, a sleep_forever thread and the main thread.
|
15
15
|
if num_threads == 1
|
16
16
|
ruby_file_paths.each do |path|
|
17
|
-
metrics.each
|
18
|
-
result.add(:cyclomatic_complexity, path, ::CodeKeeper::Metrics::MAPPINGS[metric].new(path).score)
|
19
|
-
end
|
17
|
+
metrics.each { |metric| calculate_score(metric, path, result) }
|
20
18
|
end
|
21
19
|
else
|
22
20
|
Parallel.map(ruby_file_paths, in_threads: num_threads) do |path|
|
23
|
-
metrics.each
|
24
|
-
result.add(:cyclomatic_complexity, path, ::CodeKeeper::Metrics::MAPPINGS[metric].new(path).score)
|
25
|
-
end
|
21
|
+
metrics.each { |metric| calculate_score(metric, path, result) }
|
26
22
|
end
|
27
23
|
end
|
28
24
|
|
29
25
|
result
|
30
26
|
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def calculate_score(metric, path, result)
|
31
|
+
score = ::CodeKeeper::Metrics::MAPPINGS[metric].new(path).score
|
32
|
+
|
33
|
+
# The class length metric's score has multiple classes.
|
34
|
+
score.each do |k, v|
|
35
|
+
result.add(metric, k.to_s, v)
|
36
|
+
end
|
37
|
+
end
|
31
38
|
end
|
32
39
|
end
|
33
40
|
end
|
data/lib/code_keeper/version.rb
CHANGED
data/lib/code_keeper.rb
CHANGED
@@ -10,7 +10,9 @@ require 'code_keeper/config'
|
|
10
10
|
require 'code_keeper/scorer'
|
11
11
|
require 'code_keeper/result'
|
12
12
|
require 'code_keeper/metrics'
|
13
|
+
require 'code_keeper/metrics/abc_metric'
|
13
14
|
require 'code_keeper/metrics/cyclomatic_complexity'
|
15
|
+
require 'code_keeper/metrics/class_length'
|
14
16
|
|
15
17
|
module CodeKeeper
|
16
18
|
class << self
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: code_keeper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yusuke Ebihara
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-09-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parallel
|
@@ -65,6 +65,7 @@ files:
|
|
65
65
|
- ".gitignore"
|
66
66
|
- ".rspec"
|
67
67
|
- ".rubocop.yml"
|
68
|
+
- CHANGELOG.md
|
68
69
|
- CODE_OF_CONDUCT.md
|
69
70
|
- Gemfile
|
70
71
|
- Gemfile.lock
|
@@ -81,6 +82,8 @@ files:
|
|
81
82
|
- lib/code_keeper/finder.rb
|
82
83
|
- lib/code_keeper/formatter.rb
|
83
84
|
- lib/code_keeper/metrics.rb
|
85
|
+
- lib/code_keeper/metrics/abc_metric.rb
|
86
|
+
- lib/code_keeper/metrics/class_length.rb
|
84
87
|
- lib/code_keeper/metrics/cyclomatic_complexity.rb
|
85
88
|
- lib/code_keeper/parser.rb
|
86
89
|
- lib/code_keeper/result.rb
|