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,12 +1,8 @@
1
- # typed: strict
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module FeatureMap
5
4
  module Private
6
5
  class AssignmentApplicator
7
- extend T::Sig
8
-
9
- sig { params(assignments: T::Array[T::Array[T.nilable(String)]]).void }
10
6
  def self.apply_assignments!(assignments)
11
7
  file_to_feature_map = map_files_to_feature
12
8
  assignments.each do |(filepath, feature)|
@@ -17,7 +13,6 @@ module FeatureMap
17
13
  end
18
14
  end
19
15
 
20
- sig { params(filepath: String, feature: String).void }
21
16
  def self.apply_assignment(filepath, feature)
22
17
  return apply_to_directory(filepath, feature) if File.directory?(filepath)
23
18
 
@@ -42,7 +37,6 @@ module FeatureMap
42
37
  end
43
38
  end
44
39
 
45
- sig { params(file: T::Array[String], filepath: String, feature: String).void }
46
40
  def self.apply_to_apex(file, filepath, feature)
47
41
  File.open(filepath, 'w') do |f|
48
42
  f.write("// @feature #{feature}\n\n")
@@ -50,14 +44,12 @@ module FeatureMap
50
44
  end
51
45
  end
52
46
 
53
- sig { params(filepath: String, feature: String).void }
54
47
  def self.apply_to_directory(filepath, feature)
55
48
  feature_path = File.join(filepath, '.feature')
56
49
 
57
50
  File.write(feature_path, "#{feature}\n")
58
51
  end
59
52
 
60
- sig { params(file: T::Array[String], filepath: String, feature: String).void }
61
53
  def self.apply_to_html(file, filepath, feature)
62
54
  File.open(filepath, 'w') do |f|
63
55
  f.write("<!-- @feature #{feature} -->\n\n")
@@ -65,7 +57,6 @@ module FeatureMap
65
57
  end
66
58
  end
67
59
 
68
- sig { params(file: T::Array[String], filepath: String, feature: String).void }
69
60
  def self.apply_to_javascript(file, filepath, feature)
70
61
  File.open(filepath, 'w') do |f|
71
62
  f.write("// @feature #{feature}\n\n")
@@ -73,7 +64,6 @@ module FeatureMap
73
64
  end
74
65
  end
75
66
 
76
- sig { params(file: T::Array[String], filepath: String, feature: String).void }
77
67
  def self.apply_to_ruby(file, filepath, feature)
78
68
  File.open(filepath, 'w') do |f|
79
69
  # NOTE: No spacing newline; doing so would separate
@@ -86,7 +76,6 @@ module FeatureMap
86
76
  end
87
77
  end
88
78
 
89
- sig { params(file: T::Array[String], filepath: String, feature: String).void }
90
79
  def self.apply_to_xml(file, filepath, feature)
91
80
  # NOTE: Installation of top-level comments in some XML files (notably, in Salesforce)
92
81
  # breaks parsing. Instead, we'll insert them right after the opening XML declaration.
@@ -99,7 +88,6 @@ module FeatureMap
99
88
  end
100
89
  end
101
90
 
102
- sig { returns(T::Hash[String, String]) }
103
91
  def self.map_files_to_feature
104
92
  Private.feature_file_assignments.reduce({}) do |content, (feature_name, files)|
105
93
  mapped_files = files.to_h { |f| [f, feature_name] }
@@ -1,29 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # typed: true
4
-
5
3
  module FeatureMap
6
4
  module Private
7
5
  module AssignmentMappers
8
6
  class DirectoryAssignment
9
- extend T::Sig
10
7
  include Mapper
11
8
 
12
9
  FEATURE_DIRECTORY_ASSIGNMENT_FILE_NAME = '.feature'
13
10
 
14
- @@directory_cache = T.let({}, T::Hash[String, T.nilable(CodeFeatures::Feature)]) # rubocop:disable Style/ClassVars
11
+ @@directory_cache = {} # rubocop:disable Style/ClassVars
15
12
 
16
- sig do
17
- override.params(file: String)
18
- .returns(T.nilable(CodeFeatures::Feature))
19
- end
20
13
  def map_file_to_feature(file)
21
14
  map_file_to_relevant_feature(file)
22
15
  end
23
16
 
24
- sig do
25
- override.params(cache: GlobsToAssignedFeatureMap, files: T::Array[String]).returns(GlobsToAssignedFeatureMap)
26
- end
27
17
  def update_cache(cache, files)
