feature_map 1.2.2 → 1.2.3

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -1
  3. data/bin/featuremap +0 -1
  4. data/lib/feature_map/cli.rb +0 -2
  5. data/lib/feature_map/code_features/plugin.rb +2 -21
  6. data/lib/feature_map/code_features/plugins/identity.rb +1 -8
  7. data/lib/feature_map/code_features.rb +1 -31
  8. data/lib/feature_map/commit.rb +0 -19
  9. data/lib/feature_map/configuration.rb +40 -17
  10. data/lib/feature_map/constants.rb +3 -5
  11. data/lib/feature_map/mapper.rb +0 -26
  12. data/lib/feature_map/output_color.rb +0 -11
  13. data/lib/feature_map/private/additional_metrics_file.rb +9 -103
  14. data/lib/feature_map/private/assignment_applicator.rb +0 -12
  15. data/lib/feature_map/private/assignment_mappers/directory_assignment.rb +4 -26
  16. data/lib/feature_map/private/assignment_mappers/feature_definition_assignment.rb +1 -21
  17. data/lib/feature_map/private/assignment_mappers/feature_globs.rb +7 -40
  18. data/lib/feature_map/private/assignment_mappers/file_annotations.rb +20 -44
  19. data/lib/feature_map/private/assignments_file.rb +8 -54
  20. data/lib/feature_map/private/code_cov.rb +2 -29
  21. data/lib/feature_map/private/cyclomatic_complexity_calculator.rb +1 -7
  22. data/lib/feature_map/private/docs/index.html +2 -2
  23. data/lib/feature_map/private/documentation_site.rb +0 -16
  24. data/lib/feature_map/private/extension_loader.rb +0 -3
  25. data/lib/feature_map/private/feature_assigner.rb +0 -4
  26. data/lib/feature_map/private/feature_metrics_calculator.rb +2 -16
  27. data/lib/feature_map/private/feature_plugins/assignment.rb +0 -6
  28. data/lib/feature_map/private/glob_cache.rb +2 -29
  29. data/lib/feature_map/private/health_calculator.rb +122 -0
  30. data/lib/feature_map/private/lines_of_code_calculator.rb +10 -21
  31. data/lib/feature_map/private/metrics_file.rb +1 -25
  32. data/lib/feature_map/private/percentile_metrics_calculator.rb +117 -0
  33. data/lib/feature_map/private/release_notification_builder.rb +1 -13
  34. data/lib/feature_map/private/test_coverage_file.rb +12 -39
  35. data/lib/feature_map/private/test_pyramid_file.rb +0 -41
  36. data/lib/feature_map/private/todo_inspector.rb +16 -30
  37. data/lib/feature_map/private/validations/features_up_to_date.rb +1 -6
  38. data/lib/feature_map/private/validations/files_have_features.rb +2 -7
  39. data/lib/feature_map/private/validations/files_have_unique_features.rb +1 -6
  40. data/lib/feature_map/private.rb +7 -44
  41. data/lib/feature_map/validator.rb +0 -13
  42. data/lib/feature_map.rb +8 -49
  43. metadata +4 -44
@@ -1,4 +1,3 @@
1
- # typed: strict
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module FeatureMap
@@ -9,44 +8,21 @@ module FeatureMap
9
8
  # a variety of engineering team utilities (e.g. PR/release announcements, documentation generation, etc).
10
9
  #
11
10
  class TestCoverageFile
12
- extend T::Sig
13
-
14
- COVERAGE_RATIO = 'coverage_ratio'
15
-
16
11
  class FileContentError < StandardError; end
17
12
 
13
+ COVERAGE_RATIO = 'coverage_ratio'
18
14
  FEATURES_KEY = 'features'
19
15
 
20
- FeatureName = T.type_alias { String }
21
- CoverageStat = T.type_alias { String }
22
-
23
- FeatureCoverage = T.type_alias do
24
- T::Hash[
25
- CoverageStat,
26
- Integer
27
- ]
28
- end
29
-
30
- FeaturesContent = T.type_alias do
31
- T::Hash[
32
- FeatureName,
33
- FeatureCoverage
34
- ]
35
- end
36
-
37
- sig { params(coverage_stats: CodeCov::TestCoverageStats).void }
38
16
  def self.write!(coverage_stats)
39
17
  FileUtils.mkdir_p(path.dirname) if !path.dirname.exist?
40
18
 
