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.
- checksums.yaml +4 -4
 - data/README.md +0 -1
 - data/bin/featuremap +0 -1
 - data/lib/feature_map/cli.rb +0 -2
 - data/lib/feature_map/code_features/plugin.rb +2 -21
 - data/lib/feature_map/code_features/plugins/identity.rb +1 -8
 - data/lib/feature_map/code_features.rb +1 -31
 - data/lib/feature_map/commit.rb +0 -19
 - data/lib/feature_map/configuration.rb +40 -17
 - data/lib/feature_map/constants.rb +3 -5
 - data/lib/feature_map/mapper.rb +0 -26
 - data/lib/feature_map/output_color.rb +0 -11
 - data/lib/feature_map/private/additional_metrics_file.rb +9 -103
 - data/lib/feature_map/private/assignment_applicator.rb +0 -12
 - data/lib/feature_map/private/assignment_mappers/directory_assignment.rb +4 -26
 - data/lib/feature_map/private/assignment_mappers/feature_definition_assignment.rb +1 -21
 - data/lib/feature_map/private/assignment_mappers/feature_globs.rb +7 -40
 - data/lib/feature_map/private/assignment_mappers/file_annotations.rb +20 -44
 - data/lib/feature_map/private/assignments_file.rb +8 -54
 - data/lib/feature_map/private/code_cov.rb +2 -29
 - data/lib/feature_map/private/cyclomatic_complexity_calculator.rb +1 -7
 - data/lib/feature_map/private/docs/index.html +2 -2
 - data/lib/feature_map/private/documentation_site.rb +0 -16
 - data/lib/feature_map/private/extension_loader.rb +0 -3
 - data/lib/feature_map/private/feature_assigner.rb +0 -4
 - data/lib/feature_map/private/feature_metrics_calculator.rb +2 -16
 - data/lib/feature_map/private/feature_plugins/assignment.rb +0 -6
 - data/lib/feature_map/private/glob_cache.rb +2 -29
 - data/lib/feature_map/private/health_calculator.rb +122 -0
 - data/lib/feature_map/private/lines_of_code_calculator.rb +10 -21
 - data/lib/feature_map/private/metrics_file.rb +1 -25
 - data/lib/feature_map/private/percentile_metrics_calculator.rb +117 -0
 - data/lib/feature_map/private/release_notification_builder.rb +1 -13
 - data/lib/feature_map/private/test_coverage_file.rb +12 -39
 - data/lib/feature_map/private/test_pyramid_file.rb +0 -41
 - data/lib/feature_map/private/todo_inspector.rb +16 -30
 - data/lib/feature_map/private/validations/features_up_to_date.rb +1 -6
 - data/lib/feature_map/private/validations/files_have_features.rb +2 -7
 - data/lib/feature_map/private/validations/files_have_unique_features.rb +1 -6
 - data/lib/feature_map/private.rb +7 -44
 - data/lib/feature_map/validator.rb +0 -13
 - data/lib/feature_map.rb +8 -49
 - 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 =  
     | 
| 
      
 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 
     | 
    
         
            -
                       
     | 
| 
       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 =  
     | 
| 
      
 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(* 
     | 
| 
      
 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 =  
     | 
| 
      
 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 =  
     | 
| 
      
 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 
     | 
    
         
            -
                     
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       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 =  
     | 
| 
      
 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 
     | 
    
         
            -
                             
     | 
| 
      
 44 
     | 
    
         
            +
                            mapped_files[filename] << MappingContext.new(glob: glob, feature: feature)
         
     | 
| 
       64 
45 
     | 
    
         
             
                          end
         
     | 
| 
       65 
46 
     | 
    
         
             
                        end
         
     | 
| 
       66 
47 
     | 
    
         
             
                      end
         
     | 
| 
       67 
48 
     | 
    
         | 
| 
       68 
     | 
    
         
            -
                      overlaps =  
     | 
| 
      
 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 =  
     | 
| 
      
 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 =  
     | 
| 
       24 
     | 
    
         
            -
                       
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                       
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       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 
     | 
    
         
            -
                       
     | 
| 
       100 
     | 
    
         
            -
             
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
             
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
      
 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 =  
     | 
| 
       88 
     | 
    
         
            -
                    files_by_feature =  
     | 
| 
       89 
     | 
    
         
            -
                    features_content =  
     | 
| 
      
 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] =  
     | 
| 
      
 57 
     | 
    
         
            +
                        files_content[path] = { FILE_FEATURE_KEY => feature.name, FILE_MAPPER_KEY => mapper_description }
         
     | 
| 
       98 
58 
     | 
    
         | 
| 
       99 
59 
     | 
    
         
             
                        files_by_feature[feature.name] ||= []
         
     | 
| 
       100 
     | 
    
         
            -
                         
     | 
| 
      
 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] =  
     | 
| 
      
 74 
     | 
    
         
            +
                      features_content[feature.name] = { 'files' => expanded_files.sort }
         
     | 
| 
       115 
75 
     | 
    
         | 
| 
       116 
76 
     | 
    
         
             
                      if !Private.configuration.skip_code_ownership
         
     | 
| 
       117 
     | 
    
         
            -
                         
     | 
| 
      
 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 =  
     | 
| 
      
 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 
     | 
    
         |