packwerk 1.4.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/Gemfile.lock +22 -19
  4. data/README.md +7 -2
  5. data/UPGRADING.md +54 -0
  6. data/USAGE.md +5 -40
  7. data/lib/packwerk/application_validator.rb +88 -93
  8. data/lib/packwerk/association_inspector.rb +1 -1
  9. data/lib/packwerk/cache.rb +169 -0
  10. data/lib/packwerk/cli.rb +32 -28
  11. data/lib/packwerk/configuration.rb +40 -5
  12. data/lib/packwerk/constant_discovery.rb +21 -5
  13. data/lib/packwerk/constant_name_inspector.rb +1 -1
  14. data/lib/packwerk/deprecated_references.rb +1 -1
  15. data/lib/packwerk/file_processor.rb +34 -15
  16. data/lib/packwerk/files_for_processing.rb +49 -22
  17. data/lib/packwerk/formatters/offenses_formatter.rb +1 -1
  18. data/lib/packwerk/formatters/progress_formatter.rb +1 -1
  19. data/lib/packwerk/generators/configuration_file.rb +4 -19
  20. data/lib/packwerk/generators/templates/packwerk.yml.erb +5 -5
  21. data/lib/packwerk/node.rb +2 -1
  22. data/lib/packwerk/node_processor.rb +6 -6
  23. data/lib/packwerk/node_processor_factory.rb +3 -4
  24. data/lib/packwerk/node_visitor.rb +3 -0
  25. data/lib/packwerk/offense.rb +10 -2
  26. data/lib/packwerk/package.rb +1 -1
  27. data/lib/packwerk/package_set.rb +3 -2
  28. data/lib/packwerk/parse_run.rb +37 -17
  29. data/lib/packwerk/parsed_constant_definitions.rb +4 -4
  30. data/lib/packwerk/parsers/erb.rb +2 -0
  31. data/lib/packwerk/parsers/factory.rb +2 -0
  32. data/lib/packwerk/parsers/parser_interface.rb +17 -0
  33. data/lib/packwerk/parsers/ruby.rb +2 -0
  34. data/lib/packwerk/parsers.rb +1 -0
  35. data/lib/packwerk/reference_checking/checkers/checker.rb +1 -1
  36. data/lib/packwerk/reference_checking/reference_checker.rb +2 -1
  37. data/lib/packwerk/reference_extractor.rb +78 -20
  38. data/lib/packwerk/reference_offense.rb +8 -3
  39. data/lib/packwerk/result.rb +2 -2
  40. data/lib/packwerk/run_context.rb +62 -38
  41. data/lib/packwerk/spring_command.rb +1 -1
  42. data/lib/packwerk/unresolved_reference.rb +10 -0
  43. data/lib/packwerk/version.rb +1 -1
  44. data/lib/packwerk.rb +5 -9
  45. data/packwerk.gemspec +1 -0
  46. data/sorbet/config +1 -0
  47. data/sorbet/rbi/gems/tapioca@0.4.19.rbi +1 -1
  48. data/sorbet/tapioca/require.rb +1 -1
  49. metadata +21 -7
  50. data/lib/packwerk/generators/inflections_file.rb +0 -43
  51. data/lib/packwerk/generators/templates/inflections.yml +0 -6
  52. data/lib/packwerk/inflections/custom.rb +0 -33
  53. data/lib/packwerk/inflections/default.rb +0 -73
  54. data/lib/packwerk/inflector.rb +0 -49
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require "digest"
5
+
6
+ module Packwerk
7
+ class Cache
8
+ extend T::Sig
9
+
10
+ class CacheContents < T::Struct
11
+ extend T::Sig
12
+
13
+ const :file_contents_digest, String
14
+ const :unresolved_references, T::Array[UnresolvedReference]
15
+
16
+ sig { returns(String) }
17
+ def serialize
18
+ to_json
19
+ end
20
+
21
+ sig { params(serialized_cache_contents: String).returns(CacheContents) }
22
+ def self.deserialize(serialized_cache_contents)
23
+ cache_contents_json = JSON.parse(serialized_cache_contents)
24
+ unresolved_references = cache_contents_json["unresolved_references"].map do |json|
25
+ UnresolvedReference.new(
26
+ json["constant_name"],
27
+ json["namespace_path"],
28
+ json["relative_path"],
29
+ Node::Location.new(json["source_location"]["line"], json["source_location"]["column"],)
30
+ )
31
+ end
32
+
33
+ CacheContents.new(
34
+ file_contents_digest: cache_contents_json["file_contents_digest"],
35
+ unresolved_references: unresolved_references,
36
+ )
37
+ end
38
+ end
39
+
40
+ CACHE_SHAPE = T.type_alias do
41
+ T::Hash[
42
+ String,
43
+ CacheContents
44
+ ]
45
+ end
46
+
47
+ sig { params(enable_cache: T::Boolean, cache_directory: Pathname, config_path: T.nilable(String)).void }
48
+ def initialize(enable_cache:, cache_directory:, config_path:)
49
+ @enable_cache = enable_cache
50
+ @cache = T.let({}, CACHE_SHAPE)
51
+ @files_by_digest = T.let({}, T::Hash[String, String])
52
+ @config_path = config_path
53
+ @cache_directory = cache_directory
54
+
55
+ if @enable_cache
56
+ create_cache_directory!
57
+ bust_cache_if_packwerk_yml_has_changed!
58
+ bust_cache_if_inflections_have_changed!
59
+ end
60
+ end
61
+
62
+ sig { void }
63
+ def bust_cache!
64
+ FileUtils.rm_rf(@cache_directory)
65
+ end
66
+
67
+ sig do
68
+ params(
69
+ file_path: String,
70
+ block: T.proc.returns(T::Array[UnresolvedReference])
71
+ ).returns(T::Array[UnresolvedReference])
72
+ end
73
+ def with_cache(file_path, &block)
74
+ return block.call unless @enable_cache
75
+
76
+ cache_location = @cache_directory.join(digest_for_string(file_path))
77
+
78
+ cache_contents = if cache_location.exist?
79
+ T.let(CacheContents.deserialize(cache_location.read),
80
+ CacheContents)
81
+ end
82
+
83
+ file_contents_digest = digest_for_file(file_path)
84
+
85
+ if !cache_contents.nil? && cache_contents.file_contents_digest == file_contents_digest
86
+ Debug.out("Cache hit for #{file_path}")
87
+
88
+ cache_contents.unresolved_references
89
+ else
90
+ Debug.out("Cache miss for #{file_path}")
91
+
92
+ unresolved_references = block.call
93
+
94
+ cache_contents = CacheContents.new(
95
+ file_contents_digest: file_contents_digest,
96
+ unresolved_references: unresolved_references,
97
+ )
98
+ cache_location.write(cache_contents.serialize)
99
+
100
+ unresolved_references
101
+ end
102
+ end
103
+
104
+ sig { params(file: String).returns(String) }
105
+ def digest_for_file(file)
106
+ digest_for_string(File.read(file))
107
+ end
108
+
109
+ sig { params(str: String).returns(String) }
110
+ def digest_for_string(str)
111
+ # MD5 appears to be the fastest
112
+ # https://gist.github.com/morimori/1330095
113
+ Digest::MD5.hexdigest(str)
114
+ end
115
+
116
+ sig { void }
117
+ def bust_cache_if_packwerk_yml_has_changed!
118
+ return nil if @config_path.nil?
119
+ bust_cache_if_contents_have_changed(File.read(@config_path), :packwerk_yml)
120
+ end
121
+
122
+ sig { void }
123
+ def bust_cache_if_inflections_have_changed!
124
+ bust_cache_if_contents_have_changed(YAML.dump(ActiveSupport::Inflector.inflections), :inflections)
125
+ end
126
+
127
+ sig { params(contents: String, contents_key: Symbol).void }
128
+ def bust_cache_if_contents_have_changed(contents, contents_key)
129
+ current_digest = digest_for_string(contents)
130
+ cached_digest_path = @cache_directory.join(contents_key.to_s)
131
+
132
+ if !cached_digest_path.exist?
133
+ # In this case, we have nothing cached
134
+ # We save the current digest. This way the next time we compare current digest to cached digest,
135
+ # we can accurately determine if we should bust the cache
136
+ cached_digest_path.write(current_digest)
137
+
138
+ nil
139
+ elsif cached_digest_path.read == current_digest
140
+ Debug.out("#{contents_key} contents have NOT changed, preserving cache")
141
+ else
142
+ Debug.out("#{contents_key} contents have changed, busting cache")
143
+
144
+ bust_cache!
145
+ create_cache_directory!
146
+
147
+ cached_digest_path.write(current_digest)
148
+ end
149
+ end
150
+
151
+ sig { void }
152
+ def create_cache_directory!
153
+ FileUtils.mkdir_p(@cache_directory)
154
+ end
155
+ end
156
+
157
+ class Debug
158
+ extend T::Sig
159
+
160
+ sig { params(out: String).void }
161
+ def self.out(out)
162
+ if ENV["DEBUG_PACKWERK_CACHE"]
163
+ puts(out)
164
+ end
165
+ end
166
+ end
167
+
168
+ private_constant :Debug
169
+ end
data/lib/packwerk/cli.rb CHANGED
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "optparse"
@@ -30,9 +30,10 @@ module Packwerk
30
30
  @err_out = err_out
