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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -5
- data/.ruby-version +1 -1
- data/Gemfile +0 -1
- data/Gemfile.lock +5 -95
- data/README.md +2 -7
- data/RESOLVING_VIOLATIONS.md +7 -7
- data/TROUBLESHOOT.md +1 -23
- data/USAGE.md +149 -59
- data/dev.yml +1 -1
- data/exe/packwerk +1 -0
- data/gemfiles/Gemfile-rails-6-1 +1 -1
- data/lib/packwerk/application_validator.rb +54 -285
- data/lib/packwerk/association_inspector.rb +2 -0
- data/lib/packwerk/cache.rb +6 -5
- data/lib/packwerk/checker.rb +54 -0
- data/lib/packwerk/cli/result.rb +11 -0
- data/lib/packwerk/cli.rb +56 -31
- data/lib/packwerk/configuration.rb +61 -40
- data/lib/packwerk/const_node_inspector.rb +2 -0
- data/lib/packwerk/constant_context.rb +8 -0
- data/lib/packwerk/constant_discovery.rb +5 -6
- data/lib/packwerk/constant_name_inspector.rb +2 -0
- data/lib/packwerk/disable_sorbet.rb +41 -0
- data/lib/packwerk/extension_loader.rb +24 -0
- data/lib/packwerk/file_processor.rb +3 -1
- data/lib/packwerk/files_for_processing.rb +25 -12
- data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
- data/lib/packwerk/formatters/progress_formatter.rb +31 -12
- data/lib/packwerk/generators/configuration_file.rb +7 -2
- data/lib/packwerk/generators/root_package.rb +5 -1
- data/lib/packwerk/generators/templates/package.yml +0 -10
- data/lib/packwerk/graph.rb +10 -2
- data/lib/packwerk/node.rb +1 -1
- data/lib/packwerk/node_helpers.rb +14 -7
- data/lib/packwerk/node_processor.rb +2 -0
- data/lib/packwerk/node_processor_factory.rb +6 -4
- data/lib/packwerk/node_visitor.rb +10 -1
- data/lib/packwerk/offense_collection.rb +43 -23
- data/lib/packwerk/offenses_formatter.rb +59 -2
- data/lib/packwerk/package.rb +7 -35
- data/lib/packwerk/package_set.rb +1 -1
- data/lib/packwerk/{deprecated_references.rb → package_todo.rb} +29 -13
- data/lib/packwerk/parse_run.rb +29 -36
- data/lib/packwerk/parsed_constant_definitions.rb +28 -5
- data/lib/packwerk/parsers/erb.rb +23 -4
- data/lib/packwerk/parsers/factory.rb +11 -2
- data/lib/packwerk/parsers/parser_interface.rb +1 -1
- data/lib/packwerk/parsers/ruby.rb +13 -3
- data/lib/packwerk/parsers.rb +6 -2
- data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
- data/lib/packwerk/reference.rb +7 -1
- data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
- data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
- data/lib/packwerk/reference_extractor.rb +24 -12
- data/lib/packwerk/reference_offense.rb +2 -2
- data/lib/packwerk/run_context.rb +7 -10
- data/lib/packwerk/spring_command.rb +11 -2
- data/lib/packwerk/unresolved_reference.rb +9 -1
- data/lib/packwerk/validator/result.rb +18 -0
- data/lib/packwerk/validator.rb +90 -0
- data/lib/packwerk/validators/dependency_validator.rb +154 -0
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +64 -26
- data/packwerk.gemspec +4 -2
- data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
- data/sorbet/rbi/shims/minitest/test.rb +8 -0
- data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
- data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
- data/sorbet/rbi/shims/parser.rbi +13 -0
- metadata +35 -16
- data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
- data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
- data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
- data/lib/packwerk/result.rb +0 -9
- data/lib/packwerk/sanity_checker.rb +0 -8
- data/lib/packwerk/violation_type.rb +0 -11
- data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
- 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
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
21
|
+
sig { override.params(package_set: PackageSet, configuration: Configuration).returns(Validator::Result) }
|
22
|
+
def call(package_set, configuration)
|
43
23
|
results = [
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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(
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
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 |
|
93
|
-
hash = YAML.load_file(
|
44
|
+
package_manifests(configuration).each do |manifest|
|
45
|
+
hash = YAML.load_file(manifest)
|
94
46
|
next unless hash
|
95
47
|
|
96
|
-
known_keys =
|
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 << "
|
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
|
-
|
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:
|
140
|
-
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(
|
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 {
|
277
|
-
def
|
278
|
-
|
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
|
data/lib/packwerk/cache.rb
CHANGED
@@ -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
|
-
|
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
|
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:
|
18
|
-
offenses_formatter: T.nilable(
|
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 ||
|
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 "
|
56
|
-
output_result(parse_run(args).
|
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 =
|
83
|
+
configuration_file = Generators::ConfigurationFile.generate(
|
83
84
|
root: @configuration.root_path,
|
84
85
|
out: @out
|
85
86
|
)
|
86
87
|
|
87
|
-
root_package =
|
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-
|
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
|
137
|
+
).returns(FilesForProcessing)
|
136
138
|
end
|
137
139
|
def fetch_files_to_process(relative_file_paths, ignore_nested_packages)
|
138
|
-
|
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
|
-
|
144
|
-
|
145
|
-
|
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 =
|
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
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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:
|
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(
|
180
|
-
def parse_run(
|
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
|
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!(
|
202
|
+
end.parse!(args)
|
190
203
|
ignore_nested_packages = true
|
191
204
|
else
|
192
|
-
relative_file_paths =
|
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:
|
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:
|
225
|
+
offenses_formatter: formatter
|
201
226
|
)
|
202
227
|
end
|
203
228
|
end
|