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