31
31
  @environment = environment
32
32
  @style = style
33
- @configuration = configuration || Configuration.from_path
34
- @progress_formatter = Formatters::ProgressFormatter.new(@out, style: style)
35
- @offenses_formatter = offenses_formatter || Formatters::OffensesFormatter.new(style: @style)
33
+ @configuration = T.let(configuration || Configuration.from_path, Configuration)
34
+ @progress_formatter = T.let(Formatters::ProgressFormatter.new(@out, style: style), Formatters::ProgressFormatter)
35
+ @offenses_formatter = T.let(offenses_formatter || Formatters::OffensesFormatter.new(style: @style),
36
+ OffensesFormatter)
36
37
  end
37
38
 
38
39
  sig { params(args: T::Array[String]).returns(T.noreturn) }
@@ -53,8 +54,6 @@ module Packwerk
53
54
  output_result(parse_run(args).check)
54
55
  when "detect-stale-violations"
55
56
  output_result(parse_run(args).detect_stale_violations)
56
- when "update"
57
- update(args)
58
57
  when "update-deprecations"
59
58
  output_result(parse_run(args).update_deprecations)
60
59
  when "validate"
@@ -80,22 +79,23 @@ module Packwerk
80
79
 
81
80
  private
82
81
 
82
+ sig { returns(T::Boolean) }
83
83
  def init
