feature_map 1.2.0 → 1.2.1

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.
@@ -74,6 +74,7 @@ module FeatureMap
74
74
  feature_metrics: MetricsFile::FeaturesContent,
75
75
  feature_test_coverage: TestCoverageFile::FeaturesContent,
76
76
  feature_test_pyramid: TestPyramidFile::FeaturesContent,
77
+ feature_additional_metrics: AdditionalMetricsFile::FeaturesContent,
77
78
  project_configuration: T::Hash[T.untyped, T.untyped],
78
79
  git_ref: String
79
80
  ).void
@@ -83,6 +84,7 @@ module FeatureMap
83
84
  feature_metrics,
84
85
  feature_test_coverage,
85
86
  feature_test_pyramid,
87
+ feature_additional_metrics,
86
88
  project_configuration,
87
89
  git_ref
88
90
  )
@@ -95,7 +97,8 @@ module FeatureMap
95
97
  assignments: feature_assignments[feature_name],
96
98
  metrics: feature_metrics[feature_name],
97
99
  test_coverage: feature_test_coverage[feature_name],
98
- test_pyramid: feature_test_pyramid[feature_name]
100
+ test_pyramid: feature_test_pyramid[feature_name],
101
+ additional_metrics: feature_additional_metrics[feature_name]
99
102
  )
100
103
  end
101
104
 
@@ -11,18 +11,22 @@ module FeatureMap
11
11
  CYCLOMATIC_COMPLEXITY_METRIC = 'cyclomatic_complexity'
12
12
  LINES_OF_CODE_METRIC = 'lines_of_code'
13
13
  TODO_LOCATIONS_METRIC = 'todo_locations'
14
+ COMPLEXITY_RATIO_METRIC = 'complexity_ratio'
15
+ ENCAPSULATION_RATIO_METRIC = 'encapsulation_ratio'
14
16
 
15
17
  SUPPORTED_METRICS = T.let([
16
18
  ABC_SIZE_METRIC,
17
19
  CYCLOMATIC_COMPLEXITY_METRIC,
18
20
  LINES_OF_CODE_METRIC,
19
- TODO_LOCATIONS_METRIC
21
+ TODO_LOCATIONS_METRIC,
22
+ COMPLEXITY_RATIO_METRIC,
23
+ ENCAPSULATION_RATIO_METRIC
20
24
  ].freeze, T::Array[String])
21
25
 
22
26
  FeatureMetrics = T.type_alias do
23
27
  T::Hash[
24
28
  String, # metric name
25
- T.any(Integer, Float, T::Hash[String, String]) # score or todo locations with messages
29
+ T.any(Integer, T.nilable(Float), T::Hash[String, String]) # score or todo locations with messages
26
30
  ]
27
31
  end
28
32
 
@@ -31,15 +35,15 @@ module FeatureMap
31
35
  metrics = file_paths.map { |file| calculate_for_file(file) }
32
36
 
33
37
  # Handle numeric metrics
34
- aggregate_metrics = SUPPORTED_METRICS.each_with_object({}) do |metric_key, agg|
35
- next if metric_key == TODO_LOCATIONS_METRIC
36
-
38
+ aggregate_metrics = [ABC_SIZE_METRIC, CYCLOMATIC_COMPLEXITY_METRIC, LINES_OF_CODE_METRIC].each_with_object({}) do |metric_key, agg|
37
39
  agg[metric_key] = metrics.sum { |m| m[metric_key] || 0 }
38
40
  end
39
41
 
40
- # Merge all todo locations
42
+ # Handle additional supported metrics
41
43
  todo_locations = metrics.map { |m| m[TODO_LOCATIONS_METRIC] }.compact.reduce({}, :merge)
42
44
  aggregate_metrics[TODO_LOCATIONS_METRIC] = todo_locations