41
19
  path.write([header_comment, "\n", generate_content(coverage_stats).to_yaml].join)
42
20
  end
43
21
 
44
- sig { returns(Pathname) }
45
22
  def self.path
46
23
  Pathname.pwd.join('.feature_map/test-coverage.yml')
47
24
  end
48
25
 
49
- sig { returns(String) }
50
26
  def self.header_comment
51
27
  <<~HEADER
52
28
  # STOP! - DO NOT EDIT THIS FILE MANUALLY
@@ -61,34 +37,31 @@ module FeatureMap
61
37
  HEADER
62
38
  end
63
39
 
64
- sig { params(coverage_stats: CodeCov::TestCoverageStats).returns(T::Hash[String, FeaturesContent]) }
65
40
  def self.generate_content(coverage_stats)
66
- feature_test_coverage = T.let({}, FeaturesContent)
41
+ feature_test_coverage = {}
67
42
 
68
43
  Private.feature_file_assignments.each do |feature_name, files|
69
- feature_test_coverage[feature_name] = T.let({ 'lines' => 0, 'hits' => 0, 'misses' => 0, 'coverage_ratio' => 0 }, FeatureCoverage)
44
+ feature_test_coverage[feature_name] = { 'lines' => 0, 'hits' => 0, 'misses' => 0, 'coverage_ratio' => 0 }
70
45
 
71
- files.each_with_object(T.must(feature_test_coverage[feature_name])) do |file_path, coverage|
46
+ files.each_with_object(feature_test_coverage[feature_name]) do |file_path, coverage|
72
47
  next unless coverage_stats[file_path]
73
48
 
74
- coverage['lines'] = T.must(coverage['lines']) + (T.must(coverage_stats[file_path])['lines'] || 0)
75
- coverage['hits'] = T.must(coverage['hits']) + (T.must(coverage_stats[file_path])['hits'] || 0)
76
- coverage['misses'] = T.must(coverage['misses']) + (T.must(coverage_stats[file_path])['misses'] || 0)
49
+ coverage['lines'] = coverage['lines'] + (coverage_stats[file_path]['lines'] || 0)
50
+ coverage['hits'] = coverage['hits'] + (coverage_stats[file_path]['hits'] || 0)
51
+ coverage['misses'] = coverage['misses'] + (coverage_stats[file_path]['misses'] || 0)
77
52
 
78
53
  coverage
79
54
  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
55
+ feature_test_coverage[feature_name][COVERAGE_RATIO] = if feature_test_coverage[feature_name]['lines'].zero?
56
+ 0
57
+ else
58
+ ((feature_test_coverage[feature_name]['hits'].to_f / feature_test_coverage[feature_name]['lines']) * 100).round
59
+ end
86
60
  end
87
61
 
88
62
  { FEATURES_KEY => feature_test_coverage }
89
63
  end
90
64
 
91
- sig { returns(FeaturesContent) }
92
65
  def self.load_features!
93
66
  test_coverage_content = YAML.load_file(path)
94
67
 
@@ -1,4 +1,3 @@
1
- # typed: strict
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module FeatureMap
@@ -9,37 +8,10 @@ module FeatureMap
9
8
  # (e.g. PR/release announcements, documentation generation, etc).
10
9
  #
11
10
  class TestPyramidFile
12
- extend T::Sig
13
-
14
11
  class FileContentError < StandardError; end
15
12
 
16
13
  FEATURES_KEY = 'features'
17
14
 
18
- FeatureName = T.type_alias { String }
19
- PyramidStat = T.type_alias { String }
20
-
21
- FeaturePyramid = T.type_alias do
22
- T::Hash[
23
- PyramidStat,
24
- Integer
25
- ]
26
- end
27
-
28
- FeaturesContent = T.type_alias do
29
- T::Hash[
30
- FeatureName,
31
- FeaturePyramid
32
- ]
33
- end
34
-
35
- sig do
36
- params(
37
- unit_examples: T::Array[T::Hash[T.untyped, T.untyped]],
38
- integration_examples: T::Array[T::Hash[T.untyped, T.untyped]],
39
- regression_examples: T::Array[T::Hash[T.untyped, T.untyped]],
40
- regression_assignments: T::Hash[T.untyped, T.untyped]
41
- ).void
42
- end
43
15
  def self.write!(unit_examples, integration_examples, regression_examples, regression_assignments)
44
16
  FileUtils.mkdir_p(path.dirname) if !path.dirname.exist?
