packwerk 2.2.2 → 3.0.0

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -5
  3. data/.ruby-version +1 -1
  4. data/Gemfile +0 -1
  5. data/Gemfile.lock +5 -95
  6. data/README.md +2 -7
  7. data/RESOLVING_VIOLATIONS.md +7 -7
  8. data/TROUBLESHOOT.md +1 -23
  9. data/USAGE.md +149 -59
  10. data/dev.yml +1 -1
  11. data/exe/packwerk +1 -0
  12. data/gemfiles/Gemfile-rails-6-1 +1 -1
  13. data/lib/packwerk/application_validator.rb +54 -285
  14. data/lib/packwerk/association_inspector.rb +2 -0
  15. data/lib/packwerk/cache.rb +6 -5
  16. data/lib/packwerk/checker.rb +54 -0
  17. data/lib/packwerk/cli/result.rb +11 -0
  18. data/lib/packwerk/cli.rb +56 -31
  19. data/lib/packwerk/configuration.rb +61 -40
  20. data/lib/packwerk/const_node_inspector.rb +2 -0
  21. data/lib/packwerk/constant_context.rb +8 -0
  22. data/lib/packwerk/constant_discovery.rb +5 -6
  23. data/lib/packwerk/constant_name_inspector.rb +2 -0
  24. data/lib/packwerk/disable_sorbet.rb +41 -0
  25. data/lib/packwerk/extension_loader.rb +24 -0
  26. data/lib/packwerk/file_processor.rb +3 -1
  27. data/lib/packwerk/files_for_processing.rb +25 -12
  28. data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
  29. data/lib/packwerk/formatters/progress_formatter.rb +31 -12
  30. data/lib/packwerk/generators/configuration_file.rb +7 -2
  31. data/lib/packwerk/generators/root_package.rb +5 -1
  32. data/lib/packwerk/generators/templates/package.yml +0 -10
  33. data/lib/packwerk/graph.rb +10 -2
  34. data/lib/packwerk/node.rb +1 -1
  35. data/lib/packwerk/node_helpers.rb +14 -7
  36. data/lib/packwerk/node_processor.rb +2 -0
  37. data/lib/packwerk/node_processor_factory.rb +6 -4
  38. data/lib/packwerk/node_visitor.rb +10 -1
  39. data/lib/packwerk/offense_collection.rb +43 -23
  40. data/lib/packwerk/offenses_formatter.rb +59 -2
  41. data/lib/packwerk/package.rb +7 -35
  42. data/lib/packwerk/package_set.rb +1 -1
  43. data/lib/packwerk/{deprecated_references.rb → package_todo.rb} +29 -13
  44. data/lib/packwerk/parse_run.rb +29 -36
  45. data/lib/packwerk/parsed_constant_definitions.rb +28 -5
  46. data/lib/packwerk/parsers/erb.rb +23 -4
  47. data/lib/packwerk/parsers/factory.rb +11 -2
  48. data/lib/packwerk/parsers/parser_interface.rb +1 -1
  49. data/lib/packwerk/parsers/ruby.rb +13 -3
  50. data/lib/packwerk/parsers.rb +6 -2
  51. data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
  52. data/lib/packwerk/reference.rb +7 -1
  53. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
  54. data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
  55. data/lib/packwerk/reference_extractor.rb +24 -12
  56. data/lib/packwerk/reference_offense.rb +2 -2
  57. data/lib/packwerk/run_context.rb +7 -10
  58. data/lib/packwerk/spring_command.rb +11 -2
  59. data/lib/packwerk/unresolved_reference.rb +9 -1
  60. data/lib/packwerk/validator/result.rb +18 -0
  61. data/lib/packwerk/validator.rb +90 -0
  62. data/lib/packwerk/validators/dependency_validator.rb +154 -0
  63. data/lib/packwerk/version.rb +1 -1
  64. data/lib/packwerk.rb +64 -26
  65. data/packwerk.gemspec +4 -2
  66. data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
  67. data/sorbet/rbi/shims/minitest/test.rb +8 -0
  68. data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
  69. data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
  70. data/sorbet/rbi/shims/parser.rbi +13 -0
  71. metadata +35 -16
  72. data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
  73. data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
  74. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
  75. data/lib/packwerk/result.rb +0 -9
  76. data/lib/packwerk/sanity_checker.rb +0 -8
  77. data/lib/packwerk/violation_type.rb +0 -11
  78. data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
  79. data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +0 -8
