packwerk 1.1.0 → 1.2.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 +14 -5
- data/.ruby-version +1 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +129 -111
- data/README.md +8 -1
- data/USAGE.md +17 -16
- data/dev.yml +1 -1
- data/exe/packwerk +1 -1
- data/gemfiles/Gemfile-rails-6-0 +22 -0
- data/lib/packwerk/application_load_paths.rb +3 -2
- data/lib/packwerk/application_validator.rb +83 -67
- data/lib/packwerk/association_inspector.rb +23 -11
- data/lib/packwerk/checker.rb +4 -7
- data/lib/packwerk/cli.rb +32 -100
- data/lib/packwerk/configuration.rb +10 -2
- data/lib/packwerk/const_node_inspector.rb +13 -14
- data/lib/packwerk/constant_discovery.rb +2 -0
- data/lib/packwerk/constant_name_inspector.rb +0 -1
- data/lib/packwerk/dependency_checker.rb +12 -17
- data/lib/packwerk/deprecated_references.rb +7 -9
- data/lib/packwerk/file_processor.rb +0 -4
- data/lib/packwerk/formatters/offenses_formatter.rb +43 -0
- data/lib/packwerk/formatters/progress_formatter.rb +9 -4
- data/lib/packwerk/generators/configuration_file.rb +0 -1
- data/lib/packwerk/inflector.rb +0 -2
- data/lib/packwerk/node.rb +9 -2
- data/lib/packwerk/node_processor.rb +15 -32
- data/lib/packwerk/node_processor_factory.rb +0 -5
- data/lib/packwerk/node_visitor.rb +1 -4
- data/lib/packwerk/offense.rb +2 -8
- data/lib/packwerk/offense_collection.rb +84 -0
- data/lib/packwerk/offenses_formatter.rb +15 -0
- data/lib/packwerk/output_style.rb +20 -0
- data/lib/packwerk/output_styles/coloured.rb +29 -0
- data/lib/packwerk/output_styles/plain.rb +26 -0
- data/lib/packwerk/package.rb +8 -0
- data/lib/packwerk/package_set.rb +2 -3
- data/lib/packwerk/parse_run.rb +104 -0
- data/lib/packwerk/parsed_constant_definitions.rb +2 -4
- data/lib/packwerk/parsers/erb.rb +0 -2
- data/lib/packwerk/parsers/factory.rb +1 -3
- data/lib/packwerk/parsers.rb +0 -2
- data/lib/packwerk/privacy_checker.rb +22 -17
- data/lib/packwerk/reference_extractor.rb +0 -8
- data/lib/packwerk/reference_offense.rb +49 -0
- data/lib/packwerk/result.rb +9 -0
- data/lib/packwerk/run_context.rb +4 -21
- data/lib/packwerk/sanity_checker.rb +1 -3
- data/lib/packwerk/spring_command.rb +1 -1
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk/violation_type.rb +0 -2
- data/lib/packwerk.rb +73 -34
- data/library.yml +1 -1
- data/packwerk.gemspec +1 -0
- data/service.yml +1 -4
- data/shipit.rubygems.yml +5 -1
- data/sorbet/rbi/gems/{actioncable@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +56 -36
- data/sorbet/rbi/gems/{actionmailbox@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +25 -28
- data/sorbet/rbi/gems/{actionmailer@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +43 -24
- data/sorbet/rbi/gems/{actionpack@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +382 -284
- data/sorbet/rbi/gems/{actiontext@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +76 -40
- data/sorbet/rbi/gems/{actionview@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +206 -195
- data/sorbet/rbi/gems/{activejob@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +64 -75
- data/sorbet/rbi/gems/{activemodel@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +103 -56
- data/sorbet/rbi/gems/{activerecord@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1250 -898
- data/sorbet/rbi/gems/{activestorage@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +92 -120
- data/sorbet/rbi/gems/{activesupport@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +292 -193
- data/sorbet/rbi/gems/{ast@2.4.1.rbi → ast@2.4.2.rbi} +2 -1
- data/sorbet/rbi/gems/{better_html@1.0.15.rbi → better_html@1.0.16.rbi} +2 -2
- data/sorbet/rbi/gems/{concurrent-ruby@1.1.6.rbi → concurrent-ruby@1.1.8.rbi} +12 -9
- data/sorbet/rbi/gems/{erubi@1.9.0.rbi → erubi@1.10.0.rbi} +3 -1
- data/sorbet/rbi/gems/{i18n@1.8.2.rbi → i18n@1.8.10.rbi} +19 -52
- data/sorbet/rbi/gems/{loofah@2.5.0.rbi → loofah@2.9.0.rbi} +3 -1
- data/sorbet/rbi/gems/marcel@1.0.0.rbi +70 -0
- data/sorbet/rbi/gems/{mini_mime@1.0.2.rbi → mini_mime@1.0.3.rbi} +6 -6
- data/sorbet/rbi/gems/{mini_portile2@2.4.0.rbi → minitest-focus@1.2.1.rbi} +2 -2
- data/sorbet/rbi/gems/{minitest@5.14.0.rbi → minitest@5.14.4.rbi} +31 -29
- data/sorbet/rbi/gems/{mocha@1.11.2.rbi → mocha@1.12.0.rbi} +25 -36
- data/sorbet/rbi/gems/{nio4r@2.5.2.rbi → nio4r@2.5.7.rbi} +21 -20
- data/sorbet/rbi/gems/{nokogiri@1.10.9.rbi → nokogiri@1.11.2.rbi} +193 -154
- data/sorbet/rbi/gems/parallel@1.20.1.rbi +117 -0
- data/sorbet/rbi/gems/parlour@6.0.0.rbi +1272 -0
- data/sorbet/rbi/gems/{parser@2.7.1.4.rbi → parser@3.0.0.0.rbi} +287 -174
- data/sorbet/rbi/gems/{pry@0.13.1.rbi → pry@0.14.0.rbi} +1 -1
- data/sorbet/rbi/gems/racc@1.5.2.rbi +57 -0
- data/sorbet/rbi/gems/{rack@2.2.2.rbi → rack@2.2.3.rbi} +23 -35
- data/sorbet/rbi/gems/{rails@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1 -1
- data/sorbet/rbi/gems/{railties@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +132 -121
- data/sorbet/rbi/gems/{rake@13.0.1.rbi → rake@13.0.3.rbi} +16 -20
- data/sorbet/rbi/gems/{parallel@1.19.1.rbi → regexp_parser@2.1.1.rbi} +2 -2
- data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +8 -0
- data/sorbet/rbi/gems/{rubocop-performance@1.5.2.rbi → rubocop-performance@1.10.2.rbi} +1 -1
- data/sorbet/rbi/gems/{rubocop-shopify@1.0.2.rbi → rubocop-shopify@2.0.1.rbi} +1 -1
- data/sorbet/rbi/gems/{rubocop-sorbet@0.3.7.rbi → rubocop-sorbet@0.6.1.rbi} +1 -1
- data/sorbet/rbi/gems/{rubocop@0.82.0.rbi → rubocop@1.12.0.rbi} +1 -1
- data/sorbet/rbi/gems/{ruby-progressbar@1.10.1.rbi → ruby-progressbar@1.11.0.rbi} +1 -1
- data/sorbet/rbi/gems/spoom@1.1.0.rbi +1061 -0
- data/sorbet/rbi/gems/{spring@2.1.0.rbi → spring@2.1.1.rbi} +7 -7
- data/sorbet/rbi/gems/{sprockets-rails@3.2.1.rbi → sprockets-rails@3.2.2.rbi} +88 -68
- data/sorbet/rbi/gems/{sprockets@4.0.0.rbi → sprockets@4.0.2.rbi} +8 -7
- data/sorbet/rbi/gems/{tapioca@0.4.5.rbi → tapioca@0.4.19.rbi} +109 -24
- data/sorbet/rbi/gems/{thor@1.0.1.rbi → thor@1.1.0.rbi} +16 -15
- data/sorbet/rbi/gems/{tzinfo@2.0.2.rbi → tzinfo@2.0.4.rbi} +21 -2
- data/sorbet/rbi/gems/{unicode-display_width@1.7.0.rbi → unicode-display_width@2.0.0.rbi} +1 -1
- data/sorbet/rbi/gems/{websocket-driver@0.7.1.rbi → websocket-driver@0.7.3.rbi} +29 -29
- data/sorbet/rbi/gems/{websocket-extensions@0.1.4.rbi → websocket-extensions@0.1.5.rbi} +2 -2
- data/sorbet/rbi/gems/zeitwerk@2.4.2.rbi +177 -0
- data/sorbet/tapioca/require.rb +1 -0
- metadata +78 -61
- data/lib/packwerk/cache_deprecated_references.rb +0 -47
- data/lib/packwerk/checking_deprecated_references.rb +0 -40
- data/lib/packwerk/commands/detect_stale_violations_command.rb +0 -63
- data/lib/packwerk/commands/offense_progress_marker.rb +0 -24
- data/lib/packwerk/detect_stale_deprecated_references.rb +0 -14
- data/lib/packwerk/output_styles.rb +0 -41
- data/lib/packwerk/reference_lister.rb +0 -23
- data/lib/packwerk/updating_deprecated_references.rb +0 -14
- data/sorbet/rbi/gems/jaro_winkler@1.5.4.rbi +0 -8
- data/sorbet/rbi/gems/marcel@0.3.3.rbi +0 -30
- data/sorbet/rbi/gems/mimemagic@0.3.5.rbi +0 -47
- data/sorbet/rbi/gems/parlour@4.0.1.rbi +0 -561
- data/sorbet/rbi/gems/spoom@1.0.4.rbi +0 -418
- data/sorbet/rbi/gems/zeitwerk@2.3.0.rbi +0 -8
|
@@ -6,11 +6,6 @@ require "constant_resolver"
|
|
|
6
6
|
require "pathname"
|
|
7
7
|
require "yaml"
|
|
8
8
|
|
|
9
|
-
require "packwerk/package_set"
|
|
10
|
-
require "packwerk/graph"
|
|
11
|
-
require "packwerk/inflector"
|
|
12
|
-
require "packwerk/application_load_paths"
|
|
13
|
-
|
|
14
9
|
module Packwerk
|
|
15
10
|
class ApplicationValidator
|
|
16
11
|
def initialize(config_file_path:, configuration:)
|
|
@@ -35,16 +30,7 @@ module Packwerk
|
|
|
35
30
|
check_root_package_exists,
|
|
36
31
|
]
|
|
37
32
|
|
|
38
|
-
results
|
|
39
|
-
|
|
40
|
-
if results.empty?
|
|
41
|
-
Result.new(true)
|
|
42
|
-
else
|
|
43
|
-
Result.new(
|
|
44
|
-
false,
|
|
45
|
-
results.map(&:error_value).join("\n===\n")
|
|
46
|
-
)
|
|
47
|
-
end
|
|
33
|
+
merge_results(results)
|
|
48
34
|
end
|
|
49
35
|
|
|
50
36
|
def check_autoload_path_cache
|
|
@@ -65,51 +51,37 @@ module Packwerk
|
|
|
65
51
|
def check_package_manifests_for_privacy
|
|
66
52
|
privacy_settings = package_manifests_settings_for("enforce_privacy")
|
|
67
53
|
|
|
68
|
-
autoload_paths = @configuration.load_paths
|
|
69
|
-
|
|
70
54
|
resolver = ConstantResolver.new(
|
|
71
55
|
root_path: @configuration.root_path,
|
|
72
|
-
load_paths:
|
|
56
|
+
load_paths: @configuration.load_paths
|
|
73
57
|
)
|
|
74
58
|
|
|
75
|
-
|
|
59
|
+
results = []
|
|
76
60
|
|
|
77
|
-
privacy_settings.each do |
|
|
61
|
+
privacy_settings.each do |config_file_path, setting|
|
|
78
62
|
next unless setting.is_a?(Array)
|
|
63
|
+
constants = setting
|
|
79
64
|
|
|
80
|
-
|
|
81
|
-
# make sure the constant can be loaded
|
|
82
|
-
constant.constantize # rubocop:disable Sorbet/ConstantsFromStrings
|
|
83
|
-
context = resolver.resolve(constant)
|
|
84
|
-
|
|
85
|
-
unless context
|
|
86
|
-
errors << "#{constant}, listed in #{filepath.inspect}, could not be resolved"
|
|
87
|
-
next
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
expected_filename = constant.underscore + ".rb"
|
|
65
|
+
assert_constants_can_be_loaded(constants)
|
|
91
66
|
|
|
92
|
-
|
|
93
|
-
# file that defines their parent namespace. This restriction makes sure that we don't.
|
|
94
|
-
next if context.location.end_with?(expected_filename)
|
|
67
|
+
constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
|
|
95
68
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
69
|
+
constant_locations.each do |name, location|
|
|
70
|
+
results << if location
|
|
71
|
+
check_private_constant_location(name, location, config_file_path)
|
|
72
|
+
else
|
|
73
|
+
private_constant_unresolvable(name, config_file_path)
|
|
74
|
+
end
|
|
99
75
|
end
|
|
100
76
|
end
|
|
101
77
|
|
|
102
|
-
|
|
103
|
-
Result.new(true)
|
|
104
|
-
else
|
|
105
|
-
Result.new(false, errors.join("\n---\n"))
|
|
106
|
-
end
|
|
78
|
+
merge_results(results, separator: "\n---\n")
|
|
107
79
|
end
|
|
108
80
|
|
|
109
81
|
def check_package_manifest_syntax
|
|
110
82
|
errors = []
|
|
111
83
|
|
|
112
|
-
package_manifests
|
|
84
|
+
package_manifests.each do |f|
|
|
113
85
|
hash = YAML.load_file(f)
|
|
114
86
|
next unless hash
|
|
115
87
|
|
|
@@ -124,26 +96,26 @@ module Packwerk
|
|
|
124
96
|
|
|
125
97
|
if hash.key?("enforce_privacy")
|
|
126
98
|
unless [TrueClass, FalseClass, Array].include?(hash["enforce_privacy"].class)
|
|
127
|
-
errors << "Invalid 'enforce_privacy' option in #{f.inspect}: #{hash[
|
|
99
|
+
errors << "Invalid 'enforce_privacy' option in #{f.inspect}: #{hash["enforce_privacy"].inspect}"
|
|
128
100
|
end
|
|
129
101
|
end
|
|
130
102
|
|
|
131
103
|
if hash.key?("enforce_dependencies")
|
|
132
104
|
unless [TrueClass, FalseClass].include?(hash["enforce_dependencies"].class)
|
|
133
|
-
errors << "Invalid 'enforce_dependencies' option in #{f.inspect}: #{hash[
|
|
105
|
+
errors << "Invalid 'enforce_dependencies' option in #{f.inspect}: #{hash["enforce_dependencies"].inspect}"
|
|
134
106
|
end
|
|
135
107
|
end
|
|
136
108
|
|
|
137
109
|
if hash.key?("public_path")
|
|
138
110
|
unless hash["public_path"].is_a?(String)
|
|
139
|
-
errors << "'public_path' option must be a string in #{f.inspect}: #{hash[
|
|
111
|
+
errors << "'public_path' option must be a string in #{f.inspect}: #{hash["public_path"].inspect}"
|
|
140
112
|
end
|
|
141
113
|
end
|
|
142
114
|
|
|
143
115
|
next unless hash.key?("dependencies")
|
|
144
116
|
next if hash["dependencies"].is_a?(Array)
|
|
145
117
|
|
|
146
|
-
errors << "Invalid 'dependencies' option in #{f.inspect}: #{hash[
|
|
118
|
+
errors << "Invalid 'dependencies' option in #{f.inspect}: #{hash["dependencies"].inspect}"
|
|
147
119
|
end
|
|
148
120
|
|
|
149
121
|
if errors.empty?
|
|
@@ -193,24 +165,16 @@ module Packwerk
|
|
|
193
165
|
end
|
|
194
166
|
end
|
|
195
167
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
Result.new(
|
|
202
|
-
false,
|
|
203
|
-
"Inflections specified in #{inflections_file} don't line up with application!\n" +
|
|
204
|
-
errors.map(&:error_value).join("\n")
|
|
205
|
-
)
|
|
206
|
-
end
|
|
168
|
+
merge_results(
|
|
169
|
+
results,
|
|
170
|
+
separator: "\n",
|
|
171
|
+
errors_headline: "Inflections specified in #{inflections_file} don't line up with application!\n"
|
|
172
|
+
)
|
|
207
173
|
end
|
|
208
174
|
|
|
209
175
|
def check_acyclic_graph
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
edges = packages.flat_map do |package|
|
|
213
|
-
package.dependencies.map { |dependency| [package, packages.fetch(dependency)] }
|
|
176
|
+
edges = package_set.flat_map do |package|
|
|
177
|
+
package.dependencies.map { |dependency| [package, package_set.fetch(dependency)] }
|
|
214
178
|
end
|
|
215
179
|
dependency_graph = Packwerk::Graph.new(*edges)
|
|
216
180
|
|
|
@@ -225,7 +189,7 @@ module Packwerk
|
|
|
225
189
|
cycle_strings = dependency_graph.cycles.map do |cycle|
|
|
226
190
|
cycle_strings = cycle.map(&:to_s)
|
|
227
191
|
cycle_strings << cycle.first.to_s
|
|
228
|
-
"\t- #{cycle_strings.join(
|
|
192
|
+
"\t- #{cycle_strings.join(" → ")}"
|
|
229
193
|
end
|
|
230
194
|
|
|
231
195
|
if dependency_graph.acyclic?
|
|
@@ -316,8 +280,7 @@ module Packwerk
|
|
|
316
280
|
private
|
|
317
281
|
|
|
318
282
|
def package_manifests_settings_for(setting)
|
|
319
|
-
package_manifests(
|
|
320
|
-
.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
|
283
|
+
package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
|
321
284
|
end
|
|
322
285
|
|
|
323
286
|
def format_yaml_strings(list)
|
|
@@ -328,13 +291,17 @@ module Packwerk
|
|
|
328
291
|
@configuration.package_paths || "**"
|
|
329
292
|
end
|
|
330
293
|
|
|
331
|
-
def package_manifests(glob_pattern)
|
|
294
|
+
def package_manifests(glob_pattern = package_glob)
|
|
332
295
|
PackageSet.package_paths(@configuration.root_path, glob_pattern)
|
|
333
296
|
.map { |f| File.realpath(f) }
|
|
334
297
|
end
|
|
335
298
|
|
|
336
299
|
def relative_paths(paths)
|
|
337
|
-
paths.map { |path|
|
|
300
|
+
paths.map { |path| relative_path(path) }
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def relative_path(path)
|
|
304
|
+
Pathname.new(path).relative_path_from(@configuration.root_path)
|
|
338
305
|
end
|
|
339
306
|
|
|
340
307
|
def invalid_package_path?(path)
|
|
@@ -344,5 +311,54 @@ module Packwerk
|
|
|
344
311
|
package_path = File.join(@configuration.root_path, path, Packwerk::PackageSet::PACKAGE_CONFIG_FILENAME)
|
|
345
312
|
!File.file?(package_path)
|
|
346
313
|
end
|
|
314
|
+
|
|
315
|
+
def assert_constants_can_be_loaded(constants)
|
|
316
|
+
constants.each(&:constantize)
|
|
317
|
+
nil
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def private_constant_unresolvable(name, config_file_path)
|
|
321
|
+
explicit_filepath = (name.start_with?("::") ? name[2..-1] : name).underscore + ".rb"
|
|
322
|
+
|
|
323
|
+
Result.new(
|
|
324
|
+
false,
|
|
325
|
+
"'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
|
|
326
|
+
"This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
|
|
327
|
+
"file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
|
|
328
|
+
"private. Add a #{explicit_filepath} file to explicitly define the constant."
|
|
329
|
+
)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def check_private_constant_location(name, location, config_file_path)
|
|
333
|
+
declared_package = package_set.package_from_path(relative_path(config_file_path))
|
|
334
|
+
constant_package = package_set.package_from_path(location)
|
|
335
|
+
|
|
336
|
+
if constant_package == declared_package
|
|
337
|
+
Result.new(true)
|
|
338
|
+
else
|
|
339
|
+
Result.new(
|
|
340
|
+
false,
|
|
341
|
+
"'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
|
|
342
|
+
"defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
|
|
343
|
+
)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def package_set
|
|
348
|
+
@package_set ||= Packwerk::PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def merge_results(results, separator: "\n===\n", errors_headline: "")
|
|
352
|
+
results.reject!(&:ok?)
|
|
353
|
+
|
|
354
|
+
if results.empty?
|
|
355
|
+
Result.new(true)
|
|
356
|
+
else
|
|
357
|
+
Result.new(
|
|
358
|
+
false,
|
|
359
|
+
errors_headline + results.map(&:error_value).join(separator)
|
|
360
|
+
)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
347
363
|
end
|
|
348
364
|
end
|
|
@@ -1,26 +1,35 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "packwerk/constant_name_inspector"
|
|
5
|
-
require "packwerk/node"
|
|
6
|
-
|
|
7
4
|
module Packwerk
|
|
8
5
|
# Extracts the implicit constant reference from an active record association
|
|
9
6
|
class AssociationInspector
|
|
7
|
+
extend T::Sig
|
|
10
8
|
include ConstantNameInspector
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
CustomAssociations = T.type_alias { T.any(T::Array[Symbol], T::Set[Symbol]) }
|
|
11
|
+
|
|
12
|
+
RAILS_ASSOCIATIONS = T.let(
|
|
13
|
+
%i(
|
|
14
|
+
belongs_to
|
|
15
|
+
has_many
|
|
16
|
+
has_one
|
|
17
|
+
has_and_belongs_to_many
|
|
18
|
+
).to_set,
|
|
19
|
+
CustomAssociations
|
|
20
|
+
)
|
|
18
21
|
|
|
22
|
+
sig { params(inflector: Inflector, custom_associations: CustomAssociations).void }
|
|
19
23
|
def initialize(inflector:, custom_associations: Set.new)
|
|
20
24
|
@inflector = inflector
|
|
21
|
-
@associations = RAILS_ASSOCIATIONS + custom_associations
|
|
25
|
+
@associations = T.let(RAILS_ASSOCIATIONS + custom_associations, CustomAssociations)
|
|
22
26
|
end
|
|
23
27
|
|
|
28
|
+
sig do
|
|
29
|
+
override
|
|
30
|
+
.params(node: AST::Node, ancestors: T::Array[AST::Node])
|
|
31
|
+
.returns(T.nilable(String))
|
|
32
|
+
end
|
|
24
33
|
def constant_name_from_node(node, ancestors:)
|
|
25
34
|
return unless Node.method_call?(node)
|
|
26
35
|
return unless association?(node)
|
|
@@ -38,11 +47,13 @@ module Packwerk
|
|
|
38
47
|
|
|
39
48
|
private
|
|
40
49
|
|
|
50
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
|
41
51
|
def association?(node)
|
|
42
52
|
method_name = Node.method_name(node)
|
|
43
53
|
@associations.include?(method_name)
|
|
44
54
|
end
|
|
45
55
|
|
|
56
|
+
sig { params(arguments: T::Array[AST::Node]).returns(T.nilable(AST::Node)) }
|
|
46
57
|
def custom_class_name(arguments)
|
|
47
58
|
association_options = arguments.detect { |n| Node.hash?(n) }
|
|
48
59
|
return unless association_options
|
|
@@ -50,6 +61,7 @@ module Packwerk
|
|
|
50
61
|
Node.value_from_hash(association_options, :class_name)
|
|
51
62
|
end
|
|
52
63
|
|
|
64
|
+
sig { params(arguments: T::Array[AST::Node]).returns(T.any(T.nilable(Symbol), T.nilable(String))) }
|
|
53
65
|
def association_name(arguments)
|
|
54
66
|
return unless Node.symbol?(arguments[0])
|
|
55
67
|
|
data/lib/packwerk/checker.rb
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# typed: true
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "sorbet-runtime"
|
|
5
|
-
require "packwerk/reference_lister"
|
|
6
|
-
|
|
7
4
|
module Packwerk
|
|
8
5
|
module Checker
|
|
9
6
|
extend T::Sig
|
|
@@ -11,10 +8,10 @@ module Packwerk
|
|
|
11
8
|
|
|
12
9
|
interface!
|
|
13
10
|
|
|
14
|
-
sig {
|
|
15
|
-
def
|
|
11
|
+
sig { returns(ViolationType).abstract }
|
|
12
|
+
def violation_type; end
|
|
16
13
|
|
|
17
|
-
sig { params(reference: Reference).returns(
|
|
18
|
-
def
|
|
14
|
+
sig { params(reference: Reference).returns(T::Boolean).abstract }
|
|
15
|
+
def invalid_reference?(reference); end
|
|
19
16
|
end
|
|
20
17
|
end
|
data/lib/packwerk/cli.rb
CHANGED
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
# typed: true
|
|
2
2
|
# frozen_string_literal: true
|
|
3
|
-
require "benchmark"
|
|
4
|
-
require "sorbet-runtime"
|
|
5
|
-
|
|
6
|
-
require "packwerk/application_validator"
|
|
7
|
-
require "packwerk/configuration"
|
|
8
|
-
require "packwerk/files_for_processing"
|
|
9
|
-
require "packwerk/formatters/progress_formatter"
|
|
10
|
-
require "packwerk/inflector"
|
|
11
|
-
require "packwerk/output_styles"
|
|
12
|
-
require "packwerk/run_context"
|
|
13
|
-
require "packwerk/updating_deprecated_references"
|
|
14
|
-
require "packwerk/checking_deprecated_references"
|
|
15
|
-
require "packwerk/commands/detect_stale_violations_command"
|
|
16
|
-
require "packwerk/commands/offense_progress_marker"
|
|
17
3
|
|
|
18
4
|
module Packwerk
|
|
19
5
|
class Cli
|
|
20
6
|
extend T::Sig
|
|
21
|
-
include OffenseProgressMarker
|
|
22
7
|
|
|
23
|
-
|
|
8
|
+
sig do
|
|
9
|
+
params(
|
|
10
|
+
configuration: T.nilable(Configuration),
|
|
11
|
+
out: T.any(StringIO, IO),
|
|
12
|
+
err_out: T.any(StringIO, IO),
|
|
13
|
+
style: Packwerk::OutputStyle,
|
|
14
|
+
offenses_formatter: T.nilable(Packwerk::OffensesFormatter)
|
|
15
|
+
).void
|
|
16
|
+
end
|
|
17
|
+
def initialize(
|
|
18
|
+
configuration: nil,
|
|
19
|
+
out: $stdout,
|
|
20
|
+
err_out: $stderr,
|
|
21
|
+
style: OutputStyles::Plain.new,
|
|
22
|
+
offenses_formatter: nil
|
|
23
|
+
)
|
|
24
24
|
@out = out
|
|
25
25
|
@err_out = err_out
|
|
26
26
|
@style = style
|
|
27
27
|
@configuration = configuration || Configuration.from_path
|
|
28
|
-
@run_context = run_context || Packwerk::RunContext.from_configuration(
|
|
29
|
-
@configuration,
|
|
30
|
-
reference_lister: ::Packwerk::CheckingDeprecatedReferences.new(@configuration.root_path),
|
|
31
|
-
)
|
|
32
28
|
@progress_formatter = Formatters::ProgressFormatter.new(@out, style: style)
|
|
29
|
+
@offenses_formatter = offenses_formatter || Formatters::OffensesFormatter.new(style: @style)
|
|
33
30
|
end
|
|
34
31
|
|
|
35
32
|
sig { params(args: T::Array[String]).returns(T.noreturn) }
|
|
@@ -47,13 +44,13 @@ module Packwerk
|
|
|
47
44
|
when "generate_configs"
|
|
48
45
|
generate_configs
|
|
49
46
|
when "check"
|
|
50
|
-
|
|
47
|
+
output_result(parse_run(args).check)
|
|
51
48
|
when "detect-stale-violations"
|
|
52
|
-
|
|
49
|
+
output_result(parse_run(args).detect_stale_violations)
|
|
53
50
|
when "update"
|
|
54
51
|
update(args)
|
|
55
52
|
when "update-deprecations"
|
|
56
|
-
|
|
53
|
+
output_result(parse_run(args).update_deprecations)
|
|
57
54
|
when "validate"
|
|
58
55
|
validate(args)
|
|
59
56
|
when nil, "help"
|
|
@@ -101,7 +98,7 @@ module Packwerk
|
|
|
101
98
|
|
|
102
99
|
def generate_configs
|
|
103
100
|
configuration_file = Packwerk::Generators::ConfigurationFile.generate(
|
|
104
|
-
load_paths:
|
|
101
|
+
load_paths: Packwerk::ApplicationLoadPaths.extract_relevant_paths,
|
|
105
102
|
root: @configuration.root_path,
|
|
106
103
|
out: @out
|
|
107
104
|
)
|
|
@@ -130,71 +127,10 @@ module Packwerk
|
|
|
130
127
|
|
|
131
128
|
def update(paths)
|
|
132
129
|
warn("`packwerk update` is deprecated in favor of `packwerk update-deprecations`.")
|
|
133
|
-
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def update_deprecations(paths)
|
|
137
|
-
updating_deprecated_references = ::Packwerk::UpdatingDeprecatedReferences.new(@configuration.root_path)
|
|
138
|
-
@run_context = Packwerk::RunContext.from_configuration(
|
|
139
|
-
@configuration,
|
|
140
|
-
reference_lister: updating_deprecated_references
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
files = fetch_files_to_process(paths)
|
|
144
|
-
|
|
145
|
-
@progress_formatter.started(files)
|
|
146
|
-
|
|
147
|
-
all_offenses = T.let([], T.untyped)
|
|
148
|
-
execution_time = Benchmark.realtime do
|
|
149
|
-
all_offenses = files.flat_map do |path|
|
|
150
|
-
@run_context.process_file(file: path).tap do |offenses|
|
|
151
|
-
mark_progress(offenses: offenses, progress_formatter: @progress_formatter)
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
updating_deprecated_references.dump_deprecated_references_files
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
@out.puts # put a new line after the progress dots
|
|
159
|
-
show_offenses(all_offenses)
|
|
160
|
-
@progress_formatter.finished(execution_time)
|
|
161
|
-
@out.puts("✅ `deprecated_references.yml` has been updated.")
|
|
162
|
-
|
|
163
|
-
all_offenses.empty?
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def check(paths)
|
|
167
|
-
files = fetch_files_to_process(paths)
|
|
168
|
-
|
|
169
|
-
@progress_formatter.started(files)
|
|
170
|
-
|
|
171
|
-
all_offenses = T.let([], T.untyped)
|
|
172
|
-
execution_time = Benchmark.realtime do
|
|
173
|
-
files.each do |path|
|
|
174
|
-
@run_context.process_file(file: path).tap do |offenses|
|
|
175
|
-
mark_progress(offenses: offenses, progress_formatter: @progress_formatter)
|
|
176
|
-
all_offenses.concat(offenses)
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
rescue Interrupt
|
|
180
|
-
@out.puts
|
|
181
|
-
@out.puts("Manually interrupted. Violations caught so far are listed below:")
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
@out.puts # put a new line after the progress dots
|
|
185
|
-
show_offenses(all_offenses)
|
|
186
|
-
@progress_formatter.finished(execution_time)
|
|
187
|
-
|
|
188
|
-
all_offenses.empty?
|
|
130
|
+
output_result(parse_run(paths).update_deprecations)
|
|
189
131
|
end
|
|
190
132
|
|
|
191
|
-
def
|
|
192
|
-
detect_stale_violations = DetectStaleViolationsCommand.new(
|
|
193
|
-
files: fetch_files_to_process(paths),
|
|
194
|
-
configuration: @configuration,
|
|
195
|
-
progress_formatter: @progress_formatter
|
|
196
|
-
)
|
|
197
|
-
result = detect_stale_violations.run
|
|
133
|
+
def output_result(result)
|
|
198
134
|
@out.puts
|
|
199
135
|
@out.puts(result.message)
|
|
200
136
|
result.status
|
|
@@ -225,19 +161,6 @@ module Packwerk
|
|
|
225
161
|
end
|
|
226
162
|
end
|
|
227
163
|
|
|
228
|
-
def show_offenses(offenses)
|
|
229
|
-
if offenses.empty?
|
|
230
|
-
@out.puts("No offenses detected 🎉")
|
|
231
|
-
else
|
|
232
|
-
offenses.each do |offense|
|
|
233
|
-
@out.puts(offense.to_s(@style))
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
offenses_string = Inflector.default.pluralize("offense", offenses.length)
|
|
237
|
-
@out.puts("#{offenses.length} #{offenses_string} detected")
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
|
|
241
164
|
def list_validation_errors(result)
|
|
242
165
|
@out.puts
|
|
243
166
|
if result.ok?
|
|
@@ -256,5 +179,14 @@ module Packwerk
|
|
|
256
179
|
false
|
|
257
180
|
end
|
|
258
181
|
end
|
|
182
|
+
|
|
183
|
+
def parse_run(paths)
|
|
184
|
+
ParseRun.new(
|
|
185
|
+
files: fetch_files_to_process(paths),
|
|
186
|
+
configuration: @configuration,
|
|
187
|
+
progress_formatter: @progress_formatter,
|
|
188
|
+
offenses_formatter: @offenses_formatter
|
|
189
|
+
)
|
|
190
|
+
end
|
|
259
191
|
end
|
|
260
192
|
end
|
|
@@ -22,7 +22,10 @@ module Packwerk
|
|
|
22
22
|
private
|
|
23
23
|
|
|
24
24
|
def from_packwerk_config(path)
|
|
25
|
-
new(
|
|
25
|
+
new(
|
|
26
|
+
YAML.load_file(path) || {},
|
|
27
|
+
config_path: path
|
|
28
|
+
)
|
|
26
29
|
end
|
|
27
30
|
end
|
|
28
31
|
|
|
@@ -42,10 +45,15 @@ module Packwerk
|
|
|
42
45
|
@root_path = File.expand_path(root)
|
|
43
46
|
@package_paths = configs["package_paths"] || "**/"
|
|
44
47
|
@custom_associations = configs["custom_associations"] || []
|
|
45
|
-
@load_paths = configs["load_paths"]
|
|
48
|
+
@load_paths = configs["load_paths"] || []
|
|
46
49
|
@inflections_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
|
|
50
|
+
@parallel = configs.key?("parallel") ? configs["parallel"] : true
|
|
47
51
|
|
|
48
52
|
@config_path = config_path
|
|
49
53
|
end
|
|
54
|
+
|
|
55
|
+
def parallel?
|
|
56
|
+
@parallel
|
|
57
|
+
end
|
|
50
58
|
end
|
|
51
59
|
end
|
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "packwerk/constant_name_inspector"
|
|
5
|
-
|
|
6
4
|
module Packwerk
|
|
7
5
|
# Extracts a constant name from an AST node of type :const
|
|
8
6
|
class ConstNodeInspector
|
|
7
|
+
extend T::Sig
|
|
9
8
|
include ConstantNameInspector
|
|
10
9
|
|
|
10
|
+
sig do
|
|
11
|
+
override
|
|
12
|
+
.params(node: AST::Node, ancestors: T::Array[AST::Node])
|
|
13
|
+
.returns(T.nilable(String))
|
|
14
|
+
end
|
|
11
15
|
def constant_name_from_node(node, ancestors:)
|
|
12
16
|
return nil unless Node.constant?(node)
|
|
13
17
|
parent = ancestors.first
|
|
14
18
|
return nil unless root_constant?(parent)
|
|
15
19
|
|
|
16
20
|
if parent && constant_in_module_or_class_definition?(node, parent: parent)
|
|
17
|
-
fully_qualify_constant(
|
|
21
|
+
fully_qualify_constant(ancestors)
|
|
18
22
|
else
|
|
19
23
|
begin
|
|
20
24
|
Node.constant_name(node)
|
|
@@ -28,27 +32,22 @@ module Packwerk
|
|
|
28
32
|
|
|
29
33
|
# Only process the root `const` node for namespaced constant references. For example, in the
|
|
30
34
|
# reference `Spam::Eggs::Thing`, we only process the const node associated with `Spam`.
|
|
35
|
+
sig { params(parent: T.nilable(AST::Node)).returns(T::Boolean) }
|
|
31
36
|
def root_constant?(parent)
|
|
32
37
|
!(parent && Node.constant?(parent))
|
|
33
38
|
end
|
|
34
39
|
|
|
40
|
+
sig { params(node: AST::Node, parent: AST::Node).returns(T.nilable(T::Boolean)) }
|
|
35
41
|
def constant_in_module_or_class_definition?(node, parent:)
|
|
36
42
|
parent_name = Node.module_name_from_definition(parent)
|
|
37
43
|
parent_name && parent_name == Node.constant_name(node)
|
|
38
44
|
end
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
sig { params(ancestors: T::Array[AST::Node]).returns(String) }
|
|
47
|
+
def fully_qualify_constant(ancestors)
|
|
41
48
|
# We're defining a class with this name, in which case the constant is implicitly fully qualified by its
|
|
42
49
|
# enclosing namespace
|
|
43
|
-
|
|
44
|
-
name ||= generate_qualified_constant(node, ancestors)
|
|
45
|
-
"::" + name
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def generate_qualified_constant(node, ancestors:)
|
|
49
|
-
namespace_path = Node.enclosing_namespace_path(node, ancestors: ancestors)
|
|
50
|
-
constant_name = Node.constant_name(node)
|
|
51
|
-
namespace_path.push(constant_name).join("::")
|
|
50
|
+
"::" + Node.parent_module_name(ancestors: ancestors)
|
|
52
51
|
end
|
|
53
52
|
end
|
|
54
53
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# typed: true
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "constant_resolver"
|
|
5
|
+
|
|
4
6
|
module Packwerk
|
|
5
7
|
# Get information about (partially qualified) constants without loading the application code.
|
|
6
8
|
# Information gathered: Fully qualified name, path to file containing the definition, package,
|
|
@@ -1,31 +1,26 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "packwerk/violation_type"
|
|
5
|
-
require "packwerk/checker"
|
|
6
|
-
|
|
7
4
|
module Packwerk
|
|
8
5
|
class DependencyChecker
|
|
6
|
+
extend T::Sig
|
|
9
7
|
include Checker
|
|
10
8
|
|
|
9
|
+
sig { override.returns(ViolationType) }
|
|
11
10
|
def violation_type
|
|
12
11
|
ViolationType::Dependency
|
|
13
12
|
end
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return if reference_lister.listed?(reference, violation_type: violation_type)
|
|
20
|
-
true
|
|
14
|
+
sig do
|
|
15
|
+
override
|
|
16
|
+
.params(reference: Packwerk::Reference)
|
|
17
|
+
.returns(T::Boolean)
|
|
21
18
|
end
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"Are we missing an abstraction?\n" \
|
|
28
|
-
"Is the code making the reference, and the referenced constant, in the right packages?\n"
|
|
19
|
+
def invalid_reference?(reference)
|
|
20
|
+
return false unless reference.source_package
|
|
21
|
+
return false unless reference.source_package.enforce_dependencies?
|
|
22
|
+
return false if reference.source_package.dependency?(reference.constant.package)
|
|
23
|
+
true
|
|
29
24
|
end
|
|
30
25
|
end
|
|
31
26
|
end
|