28
18
  globs_to_feature(files)
29
19
  end
@@ -36,14 +26,8 @@ module FeatureMap
36
26
  # but in practice this is not of consequence because in reality we never really want to generate feature assignments for only a
37
27
  # subset of files, but rather we want feature assignments for all files.
38
28
  #
39
- sig do
40
- override.params(files: T::Array[String])
41
- .returns(T::Hash[String, CodeFeatures::Feature])
42
- end
43
29
  def globs_to_feature(files)
44
- # The T.unsafe is because the upstream RBI is wrong for Pathname.glob
45
- T
46
- .unsafe(Pathname)
30
+ Pathname
47
31
  .glob(File.join('**/', FEATURE_DIRECTORY_ASSIGNMENT_FILE_NAME))
48
32
  .map(&:cleanpath)
49
33
  .each_with_object({}) do |pathname, res|
@@ -53,19 +37,16 @@ module FeatureMap
53
37
  end
54
38
  end
55
39
 
56
- sig { override.returns(String) }
57
40
  def description
58
41
  'Feature Assigned in .feature'
59
42
  end
60
43
 
61
- sig { override.void }
62
44
  def bust_caches!
63
45
  @@directory_cache = {} # rubocop:disable Style/ClassVars
64
46
  end
65
47
 
66
48
  private
67
49
 
68
- sig { params(file: Pathname).returns(CodeFeatures::Feature) }
69
50
  def feature_for_directory_assignment_file(file)
70
51
  raw_feature_value = File.foreach(file).first.strip
71
52
 
@@ -80,10 +61,9 @@ module FeatureMap
80
61
  # and `.feature` in that order, stopping at the first file to actually exist.
81
62
  # If the provided file is a directory, it will look for `.feature` in that directory and then upwards.
82
63
  # We do additional caching so that we don't have to check for file existence every time.
83
- sig { params(file: String).returns(T.nilable(CodeFeatures::Feature)) }
84
64
  def map_file_to_relevant_feature(file)
85
65
  file_path = Pathname.new(file)
86
- feature = T.let(nil, T.nilable(CodeFeatures::Feature))
66
+ feature = nil
87
67
 
88
68
  if File.directory?(file)
89
69
  feature = get_feature_from_assignment_file_within_directory(file_path)
@@ -97,7 +77,7 @@ module FeatureMap
97
77
 
98
78
  (path_components.length - 1).downto(0).each do |i|
99
79
  feature = get_feature_from_assignment_file_within_directory(
100
- Pathname.new(File.join(*T.unsafe(path_components[0...i])))
80
+ Pathname.new(File.join(*path_components[0...i]))
101
81
  )
102
82
  return feature unless feature.nil?
103
83
  end
@@ -105,7 +85,6 @@ module FeatureMap
105
85
  feature
106
86
  end
107
87
 
108
- sig { params(directory: Pathname).returns(T.nilable(CodeFeatures::Feature)) }
109
88
  def get_feature_from_assignment_file_within_directory(directory)
110
89
  potential_directory_assignment_file = directory.join(FEATURE_DIRECTORY_ASSIGNMENT_FILE_NAME)
111
90
 
@@ -125,7 +104,6 @@ module FeatureMap
125
104
  feature
126
105
  end
127
106
 
128
- sig { params(file: Pathname).returns(String) }
129
107
  def glob_for_directory_assignment_file(file)
130
108
  unescaped = file.dirname.cleanpath.join('**/**').to_s
131
109
 
@@ -1,21 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # typed: true
4
-
5
3
  module FeatureMap
6
4
  module Private
7
5
  module AssignmentMappers
8
6
  class FeatureDefinitionAssignment
9
- extend T::Sig
10
7
  include Mapper
11
8
 
12
- @@map_files_to_features = T.let(@map_files_to_features, T.nilable(T::Hash[String, CodeFeatures::Feature])) # rubocop:disable Style/ClassVars
9
+ @@map_files_to_features = @map_files_to_features # rubocop:disable Style/ClassVars
13
10
  @@map_files_to_features = {} # rubocop:disable Style/ClassVars
14
11
 
15
- sig do
16
- params(files: T::Array[String])
17
- .returns(T::Hash[String, CodeFeatures::Feature])
18
- end
19
12
  def map_files_to_features(files)
20
13
  return @@map_files_to_features if @@map_files_to_features&.keys && @@map_files_to_features.keys.count.positive?
21
14
 
@@ -24,20 +17,12 @@ module FeatureMap
24
17
  end