@@ -9,234 +9,107 @@ module Packwerk
9
9
  # Checks the structure of the application and its packwerk configuration to make sure we can run a check and deliver
10
10
  # correct results.
11
11
  class ApplicationValidator
12
+ include Validator
12
13
  extend T::Sig
13
14
 
14
- sig do
15
- params(
16
- config_file_path: String,
17
- configuration: Configuration,
18
- environment: String
19
- ).void
20
- end
21
- def initialize(config_file_path:, configuration:, environment:)
22
- @config_file_path = config_file_path
23
- @configuration = configuration
24
- @environment = environment
25
- @package_set = T.let(PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob),
26
- PackageSet)
27
- end
28
-
29
- class Result < T::Struct
30
- extend T::Sig
31
-
32
- const :ok, T::Boolean
33
- const :error_value, T.nilable(String)
34
-
35
- sig { returns(T::Boolean) }
36
- def ok?
37
- ok
38
- end
15
+ sig { params(package_set: PackageSet, configuration: Configuration).returns(Validator::Result) }
16
+ def check_all(package_set, configuration)
17
+ results = Validator.all.flat_map { |validator| validator.call(package_set, configuration) }
18
+ merge_results(results)
39
19
  end
40
20
 
41
- sig { returns(Result) }
42
- def check_all
21
+ sig { override.params(package_set: PackageSet, configuration: Configuration).returns(Validator::Result) }
22
+ def call(package_set, configuration)
43
23
  results = [
44
- check_package_manifests_for_privacy,
45
- check_package_manifest_syntax,
46
- check_application_structure,
47
- check_acyclic_graph,
48
- check_package_manifest_paths,
49
- check_valid_package_dependencies,
50
- check_root_package_exists,
24
+ check_package_manifest_syntax(configuration),
25
+ check_application_structure(configuration),
26
+ check_package_manifest_paths(configuration),
27
+ check_root_package_exists(configuration),
51
28
  ]
52
29
 
53
- merge_results(results)
30
+ merge_results(results, separator: "\n❓ ")
54
31
  end
55
32
 
56
- sig { returns(Result) }
57
- def check_package_manifests_for_privacy
58
- privacy_settings = package_manifests_settings_for("enforce_privacy")
59
-
60
- resolver = ConstantResolver.new(
61
- root_path: @configuration.root_path,
62
- load_paths: @configuration.load_paths
63
- )
64
-
65
- results = T.let([], T::Array[Result])
66
-
67
- privacy_settings.each do |config_file_path, setting|
68
- next unless setting.is_a?(Array)
69
-
70
- constants = setting
71
-
72
- results += assert_constants_can_be_loaded(constants, config_file_path)
73
-
74
- constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
75
-
76
- constant_locations.each do |name, location|
77
- results << if location
78
- check_private_constant_location(name, location, config_file_path)
79
- else
80
- private_constant_unresolvable(name, config_file_path)
81
- end
82
- end
83
- end
84
-
85
- merge_results(results, separator: "\n---\n")
33
+ sig { override.returns(T::Array[String]) }
34
+ def permitted_keys
35
+ [
36
+ "metadata",
37
+ ]
86
38
  end
87
39
 
88
- sig { returns(Result) }
89
- def check_package_manifest_syntax
40
+ sig { params(configuration: Configuration).returns(Validator::Result) }
41
+ def check_package_manifest_syntax(configuration)
90
42
  errors = []
91
43
 
92
- package_manifests.each do |f|
93
- hash = YAML.load_file(f)
44
+ package_manifests(configuration).each do |manifest|
45
+ hash = YAML.load_file(manifest)
94
46
  next unless hash
95
47
 
96
- known_keys = ["enforce_privacy", "enforce_dependencies", "public_path", "dependencies", "metadata"]
48
+ known_keys = Validator.all.flat_map(&:permitted_keys)
97
49
  unknown_keys = hash.keys - known_keys
98
50
 
99
51
  unless unknown_keys.empty?
