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