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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da70c28c42b1b848e40adb6145d25686ed8e8bea4dbf25adeb49bebbb82555a8
4
- data.tar.gz: dcf7b9aebd57660ded6869224c87769d836c8518a19b46c74a8d1c86edd1e9b4
3
+ metadata.gz: 1d30fec72da618c4ce29c0c819fb0a912cb968d829182143337b537ec3a69dbc
4
+ data.tar.gz: f24e1bc9e08780f4e6c2f1943b4620671da06d1ff71026d8e81a00300137b352
5
5
  SHA512:
6
- metadata.gz: e6dcab3c8280feb01e837e87025d5d71f6913f74b64c5afa22c36e126e8d69f8321c4445653088fc8fa82a4ee5c4bf2dffef825f92e6c5e5fbf4177a9ce725fd
7
- data.tar.gz: 6d40b0fe93e685cbe3d982dd467dc843138686bdf08a95964b90da4187587fb9e4a3ffcab454dbc1b90ad086e9df6bf56d96cb2152dabf72450bc203790bde42
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
 
@@ -6,7 +6,7 @@ module CodeKeeper
6
6
  attr_accessor :metrics, :number_of_threads
7
7
 
8
8
  def initialize
9
- @metrics = [:cyclomatic_complexity]
9
+ @metrics = %i[cyclomatic_complexity class_length abc_metric]
10
10
  @number_of_threads = 2
11
11
  end
12
12
  end
@@ -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
- Filename: #{k}
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
- ps = Parser.parse(file_path)
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
@@ -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 = { cyclomatic_complexity: CodeKeeper::Metrics::CyclomaticComplexity }.freeze
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
@@ -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, path, score)
13
- scores[:"#{metric}"].store(path, score)
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
@@ -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 do |metric|
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 do |metric|
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CodeKeeper
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
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.1.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-08-18 00:00:00.000000000 Z
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