100
- errors << "Unknown keys in #{f}: #{unknown_keys.inspect}\n"\
101
- "If you think a key should be included in your package.yml, please "\
102
- "open an issue in https://github.com/Shopify/packwerk"
103
- end
104
-
105
- if hash.key?("enforce_privacy")
106
- unless [TrueClass, FalseClass, Array].include?(hash["enforce_privacy"].class)
107
- errors << "Invalid 'enforce_privacy' option in #{f.inspect}: #{hash["enforce_privacy"].inspect}"
108
- end
109
- end
110
-
111
- if hash.key?("enforce_dependencies")
112
- unless [TrueClass, FalseClass].include?(hash["enforce_dependencies"].class)
113
- errors << "Invalid 'enforce_dependencies' option in #{f.inspect}: #{hash["enforce_dependencies"].inspect}"
114
- end
115
- end
116
-
117
- if hash.key?("public_path")
118
- unless hash["public_path"].is_a?(String)
119
- errors << "'public_path' option must be a string in #{f.inspect}: #{hash["public_path"].inspect}"
120
- end
52
+ errors << "\tUnknown keys: #{unknown_keys.inspect} in #{manifest.inspect}"
121
53
  end
122
-
123
- next unless hash.key?("dependencies")
124
- next if hash["dependencies"].is_a?(Array)
125
-
126
- errors << "Invalid 'dependencies' option in #{f.inspect}: #{hash["dependencies"].inspect}"
127
54
  end
128
55
 
129
56
  if errors.empty?
130
- Result.new(ok: true)
57
+ Validator::Result.new(ok: true)
131
58
  else
132
- Result.new(ok: false, error_value: errors.join("\n---\n"))
59
+ merge_results(
60
+ errors.map { |error| Validator::Result.new(ok: false, error_value: error) },
61
+ separator: "\n",
62
+ before_errors: "Malformed syntax in the following manifests:\n\n",
63
+ after_errors: "\n",
64
+ )
133
65
  end
134
66
  end
135
67
 
136
- sig { returns(Result) }
137
- def check_application_structure
68
+ sig { params(configuration: Configuration).returns(Validator::Result) }
69
+ def check_application_structure(configuration)
138
70
  resolver = ConstantResolver.new(
139
- root_path: @configuration.root_path.to_s,
140
- load_paths: @configuration.load_paths
71
+ root_path: configuration.root_path.to_s,
72
+ load_paths: configuration.load_paths
141
73
  )
142
74
 
143
75
  begin
144
76
  resolver.file_map
145
- Result.new(ok: true)
77
+ Validator::Result.new(ok: true)
146
78
  rescue => e
147
- Result.new(ok: false, error_value: e.message)
148
- end
149
- end
150
-
151
- sig { returns(Result) }
152
- def check_acyclic_graph
153
- edges = @package_set.flat_map do |package|
154
- package.dependencies.map { |dependency| [package, @package_set.fetch(dependency)] }
155
- end
156
- dependency_graph = Graph.new(*T.unsafe(edges))
157
-
158
- cycle_strings = build_cycle_strings(dependency_graph.cycles)
159
-
160
- if dependency_graph.acyclic?
161
- Result.new(ok: true)
162
- else
163
- Result.new(
164
- ok: false,
165
- error_value: <<~EOS
166
- Expected the package dependency graph to be acyclic, but it contains the following cycles:
167
-
168
- #{cycle_strings.join("\n")}
169
- EOS
170
- )
79
+ Validator::Result.new(ok: false, error_value: e.message)
171
80
  end
172
81
  end
173
82
 
174
- sig { returns(Result) }
175
- def check_package_manifest_paths
176
- all_package_manifests = package_manifests("**/")
177
- package_paths_package_manifests = package_manifests(package_glob)
83
+ sig { params(configuration: Configuration).returns(Validator::Result) }
84
+ def check_package_manifest_paths(configuration)
85
+ all_package_manifests = package_manifests(configuration, "**/")
86
+ package_paths_package_manifests = package_manifests(configuration, package_glob(configuration))
178
87
 
179
88
  difference = all_package_manifests - package_paths_package_manifests
180
89
 
181
90
  if difference.empty?
182
- Result.new(ok: true)
91
+ Validator::Result.new(ok: true)
183
92
  else
