packwerk 2.1.0 → 2.1.1
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/Gemfile.lock +20 -17
- data/lib/packwerk/application_validator.rb +78 -39
- data/lib/packwerk/cache.rb +2 -1
- data/lib/packwerk/cli.rb +22 -13
- data/lib/packwerk/constant_discovery.rb +17 -1
- data/lib/packwerk/constant_name_inspector.rb +1 -1
- data/lib/packwerk/file_processor.rb +20 -15
- data/lib/packwerk/files_for_processing.rb +49 -22
- data/lib/packwerk/node_processor.rb +5 -5
- data/lib/packwerk/node_processor_factory.rb +3 -3
- data/lib/packwerk/offense.rb +10 -2
- data/lib/packwerk/package_set.rb +2 -2
- data/lib/packwerk/parse_run.rb +37 -17
- data/lib/packwerk/parsers/erb.rb +2 -0
- data/lib/packwerk/parsers/factory.rb +2 -0
- data/lib/packwerk/parsers/parser_interface.rb +17 -0
- data/lib/packwerk/parsers/ruby.rb +2 -0
- data/lib/packwerk/parsers.rb +1 -0
- data/lib/packwerk/reference_checking/checkers/checker.rb +1 -1
- data/lib/packwerk/reference_checking/reference_checker.rb +2 -1
- data/lib/packwerk/reference_extractor.rb +20 -9
- data/lib/packwerk/reference_offense.rb +8 -3
- data/lib/packwerk/result.rb +2 -2
- data/lib/packwerk/run_context.rb +47 -41
- data/lib/packwerk/spring_command.rb +1 -1
- data/lib/packwerk/version.rb +1 -1
- data/sorbet/config +1 -0
- data/sorbet/rbi/gems/tapioca@0.4.19.rbi +1 -1
- data/sorbet/tapioca/require.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0270de859808f51bbfa45a7140813744da0555c57d9d60432ba4e166ab63c4a
|
4
|
+
data.tar.gz: 90465398a3e04f83ba4c0e3b290dca72677b166003640504c7470b181e556d73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86e386674412f99d605013799ebd09bbff4d9e0dd2718de534db33e71c148fd0b8aa2ec905ca96c34c48ce85e31e922eae41352c05676de8f4af6baea749c03e
|
7
|
+
data.tar.gz: 6406b7ddbcac8c91f3e51660d5c5e111145b6f8f01ef6ca2bfc1797cdec39b13cb0db1e3d8d04026bc59835bb67e9e8acb41a5edbd0cd207f609bbb532ffa067
|
data/Gemfile.lock
CHANGED
@@ -87,7 +87,7 @@ GIT
|
|
87
87
|
PATH
|
88
88
|
remote: .
|
89
89
|
specs:
|
90
|
-
packwerk (2.1.
|
90
|
+
packwerk (2.1.1)
|
91
91
|
activesupport (>= 5.2)
|
92
92
|
ast
|
93
93
|
better_html
|
@@ -138,14 +138,16 @@ GEM
|
|
138
138
|
marcel (1.0.0)
|
139
139
|
method_source (1.0.0)
|
140
140
|
mini_mime (1.0.3)
|
141
|
-
mini_portile2 (2.
|
141
|
+
mini_portile2 (2.8.0)
|
142
142
|
minitest (5.14.4)
|
143
143
|
minitest-focus (1.2.1)
|
144
144
|
minitest (>= 4, < 6)
|
145
145
|
mocha (1.12.0)
|
146
146
|
nio4r (2.5.7)
|
147
|
-
nokogiri (1.
|
148
|
-
mini_portile2 (~> 2.
|
147
|
+
nokogiri (1.13.3)
|
148
|
+
mini_portile2 (~> 2.8.0)
|
149
|
+
racc (~> 1.4)
|
150
|
+
nokogiri (1.13.3-x86_64-darwin)
|
149
151
|
racc (~> 1.4)
|
150
152
|
parallel (1.20.1)
|
151
153
|
parlour (6.0.0)
|
@@ -159,7 +161,7 @@ GEM
|
|
159
161
|
coderay (~> 1.1)
|
160
162
|
method_source (~> 1.0)
|
161
163
|
psych (3.3.2)
|
162
|
-
racc (1.
|
164
|
+
racc (1.6.0)
|
163
165
|
rack (2.2.3)
|
164
166
|
rack-test (1.1.0)
|
165
167
|
rack (>= 1.0, < 3)
|
@@ -191,18 +193,19 @@ GEM
|
|
191
193
|
rubocop-sorbet (0.6.1)
|
192
194
|
rubocop
|
193
195
|
ruby-progressbar (1.11.0)
|
194
|
-
smart_properties (1.
|
195
|
-
sorbet (0.5.
|
196
|
-
sorbet-static (= 0.5.
|
197
|
-
sorbet-runtime (0.5.
|
198
|
-
sorbet-static (0.5.
|
199
|
-
sorbet-static (0.5.
|
200
|
-
sorbet-static (0.5.
|
201
|
-
sorbet-static (0.5.
|
202
|
-
sorbet-static (0.5.
|
203
|
-
sorbet-static (0.5.
|
204
|
-
sorbet-static (0.5.
|
205
|
-
sorbet-static (0.5.
|
196
|
+
smart_properties (1.17.0)
|
197
|
+
sorbet (0.5.9538)
|
198
|
+
sorbet-static (= 0.5.9538)
|
199
|
+
sorbet-runtime (0.5.9538)
|
200
|
+
sorbet-static (0.5.9538-universal-darwin-14)
|
201
|
+
sorbet-static (0.5.9538-universal-darwin-15)
|
202
|
+
sorbet-static (0.5.9538-universal-darwin-16)
|
203
|
+
sorbet-static (0.5.9538-universal-darwin-17)
|
204
|
+
sorbet-static (0.5.9538-universal-darwin-18)
|
205
|
+
sorbet-static (0.5.9538-universal-darwin-19)
|
206
|
+
sorbet-static (0.5.9538-universal-darwin-20)
|
207
|
+
sorbet-static (0.5.9538-universal-darwin-21)
|
208
|
+
sorbet-static (0.5.9538-x86_64-linux)
|
206
209
|
spoom (1.1.0)
|
207
210
|
colorize
|
208
211
|
sorbet (>= 0.5.6347)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "constant_resolver"
|
@@ -9,14 +9,36 @@ 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
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig do
|
15
|
+
params(
|
16
|
+
config_file_path: String,
|
17
|
+
configuration: Configuration,
|
18
|
+
environment: String
|
19
|
+
).void
|
20
|
+
end
|
12
21
|
def initialize(config_file_path:, configuration:, environment:)
|
13
22
|
@config_file_path = config_file_path
|
14
23
|
@configuration = configuration
|
15
24
|
@environment = environment
|
25
|
+
@package_set = T.let(PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob),
|
26
|
+
PackageSet)
|
16
27
|
end
|
17
28
|
|
18
|
-
Result
|
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
|
39
|
+
end
|
19
40
|
|
41
|
+
sig { returns(Result) }
|
20
42
|
def check_all
|
21
43
|
results = [
|
22
44
|
check_package_manifests_for_privacy,
|
@@ -31,6 +53,7 @@ module Packwerk
|
|
31
53
|
merge_results(results)
|
32
54
|
end
|
33
55
|
|
56
|
+
sig { returns(Result) }
|
34
57
|
def check_package_manifests_for_privacy
|
35
58
|
privacy_settings = package_manifests_settings_for("enforce_privacy")
|
36
59
|
|
@@ -39,7 +62,7 @@ module Packwerk
|
|
39
62
|
load_paths: @configuration.load_paths
|
40
63
|
)
|
41
64
|
|
42
|
-
results = T.let([], T::Array[
|
65
|
+
results = T.let([], T::Array[Result])
|
43
66
|
|
44
67
|
privacy_settings.each do |config_file_path, setting|
|
45
68
|
next unless setting.is_a?(Array)
|
@@ -61,6 +84,7 @@ module Packwerk
|
|
61
84
|
merge_results(results, separator: "\n---\n")
|
62
85
|
end
|
63
86
|
|
87
|
+
sig { returns(Result) }
|
64
88
|
def check_package_manifest_syntax
|
65
89
|
errors = []
|
66
90
|
|
@@ -102,12 +126,13 @@ module Packwerk
|
|
102
126
|
end
|
103
127
|
|
104
128
|
if errors.empty?
|
105
|
-
Result.new(true)
|
129
|
+
Result.new(ok: true)
|
106
130
|
else
|
107
|
-
Result.new(false, errors.join("\n---\n"))
|
131
|
+
Result.new(ok: false, error_value: errors.join("\n---\n"))
|
108
132
|
end
|
109
133
|
end
|
110
134
|
|
135
|
+
sig { returns(Result) }
|
111
136
|
def check_application_structure
|
112
137
|
resolver = ConstantResolver.new(
|
113
138
|
root_path: @configuration.root_path.to_s,
|
@@ -116,26 +141,27 @@ module Packwerk
|
|
116
141
|
|
117
142
|
begin
|
118
143
|
resolver.file_map
|
119
|
-
Result.new(true)
|
144
|
+
Result.new(ok: true)
|
120
145
|
rescue => e
|
121
|
-
Result.new(false, e.message)
|
146
|
+
Result.new(ok: false, error_value: e.message)
|
122
147
|
end
|
123
148
|
end
|
124
149
|
|
150
|
+
sig { returns(Result) }
|
125
151
|
def check_acyclic_graph
|
126
|
-
edges = package_set.flat_map do |package|
|
127
|
-
package.dependencies.map { |dependency| [package, package_set.fetch(dependency)] }
|
152
|
+
edges = @package_set.flat_map do |package|
|
153
|
+
package.dependencies.map { |dependency| [package, @package_set.fetch(dependency)] }
|
128
154
|
end
|
129
|
-
dependency_graph =
|
155
|
+
dependency_graph = Graph.new(*T.unsafe(edges))
|
130
156
|
|
131
157
|
cycle_strings = build_cycle_strings(dependency_graph.cycles)
|
132
158
|
|
133
159
|
if dependency_graph.acyclic?
|
134
|
-
Result.new(true)
|
160
|
+
Result.new(ok: true)
|
135
161
|
else
|
136
162
|
Result.new(
|
137
|
-
false,
|
138
|
-
<<~EOS
|
163
|
+
ok: false,
|
164
|
+
error_value: <<~EOS
|
139
165
|
Expected the package dependency graph to be acyclic, but it contains the following cycles:
|
140
166
|
|
141
167
|
#{cycle_strings.join("\n")}
|
@@ -144,6 +170,7 @@ module Packwerk
|
|
144
170
|
end
|
145
171
|
end
|
146
172
|
|
173
|
+
sig { returns(Result) }
|
147
174
|
def check_package_manifest_paths
|
148
175
|
all_package_manifests = package_manifests("**/")
|
149
176
|
package_paths_package_manifests = package_manifests(package_glob)
|
@@ -151,11 +178,11 @@ module Packwerk
|
|
151
178
|
difference = all_package_manifests - package_paths_package_manifests
|
152
179
|
|
153
180
|
if difference.empty?
|
154
|
-
Result.new(true)
|
181
|
+
Result.new(ok: true)
|
155
182
|
else
|
156
183
|
Result.new(
|
157
|
-
false,
|
158
|
-
<<~EOS
|
184
|
+
ok: false,
|
185
|
+
error_value: <<~EOS
|
159
186
|
Expected package paths for all package.ymls to be specified, but paths were missing for the following manifests:
|
160
187
|
|
161
188
|
#{relative_paths(difference).join("\n")}
|
@@ -164,6 +191,7 @@ module Packwerk
|
|
164
191
|
end
|
165
192
|
end
|
166
193
|
|
194
|
+
sig { returns(Result) }
|
167
195
|
def check_valid_package_dependencies
|
168
196
|
packages_dependencies = package_manifests_settings_for("dependencies")
|
169
197
|
.delete_if { |_, deps| deps.nil? }
|
@@ -175,7 +203,7 @@ module Packwerk
|
|
175
203
|
end
|
176
204
|
|
177
205
|
if packages_with_invalid_dependencies.empty?
|
178
|
-
Result.new(true)
|
206
|
+
Result.new(ok: true)
|
179
207
|
else
|
180
208
|
error_locations = packages_with_invalid_dependencies.map do |package, invalid_dependencies|
|
181
209
|
package ||= @configuration.root_path
|
@@ -189,8 +217,8 @@ module Packwerk
|
|
189
217
|
end
|
190
218
|
|
191
219
|
Result.new(
|
192
|
-
false,
|
193
|
-
<<~EOS
|
220
|
+
ok: false,
|
221
|
+
error_value: <<~EOS
|
194
222
|
These dependencies do not point to valid packages:
|
195
223
|
|
196
224
|
#{error_locations.join("\n")}
|
@@ -199,16 +227,17 @@ module Packwerk
|
|
199
227
|
end
|
200
228
|
end
|
201
229
|
|
230
|
+
sig { returns(Result) }
|
202
231
|
def check_root_package_exists
|
203
232
|
root_package_path = File.join(@configuration.root_path, "package.yml")
|
204
233
|
all_packages_manifests = package_manifests(package_glob)
|
205
234
|
|
206
235
|
if all_packages_manifests.include?(root_package_path)
|
207
|
-
Result.new(true)
|
236
|
+
Result.new(ok: true)
|
208
237
|
else
|
209
238
|
Result.new(
|
210
|
-
false,
|
211
|
-
<<~EOS
|
239
|
+
ok: false,
|
240
|
+
error_value: <<~EOS
|
212
241
|
A root package does not exist. Create an empty `package.yml` at the root directory.
|
213
242
|
EOS
|
214
243
|
)
|
@@ -224,6 +253,7 @@ module Packwerk
|
|
224
253
|
# to the string:
|
225
254
|
#
|
226
255
|
# ["a -> b -> c -> a", "b -> c -> b"]
|
256
|
+
sig { params(cycles: T.untyped).returns(T::Array[String]) }
|
227
257
|
def build_cycle_strings(cycles)
|
228
258
|
cycles.map do |cycle|
|
229
259
|
cycle_strings = cycle.map(&:to_s)
|
@@ -232,93 +262,102 @@ module Packwerk
|
|
232
262
|
end
|
233
263
|
end
|
234
264
|
|
265
|
+
sig { params(setting: T.untyped).returns(T.untyped) }
|
235
266
|
def package_manifests_settings_for(setting)
|
236
267
|
package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
237
268
|
end
|
238
269
|
|
270
|
+
sig { params(list: T.untyped).returns(T.untyped) }
|
239
271
|
def format_yaml_strings(list)
|
240
272
|
list.sort.map { |p| "- \"#{p}\"" }.join("\n")
|
241
273
|
end
|
242
274
|
|
275
|
+
sig { returns(T.any(T::Array[String], String)) }
|
243
276
|
def package_glob
|
244
277
|
@configuration.package_paths || "**"
|
245
278
|
end
|
246
279
|
|
280
|
+
sig { params(glob_pattern: T.any(T::Array[String], String)).returns(T::Array[String]) }
|
247
281
|
def package_manifests(glob_pattern = package_glob)
|
248
282
|
PackageSet.package_paths(@configuration.root_path, glob_pattern, @configuration.exclude)
|
249
283
|
.map { |f| File.realpath(f) }
|
250
284
|
end
|
251
285
|
|
286
|
+
sig { params(paths: T::Array[String]).returns(T::Array[Pathname]) }
|
252
287
|
def relative_paths(paths)
|
253
288
|
paths.map { |path| relative_path(path) }
|
254
289
|
end
|
255
290
|
|
291
|
+
sig { params(path: String).returns(Pathname) }
|
256
292
|
def relative_path(path)
|
257
293
|
Pathname.new(path).relative_path_from(@configuration.root_path)
|
258
294
|
end
|
259
295
|
|
296
|
+
sig { params(path: T.untyped).returns(T::Boolean) }
|
260
297
|
def invalid_package_path?(path)
|
261
298
|
# Packages at the root can be implicitly specified as "."
|
262
299
|
return false if path == "."
|
263
300
|
|
264
|
-
package_path = File.join(@configuration.root_path, path,
|
301
|
+
package_path = File.join(@configuration.root_path, path, PackageSet::PACKAGE_CONFIG_FILENAME)
|
265
302
|
!File.file?(package_path)
|
266
303
|
end
|
267
304
|
|
305
|
+
sig { params(constants: T.untyped, config_file_path: String).returns(T::Array[Result]) }
|
268
306
|
def assert_constants_can_be_loaded(constants, config_file_path)
|
269
307
|
constants.map do |constant|
|
270
308
|
if !constant.start_with?("::")
|
271
309
|
Result.new(
|
272
|
-
false,
|
273
|
-
"'#{constant}', listed in the 'enforce_privacy' option in #{config_file_path}, is invalid.\n"\
|
310
|
+
ok: false,
|
311
|
+
error_value: "'#{constant}', listed in the 'enforce_privacy' option in #{config_file_path}, is invalid.\n"\
|
274
312
|
"Private constants need to be prefixed with the top-level namespace operator `::`."
|
275
313
|
)
|
276
314
|
else
|
277
|
-
constant.try(&:constantize) && Result.new(true)
|
315
|
+
constant.try(&:constantize) && Result.new(ok: true)
|
278
316
|
end
|
279
317
|
end
|
280
318
|
end
|
281
319
|
|
320
|
+
sig { params(name: T.untyped, config_file_path: T.untyped).returns(Result) }
|
282
321
|
def private_constant_unresolvable(name, config_file_path)
|
283
322
|
explicit_filepath = (name.start_with?("::") ? name[2..-1] : name).underscore + ".rb"
|
284
323
|
|
285
324
|
Result.new(
|
286
|
-
false,
|
287
|
-
"'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
|
325
|
+
ok: false,
|
326
|
+
error_value: "'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
|
288
327
|
"This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
|
289
328
|
"file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
|
290
329
|
"private. Add a #{explicit_filepath} file to explicitly define the constant."
|
291
330
|
)
|
292
331
|
end
|
293
332
|
|
333
|
+
sig { params(name: T.untyped, location: T.untyped, config_file_path: T.untyped).returns(Result) }
|
294
334
|
def check_private_constant_location(name, location, config_file_path)
|
295
|
-
declared_package = package_set.package_from_path(relative_path(config_file_path))
|
296
|
-
constant_package = package_set.package_from_path(location)
|
335
|
+
declared_package = @package_set.package_from_path(relative_path(config_file_path))
|
336
|
+
constant_package = @package_set.package_from_path(location)
|
297
337
|
|
298
338
|
if constant_package == declared_package
|
299
|
-
Result.new(true)
|
339
|
+
Result.new(ok: true)
|
300
340
|
else
|
301
341
|
Result.new(
|
302
|
-
false,
|
303
|
-
"'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
|
342
|
+
ok: false,
|
343
|
+
error_value: "'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
|
304
344
|
"defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
|
305
345
|
)
|
306
346
|
end
|
307
347
|
end
|
308
348
|
|
309
|
-
|
310
|
-
|
349
|
+
sig do
|
350
|
+
params(results: T::Array[Result], separator: String, errors_headline: String).returns(Result)
|
311
351
|
end
|
312
|
-
|
313
352
|
def merge_results(results, separator: "\n===\n", errors_headline: "")
|
314
353
|
results.reject!(&:ok?)
|
315
354
|
|
316
355
|
if results.empty?
|
317
|
-
Result.new(true)
|
356
|
+
Result.new(ok: true)
|
318
357
|
else
|
319
358
|
Result.new(
|
320
|
-
false,
|
321
|
-
errors_headline + results.map(&:error_value).join(separator)
|
359
|
+
ok: false,
|
360
|
+
error_value: errors_headline + results.map(&:error_value).join(separator)
|
322
361
|
)
|
323
362
|
end
|
324
363
|
end
|
data/lib/packwerk/cache.rb
CHANGED
@@ -44,7 +44,7 @@ module Packwerk
|
|
44
44
|
]
|
45
45
|
end
|
46
46
|
|
47
|
-
sig { params(enable_cache: T::Boolean, cache_directory: Pathname, config_path: String).void }
|
47
|
+
sig { params(enable_cache: T::Boolean, cache_directory: Pathname, config_path: T.nilable(String)).void }
|
48
48
|
def initialize(enable_cache:, cache_directory:, config_path:)
|
49
49
|
@enable_cache = enable_cache
|
50
50
|
@cache = T.let({}, CACHE_SHAPE)
|
@@ -115,6 +115,7 @@ module Packwerk
|
|
115
115
|
|
116
116
|
sig { void }
|
117
117
|
def bust_cache_if_packwerk_yml_has_changed!
|
118
|
+
return nil if @config_path.nil?
|
118
119
|
bust_cache_if_contents_have_changed(File.read(@config_path), :packwerk_yml)
|
119
120
|
end
|
120
121
|
|
data/lib/packwerk/cli.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "optparse"
|
@@ -30,9 +30,10 @@ module Packwerk
|
|
30
30
|
@err_out = err_out
|
31
31
|
@environment = environment
|
32
32
|
@style = style
|
33
|
-
@configuration = configuration || Configuration.from_path
|
34
|
-
@progress_formatter = Formatters::ProgressFormatter.new(@out, style: style)
|
35
|
-
@offenses_formatter = offenses_formatter || Formatters::OffensesFormatter.new(style: @style)
|
33
|
+
@configuration = T.let(configuration || Configuration.from_path, Configuration)
|
34
|
+
@progress_formatter = T.let(Formatters::ProgressFormatter.new(@out, style: style), Formatters::ProgressFormatter)
|
35
|
+
@offenses_formatter = T.let(offenses_formatter || Formatters::OffensesFormatter.new(style: @style),
|
36
|
+
OffensesFormatter)
|
36
37
|
end
|
37
38
|
|
38
39
|
sig { params(args: T::Array[String]).returns(T.noreturn) }
|
@@ -78,12 +79,14 @@ module Packwerk
|
|
78
79
|
|
79
80
|
private
|
80
81
|
|
82
|
+
sig { returns(T::Boolean) }
|
81
83
|
def init
|
82
84
|
@out.puts("📦 Initializing Packwerk...")
|
83
85
|
|
84
86
|
generate_configs
|
85
87
|
end
|
86
88
|
|
89
|
+
sig { returns(T::Boolean) }
|
87
90
|
def generate_configs
|
88
91
|
configuration_file = Packwerk::Generators::ConfigurationFile.generate(
|
89
92
|
root: @configuration.root_path,
|
@@ -112,23 +115,26 @@ module Packwerk
|
|
112
115
|
success
|
113
116
|
end
|
114
117
|
|
118
|
+
sig { params(result: Result).returns(T::Boolean) }
|
115
119
|
def output_result(result)
|
116
120
|
@out.puts
|
117
121
|
@out.puts(result.message)
|
118
122
|
result.status
|
119
123
|
end
|
120
124
|
|
121
|
-
|
122
|
-
|
123
|
-
|
125
|
+
sig { params(relative_file_paths: T::Array[String], ignore_nested_packages: T::Boolean).returns(T::Array[String]) }
|
126
|
+
def fetch_files_to_process(relative_file_paths, ignore_nested_packages)
|
127
|
+
absolute_files = FilesForProcessing.fetch(
|
128
|
+
relative_file_paths: relative_file_paths,
|
124
129
|
ignore_nested_packages: ignore_nested_packages,
|
125
130
|
configuration: @configuration
|
126
131
|
)
|
127
132
|
abort("No files found or given. "\
|
128
|
-
"Specify files or check the include and exclude glob in the config file.") if
|
129
|
-
|
133
|
+
"Specify files or check the include and exclude glob in the config file.") if absolute_files.empty?
|
134
|
+
absolute_files
|
130
135
|
end
|
131
136
|
|
137
|
+
sig { params(_paths: T::Array[String]).returns(T::Boolean) }
|
132
138
|
def validate(_paths)
|
133
139
|
@progress_formatter.started_validation do
|
134
140
|
result = checker.check_all
|
@@ -139,6 +145,7 @@ module Packwerk
|
|
139
145
|
end
|
140
146
|
end
|
141
147
|
|
148
|
+
sig { returns(ApplicationValidator) }
|
142
149
|
def checker
|
143
150
|
Packwerk::ApplicationValidator.new(
|
144
151
|
config_file_path: @configuration.config_path,
|
@@ -147,6 +154,7 @@ module Packwerk
|
|
147
154
|
)
|
148
155
|
end
|
149
156
|
|
157
|
+
sig { params(result: ApplicationValidator::Result).void }
|
150
158
|
def list_validation_errors(result)
|
151
159
|
@out.puts
|
152
160
|
if result.ok?
|
@@ -157,24 +165,25 @@ module Packwerk
|
|
157
165
|
end
|
158
166
|
end
|
159
167
|
|
168
|
+
sig { params(params: T.untyped).returns(ParseRun) }
|
160
169
|
def parse_run(params)
|
161
|
-
|
170
|
+
relative_file_paths = T.let([], T::Array[String])
|
162
171
|
ignore_nested_packages = nil
|
163
172
|
|
164
173
|
if params.any? { |p| p.include?("--packages") }
|
165
174
|
OptionParser.new do |parser|
|
166
175
|
parser.on("--packages=PACKAGESLIST", Array, "package names, comma separated") do |p|
|
167
|
-
|
176
|
+
relative_file_paths = p
|
168
177
|
end
|
169
178
|
end.parse!(params)
|
170
179
|
ignore_nested_packages = true
|
171
180
|
else
|
172
|
-
|
181
|
+
relative_file_paths = params
|
173
182
|
ignore_nested_packages = false
|
174
183
|
end
|
175
184
|
|
176
185
|
ParseRun.new(
|
177
|
-
|
186
|
+
absolute_files: fetch_files_to_process(relative_file_paths, ignore_nested_packages),
|
178
187
|
configuration: @configuration,
|
179
188
|
progress_formatter: @progress_formatter,
|
180
189
|
offenses_formatter: @offenses_formatter
|
@@ -15,10 +15,15 @@ module Packwerk
|
|
15
15
|
# have no way of inferring the file it is defined in. You could argue though that inheritance means that another
|
16
16
|
# constant with the same name exists in the inheriting class, and this view is sufficient for all our use cases.
|
17
17
|
class ConstantDiscovery
|
18
|
+
extend T::Sig
|
19
|
+
|
18
20
|
ConstantContext = Struct.new(:name, :location, :package, :public?)
|
19
21
|
|
20
22
|
# @param constant_resolver [ConstantResolver]
|
21
23
|
# @param packages [Packwerk::PackageSet]
|
24
|
+
sig do
|
25
|
+
params(constant_resolver: ConstantResolver, packages: Packwerk::PackageSet).void
|
26
|
+
end
|
22
27
|
def initialize(constant_resolver:, packages:)
|
23
28
|
@packages = packages
|
24
29
|
@resolver = constant_resolver
|
@@ -30,6 +35,11 @@ module Packwerk
|
|
30
35
|
#
|
31
36
|
# @return [Packwerk::Package] the package that contains the given file,
|
32
37
|
# or nil if the path is not owned by any component
|
38
|
+
sig do
|
39
|
+
params(
|
40
|
+
path: String,
|
41
|
+
).returns(Packwerk::Package)
|
42
|
+
end
|
33
43
|
def package_from_path(path)
|
34
44
|
@packages.package_from_path(path)
|
35
45
|
end
|
@@ -41,6 +51,12 @@ module Packwerk
|
|
41
51
|
# @param current_namespace_path [Array<String>] (optional) The namespace of the context in which the constant is
|
42
52
|
# used, e.g. ["Apps", "Models"] for `Apps::Models`. Defaults to [] which means top level.
|
43
53
|
# @return [Packwerk::ConstantDiscovery::ConstantContext]
|
54
|
+
sig do
|
55
|
+
params(
|
56
|
+
const_name: String,
|
57
|
+
current_namespace_path: T.nilable(T::Array[String]),
|
58
|
+
).returns(T.nilable(ConstantDiscovery::ConstantContext))
|
59
|
+
end
|
44
60
|
def context_for(const_name, current_namespace_path: [])
|
45
61
|
begin
|
46
62
|
constant = @resolver.resolve(const_name, current_namespace_path: current_namespace_path)
|
@@ -55,7 +71,7 @@ module Packwerk
|
|
55
71
|
constant.name,
|
56
72
|
constant.location,
|
57
73
|
package,
|
58
|
-
package
|
74
|
+
package.public_path?(constant.location),
|
59
75
|
)
|
60
76
|
end
|
61
77
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "ast/node"
|
@@ -8,6 +8,9 @@ module Packwerk
|
|
8
8
|
extend T::Sig
|
9
9
|
|
10
10
|
class UnknownFileTypeResult < Offense
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { params(file: String).void }
|
11
14
|
def initialize(file:)
|
12
15
|
super(file: file, message: "unknown file type")
|
13
16
|
end
|
@@ -23,11 +26,11 @@ module Packwerk
|
|
23
26
|
def initialize(node_processor_factory:, cache:, parser_factory: nil)
|
24
27
|
@node_processor_factory = node_processor_factory
|
25
28
|
@cache = cache
|
26
|
-
@parser_factory = parser_factory || Packwerk::Parsers::Factory.instance
|
29
|
+
@parser_factory = T.let(parser_factory || Packwerk::Parsers::Factory.instance, Parsers::Factory)
|
27
30
|
end
|
28
31
|
|
29
32
|
sig do
|
30
|
-
params(
|
33
|
+
params(absolute_file: String).returns(
|
31
34
|
T::Array[
|
32
35
|
T.any(
|
33
36
|
Packwerk::UnresolvedReference,
|
@@ -36,15 +39,15 @@ module Packwerk
|
|
36
39
|
]
|
37
40
|
)
|
38
41
|
end
|
39
|
-
def call(
|
40
|
-
|
41
|
-
|
42
|
-
@cache.with_cache(file_path) do
|
43
|
-
node = parse_into_ast(file_path)
|
42
|
+
def call(absolute_file)
|
43
|
+
parser = parser_for(absolute_file)
|
44
|
+
return [UnknownFileTypeResult.new(file: absolute_file)] if T.unsafe(parser).nil?
|
44
45
|
|
46
|
+
@cache.with_cache(absolute_file) do
|
47
|
+
node = parse_into_ast(absolute_file, T.must(parser))
|
45
48
|
return [] unless node
|
46
49
|
|
47
|
-
references_from_ast(node,
|
50
|
+
references_from_ast(node, absolute_file)
|
48
51
|
end
|
49
52
|
rescue Parsers::ParseError => e
|
50
53
|
[e.result]
|
@@ -53,24 +56,26 @@ module Packwerk
|
|
53
56
|
private
|
54
57
|
|
55
58
|
sig do
|
56
|
-
params(node: Parser::AST::Node,
|
59
|
+
params(node: Parser::AST::Node, absolute_file: String).returns(T::Array[UnresolvedReference])
|
57
60
|
end
|
58
|
-
def references_from_ast(node,
|
61
|
+
def references_from_ast(node, absolute_file)
|
59
62
|
references = []
|
60
63
|
|
61
|
-
node_processor = @node_processor_factory.for(
|
64
|
+
node_processor = @node_processor_factory.for(absolute_file: absolute_file, node: node)
|
62
65
|
node_visitor = Packwerk::NodeVisitor.new(node_processor: node_processor)
|
63
66
|
node_visitor.visit(node, ancestors: [], result: references)
|
64
67
|
|
65
68
|
references
|
66
69
|
end
|
67
70
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
+
sig { params(absolute_file: String, parser: Parsers::ParserInterface).returns(T.untyped) }
|
72
|
+
def parse_into_ast(absolute_file, parser)
|
73
|
+
File.open(absolute_file, "r", nil, external_encoding: Encoding::UTF_8) do |file|
|
74
|
+
parser.call(io: file, file_path: absolute_file)
|
71
75
|
end
|
72
76
|
end
|
73
77
|
|
78
|
+
sig { params(file_path: String).returns(T.nilable(Parsers::ParserInterface)) }
|
74
79
|
def parser_for(file_path)
|
75
80
|
@parser_factory.for_path(file_path)
|
76
81
|
end
|