84
84
  @out.puts("📦 Initializing Packwerk...")
85
85
 
86
86
  generate_configs
87
87
  end
88
88
 
89
+ sig { returns(T::Boolean) }
89
90
  def generate_configs
90
91
  configuration_file = Packwerk::Generators::ConfigurationFile.generate(
91
- load_paths: Packwerk::ApplicationLoadPaths.extract_relevant_paths(@configuration.root_path, @environment),
92
92
  root: @configuration.root_path,
93
93
  out: @out
94
94
  )
95
- inflections_file = Packwerk::Generators::InflectionsFile.generate(root: @configuration.root_path, out: @out)
95
+
96
96
  root_package = Packwerk::Generators::RootPackage.generate(root: @configuration.root_path, out: @out)
97
97
 
98
- success = configuration_file && inflections_file && root_package
98
+ success = configuration_file && root_package
99
99
 
100
100
  result = if success
101
101
  <<~EOS
@@ -115,35 +115,28 @@ module Packwerk
115
115
  success
116
116
  end
117
117
 
118
- def update(paths)
119
- warn("`packwerk update` is deprecated in favor of `packwerk update-deprecations`.")
120
- output_result(parse_run(paths).update_deprecations)
121
- end
122
-
118
+ sig { params(result: Result).returns(T::Boolean) }
123
119
  def output_result(result)
124
120
  @out.puts
125
121
  @out.puts(result.message)
126
122
  result.status
127
123
  end
128
124
 
129
- def fetch_files_to_process(paths, ignore_nested_packages)
130
- files = FilesForProcessing.fetch(
131
- paths: paths,
125
+ sig { params(relative_file_paths: T::Array[String], ignore_nested_packages: T::Boolean).returns(T::Array[String]) }
126
+ def fetch_files_to_process(relative_file_paths, ignore_nested_packages)
127
+ absolute_files = FilesForProcessing.fetch(
128
+ relative_file_paths: relative_file_paths,
132
129
  ignore_nested_packages: ignore_nested_packages,
133
130
  configuration: @configuration
134
131
  )
135
132
  abort("No files found or given. "\
136
- "Specify files or check the include and exclude glob in the config file.") if files.empty?
137
- files
133
+ "Specify files or check the include and exclude glob in the config file.") if absolute_files.empty?
134
+ absolute_files
138
135
  end
139
136
 