184
- Result.new(
93
+ Validator::Result.new(
185
94
  ok: false,
186
95
  error_value: <<~EOS
187
96
  Expected package paths for all package.ymls to be specified, but paths were missing for the following manifests:
188
97
 
189
- #{relative_paths(difference).join("\n")}
190
- EOS
191
- )
192
- end
193
- end
194
-
195
- sig { returns(Result) }
196
- def check_valid_package_dependencies
197
- packages_dependencies = package_manifests_settings_for("dependencies")
198
- .delete_if { |_, deps| deps.nil? }
199
-
200
- packages_with_invalid_dependencies =
201
- packages_dependencies.each_with_object([]) do |(package, dependencies), invalid_packages|
202
- invalid_dependencies = dependencies.filter { |path| invalid_package_path?(path) }
203
- invalid_packages << [package, invalid_dependencies] if invalid_dependencies.any?
204
- end
205
-
206
- if packages_with_invalid_dependencies.empty?
207
- Result.new(ok: true)
208
- else
209
- error_locations = packages_with_invalid_dependencies.map do |package, invalid_dependencies|
210
- package ||= @configuration.root_path
211
- package_path = Pathname.new(package).relative_path_from(@configuration.root_path)
212
- all_invalid_dependencies = invalid_dependencies.map { |d| " - #{d}" }
213
-
214
- <<~EOS
215
- #{package_path}:
216
- #{all_invalid_dependencies.join("\n")}
217
- EOS
218
- end
219
-
220
- Result.new(
221
- ok: false,
222
- error_value: <<~EOS
223
- These dependencies do not point to valid packages:
224
-
225
- #{error_locations.join("\n")}
98
+ #{relative_paths(configuration, difference).join("\n")}
226
99
  EOS
227
100
  )
228
101
  end
229
102
  end
230
103
 
231
- sig { returns(Result) }
232
- def check_root_package_exists
233
- root_package_path = File.join(@configuration.root_path, "package.yml")
234
- all_packages_manifests = package_manifests(package_glob)
104
+ sig { params(configuration: Configuration).returns(Validator::Result) }
105
+ def check_root_package_exists(configuration)
106
+ root_package_path = File.join(configuration.root_path, "package.yml")
107
+ all_packages_manifests = package_manifests(configuration, package_glob(configuration))
235
108
 
236
109
  if all_packages_manifests.include?(root_package_path)
237
- Result.new(ok: true)
110
+ Validator::Result.new(ok: true)
238
111
  else
239
- Result.new(
112
+ Validator::Result.new(
240
113
  ok: false,
241
114
  error_value: <<~EOS
242
115
  A root package does not exist. Create an empty `package.yml` at the root directory.
@@ -247,120 +120,16 @@ module Packwerk
247
120
 
248
121
  private
249
122
 
250
- # Convert the cycles:
251
- #
252
- # [[a, b, c], [b, c]]
253
- #
254
- # to the string:
255
- #
256
- # ["a -> b -> c -> a", "b -> c -> b"]
257
- sig { params(cycles: T.untyped).returns(T::Array[String]) }
258
- def build_cycle_strings(cycles)
259
- cycles.map do |cycle|
260
- cycle_strings = cycle.map(&:to_s)
261
- cycle_strings << cycle.first.to_s
262
- "\t- #{cycle_strings.join(" → ")}"
263
- end
264
- end
265
-
266
- sig { params(setting: T.untyped).returns(T.untyped) }
267
- def package_manifests_settings_for(setting)
268
- package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
269
- end
270
-
271
123
  sig { params(list: T.untyped).returns(T.untyped) }
272
124
  def format_yaml_strings(list)
273
125
  list.sort.map { |p| "- \"#{p}\"" }.join("\n")
274
126
  end
275
127
 
276
- sig { returns(T.any(T::Array[String], String)) }
277
- def package_glob
278
- @configuration.package_paths || "**"
279
- end
280
-
281
- sig { params(glob_pattern: T.any(T::Array[String], String)).returns(T::Array[String]) }
282
- def package_manifests(glob_pattern = package_glob)
283
- PackageSet.package_paths(@configuration.root_path, glob_pattern, @configuration.exclude)
284
- .map { |f| File.realpath(f) }
285
- end
286
-
287
- sig { params(paths: T::Array[String]).returns(T::Array[Pathname]) }
288
- def relative_paths(paths)
289
- paths.map { |path| relative_path(path) }
290
- end
291
-
292
- sig { params(path: String).returns(Pathname) }
293
- def relative_path(path)
294
- Pathname.new(path).relative_path_from(@configuration.root_path)
295
- end
296
-
297
- sig { params(path: T.untyped).returns(T::Boolean) }
298
- def invalid_package_path?(path)
299
- # Packages at the root can be implicitly specified as "."
300
- return false if path == "."
301
-
302
- package_path = File.join(@configuration.root_path, path, PackageSet::PACKAGE_CONFIG_FILENAME)
303
- !File.file?(package_path)
304
- end
305
-
306
- sig { params(constants: T.untyped, config_file_path: String).returns(T::Array[Result]) }
307
- def assert_constants_can_be_loaded(constants, config_file_path)
308
- constants.map do |constant|
309
- if !constant.start_with?("::")
310
- Result.new(
311
- ok: false,
312
- error_value: "'#{constant}', listed in the 'enforce_privacy' option in #{config_file_path}, is invalid.\n"\
313
- "Private constants need to be prefixed with the top-level namespace operator `::`."
314
- )
315
- else
316
- constant.try(&:constantize) && Result.new(ok: true)
317
- end
318
- end
319
- end
320
-
321
- sig { params(name: T.untyped, config_file_path: T.untyped).returns(Result) }
322
- def private_constant_unresolvable(name, config_file_path)
323
- explicit_filepath = (name.start_with?("::") ? name[2..-1] : name).underscore + ".rb"
324
-
325
- Result.new(
326
- ok: false,
327
- error_value: "'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
328
- "This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
329
- "file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
330
- "private. Add a #{explicit_filepath} file to explicitly define the constant."
331
- )
332
- end
333
-
334
- sig { params(name: T.untyped, location: T.untyped, config_file_path: T.untyped).returns(Result) }
335
- def check_private_constant_location(name, location, config_file_path)
336
- declared_package = @package_set.package_from_path(relative_path(config_file_path))
337
- constant_package = @package_set.package_from_path(location)
338
-
339
- if constant_package == declared_package
340
- Result.new(ok: true)
341
- else
342
- Result.new(
343
- ok: false,
344
- error_value: "'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
345
- "defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
346
- )
347
- end
348
- end
349
-
350
- sig do
351
- params(results: T::Array[Result], separator: String, errors_headline: String).returns(Result)
352
- end
353
- def merge_results(results, separator: "\n===\n", errors_headline: "")
354
- results.reject!(&:ok?)
355
-
356
- if results.empty?
357
- Result.new(ok: true)
358
- else
359
- Result.new(
360
- ok: false,
361
- error_value: errors_headline + results.map(&:error_value).join(separator)
362
- )
363
- end
128
+ sig { params(configuration: Configuration, paths: T::Array[String]).returns(T::Array[Pathname]) }
129
+ def relative_paths(configuration, paths)
130
+ paths.map { |path| relative_path(configuration, path) }
364
131
  end
365
132
  end
133
+
134
+ private_constant :ApplicationValidator
366
135
  end
@@ -70,4 +70,6 @@ module Packwerk
70
70
  NodeHelpers.literal_value(association_name_node)
71
71
  end
72
72
  end
73
+
74
+ private_constant :AssociationInspector
73
75
  end
@@ -21,10 +21,10 @@ module Packwerk
21
21
  cache_contents_json = JSON.parse(serialized_cache_contents)
22
22
  unresolved_references = cache_contents_json["unresolved_references"].map do |json|
23
23
  UnresolvedReference.new(
24
- json["constant_name"],
25
- json["namespace_path"],
26
- json["relative_path"],
27
- Node::Location.new(json["source_location"]["line"], json["source_location"]["column"],)
24
+ constant_name: json["constant_name"],
25
+ namespace_path: json["namespace_path"],
26
+ relative_path: json["relative_path"],
27
+ source_location: Node::Location.new(json["source_location"]["line"], json["source_location"]["column"],)
28
28
  )
29
29
  end
30
30
 
@@ -169,8 +169,9 @@ module Packwerk
169
169
  puts(out)
170
170
  end
171
171
  end
172
- end
172
+ end
173
173
  end
174
174
 
175
+ private_constant :Cache
175
176
  private_constant :Debug
176
177
  end
@@ -0,0 +1,54 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Checker
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ abstract!
10
+
11
+ class << self
12
+ extend T::Sig
13
+
14
+ sig { params(base: Class).void }
15
+ def included(base)
16
+ @checkers ||= T.let(@checkers, T.nilable(T::Array[Class]))
17
+ @checkers ||= []
18
+ @checkers << base
19
+ end
20
+
21
+ sig { returns(T::Array[Checker]) }
22
+ def all
23
+ T.unsafe(@checkers).map(&:new)
24
+ end
25
+
26
+ sig { params(violation_type: String).returns(Checker) }
27
+ def find(violation_type)
28
+ checker_by_violation_type(violation_type)
29
+ end
30
+
31
+ private
32
+
33
+ sig { params(name: String).returns(Checker) }
34
+ def checker_by_violation_type(name)
35
+ @checker_by_violation_type ||= T.let(Checker.all.to_h do |checker|
36
+ [checker.violation_type, checker]
37
+ end, T.nilable(T::Hash[String, Checker]))
38
+ @checker_by_violation_type.fetch(name)
39
+ end
40
+ end
41
+
42
+ sig { abstract.returns(String) }
43
+ def violation_type; end
44
+
45
+ sig { abstract.params(listed_offense: ReferenceOffense).returns(T::Boolean) }
46
+ def strict_mode_violation?(listed_offense); end
47
+
48
+ sig { abstract.params(reference: Reference).returns(T::Boolean) }
49
+ def invalid_reference?(reference); end
50
+
51
+ sig { abstract.params(reference: Reference).returns(String) }
52
+ def message(reference); end
53
+ end
54
+ end
@@ -0,0 +1,11 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ class Cli
6
+ class Result < T::Struct
7
+ const :message, String
8
+ const :status, T::Boolean
9
+ end
10
+ end
11
+ end
data/lib/packwerk/cli.rb CHANGED
@@ -14,8 +14,8 @@ module Packwerk
14
14
  out: T.any(StringIO, IO),
15
15
  err_out: T.any(StringIO, IO),
16
16
  environment: String,
17
- style: Packwerk::OutputStyle,
18
- offenses_formatter: T.nilable(Packwerk::OffensesFormatter)
17
+ style: OutputStyle,
18
+ offenses_formatter: T.nilable(OffensesFormatter)
19
19
  ).void
20
20
  end
21
21
  def initialize(
@@ -33,7 +33,7 @@ module Packwerk
33
33
  @configuration = T.let(configuration || Configuration.from_path, Configuration)
34
34
  @progress_formatter = T.let(Formatters::ProgressFormatter.new(@out, style: style), Formatters::ProgressFormatter)
35
35
  @offenses_formatter = T.let(
36
- offenses_formatter || Formatters::OffensesFormatter.new(style: @style),
36
+ offenses_formatter || @configuration.offenses_formatter,
37
37
  OffensesFormatter
38
38
  )
39
39
  end
@@ -52,12 +52,13 @@ module Packwerk
52
52
  init
53
53
  when "check"
54
54
  output_result(parse_run(args).check)
55
- when "detect-stale-violations"
56
- output_result(parse_run(args).detect_stale_violations)
57
- when "update-deprecations"
58
- output_result(parse_run(args).update_deprecations)
55
+ when "update-todo", "update"
56
+ output_result(parse_run(args).update_todo)
59
57
  when "validate"
60
58
  validate(args)
59
+ when "version"
60
+ @out.puts(Packwerk::VERSION)
61
+ true
61
62
  when nil, "help"
62
63
  usage
63
64
  else
@@ -79,12 +80,12 @@ module Packwerk
79
80
 
80
81
  sig { returns(T::Boolean) }
81
82
  def generate_configs
82
- configuration_file = Packwerk::Generators::ConfigurationFile.generate(
83
+ configuration_file = Generators::ConfigurationFile.generate(
83
84
  root: @configuration.root_path,
84
85
  out: @out
85
86
  )
86
87
 
87
- root_package = Packwerk::Generators::RootPackage.generate(root: @configuration.root_path, out: @out)
88
+ root_package = Generators::RootPackage.generate(root: @configuration.root_path, out: @out)
88
89
 
89
90
  success = configuration_file && root_package
90
91
 
@@ -114,8 +115,9 @@ module Packwerk
114
115
  Subcommands:
115
116
  init - set up packwerk
116
117
  check - run all checks
117
- update-deprecations - update deprecated_references.yml files
118
+ update-todo - update package_todo.yml files
118
119
  validate - verify integrity of packwerk and package configuration
120
+ version - output packwerk version
119
121
  help - display help information about packwerk
120
122
  USAGE
121
123
  true
@@ -132,72 +134,95 @@ module Packwerk
132
134
  params(
133
135
  relative_file_paths: T::Array[String],
134
136
  ignore_nested_packages: T::Boolean
135
- ).returns(FilesForProcessing::RelativeFileSet)
137
+ ).returns(FilesForProcessing)
136
138
  end
137
139
  def fetch_files_to_process(relative_file_paths, ignore_nested_packages)
138
- relative_file_set = FilesForProcessing.fetch(
140
+ files_for_processing = FilesForProcessing.fetch(
139
141
  relative_file_paths: relative_file_paths,
140
142
  ignore_nested_packages: ignore_nested_packages,
141
143
  configuration: @configuration
142
144
  )
143
- abort("No files found or given. "\
144
- "Specify files or check the include and exclude glob in the config file.") if relative_file_set.empty?
145
- relative_file_set
145
+ @out.puts(<<~MSG.squish) if files_for_processing.files.empty?
146
+ No files found or given.
147
+ Specify files or check the include and exclude glob in the config file.
148
+ MSG
149
+
150
+ files_for_processing
146
151
  end
147
152
 
148
153
  sig { params(_paths: T::Array[String]).returns(T::Boolean) }
149
154
  def validate(_paths)
155
+ result = T.let(nil, T.nilable(Validator::Result))
156
+
150
157
  @progress_formatter.started_validation do
151
- result = checker.check_all
158
+ result = validator.check_all(package_set, @configuration)
152
159
 
153
160
  list_validation_errors(result)
154
-
155
- return result.ok?
156
161
  end
162
+
163
+ T.must(result).ok?
157
164
  end
158
165
 
159
166
  sig { returns(ApplicationValidator) }
160
- def checker
161
- Packwerk::ApplicationValidator.new(
162
- config_file_path: @configuration.config_path,
163
- configuration: @configuration,
164
- environment: @environment,
167
+ def validator
168
+ ApplicationValidator.new
169
+ end
170
+
171
+ sig { returns(PackageSet) }
172
+ def package_set
173
+ PackageSet.load_all_from(
174
+ @configuration.root_path,
175
+ package_pathspec: @configuration.package_paths
165
176
  )
166
177
  end
167
178
 
168
- sig { params(result: ApplicationValidator::Result).void }
179
+ sig { params(result: Validator::Result).void }
169
180
  def list_validation_errors(result)
170
181
  @out.puts
171
182
  if result.ok?
172
183
  @out.puts("Validation successful 🎉")
173
184
  else
174
185
  @out.puts("Validation failed ❗")
186
+ @out.puts
175
187
  @out.puts(result.error_value)
176
188
  end
177
189
  end
178
190
 
179
- sig { params(params: T.untyped).returns(ParseRun) }
180
- def parse_run(params)
191
+ sig { params(args: T::Array[String]).returns(ParseRun) }
192
+ def parse_run(args)
181
193
  relative_file_paths = T.let([], T::Array[String])
182
194
  ignore_nested_packages = nil
195
+ formatter = @offenses_formatter
183
196
 
184
- if params.any? { |p| p.include?("--packages") }
197
+ if args.any? { |arg| arg.include?("--packages") }
185
198
  OptionParser.new do |parser|
186
199
  parser.on("--packages=PACKAGESLIST", Array, "package names, comma separated") do |p|
187
200
  relative_file_paths = p
188
201
  end
189
- end.parse!(params)
202
+ end.parse!(args)
190
203
  ignore_nested_packages = true
191
204
  else
192
- relative_file_paths = params
205
+ relative_file_paths = args
193
206
  ignore_nested_packages = false
194
207
  end
195
208
 
209
+ if args.any? { |arg| arg.include?("--offenses-formatter") }
210
+ OptionParser.new do |parser|
211
+ parser.on("--offenses-formatter=FORMATTER", String,
212
+ "identifier of offenses formatter to use") do |formatter_identifier|
213
+ formatter = OffensesFormatter.find(formatter_identifier)
214
+ end
215
+ end.parse!(args)
216
+ end
217
+
218
+ files_for_processing = fetch_files_to_process(relative_file_paths, ignore_nested_packages)
219
+
196
220
  ParseRun.new(
197
- relative_file_set: fetch_files_to_process(relative_file_paths, ignore_nested_packages),
221
+ relative_file_set: files_for_processing.files,
222
+ file_set_specified: files_for_processing.files_specified?,
198
223
  configuration: @configuration,
199
224
  progress_formatter: @progress_formatter,
200
- offenses_formatter: @offenses_formatter
225
+ offenses_formatter: formatter
201
226
  )
202
227
  end
203
228
  end