45
+ aggregate_metrics[COMPLEXITY_RATIO_METRIC] = complexity_ratio(aggregate_metrics)
46
+ aggregate_metrics[ENCAPSULATION_RATIO_METRIC] = encapsulation_ratio(file_paths, aggregate_metrics)
43
47
 
44
48
  aggregate_metrics
45
49
  end
@@ -71,6 +75,20 @@ module FeatureMap
71
75
  TODO_LOCATIONS_METRIC => todo_locations
72
76
  )
73
77
  end
78
+
79
+ sig { params(aggregate_metrics: T::Hash[String, T.untyped]).returns(T.nilable(Float)) }
80
+ def self.complexity_ratio(aggregate_metrics)
81
+ return 0.0 if aggregate_metrics[LINES_OF_CODE_METRIC].nil? || aggregate_metrics[CYCLOMATIC_COMPLEXITY_METRIC].nil? || aggregate_metrics[CYCLOMATIC_COMPLEXITY_METRIC].zero?
82
+
83
+ aggregate_metrics[LINES_OF_CODE_METRIC].to_f / aggregate_metrics[CYCLOMATIC_COMPLEXITY_METRIC]
84
+ end
85
+
86
+ sig { params(file_paths: T.nilable(T::Array[String]), aggregate_metrics: T::Hash[String, T.untyped]).returns(T.nilable(Float)) }
87
+ def self.encapsulation_ratio(file_paths, aggregate_metrics)
88
+ return 0.0 if file_paths.nil? || aggregate_metrics[LINES_OF_CODE_METRIC].nil? || aggregate_metrics[LINES_OF_CODE_METRIC].zero?
89
+
90
+ file_paths.length.to_f / aggregate_metrics[LINES_OF_CODE_METRIC]
91
+ end
74
92
  end
75
93
  end
76
94
  end
@@ -20,7 +20,7 @@ module FeatureMap
20
20
  FeatureMetrics = T.type_alias do
21
21
  T::Hash[
22
22
  String,
23
- T.any(Integer, Float, T::Hash[String, String])
23
+ T.any(Integer, T.nilable(Float), T::Hash[String, String])
24
24
  ]
25
25
  end
26
26
 
@@ -11,6 +11,8 @@ module FeatureMap
11
11
  class TestCoverageFile
12
12
  extend T::Sig
13
13
 
14
+ COVERAGE_RATIO = 'coverage_ratio'
15
+
14
16
  class FileContentError < StandardError; end
15
17
 
16
18
  FEATURES_KEY = 'features'
@@ -64,7 +66,7 @@ module FeatureMap
64
66
  feature_test_coverage = T.let({}, FeaturesContent)
65
67
 
66
68
  Private.feature_file_assignments.each do |feature_name, files|
67
- feature_test_coverage[feature_name] = T.let({ 'lines' => 0, 'hits' => 0, 'misses' => 0 }, FeatureCoverage)
69
+ feature_test_coverage[feature_name] = T.let({ 'lines' => 0, 'hits' => 0, 'misses' => 0, 'coverage_ratio' => 0 }, FeatureCoverage)
68
70
 
69
71
  files.each_with_object(T.must(feature_test_coverage[feature_name])) do |file_path, coverage|
70
72
  next unless coverage_stats[file_path]
@@ -75,6 +77,12 @@ module FeatureMap
75
77
 
76
78
  coverage
77
79
  end
80
+
81
+ T.must(feature_test_coverage[feature_name])[COVERAGE_RATIO] = if T.must(T.must(feature_test_coverage[feature_name])['lines']).zero?
82
+ 0
83
+ else
84
+ ((T.must(feature_test_coverage[feature_name])['hits'].to_f / T.must(T.must(feature_test_coverage[feature_name])['lines'])) * 100).round
85
+ end
78
86
  end
79
87
 
80
88
  { FEATURES_KEY => feature_test_coverage }
@@ -20,6 +20,7 @@ require 'feature_map/private/documentation_site'
20
20
  require 'feature_map/private/code_cov'