25
18
  end
26
19
 
27
- sig do
28
- override.params(file: String)
29
- .returns(T.nilable(CodeFeatures::Feature))
30
- end
31
20
  def map_file_to_feature(file)
32
21
  return nil if Private.configuration.ignore_feature_definitions
33
22
 
34
23
  map_files_to_features([file])[file]
35
24
  end
36
25
 
37
- sig do
38
- override.params(files: T::Array[String])
39
- .returns(T::Hash[String, CodeFeatures::Feature])
40
- end
41
26
  def globs_to_feature(files)
42
27
  return {} if Private.configuration.ignore_feature_definitions
43
28
 
@@ -46,19 +31,14 @@ module FeatureMap
46
31
  end
47
32
  end
48
33
 
49
- sig { override.void }
50
34
  def bust_caches!
51
35
  @@map_files_to_features = {} # rubocop:disable Style/ClassVars
52
36
  end
53
37
 
54
- sig do
55
- override.params(cache: GlobsToAssignedFeatureMap, files: T::Array[String]).returns(GlobsToAssignedFeatureMap)
56
- end
57
38
  def update_cache(cache, files)
58
39
  globs_to_feature(files)
59
40
  end
60
41
 
61
- sig { override.returns(String) }
62
42
  def description
63
43
  'Feature definition file assignment'
64
44
  end
@@ -1,22 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # typed: true
4
-
5
3
  module FeatureMap
6
4
  module Private
7
5
  module AssignmentMappers
8
6
  class FeatureGlobs
9
- extend T::Sig
10
7
  include Mapper
11
8
  include Validator
12
9
 
13
- @@map_files_to_features = T.let(@map_files_to_features, T.nilable(T::Hash[String, FeatureMap::CodeFeatures::Feature])) # rubocop:disable Style/ClassVars
10
+ @@map_files_to_features = @map_files_to_features # rubocop:disable Style/ClassVars
14
11
  @@map_files_to_features = {} # rubocop:disable Style/ClassVars
15
12
 
16
- sig do
17
- params(files: T::Array[String])
18
- .returns(T::Hash[String, FeatureMap::CodeFeatures::Feature])
19
- end
20
13
  def map_files_to_features(files)
21
14
  return @@map_files_to_features if @@map_files_to_features&.keys && @@map_files_to_features.keys.count.positive?
22
15
 
@@ -29,17 +22,8 @@ module FeatureMap
29
22
  end
30
23
  end
31
24
 
32
- class MappingContext < T::Struct
33
- const :glob, String
34
- const :feature, FeatureMap::CodeFeatures::Feature
35
- end
36
-
37
- class GlobOverlap < T::Struct
38
- extend T::Sig
39
-
40
- const :mapping_contexts, T::Array[MappingContext]
41
-
42
- sig { returns(String) }
25
+ MappingContext = Struct.new(:glob, :feature, keyword_init: true)
26
+ GlobOverlap = Struct.new(:mapping_contexts, keyword_init: true) do
43
27
  def description
44
28
  # These are sorted only to prevent non-determinism in output between local and CI environments.
45
29
  sorted_contexts = mapping_contexts.sort_by { |context| context.feature.config_yml.to_s }
@@ -51,21 +35,18 @@ module FeatureMap
51
35
  end
52
36
  end
53
37
 
54
- sig do
55
- returns(T::Array[GlobOverlap])
56
- end
57
38
  def find_overlapping_globs
58
- mapped_files = T.let({}, T::Hash[String, T::Array[MappingContext]])
39
+ mapped_files = {}
59
40
  FeatureMap::CodeFeatures.all.each_with_object({}) do |feature, _map|
60
41
  FeaturePlugins::Assignment.for(feature).assigned_globs.each do |glob|
61
42
  Dir.glob(glob).each do |filename|
62
43
  mapped_files[filename] ||= []
63
- T.must(mapped_files[filename]) << MappingContext.new(glob: glob, feature: feature)
44
+ mapped_files[filename] << MappingContext.new(glob: glob, feature: feature)
64
45
  end
65
46
  end
66
47
  end
67
48
 
68
- overlaps = T.let([], T::Array[GlobOverlap])
49
+ overlaps = []
69
50
  mapped_files.each_value do |mapping_contexts|
70
51
  if mapping_contexts.count > 1
71
52
  overlaps << GlobOverlap.new(mapping_contexts: mapping_contexts)
