code_keeper 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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