packwerk 2.2.2 → 3.0.0

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