@@ -79,25 +60,14 @@ module FeatureMap
79
60
  end
80
61
  end
81
62
 
82
- sig do
83
- override.params(file: String)
84
- .returns(T.nilable(FeatureMap::CodeFeatures::Feature))
85
- end
86
63
  def map_file_to_feature(file)
87
64
  map_files_to_features([file])[file]
88
65
  end
89
66
 
90
- sig do
91
- override.params(cache: GlobsToAssignedFeatureMap, files: T::Array[String]).returns(GlobsToAssignedFeatureMap)
92
- end
93
67
  def update_cache(cache, files)
94
68
  globs_to_feature(files)
95
69
  end
96
70
 
97
- sig do
98
- override.params(files: T::Array[String])
99
- .returns(T::Hash[String, FeatureMap::CodeFeatures::Feature])
100
- end
101
71
  def globs_to_feature(files)
102
72
  FeatureMap::CodeFeatures.all.each_with_object({}) do |feature, map|
103
73
  FeaturePlugins::Assignment.for(feature).assigned_globs.each do |assigned_glob|
@@ -106,21 +76,18 @@ module FeatureMap
106
76
  end
107
77
  end
108
78
 
109
- sig { override.void }
110
79
  def bust_caches!
111
80
  @@map_files_to_features = {} # rubocop:disable Style/ClassVars
112
81
  end
113
82
 
114
- sig { override.returns(String) }
115
83
  def description
116
84
  'Feature-specific assigned globs'
117
85
  end
118
86
 
119
- sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
120
87
  def validation_errors(files:, autocorrect: true, stage_changes: true)
121
88
  overlapping_globs = AssignmentMappers::FeatureGlobs.new.find_overlapping_globs
122
89
 
123
- errors = T.let([], T::Array[String])
90
+ errors = []
124
91
 
125
92
  if overlapping_globs.any?
126
93
  errors << <<~MSG
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # typed: strict
4
-
5
3
  module FeatureMap
6
4
  module Private
7
5
  module AssignmentMappers
@@ -15,45 +13,31 @@ module FeatureMap
15
13
  # ...
16
14
  # }
17
15
  class FileAnnotations
18
- extend T::Sig
19
16
  include Mapper
20
17
 
21
18
  # NOTE: regex 'x' arg ignores whitespace within the _construction_ of the regex.
22
19
  # regex 'm' arg allows the regex to _execute_ on multiline strings.
