packwerk 1.4.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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