137
+ sig { params(_paths: T::Array[String]).returns(T::Boolean) }
140
138
  def validate(_paths)
141
139
  @progress_formatter.started_validation do
142
- checker = Packwerk::ApplicationValidator.new(
143
- config_file_path: @configuration.config_path,
144
- configuration: @configuration,
145
- environment: @environment,
146
- )
147
140
  result = checker.check_all
148
141
 
149
142
  list_validation_errors(result)
@@ -152,6 +145,16 @@ module Packwerk
152
145
  end
153
146
  end
154
147
 
148
+ sig { returns(ApplicationValidator) }
149
+ def checker
150
+ Packwerk::ApplicationValidator.new(
151
+ config_file_path: @configuration.config_path,
152
+ configuration: @configuration,
153
+ environment: @environment,
154
+ )
155
+ end
156
+
157
+ sig { params(result: ApplicationValidator::Result).void }
155
158
  def list_validation_errors(result)
156
159
  @out.puts
157
160
  if result.ok?
@@ -162,24 +165,25 @@ module Packwerk
162
165
  end
163
166
  end
164
167
 
168
+ sig { params(params: T.untyped).returns(ParseRun) }
165
169
  def parse_run(params)
166
- paths = T.let([], T::Array[String])
170
+ relative_file_paths = T.let([], T::Array[String])
167
171
  ignore_nested_packages = nil
168
172
 
169
173
  if params.any? { |p| p.include?("--packages") }
170
174
  OptionParser.new do |parser|
171
175
  parser.on("--packages=PACKAGESLIST", Array, "package names, comma separated") do |p|
172
- paths = p
176
+ relative_file_paths = p
173
177
  end
174
178
  end.parse!(params)
175
179
  ignore_nested_packages = true
176
180
  else
177
- paths = params
181
+ relative_file_paths = params
178
182
  ignore_nested_packages = false
179
183
  end
180
184
 