45
17
 
@@ -56,12 +28,10 @@ module FeatureMap
56
28
  path.write([header_comment, "\n", { FEATURES_KEY => content }.to_yaml].join)
57
29
  end
58
30
 
59
- sig { returns(Pathname) }
60
31
  def self.path
61
32
  Pathname.pwd.join('.feature_map/test-pyramid.yml')
62
33
  end
63
34
 
64
- sig { returns(String) }
65
35
  def self.header_comment
66
36
  <<~HEADER
67
37
  # STOP! - DO NOT EDIT THIS FILE MANUALLY
@@ -76,14 +46,6 @@ module FeatureMap
76
46
  HEADER
77
47
  end
78
48
 
79
- sig do
80
- params(
81
- unit_examples: T::Hash[T.untyped, T.untyped],
82
- integration_examples: T::Hash[T.untyped, T.untyped],
83
- regression_examples: T::Hash[T.untyped, T.untyped],
84
- regression_file_assignments: T::Hash[T.untyped, T.untyped]
85
- ).returns(FeaturesContent)
86
- end
87
49
  def self.generate_content(unit_examples, integration_examples, regression_examples, regression_file_assignments)
88
50
  Private.feature_file_assignments.reduce({}) do |content, (feature_name, files)|
89
51
  regression_files = regression_file_assignments[feature_name] || []
@@ -118,7 +80,6 @@ module FeatureMap
118
80
  end
119
81
  end
120
82
 
121
- sig { returns(FeaturesContent) }
122
83
  def self.load_features!
123
84
  test_coverage_content = YAML.load_file(path)
124
85
 
@@ -131,14 +92,12 @@ module FeatureMap
131
92
  raise FileContentError, "No feature test coverage file found at #{path}. Use `bin/featuremap test_coverage` to generate it and try again."
132
93
  end
133
94
 
134
- sig { params(examples: T.nilable(T::Array[T::Hash[T.untyped, T.untyped]])).returns(T::Array[Integer]) }
135
95
  def self.split(examples)
136
96
  return [0, 0] if examples.nil?
137
97
 
138
98
  examples.partition { |ex| ex['status'] == 'passed' }.map(&:count)
139
99
  end
140
100
 
141
- sig { params(pathlike: String).returns(String) }
142
101
  def self.filepath(pathlike)
143
102
  File
144
103
  .join(File.dirname(pathlike), File.basename(pathlike, '.*'))
@@ -1,50 +1,36 @@
1
- # typed: strict
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module FeatureMap
5
4
  module Private
6
5
  class TodoInspector