21
21
  require 'feature_map/private/test_coverage_file'
22
22
  require 'feature_map/private/test_pyramid_file'
23
+ require 'feature_map/private/additional_metrics_file'
23
24
  require 'feature_map/private/feature_plugins/assignment'
24
25
  require 'feature_map/private/validations/files_have_features'
25
26
  require 'feature_map/private/validations/features_up_to_date'
@@ -104,11 +105,15 @@ module FeatureMap
104
105
  # and review the feature documentation without this data.
105
106
  feature_test_coverage = TestCoverageFile.path.exist? ? TestCoverageFile.load_features! : {}
106
107
 
108
+ # Additional metrics must be calculated after the initial metrics are loaded
109
+ feature_additional_metrics = AdditionalMetricsFile.path.exist? ? AdditionalMetricsFile.load_features! : {}
110
+
107
111
  DocumentationSite.generate(
108
112
  feature_assignments,
109
113
  feature_metrics,
110
114
  feature_test_coverage,
111
115
  feature_test_pyramid,
116
+ feature_additional_metrics,
112
117
  configuration.raw_hash,
113
118
  T.must(git_ref || configuration.repository['main_branch'])
114
119
  )
@@ -137,6 +142,14 @@ module FeatureMap
137
142
  TestCoverageFile.write!(coverage_stats)
138
143
  end
139
144
 
145
+ sig { void }
146
+ def self.generate_additional_metrics!
147
+ feature_metrics = MetricsFile.load_features!
148
+ feature_test_coverage = TestCoverageFile.path.exist? ? TestCoverageFile.load_features! : {}
149
+
150
+ AdditionalMetricsFile.write!(feature_metrics, feature_test_coverage, configuration.raw_hash['documentation_site']['health'])
151
+ end
152
+
140
153
  # Returns a string version of the relative path to a Rails constant,
141
154
  # or nil if it can't find something
142
155
  sig { params(klass: T.nilable(T.any(T::Class[T.anything], Module))).returns(T.nilable(String)) }
data/lib/feature_map.rb CHANGED
@@ -135,6 +135,11 @@ module FeatureMap
135
135
  Private.gather_test_coverage!(commit_sha, code_cov_token)
136
136
  end
137
137
 
138
+ sig { void }
139
+ def generate_additional_metrics!
140
+ Private.generate_additional_metrics!
141
+ end
142
+
138
143
  # Given a backtrace from either `Exception#backtrace` or `caller`, find the
139
144
  # first line that corresponds to a file with an assigned feature
140
145
  sig { params(backtrace: T.nilable(T::Array[String]), excluded_features: T::Array[CodeFeatures::Feature]).returns(T.nilable(CodeFeatures::Feature)) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feature_map
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Beyond Finance
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-07 00:00:00.000000000 Z
11
+ date: 2025-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: code_ownership
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '1.9'
111
+ - !ruby/object:Gem::Dependency
112
+ name: github-pages
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '232'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '232'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: railties
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -203,7 +217,9 @@ extensions: []
203
217
  extra_rdoc_files: []
204
218
  files:
205
219
  - README.md
220
+ - bin/docs
206
221
  - bin/featuremap
222
+ - bin/readme
207
223
  - lib/feature_map.rb
208
224
  - lib/feature_map/cli.rb
209
225
  - lib/feature_map/code_features.rb
@@ -215,6 +231,7 @@ files:
215
231
  - lib/feature_map/mapper.rb
216
232
  - lib/feature_map/output_color.rb
217
233
  - lib/feature_map/private.rb
234
+ - lib/feature_map/private/additional_metrics_file.rb
218
235
  - lib/feature_map/private/assignment_applicator.rb
219
236
  - lib/feature_map/private/assignment_mappers/directory_assignment.rb
220
237
  - lib/feature_map/private/assignment_mappers/feature_definition_assignment.rb