181
185
  ParseRun.new(
182
- files: fetch_files_to_process(paths, ignore_nested_packages),
186
+ absolute_files: fetch_files_to_process(relative_file_paths, ignore_nested_packages),
183
187
  configuration: @configuration,
184
188
  progress_formatter: @progress_formatter,
185
189
  offenses_formatter: @offenses_formatter
@@ -34,8 +34,7 @@ module Packwerk
34
34
  DEFAULT_EXCLUDE_GLOBS = ["{bin,node_modules,script,tmp,vendor}/**/*"]
35
35
 
36
36
  attr_reader(
37
- :include, :exclude, :root_path, :package_paths, :custom_associations, :load_paths, :inflections_file,
38
- :config_path,
37
+ :include, :exclude, :root_path, :package_paths, :custom_associations, :config_path, :cache_directory
39
38
  )
40
39
 
41
40
  def initialize(configs = {}, config_path: nil)
@@ -45,15 +44,51 @@ module Packwerk
45
44
  @root_path = File.expand_path(root)
46
45
  @package_paths = configs["package_paths"] || "**/"
47
46
  @custom_associations = configs["custom_associations"] || []
48
- @load_paths = (configs["load_paths"] || []).uniq
49
- @inflections_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
50
47
  @parallel = configs.key?("parallel") ? configs["parallel"] : true
51
-
48
+ @cache_enabled = configs.key?("cache") ? configs["cache"] : false
49
+ @cache_directory = Pathname.new(configs["cache_directory"] || "tmp/cache/packwerk")
52
50
  @config_path = config_path
51
+
52
+ if configs["load_paths"]
53
+ warning = <<~WARNING
54
+ DEPRECATION WARNING: The 'load_paths' key in `packwerk.yml` is deprecated.
55
+ This value is no longer cached, and you can remove the key from `packwerk.yml`.
56
+ WARNING
57
+
58
+ warn(warning)
59
+ end
60
+
61
+ if configs["inflections_file"]
62
+ warning = <<~WARNING
63
+ DEPRECATION WARNING: The 'inflections_file' key in `packwerk.yml` is deprecated.
64
+ This value is no longer cached, and you can remove the key from `packwerk.yml`.
65
+ You can also delete #{configs["inflections_file"]}.
66
+ WARNING
67
+
68
+ warn(warning)
69
+ end
70
+
71
+ inflection_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
72
+ if Pathname.new(inflection_file).exist?
73
+ warning = <<~WARNING
74
+ DEPRECATION WARNING: Inflections YMLs in packwerk are now deprecated.
75
+ This value is no longer cached, and you can now delete #{inflection_file}
76
+ WARNING
77
+
78
+ warn(warning)
79
+ end
80
+ end
81
+
82
+ def load_paths
83
+ @load_paths ||= ApplicationLoadPaths.extract_relevant_paths(@root_path, "test")
53
84
  end
54
85
 
55
86
  def parallel?
56
87
  @parallel
57
88
  end
89
+
90
+ def cache_enabled?
91
+ @cache_enabled
92
+ end
58
93
  end
59
94
  end
@@ -4,7 +4,7 @@
4
4
  require "constant_resolver"
5
5
 
6
6
  module Packwerk
7
- # Get information about (partially qualified) constants without loading the application code.
7
+ # Get information about unresolved constants without loading the application code.
8
8
  # Information gathered: Fully qualified name, path to file containing the definition, package,
9
9
  # and visibility (public/private to the package).
10
10
  #
@@ -15,10 +15,15 @@ module Packwerk
15
15
  # have no way of inferring the file it is defined in. You could argue though that inheritance means that another
16
16
  # constant with the same name exists in the inheriting class, and this view is sufficient for all our use cases.
17
17
  class ConstantDiscovery
18
+ extend T::Sig
19
+
18
20
  ConstantContext = Struct.new(:name, :location, :package, :public?)
19
21
 
20
22
  # @param constant_resolver [ConstantResolver]
21
23
  # @param packages [Packwerk::PackageSet]
24
+ sig do
25
+ params(constant_resolver: ConstantResolver, packages: Packwerk::PackageSet).void
26
+ end
22
27
  def initialize(constant_resolver:, packages:)
23
28
  @packages = packages
24
29
  @resolver = constant_resolver
@@ -30,22 +35,33 @@ module Packwerk
30
35
  #
31
36
  # @return [Packwerk::Package] the package that contains the given file,
32
37
  # or nil if the path is not owned by any component
38
+ sig do
39
+ params(
40
+ path: String,
41
+ ).returns(Packwerk::Package)
42
+ end
33
43
  def package_from_path(path)
34
44
  @packages.package_from_path(path)
35
45
  end
36
46
 
37
47
  # Analyze a constant via its name.
38
- # If the name is partially qualified, we need the current namespace path to correctly infer its full name
48
+ # If the constant is unresolved, we need the current namespace path to correctly infer its full name
39
49
  #
40
- # @param const_name [String] The constant's name, fully or partially qualified.
50
+ # @param const_name [String] The unresolved constant's name.
41
51
  # @param current_namespace_path [Array<String>] (optional) The namespace of the context in which the constant is
42
52
  # used, e.g. ["Apps", "Models"] for `Apps::Models`. Defaults to [] which means top level.
43
53
  # @return [Packwerk::ConstantDiscovery::ConstantContext]
54
+ sig do
55
+ params(
56
+ const_name: String,
57
+ current_namespace_path: T.nilable(T::Array[String]),
58
+ ).returns(T.nilable(ConstantDiscovery::ConstantContext))
59
+ end
44
60
  def context_for(const_name, current_namespace_path: [])
45
61
  begin
46
62
  constant = @resolver.resolve(const_name, current_namespace_path: current_namespace_path)
47
63
  rescue ConstantResolver::Error => e
48
- raise(ConstantResolver::Error, e.message + "\n Make sure autoload paths are added to the config file.")
64
+ raise(ConstantResolver::Error, e.message)
49
65
  end
50
66
 
51
67
  return unless constant
@@ -55,7 +71,7 @@ module Packwerk
55
71
  constant.name,
56
72
  constant.location,
57
73
  package,
58
- package&.public_path?(constant.location),
74
+ package.public_path?(constant.location),
59
75
  )
60
76
  end
61
77
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "ast"
@@ -16,6 +16,7 @@ module Packwerk
16
16
  @package = package
17
17
  @filepath = filepath
18
18
  @new_entries = T.let({}, ENTRIES_TYPE)
19
+ @deprecated_references = T.let(nil, T.nilable(ENTRIES_TYPE))
19
20
  end
20
21
 
21
22
  sig do
@@ -103,7 +104,6 @@ module Packwerk
103
104
 
104
105
  sig { returns(ENTRIES_TYPE) }
105
106
  def deprecated_references
106
- @deprecated_references ||= T.let(@deprecated_references, T.nilable(ENTRIES_TYPE))
107
107
  @deprecated_references ||= if File.exist?(@filepath)