7
- extend T::Sig
8
-
9
- ENTERING_COMMENT = T.let(
10
- /
11
- (#{(Constants::SINGLE_LINE_COMMENT_PATTERNS + Constants::MULTILINE_COMMENT_START_PATTERNS).join('|')})
12
- /x.freeze,
13
- Regexp
14
- )
15
-
16
- EXITING_COMMENT = T.let(
17
- /
18
- (#{(Constants::SINGLE_LINE_COMMENT_PATTERNS + Constants::MULTILINE_COMMENT_END_PATTERNS).join('|')})
19
- /x.freeze,
20
- Regexp
21
- )
22
-
23
- TODO_PATTERN = T.let(
24
- /
25
- TODO:?\s* # TODO with optional colon with whitespace
26
- (?<content>.*?) # The actual TODO content
27
- (#{Constants::MULTILINE_COMMENT_END_PATTERNS.join('|')})?$ # ignores comment end patterns
28
- /xi.freeze,
29
- Regexp
30
- )
31
-
32
- sig { params(file_path: String).void }
6
+ ENTERING_COMMENT = /
7
+ (#{(Constants::SINGLE_LINE_COMMENT_PATTERNS + Constants::MULTILINE_COMMENT_START_PATTERNS).join('|')})
8
+ /x.freeze
9
+
10
+ EXITING_COMMENT = /
11
+ (#{(Constants::SINGLE_LINE_COMMENT_PATTERNS + Constants::MULTILINE_COMMENT_END_PATTERNS).join('|')})
12
+ /x.freeze
13
+
14
+ TODO_PATTERN = /
15
+ TODO:?\s* # TODO with optional colon with whitespace
16
+ (?<content>.*?) # The actual TODO content
17
+ (#{Constants::MULTILINE_COMMENT_END_PATTERNS.join('|')})?$ # ignores comment end patterns
18
+ /xi.freeze
19
+
33
20
  def initialize(file_path)
34
21
  @file_path = file_path
35
22
  end
36
23
 
37
- sig { returns(T::Hash[String, String]) }
38
24
  def calculate
39
25
  todos = {}
40
26
  content = File.read(@file_path)
41
- in_comment = T.let(false, T::Boolean)
27
+ in_comment = false
42
28
 
43
29
  content.each_line.with_index do |line, index|
44
30
  in_comment ||= line.match?(ENTERING_COMMENT)
45
31
 
46
32
  if in_comment && (match = line.match(TODO_PATTERN))
47
- todos["#{@file_path}:#{index + 1}"] = T.must(match[:content]).strip
33
+ todos["#{@file_path}:#{index + 1}"] = match[:content].strip
48
34
  end
49
35
 
50
36
  in_comment &&= !line.match?(EXITING_COMMENT)
@@ -1,14 +1,9 @@
1
- # typed: strict
2
-
3
1
  module FeatureMap
4
2
  module Private
5
3
  module Validations
6
4
  class FeaturesUpToDate
7
- extend T::Sig
8
- extend T::Helpers
9
5
  include Validator
10
6
 
11
- sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
12
7
  def validation_errors(files:, autocorrect: true, stage_changes: true)
13
8
  return [] if Private.configuration.skip_features_validation
14
9
 
@@ -16,7 +11,7 @@ module FeatureMap
16
11
  expected_content_lines = AssignmentsFile.expected_contents_lines
17
12
 
18
13
  features_file_up_to_date = actual_content_lines == expected_content_lines
19
- errors = T.let([], T::Array[String])
14
+ errors = []
20
15
 
21
16
  if !features_file_up_to_date
22
17
  if autocorrect
@@ -1,16 +1,11 @@
1
- # typed: strict
2
-
3
1
  require 'code_ownership'
4
2
 
5
3
  module FeatureMap
6
4
  module Private
7
5
  module Validations
8
6
  class FilesHaveFeatures
9
- extend T::Sig
10
- extend T::Helpers
11
7
  include Validator
12
8
 
13
- sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
14
9
  def validation_errors(files:, autocorrect: true, stage_changes: true)
15
10
  cache = Private.glob_cache
16
11
  file_mappings = cache.mapper_descriptions_that_map_files(files)
@@ -18,14 +13,14 @@ module FeatureMap
18
13
  mapper_descriptions.count.zero?
19
14
  end
20
15
 
21
- errors = T.let([], T::Array[String])
16
+ errors = []
22
17
 
23
18
  # When a set of teams are configured that require assignments, ignore any files NOT
24
19
  # assigned to one of these teams.
25
20
  unless Private.configuration.require_assignment_for_teams.nil?
26
21
  files_not_mapped_at_all.filter! do |file, _mappers|
27
22
  file_team = CodeOwnership.for_file(file)
28
- file_team && T.must(Private.configuration.require_assignment_for_teams).include?(file_team.name)
23
+ file_team && Private.configuration.require_assignment_for_teams.include?(file_team.name)
29
24
  end
30
25
  end
31
26
 
@@ -1,14 +1,9 @@
1
- # typed: strict
2
-
3
1
  module FeatureMap
4
2
  module Private
5
3
  module Validations
6
4
  class FilesHaveUniqueFeatures
7
- extend T::Sig
8
- extend T::Helpers
9
5
  include Validator
10
6
 
11
- sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
12
7
  def validation_errors(files:, autocorrect: true, stage_changes: true)
13
8
  cache = Private.glob_cache
14
9
  file_mappings = cache.mapper_descriptions_that_map_files(files)
@@ -16,7 +11,7 @@ module FeatureMap
16
11
  mapper_descriptions.count > 1
17
12
  end
18
13
 
19
- errors = T.let([], T::Array[String])
14
+ errors = []
20
15
 
21
16
  if files_mapped_by_multiple_mappers.any?
22
17
  errors << <<~MSG
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # typed: strict
4
-
5
3
  require 'code_ownership'
6
4
  require 'csv'
7
5
 
@@ -11,6 +9,8 @@ require 'feature_map/private/cyclomatic_complexity_calculator'
11
9
  require 'feature_map/private/lines_of_code_calculator'
12
10
  require 'feature_map/private/todo_inspector'
13
11
  require 'feature_map/private/feature_metrics_calculator'
12
+ require 'feature_map/private/health_calculator'
13
+ require 'feature_map/private/percentile_metrics_calculator'
14
14
  require 'feature_map/private/assignments_file'
15
15
  require 'feature_map/private/assignment_applicator'
16
16
  require 'feature_map/private/metrics_file'
@@ -33,45 +33,29 @@ require 'feature_map/private/release_notification_builder'
33
33
 
34
34
  module FeatureMap
35
35
  module Private
36
- extend T::Sig
37
-
38
- FeatureName = T.type_alias { String }
39
- FileList = T.type_alias { T::Array[String] }
40
- FeatureFiles = T.type_alias do
41
- T::Hash[
42
- FeatureName,
43
- FileList
44
- ]
45
- end
46
-
47
- sig { params(assignments_file_path: String).void }
48
36
  def self.apply_assignments!(assignments_file_path)
49
37
  assignments = CSV.read(assignments_file_path)
50
38
  AssignmentApplicator.apply_assignments!(assignments)
51
39
  end
52
40
 
53
- sig { returns(Configuration) }
54
41
  def self.configuration
55
- @configuration ||= T.let(@configuration, T.nilable(Configuration))
56
42
  @configuration ||= Configuration.fetch
57
43
  end
58
44
 
59
45
  # This is just an alias for `configuration` that makes it more explicit what we're doing instead of just calling `configuration`.
60
46
  # This is necessary because configuration may contain extensions of feature map, so those extensions should be loaded prior to
61
47
  # calling APIs that provide feature assignment information.
62
- sig { returns(Configuration) }
48
+
63
49
  def self.load_configuration!
64
50
  configuration
65
51
  end
66
52
 
67
- sig { void }
68
53
  def self.bust_caches!
69
54
  @configuration = nil
70
55
  @tracked_files = nil
71
56
  @glob_cache = nil
72
57
  end
73
58
 
74
- sig { params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).void }
75
59
  def self.validate!(files:, autocorrect: true, stage_changes: true)
76
60
  AssignmentsFile.update_cache!(files) if AssignmentsFile.use_features_cache?
77
61
 
@@ -91,7 +75,6 @@ module FeatureMap
91
75
  MetricsFile.write!
92
76
  end
93
77
 
94
- sig { params(git_ref: T.nilable(String)).void }
95
78
  def self.generate_docs!(git_ref)
96
79
  feature_assignments = AssignmentsFile.load_features!
97
80
  feature_metrics = MetricsFile.load_features!
@@ -115,18 +98,10 @@ module FeatureMap
115
98
  feature_test_pyramid,
116
99
  feature_additional_metrics,
117
100
  configuration.raw_hash,
118
- T.must(git_ref || configuration.repository['main_branch'])
101
+ git_ref || configuration.repository['main_branch']
119
102
  )
120
103
  end
121
104
 
122
- sig do
123
- params(
124
- unit_path: String,
125
- integration_path: String,
126
- regression_path: T.nilable(String),
127
- regression_assignments_path: T.nilable(String)
128
- ).void
129
- end
130
105
  def self.generate_test_pyramid!(unit_path, integration_path, regression_path, regression_assignments_path)
131
106
  unit_examples = JSON.parse(File.read(unit_path))&.fetch('examples')
132
107
  integration_examples = JSON.parse(File.read(integration_path))&.fetch('examples')
@@ -135,14 +110,12 @@ module FeatureMap
135
110
  TestPyramidFile.write!(unit_examples, integration_examples, regression_examples, regression_assignments)
136
111
  end
137
112
 
138
- sig { params(commit_sha: String, code_cov_token: String).void }
139
113
  def self.gather_test_coverage!(commit_sha, code_cov_token)
140
114
  coverage_stats = CodeCov.fetch_coverage_stats(commit_sha, code_cov_token)
141
115
 
142
116
  TestCoverageFile.write!(coverage_stats)
143
117
  end
144
118
 
145
- sig { void }
146
119
  def self.generate_additional_metrics!
147
120
  feature_metrics = MetricsFile.load_features!
148
121
  feature_test_coverage = TestCoverageFile.path.exist? ? TestCoverageFile.load_features! : {}
@@ -152,7 +125,6 @@ module FeatureMap
152
125
 
153
126
  # Returns a string version of the relative path to a Rails constant,
154
127
  # or nil if it can't find something
155
- sig { params(klass: T.nilable(T.any(T::Class[T.anything], Module))).returns(T.nilable(String)) }
156
128
  def self.path_from_klass(klass)
157
129
  if klass
158
130
  path = Object.const_source_location(klass.to_s)&.first
@@ -167,13 +139,10 @@ module FeatureMap
167
139
  #
168
140
  # The output of this function is string pathnames relative to the root.
169
141
  #
170
- sig { returns(T::Array[String]) }
171
142
  def self.tracked_files
172
- @tracked_files ||= T.let(@tracked_files, T.nilable(T::Array[String]))
173
143
  @tracked_files ||= Dir.glob(configuration.assigned_globs) - Dir.glob(configuration.unassigned_globs)
174
144
  end
175
145
 
176
- sig { params(file: String).returns(T::Boolean) }
177
146
  def self.file_tracked?(file)
178
147
  # Another way to accomplish this is
179
148
  # (Dir.glob(configuration.assigned_globs) - Dir.glob(configuration.unassigned_globs)).include?(file)
@@ -190,7 +159,6 @@ module FeatureMap
190
159
  in_assigned_globs && !in_unassigned_globs && File.exist?(file)
191
160
  end
192
161
 
193
- sig { params(feature_name: String, location_of_reference: String).returns(CodeFeatures::Feature) }
194
162
  def self.find_feature!(feature_name, location_of_reference)
195
163
  found_feature = CodeFeatures.find(feature_name)
196
164
  if found_feature.nil?
@@ -200,9 +168,7 @@ module FeatureMap
200
168
  end
201
169
  end
202
170
 
203
- sig { returns(GlobCache) }
204
171
  def self.glob_cache
205
- @glob_cache ||= T.let(@glob_cache, T.nilable(GlobCache))
206
172
  @glob_cache ||= if AssignmentsFile.use_features_cache?
207
173
  AssignmentsFile.to_glob_cache
208
174
  else
@@ -210,20 +176,18 @@ module FeatureMap
210
176
  end
211
177
  end
212
178
 
213
- sig { returns(FeatureFiles) }
214
179
  def self.feature_file_assignments
215
- glob_cache.raw_cache_contents.values.each_with_object(T.let({}, FeatureFiles)) do |assignment_map_cache, feature_files|
180
+ glob_cache.raw_cache_contents.values.each_with_object({}) do |assignment_map_cache, feature_files|
216
181
  assignment_map_cache.to_h.each do |path, feature|
217
- feature_files[feature.name] ||= T.let([], FileList)
182
+ feature_files[feature.name] ||= []
218
183
  files = Dir.glob(path).reject { |glob_entry| File.directory?(glob_entry) }
219
- files.each { |file| T.must(feature_files[feature.name]) << file }
184
+ files.each { |file| feature_files[feature.name] << file }
220
185
  end
221
186
 
222
187
  feature_files
223
188
  end
224
189
  end
225
190
 
226
- sig { params(feature: CodeFeatures::Feature).returns(T::Array[CodeTeams::Team]) }
227
191
  def self.all_teams_for_feature(feature)
228
192
  return [] if configuration.skip_code_ownership
229
193
 
@@ -235,7 +199,6 @@ module FeatureMap
235
199
  feature_files.map { |file| CodeOwnership.for_file(file) }.compact.uniq
236
200
  end
237
201
 
238
- sig { params(commits_by_feature: CommitsByFeature).returns(ReleaseNotificationBuilder::BlockKitPayload) }
239
202
  def self.generate_release_notification(commits_by_feature)
240
203
  ReleaseNotificationBuilder.build(commits_by_feature)
241
204
  end
@@ -1,26 +1,13 @@
1
- # typed: strict
2
-
3
1
  module FeatureMap
4
2
  module Validator
5
- extend T::Sig
6
- extend T::Helpers
7
-
8
- interface!
9
-
10
- sig { abstract.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
11
3
  def validation_errors(files:, autocorrect: true, stage_changes: true); end
12
4
 
13
5
  class << self
14
- extend T::Sig
15
-
16
- sig { params(base: T::Class[Validator]).void }
17
6
  def included(base)
18
- @validators ||= T.let(@validators, T.nilable(T::Array[T::Class[Validator]]))
19
7
  @validators ||= []
20
8
  @validators << base
21
9
  end
22
10
 
23
- sig { returns(T::Array[Validator]) }
24
11
  def all
25
12
  (@validators || []).map(&:new)
26
13
  end