23
- SINGLE_LINE_ANNOTATION_PATTERN = T.let(
24
- /
25
- \s* # Any amount of whitespace
26
- (#{Constants::SINGLE_LINE_COMMENT_PATTERNS.join('|')}) # Single line comment start
27
- \s* # Any amount of whitespace, not including newlines
28
- @feature\s # We find the feature annotation followed by one space
29
- (?<feature>.*?$) # A named capture grabs the rest as the feature until the line ends
30
- /x.freeze,
31
- Regexp
32
- )
33
- MULTILINE_ANNOTATION_PATTERN = T.let(
34
- /
35
- (?:#{Constants::MULTILINE_COMMENT_START_PATTERNS.join('|')}) # Any comment start
36
- .*? # Followed by any characters, including newlines, until...
37
- @feature\s # We find the feature annotation followed by one space
38
- (?<feature>.*?$) # A named capture grabs the rest as the feature until the line ends
39
- /xm.freeze,
40
- Regexp
41
- )
20
+ SINGLE_LINE_ANNOTATION_PATTERN = /
21
+ \s* # Any amount of whitespace
22
+ (#{Constants::SINGLE_LINE_COMMENT_PATTERNS.join('|')}) # Single line comment start
23
+ \s* # Any amount of whitespace, not including newlines
24
+ @feature\s # We find the feature annotation followed by one space
25
+ (?<feature>.*?$) # A named capture grabs the rest as the feature until the line ends
26
+ /x.freeze
27
+
28
+ MULTILINE_ANNOTATION_PATTERN = /
29
+ (?:#{Constants::MULTILINE_COMMENT_START_PATTERNS.join('|')}) # Any comment start
30
+ .*? # Followed by any characters, including newlines, until...
31
+ @feature\s # We find the feature annotation followed by one space
32
+ (?<feature>.*?$) # A named capture grabs the rest as the feature until the line ends
33
+ /xm.freeze
34
+
42
35
  DESCRIPTION = 'Annotations at the top of file'
43
36
 
44
- sig do
45
- override.params(file: String)
46
- .returns(T.nilable(CodeFeatures::Feature))
47
- end
48
37
  def map_file_to_feature(file)
49
38
  file_annotation_based_feature(file)
50
39
  end
51
40
 
52
- sig do
53
- override
54
- .params(files: T::Array[String])
55
- .returns(T::Hash[String, CodeFeatures::Feature])
56
- end
57
41
  def globs_to_feature(files)
58
42
  files.each_with_object({}) do |filename_relative_to_root, mapping|
59
43
  feature = file_annotation_based_feature(filename_relative_to_root)
@@ -63,9 +47,6 @@ module FeatureMap
63
47
  end
64
48
  end
65
49
 
66
- sig do
67
- override.params(cache: GlobsToAssignedFeatureMap, files: T::Array[String]).returns(GlobsToAssignedFeatureMap)
68
- end
69
50
  def update_cache(cache, files)
70
51
  # We map files to nil features so that files whose annotation have been removed will be properly
71
52
  # overwritten (i.e. removed) from the cache.
@@ -89,23 +70,21 @@ module FeatureMap
89
70
  cache
90
71
  end
91
72
 
92
- sig { params(lines: T::Array[String]).returns(T.nilable(String)) }
93
73
  def identify_feature_from(lines)
94
74
  matched_single_line_feature = lines.join("\n").match(SINGLE_LINE_ANNOTATION_PATTERN)
95
75
  matched_multiline_feature = lines.join("\n").match(MULTILINE_ANNOTATION_PATTERN)
96
76
  matched_feature = matched_single_line_feature || matched_multiline_feature
97
77
  return if matched_feature.nil?
98
78
 
99
- T.must(matched_feature
100
- .values_at(:feature)
101
- .first)
102
- .gsub(/#{Constants::MULTILINE_COMMENT_END_PATTERNS.join('|')}/, '')
103
- .strip
79
+ matched_feature
80
+ .values_at(:feature)
81
+ .first
82
+ .gsub(/#{Constants::MULTILINE_COMMENT_END_PATTERNS.join('|')}/, '')
83
+ .strip
104
84
  rescue ArgumentError => e
105
85
  raise unless e.message.include?('invalid byte sequence')
106
86
  end
107
87
 
108
- sig { params(filename: String).returns(T.nilable(CodeFeatures::Feature)) }
109
88
  def file_annotation_based_feature(filename)
110
89
  # Not too sure what the following comment means but it was carried over from the code_ownership repo, so
111
90
  # I've opted to leave it unchanged in case it is helpful for future engineers:
@@ -131,7 +110,6 @@ module FeatureMap
131
110
  )
132
111
  end
133
112
 
134
- sig { params(filename: String).void }
135
113
  def remove_file_annotation!(filename)
136
114
  if file_annotation_based_feature(filename)
137
115
  filepath = Pathname.new(filename)
@@ -145,12 +123,10 @@ module FeatureMap
145
123
  end
146
124
  end
147
125
 
148
- sig { override.returns(String) }
149
126
  def description
150
127
  DESCRIPTION
151
128
  end
152
129
 
153
- sig { override.void }
154
130
  def bust_caches!; end
155
131
  end
156
132
  end
@@ -1,4 +1,3 @@
1
- # typed: strict
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require 'code_ownership'
@@ -11,8 +10,6 @@ module FeatureMap
11
10
  # PR/release announcements, documentation generation, etc).
12
11
  #
13
12
  class AssignmentsFile
14
- extend T::Sig
15
-
16
13
  class FileContentError < StandardError; end
17
14
 
18
15
  FILES_KEY = 'files'
@@ -21,42 +18,6 @@ module FeatureMap
21
18
  FEATURES_KEY = 'features'
22
19
  FEATURE_FILES_KEY = 'files'
23
20
 
24
- FeatureName = T.type_alias { String }
25
- FilePath = T.type_alias { String }
26
- MapperDescription = T.type_alias { String }
27
-
28
- FileDetails = T.type_alias do
29
- T::Hash[
30
- String,
31
- T.any(FeatureName, MapperDescription)
32
- ]
33
- end
34
-
35
- FilesContent = T.type_alias do
36
- T::Hash[
37
- FilePath,
38
- FileDetails
39
- ]
40
- end
41
-
42
- FileList = T.type_alias { T::Array[String] }
43
- TeamList = T.type_alias { T::Array[String] }
44
-
45
- FeatureDetails = T.type_alias do
46
- T::Hash[
47
- String,
48
- T.any(FileList, TeamList)
49
- ]
50
- end
51
-
52
- FeaturesContent = T.type_alias do
53
- T::Hash[
54
- FeatureName,
55
- FeatureDetails
56
- ]
57
- end
58
-
59
- sig { returns(T::Array[String]) }
60
21
  def self.actual_contents_lines
61
22
  if path.exist?
62
23
  content = path.read
@@ -70,7 +31,6 @@ module FeatureMap
70
31
  end
71
32
  end
72
33
 
73
- sig { returns(T::Array[T.nilable(String)]) }
74
34
  def self.expected_contents_lines
75
35
  cache = Private.glob_cache.raw_cache_contents
76
36
 
@@ -84,9 +44,9 @@ module FeatureMap
84
44
  # set of files assigned to a feature change, which should be explicitly tracked.
85
45
  HEADER
86
46
 
87
- files_content = T.let({}, FilesContent)
88
- files_by_feature = T.let({}, T::Hash[FeatureName, FileList])
89
- features_content = T.let({}, FeaturesContent)
47
+ files_content = {}
48
+ files_by_feature = {}
49
+ features_content = {}
90
50
 
91
51
  cache.each do |mapper_description, assignment_map_cache|
92
52
  assignment_map_cache = assignment_map_cache.sort_by do |glob, _feature|
@@ -94,10 +54,10 @@ module FeatureMap
94
54
  end
95
55
 
96
56
  assignment_map_cache.to_h.each do |path, feature|
97
- files_content[path] = T.let({ FILE_FEATURE_KEY => feature.name, FILE_MAPPER_KEY => mapper_description }, FileDetails)
57
+ files_content[path] = { FILE_FEATURE_KEY => feature.name, FILE_MAPPER_KEY => mapper_description }
98
58
 
99
59
  files_by_feature[feature.name] ||= []
100
- T.must(files_by_feature[feature.name]) << path
60
+ files_by_feature[feature.name] << path
101
61
  end
102
62
  end
103
63
 
@@ -111,10 +71,10 @@ module FeatureMap
111
71
  # repo/application.
112
72
  next if expanded_files.empty?
113
73
 
114
- features_content[feature.name] = T.let({ 'files' => expanded_files.sort }, FeatureDetails)
74
+ features_content[feature.name] = { 'files' => expanded_files.sort }
115
75
 
116
76
  if !Private.configuration.skip_code_ownership
117
- T.must(features_content[feature.name])['teams'] = expanded_files.map { |file| CodeOwnership.for_file(file)&.name }.compact.uniq.sort
77
+ features_content[feature.name]['teams'] = expanded_files.map { |file| CodeOwnership.for_file(file)&.name }.compact.uniq.sort
118
78
  end
119
79
  end
120
80
 
@@ -126,18 +86,15 @@ module FeatureMap
126
86
  ]
127
87
  end
128
88
 
129
- sig { void }
130
89
  def self.write!
131
90
  FileUtils.mkdir_p(path.dirname) if !path.dirname.exist?
132
91
  path.write(expected_contents_lines.join("\n"))
133
92
  end
134
93
 
135
- sig { returns(Pathname) }
136
94
  def self.path
137
95
  Pathname.pwd.join('.feature_map/assignments.yml')
138
96
  end
139
97
 
140
- sig { params(files: T::Array[String]).void }
141
98
  def self.update_cache!(files)
142
99
  cache = Private.glob_cache
143
100
  # Each mapper returns a new copy of the cache subset related to that mapper,
@@ -149,14 +106,12 @@ module FeatureMap
149
106
  end
150
107
  end
151
108
 
152
- sig { returns(T::Boolean) }
153
109
  def self.use_features_cache?
154
110
  AssignmentsFile.path.exist? && !Private.configuration.skip_features_validation
155
111
  end
156
112
 
157
- sig { returns(GlobCache) }
158
113
  def self.to_glob_cache
159
- raw_cache_contents = T.let({}, GlobCache::CacheShape)
114
+ raw_cache_contents = {}
160
115
  features_by_name = CodeFeatures.all.each_with_object({}) do |feature, map|
161
116
  map[feature.name] = feature
162
117
  end
@@ -175,7 +130,6 @@ module FeatureMap
175
130
  GlobCache.new(raw_cache_contents)
176
131
  end
177
132
 
178
- sig { returns(FeaturesContent) }
179
133
  def self.load_features!
180
134
  assignments_content = YAML.load_file(path)
181
135