108
108
  load_yaml(@filepath)
109
109
  else
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "ast/node"
@@ -8,55 +8,74 @@ module Packwerk
8
8
  extend T::Sig
9
9
 
10
10
  class UnknownFileTypeResult < Offense
11
+ extend T::Sig
12
+
13
+ sig { params(file: String).void }
11
14
  def initialize(file:)
12
15
  super(file: file, message: "unknown file type")
13
16
  end
14
17
  end
15
18
 
16
- def initialize(node_processor_factory:, parser_factory: nil)
19
+ sig do
20
+ params(
21
+ node_processor_factory: NodeProcessorFactory,
22
+ cache: Cache,
23
+ parser_factory: T.nilable(Parsers::Factory)
24
+ ).void
25
+ end
26
+ def initialize(node_processor_factory:, cache:, parser_factory: nil)
17
27
  @node_processor_factory = node_processor_factory
18
- @parser_factory = parser_factory || Packwerk::Parsers::Factory.instance
28
+ @cache = cache
29
+ @parser_factory = T.let(parser_factory || Packwerk::Parsers::Factory.instance, Parsers::Factory)
19
30
  end
20
31
 
21
32
  sig do
22
- params(file_path: String).returns(
33
+ params(absolute_file: String).returns(
23
34
  T::Array[
24
35
  T.any(
25
- Packwerk::Reference,
36
+ Packwerk::UnresolvedReference,
26
37
  Packwerk::Offense,
27
38
  )
28
39
  ]
29
40
  )
30
41
  end
31
- def call(file_path)
32
- return [UnknownFileTypeResult.new(file: file_path)] if parser_for(file_path).nil?
42
+ def call(absolute_file)
43
+ parser = parser_for(absolute_file)
44
+ return [UnknownFileTypeResult.new(file: absolute_file)] if T.unsafe(parser).nil?
33
45
 
34
- node = parse_into_ast(file_path)
35
- return [] unless node
46
+ @cache.with_cache(absolute_file) do
47
+ node = parse_into_ast(absolute_file, T.must(parser))
48
+ return [] unless node
36
49
 
37
- references_from_ast(node, file_path)
50
+ references_from_ast(node, absolute_file)
51
+ end
38
52
  rescue Parsers::ParseError => e
39
53
  [e.result]
40
54
  end
41
55
 
42
56
  private
43
57
 
44
- def references_from_ast(node, file_path)
58
+ sig do
59
+ params(node: Parser::AST::Node, absolute_file: String).returns(T::Array[UnresolvedReference])
60
+ end
61
+ def references_from_ast(node, absolute_file)
45
62
  references = []
46
63
 
47
- node_processor = @node_processor_factory.for(filename: file_path, node: node)
64
+ node_processor = @node_processor_factory.for(absolute_file: absolute_file, node: node)
48
65
  node_visitor = Packwerk::NodeVisitor.new(node_processor: node_processor)
49
66
  node_visitor.visit(node, ancestors: [], result: references)
50
67
 
51
68
  references
52
69
  end
53
70
 
54
- def parse_into_ast(file_path)
55
- File.open(file_path, "r", nil, external_encoding: Encoding::UTF_8) do |file|
56
- parser_for(file_path).call(io: file, file_path: file_path)
71
+ sig { params(absolute_file: String, parser: Parsers::ParserInterface).returns(T.untyped) }
72
+ def parse_into_ast(absolute_file, parser)
73
+ File.open(absolute_file, "r", nil, external_encoding: Encoding::UTF_8) do |file|
74
+ parser.call(io: file, file_path: absolute_file)
57
75
  end
58
76
  end
59
77
 
78
+ sig { params(file_path: String).returns(T.nilable(Parsers::ParserInterface)) }
60
79
  def parser_for(file_path)
61
80
  @parser_factory.for_path(file_path)
62
81
  end
@@ -1,20 +1,40 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
5
  class FilesForProcessing
6
+ extend T::Sig
7
+
6
8
  class << self
7
- def fetch(paths:, configuration:, ignore_nested_packages: false)
8
- new(paths, configuration, ignore_nested_packages).files
9
+ extend T::Sig
10
+
11
+ sig do
12
+ params(
13
+ relative_file_paths: T::Array[String],
14
+ configuration: Configuration,
15
+ ignore_nested_packages: T::Boolean
16
+ ).returns(T::Array[String])
17
+ end
18
+ def fetch(relative_file_paths:, configuration:, ignore_nested_packages: false)
19
+ new(relative_file_paths, configuration, ignore_nested_packages).files
9
20
  end
10
21
  end
11
22
 
12
- def initialize(paths, configuration, ignore_nested_packages)
13
- @paths = paths
23
+ sig do
24
+ params(
25
+ relative_file_paths: T::Array[String],
26
+ configuration: Configuration,
27
+ ignore_nested_packages: T::Boolean
28
+ ).void
29
+ end
30
+ def initialize(relative_file_paths, configuration, ignore_nested_packages)
31
+ @relative_file_paths = relative_file_paths
14
32
  @configuration = configuration
15
33
  @ignore_nested_packages = ignore_nested_packages
34
+ @custom_files = T.let(nil, T.nilable(T::Array[String]))
16
35
  end
17
36
 
37
+ sig { returns(T::Array[String]) }
18
38
  def files
19
39
  include_files = if custom_files.empty?
20
40
  configured_included_files
@@ -27,50 +47,57 @@ module Packwerk
27
47
 
28
48
  private
29
49
 
50
+ sig { returns(T::Array[String]) }
30
51
  def custom_files
31
- @custom_files ||= @paths.flat_map do |path|
32
- path = File.expand_path(path, @configuration.root_path)
33
- if File.file?(path)
34
- path
52
+ @custom_files ||= @relative_file_paths.flat_map do |relative_file_path|
53
+ absolute_file_path = File.expand_path(relative_file_path, @configuration.root_path)
54
+ if File.file?(absolute_file_path)
55
+ absolute_file_path
35
56
  else
36
- custom_included_files(path)
57
+ custom_included_files(absolute_file_path)
37
58
  end
38
59
  end
39
60
  end
40
61
 
41
- def custom_included_files(path)
62
+ sig { params(absolute_file_path: String).returns(T::Array[String]) }
63
+ def custom_included_files(absolute_file_path)
42
64
  # Note, assuming include globs are always relative paths
43
65
  absolute_includes = @configuration.include.map do |glob|
44
66
  File.expand_path(glob, @configuration.root_path)
45
67
  end
46
68
 
47
- files = Dir.glob([File.join(path, "**", "*")]).select do |file_path|
69
+ absolute_files = Dir.glob([File.join(absolute_file_path, "**", "*")]).select do |absolute_path|
48
70
  absolute_includes.any? do |pattern|
49
- File.fnmatch?(pattern, file_path, File::FNM_EXTGLOB)
71
+ File.fnmatch?(pattern, absolute_path, File::FNM_EXTGLOB)
50
72
  end
51
73
  end
52
74
 
53
75
  if @ignore_nested_packages
54
- nested_packages_paths = Dir.glob(File.join(path, "*", "**", "package.yml"))
55
- nested_packages_globs = nested_packages_paths.map { |npp| npp.gsub("package.yml", "**/*") }
56
- nested_packages_globs.each do |glob|
57
- files -= Dir.glob(glob)
76
+ nested_packages_absolute_file_paths = Dir.glob(File.join(absolute_file_path, "*", "**", "package.yml"))
77
+ nested_packages_absolute_globs = nested_packages_absolute_file_paths.map do |npp|
78
+ npp.gsub("package.yml", "**/*")
79
+ end
80
+ nested_packages_absolute_globs.each do |absolute_glob|
81
+ absolute_files -= Dir.glob(absolute_glob)
58
82
  end
59
83
  end
60
84
 
61
- files
85
+ absolute_files
62
86
  end
63
87
 
88
+ sig { returns(T::Array[String]) }
64
89
  def configured_included_files
65
- files_for_globs(@configuration.include)
90
+ absolute_files_for_globs(@configuration.include)
66
91
  end
67
92
 
93
+ sig { returns(T::Array[String]) }
68
94
  def configured_excluded_files
69
- files_for_globs(@configuration.exclude)
95
+ absolute_files_for_globs(@configuration.exclude)
70
96
  end
71
97
 
72
- def files_for_globs(globs)
73
- globs
98
+ sig { params(relative_globs: T::Array[String]).returns(T::Array[String]) }
99
+ def absolute_files_for_globs(relative_globs)
100
+ relative_globs
74
101
  .flat_map { |glob| Dir[File.expand_path(glob, @configuration.root_path)] }
75
102
  .uniq
76
103
  end