packwerk 2.1.1 → 2.2.1
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 +29 -20
- data/.github/workflows/cla.yml +22 -0
- data/.rubocop.yml +48 -19
- data/Gemfile +7 -2
- data/Gemfile.lock +204 -177
- data/README.md +7 -2
- data/RESOLVING_VIOLATIONS.md +81 -0
- data/Rakefile +1 -1
- data/USAGE.md +14 -5
- data/bin/m +1 -1
- data/bin/rake +1 -1
- data/bin/rubocop +1 -1
- data/bin/srb +1 -1
- data/bin/tapioca +1 -1
- data/gemfiles/Gemfile-rails-6-0 +1 -1
- data/gemfiles/Gemfile-rails-6-1 +22 -0
- data/lib/packwerk/application_load_paths.rb +12 -18
- data/lib/packwerk/application_validator.rb +7 -6
- data/lib/packwerk/association_inspector.rb +17 -15
- data/lib/packwerk/cache.rb +36 -29
- data/lib/packwerk/cli.rb +14 -8
- data/lib/packwerk/const_node_inspector.rb +8 -7
- data/lib/packwerk/constant_name_inspector.rb +2 -2
- data/lib/packwerk/deprecated_references.rb +34 -19
- data/lib/packwerk/file_processor.rb +25 -23
- data/lib/packwerk/files_for_processing.rb +33 -35
- data/lib/packwerk/formatters/offenses_formatter.rb +3 -3
- data/lib/packwerk/formatters/progress_formatter.rb +2 -2
- data/lib/packwerk/node.rb +1 -294
- data/lib/packwerk/node_helpers.rb +335 -0
- data/lib/packwerk/node_processor.rb +6 -5
- data/lib/packwerk/node_processor_factory.rb +3 -3
- data/lib/packwerk/node_visitor.rb +1 -1
- data/lib/packwerk/offense_collection.rb +6 -3
- data/lib/packwerk/offenses_formatter.rb +2 -2
- data/lib/packwerk/package.rb +3 -0
- data/lib/packwerk/package_set.rb +3 -1
- data/lib/packwerk/parse_run.rb +15 -13
- data/lib/packwerk/parsed_constant_definitions.rb +23 -20
- data/lib/packwerk/parsers/erb.rb +3 -3
- data/lib/packwerk/parsers/parser_interface.rb +2 -0
- data/lib/packwerk/reference_checking/checkers/checker.rb +16 -3
- data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +16 -0
- data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +18 -0
- data/lib/packwerk/reference_checking/reference_checker.rb +4 -4
- data/lib/packwerk/reference_extractor.rb +51 -54
- data/lib/packwerk/reference_offense.rb +3 -27
- data/lib/packwerk/run_context.rb +9 -7
- data/lib/packwerk/spring_command.rb +1 -1
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +1 -0
- data/packwerk.gemspec +4 -11
- data/sorbet/rbi/gems/actioncable@7.0.3.1.rbi +2754 -0
- data/sorbet/rbi/gems/actionmailbox@7.0.3.1.rbi +1496 -0
- data/sorbet/rbi/gems/actionmailer@7.0.3.1.rbi +2362 -0
- data/sorbet/rbi/gems/actionpack@7.0.3.1.rbi +19397 -0
- data/sorbet/rbi/gems/actiontext@7.0.3.1.rbi +1569 -0
- data/sorbet/rbi/gems/actionview@7.0.3.1.rbi +14907 -0
- data/sorbet/rbi/gems/activejob@7.0.3.1.rbi +2553 -0
- data/sorbet/rbi/gems/activemodel@7.0.3.1.rbi +5999 -0
- data/sorbet/rbi/gems/activerecord@7.0.3.1.rbi +37832 -0
- data/sorbet/rbi/gems/activestorage@7.0.3.1.rbi +2321 -0
- data/sorbet/rbi/gems/activesupport@7.0.3.1.rbi +18818 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +11722 -0
- data/sorbet/rbi/gems/constant_resolver@0.2.0.rbi +90 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1079 -0
- data/sorbet/rbi/gems/digest@3.1.0.rbi +189 -0
- data/sorbet/rbi/gems/erubi@1.11.0.rbi +140 -0
- data/sorbet/rbi/gems/globalid@1.0.0.rbi +572 -0
- data/sorbet/rbi/gems/i18n@1.12.0.rbi +2296 -0
- data/sorbet/rbi/gems/json@2.6.2.rbi +1548 -0
- data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +8 -0
- data/sorbet/rbi/gems/loofah@2.18.0.rbi +877 -0
- data/sorbet/rbi/gems/m@1.6.0.rbi +257 -0
- data/sorbet/rbi/gems/marcel@1.0.2.rbi +220 -0
- data/sorbet/rbi/gems/mini_mime@1.1.2.rbi +170 -0
- data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +8 -0
- data/sorbet/rbi/gems/minitest-focus@1.3.1.rbi +104 -0
- data/sorbet/rbi/gems/minitest@5.16.2.rbi +2136 -0
- data/sorbet/rbi/gems/mocha@1.14.0.rbi +4177 -0
- data/sorbet/rbi/gems/net-imap@0.2.3.rbi +2147 -0
- data/sorbet/rbi/gems/net-pop@0.1.1.rbi +926 -0
- data/sorbet/rbi/gems/net-protocol@0.1.3.rbi +11 -0
- data/sorbet/rbi/gems/net-smtp@0.3.1.rbi +1108 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +153 -0
- data/sorbet/rbi/gems/nio4r@2.5.8.rbi +292 -0
- data/sorbet/rbi/gems/nokogiri@1.13.8.rbi +6478 -0
- data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
- data/sorbet/rbi/gems/parser@3.1.2.1.rbi +9029 -0
- data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +8 -0
- data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
- data/sorbet/rbi/gems/racc@1.6.0.rbi +152 -0
- data/sorbet/rbi/gems/rack-test@2.0.2.rbi +953 -0
- data/sorbet/rbi/gems/rack@2.2.4.rbi +5636 -0
- data/sorbet/rbi/gems/rails-html-sanitizer@1.4.3.rbi +688 -0
- data/sorbet/rbi/gems/rails@7.0.3.1.rbi +8 -0
- data/sorbet/rbi/gems/railties@7.0.3.1.rbi +3507 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +392 -0
- data/sorbet/rbi/gems/rake@13.0.6.rbi +2924 -0
- data/sorbet/rbi/gems/rbi@0.0.15.rbi +3007 -0
- data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +3383 -0
- data/sorbet/rbi/gems/rexml@3.2.5.rbi +4714 -0
- data/sorbet/rbi/gems/rubocop-ast@1.21.0.rbi +6961 -0
- data/sorbet/rbi/gems/rubocop-performance@1.14.3.rbi +2986 -0
- data/sorbet/rbi/gems/{rubocop-shopify@2.0.1.rbi → rubocop-shopify@2.9.0.rbi} +4 -4
- data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +992 -0
- data/sorbet/rbi/gems/rubocop@1.34.1.rbi +51820 -0
- data/sorbet/rbi/gems/ruby-lsp@0.2.1.rbi +11 -0
- data/sorbet/rbi/gems/smart_properties@1.17.0.rbi +474 -0
- data/sorbet/rbi/gems/spoom@1.1.11.rbi +2181 -0
- data/sorbet/rbi/gems/spring@4.0.0.rbi +411 -0
- data/sorbet/rbi/gems/strscan@3.0.4.rbi +8 -0
- data/sorbet/rbi/gems/syntax_tree@3.3.0.rbi +8 -0
- data/sorbet/rbi/gems/tapioca@0.9.2.rbi +3181 -0
- data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
- data/sorbet/rbi/gems/timeout@0.3.0.rbi +142 -0
- data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +5896 -0
- data/sorbet/rbi/gems/unicode-display_width@2.2.0.rbi +48 -0
- data/sorbet/rbi/gems/unparser@0.6.5.rbi +4529 -0
- data/sorbet/rbi/gems/webrick@1.7.0.rbi +2582 -0
- data/sorbet/rbi/gems/websocket-driver@0.7.5.rbi +993 -0
- data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +388 -0
- data/sorbet/rbi/gems/yard@0.9.28.rbi +18242 -0
- data/sorbet/rbi/gems/zeitwerk@2.6.0.rbi +867 -0
- data/sorbet/rbi/shims/psych.rbi +5 -0
- data/sorbet/tapioca/require.rb +2 -3
- metadata +88 -143
- data/.github/probots.yml +0 -2
- data/library.yml +0 -6
- data/service.yml +0 -1
- data/sorbet/rbi/gems/actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -860
- data/sorbet/rbi/gems/actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -568
- data/sorbet/rbi/gems/actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -587
- data/sorbet/rbi/gems/actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -5314
- data/sorbet/rbi/gems/actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -699
- data/sorbet/rbi/gems/actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -2515
- data/sorbet/rbi/gems/activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -624
- data/sorbet/rbi/gems/activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -1248
- data/sorbet/rbi/gems/activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8363
- data/sorbet/rbi/gems/activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -876
- data/sorbet/rbi/gems/activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -3987
- data/sorbet/rbi/gems/colorize@0.8.1.rbi +0 -40
- data/sorbet/rbi/gems/commander@4.5.2.rbi +0 -8
- data/sorbet/rbi/gems/concurrent-ruby@1.1.8.rbi +0 -1969
- data/sorbet/rbi/gems/constant_resolver@0.1.5.rbi +0 -26
- data/sorbet/rbi/gems/erubi@1.10.0.rbi +0 -41
- data/sorbet/rbi/gems/globalid@0.4.2.rbi +0 -178
- data/sorbet/rbi/gems/highline@2.0.3.rbi +0 -8
- data/sorbet/rbi/gems/i18n@1.8.10.rbi +0 -600
- data/sorbet/rbi/gems/loofah@2.9.0.rbi +0 -274
- data/sorbet/rbi/gems/m@1.5.1.rbi +0 -108
- data/sorbet/rbi/gems/marcel@1.0.0.rbi +0 -70
- data/sorbet/rbi/gems/mini_mime@1.0.3.rbi +0 -71
- data/sorbet/rbi/gems/minitest-focus@1.2.1.rbi +0 -8
- data/sorbet/rbi/gems/minitest@5.14.4.rbi +0 -544
- data/sorbet/rbi/gems/mocha@1.12.0.rbi +0 -953
- data/sorbet/rbi/gems/nio4r@2.5.7.rbi +0 -90
- data/sorbet/rbi/gems/nokogiri@1.11.2.rbi +0 -1647
- data/sorbet/rbi/gems/parallel@1.20.1.rbi +0 -117
- data/sorbet/rbi/gems/parlour@6.0.0.rbi +0 -1272
- data/sorbet/rbi/gems/parser@3.0.0.0.rbi +0 -1745
- data/sorbet/rbi/gems/pry@0.14.0.rbi +0 -8
- data/sorbet/rbi/gems/psych@3.3.2.rbi +0 -24
- data/sorbet/rbi/gems/racc@1.5.2.rbi +0 -57
- data/sorbet/rbi/gems/rack-test@1.1.0.rbi +0 -335
- data/sorbet/rbi/gems/rack@2.2.3.rbi +0 -1718
- data/sorbet/rbi/gems/rails-html-sanitizer@1.3.0.rbi +0 -213
- data/sorbet/rbi/gems/rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8
- data/sorbet/rbi/gems/railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -880
- data/sorbet/rbi/gems/rainbow@3.0.0.rbi +0 -155
- data/sorbet/rbi/gems/rake@13.0.3.rbi +0 -837
- data/sorbet/rbi/gems/regexp_parser@2.1.1.rbi +0 -8
- data/sorbet/rbi/gems/rexml@3.2.4.rbi +0 -8
- data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +0 -8
- data/sorbet/rbi/gems/rubocop-performance@1.10.2.rbi +0 -8
- data/sorbet/rbi/gems/rubocop-sorbet@0.6.1.rbi +0 -8
- data/sorbet/rbi/gems/rubocop@1.12.0.rbi +0 -8
- data/sorbet/rbi/gems/smart_properties@1.15.0.rbi +0 -168
- data/sorbet/rbi/gems/spoom@1.1.0.rbi +0 -1061
- data/sorbet/rbi/gems/spring@2.1.1.rbi +0 -160
- data/sorbet/rbi/gems/sprockets-rails@3.2.2.rbi +0 -451
- data/sorbet/rbi/gems/sprockets@4.0.2.rbi +0 -1133
- data/sorbet/rbi/gems/tapioca@0.4.19.rbi +0 -603
- data/sorbet/rbi/gems/thor@1.1.0.rbi +0 -893
- data/sorbet/rbi/gems/tzinfo@2.0.4.rbi +0 -566
- data/sorbet/rbi/gems/unicode-display_width@2.0.0.rbi +0 -8
- data/sorbet/rbi/gems/websocket-driver@0.7.3.rbi +0 -438
- data/sorbet/rbi/gems/zeitwerk@2.4.2.rbi +0 -177
data/bin/srb
CHANGED
@@ -15,7 +15,7 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
|
15
15
|
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
16
|
|
17
17
|
if File.file?(bundle_binstub)
|
18
|
-
if
|
18
|
+
if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
|
19
19
|
load(bundle_binstub)
|
20
20
|
else
|
21
21
|
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
data/bin/tapioca
CHANGED
@@ -15,7 +15,7 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
|
15
15
|
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
16
|
|
17
17
|
if File.file?(bundle_binstub)
|
18
|
-
if
|
18
|
+
if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
|
19
19
|
load(bundle_binstub)
|
20
20
|
else
|
21
21
|
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
data/gemfiles/Gemfile-rails-6-0
CHANGED
@@ -7,7 +7,7 @@ gemspec path: ".."
|
|
7
7
|
# Specify the same dependency sources as the application Gemfile
|
8
8
|
|
9
9
|
gem("spring")
|
10
|
-
gem("rails",
|
10
|
+
gem("rails", "~> 6.0.0")
|
11
11
|
gem("constant_resolver", require: false)
|
12
12
|
gem("sorbet-runtime", require: false)
|
13
13
|
gem("rubocop-performance", require: false)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source("https://rubygems.org")
|
4
|
+
|
5
|
+
gemspec path: ".."
|
6
|
+
|
7
|
+
# Specify the same dependency sources as the application Gemfile
|
8
|
+
|
9
|
+
gem("spring")
|
10
|
+
gem("rails", "~> 6.1.0")
|
11
|
+
gem("constant_resolver", require: false)
|
12
|
+
gem("sorbet-runtime", require: false)
|
13
|
+
gem("rubocop-performance", require: false)
|
14
|
+
gem("rubocop-sorbet", require: false)
|
15
|
+
gem("mocha", require: false)
|
16
|
+
gem("rubocop-shopify", require: false)
|
17
|
+
gem("tapioca", require: false)
|
18
|
+
|
19
|
+
group :development do
|
20
|
+
gem("byebug", require: false)
|
21
|
+
gem("minitest-focus", require: false)
|
22
|
+
end
|
@@ -9,7 +9,7 @@ module Packwerk
|
|
9
9
|
class << self
|
10
10
|
extend T::Sig
|
11
11
|
|
12
|
-
sig { params(root: String, environment: String).returns(T::
|
12
|
+
sig { params(root: String, environment: String).returns(T::Hash[String, Module]) }
|
13
13
|
def extract_relevant_paths(root, environment)
|
14
14
|
require_application(root, environment)
|
15
15
|
all_paths = extract_application_autoload_paths
|
@@ -18,36 +18,30 @@ module Packwerk
|
|
18
18
|
relative_path_strings(relevant_paths)
|
19
19
|
end
|
20
20
|
|
21
|
-
sig { returns(T::
|
21
|
+
sig { returns(T::Hash[String, Module]) }
|
22
22
|
def extract_application_autoload_paths
|
23
|
-
Rails.
|
24
|
-
.
|
25
|
-
|
26
|
-
.flat_map do |engine|
|
27
|
-
paths = (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths)
|
28
|
-
paths.map(&:to_s).uniq
|
29
|
-
end
|
23
|
+
Rails.autoloaders.inject({}) do |h, loader|
|
24
|
+
h.merge(loader.root_dirs)
|
25
|
+
end
|
30
26
|
end
|
31
27
|
|
32
28
|
sig do
|
33
|
-
params(all_paths: T::
|
34
|
-
.returns(T::
|
29
|
+
params(all_paths: T::Hash[String, Module], bundle_path: Pathname, rails_root: Pathname)
|
30
|
+
.returns(T::Hash[Pathname, Module])
|
35
31
|
end
|
36
32
|
def filter_relevant_paths(all_paths, bundle_path: Bundler.bundle_path, rails_root: Rails.root)
|
37
33
|
bundle_path_match = bundle_path.join("**")
|
38
34
|
rails_root_match = rails_root.join("**")
|
39
35
|
|
40
36
|
all_paths
|
41
|
-
.
|
37
|
+
.transform_keys { |path| Pathname.new(path).expand_path }
|
42
38
|
.select { |path| path.fnmatch(rails_root_match.to_s) } # path needs to be in application directory
|
43
39
|
.reject { |path| path.fnmatch(bundle_path_match.to_s) } # reject paths from vendored gems
|
44
40
|
end
|
45
41
|
|
46
|
-
sig { params(
|
47
|
-
def relative_path_strings(
|
48
|
-
|
49
|
-
.map { |path| path.relative_path_from(rails_root).to_s }
|
50
|
-
.uniq
|
42
|
+
sig { params(load_paths: T::Hash[Pathname, Module], rails_root: Pathname).returns(T::Hash[String, Module]) }
|
43
|
+
def relative_path_strings(load_paths, rails_root: Rails.root)
|
44
|
+
load_paths.transform_keys { |path| Pathname.new(path).relative_path_from(rails_root).to_s }
|
51
45
|
end
|
52
46
|
|
53
47
|
private
|
@@ -65,7 +59,7 @@ module Packwerk
|
|
65
59
|
end
|
66
60
|
end
|
67
61
|
|
68
|
-
sig { params(paths: T::
|
62
|
+
sig { params(paths: T::Hash[T.untyped, Module]).void }
|
69
63
|
def assert_load_paths_present(paths)
|
70
64
|
if paths.empty?
|
71
65
|
raise <<~EOS
|
@@ -66,6 +66,7 @@ module Packwerk
|
|
66
66
|
|
67
67
|
privacy_settings.each do |config_file_path, setting|
|
68
68
|
next unless setting.is_a?(Array)
|
69
|
+
|
69
70
|
constants = setting
|
70
71
|
|
71
72
|
results += assert_constants_can_be_loaded(constants, config_file_path)
|
@@ -92,7 +93,7 @@ module Packwerk
|
|
92
93
|
hash = YAML.load_file(f)
|
93
94
|
next unless hash
|
94
95
|
|
95
|
-
known_keys =
|
96
|
+
known_keys = ["enforce_privacy", "enforce_dependencies", "public_path", "dependencies", "metadata"]
|
96
97
|
unknown_keys = hash.keys - known_keys
|
97
98
|
|
98
99
|
unless unknown_keys.empty?
|
@@ -309,7 +310,7 @@ module Packwerk
|
|
309
310
|
Result.new(
|
310
311
|
ok: false,
|
311
312
|
error_value: "'#{constant}', listed in the 'enforce_privacy' option in #{config_file_path}, is invalid.\n"\
|
312
|
-
|
313
|
+
"Private constants need to be prefixed with the top-level namespace operator `::`."
|
313
314
|
)
|
314
315
|
else
|
315
316
|
constant.try(&:constantize) && Result.new(ok: true)
|
@@ -324,9 +325,9 @@ module Packwerk
|
|
324
325
|
Result.new(
|
325
326
|
ok: false,
|
326
327
|
error_value: "'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
|
327
|
-
|
328
|
-
|
329
|
-
|
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."
|
330
331
|
)
|
331
332
|
end
|
332
333
|
|
@@ -341,7 +342,7 @@ module Packwerk
|
|
341
342
|
Result.new(
|
342
343
|
ok: false,
|
343
344
|
error_value: "'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
|
344
|
-
|
345
|
+
"defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
|
345
346
|
)
|
346
347
|
end
|
347
348
|
end
|
@@ -10,12 +10,12 @@ module Packwerk
|
|
10
10
|
CustomAssociations = T.type_alias { T.any(T::Array[Symbol], T::Set[Symbol]) }
|
11
11
|
|
12
12
|
RAILS_ASSOCIATIONS = T.let(
|
13
|
-
|
14
|
-
belongs_to
|
15
|
-
has_many
|
16
|
-
has_one
|
17
|
-
has_and_belongs_to_many
|
18
|
-
|
13
|
+
[
|
14
|
+
:belongs_to,
|
15
|
+
:has_many,
|
16
|
+
:has_one,
|
17
|
+
:has_and_belongs_to_many,
|
18
|
+
].to_set,
|
19
19
|
CustomAssociations
|
20
20
|
)
|
21
21
|
|
@@ -31,15 +31,16 @@ module Packwerk
|
|
31
31
|
.returns(T.nilable(String))
|
32
32
|
end
|
33
33
|
def constant_name_from_node(node, ancestors:)
|
34
|
-
return unless
|
34
|
+
return unless NodeHelpers.method_call?(node)
|
35
35
|
return unless association?(node)
|
36
36
|
|
37
|
-
arguments =
|
37
|
+
arguments = NodeHelpers.method_arguments(node)
|
38
38
|
return unless (association_name = association_name(arguments))
|
39
39
|
|
40
40
|
if (class_name_node = custom_class_name(arguments))
|
41
|
-
return unless
|
42
|
-
|
41
|
+
return unless NodeHelpers.string?(class_name_node)
|
42
|
+
|
43
|
+
NodeHelpers.literal_value(class_name_node)
|
43
44
|
else
|
44
45
|
@inflector.classify(association_name.to_s)
|
45
46
|
end
|
@@ -49,23 +50,24 @@ module Packwerk
|
|
49
50
|
|
50
51
|
sig { params(node: AST::Node).returns(T::Boolean) }
|
51
52
|
def association?(node)
|
52
|
-
method_name =
|
53
|
+
method_name = NodeHelpers.method_name(node)
|
53
54
|
@associations.include?(method_name)
|
54
55
|
end
|
55
56
|
|
56
57
|
sig { params(arguments: T::Array[AST::Node]).returns(T.nilable(AST::Node)) }
|
57
58
|
def custom_class_name(arguments)
|
58
|
-
association_options = arguments.detect { |n|
|
59
|
+
association_options = arguments.detect { |n| NodeHelpers.hash?(n) }
|
59
60
|
return unless association_options
|
60
61
|
|
61
|
-
|
62
|
+
NodeHelpers.value_from_hash(association_options, :class_name)
|
62
63
|
end
|
63
64
|
|
64
65
|
sig { params(arguments: T::Array[AST::Node]).returns(T.any(T.nilable(Symbol), T.nilable(String))) }
|
65
66
|
def association_name(arguments)
|
66
|
-
|
67
|
+
association_name_node = T.must(arguments[0])
|
68
|
+
return unless NodeHelpers.symbol?(association_name_node)
|
67
69
|
|
68
|
-
|
70
|
+
NodeHelpers.literal_value(association_name_node)
|
69
71
|
end
|
70
72
|
end
|
71
73
|
end
|
data/lib/packwerk/cache.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
1
|
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "digest"
|
5
5
|
|
@@ -13,31 +13,35 @@ module Packwerk
|
|
13
13
|
const :file_contents_digest, String
|
14
14
|
const :unresolved_references, T::Array[UnresolvedReference]
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
16
|
+
class << self
|
17
|
+
extend T::Sig
|
18
|
+
|
19
|
+
sig { params(serialized_cache_contents: String).returns(CacheContents) }
|
20
|
+
def deserialize(serialized_cache_contents)
|
21
|
+
cache_contents_json = JSON.parse(serialized_cache_contents)
|
22
|
+
unresolved_references = cache_contents_json["unresolved_references"].map do |json|
|
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"],)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
CacheContents.new(
|
32
|
+
file_contents_digest: cache_contents_json["file_contents_digest"],
|
33
|
+
unresolved_references: unresolved_references,
|
30
34
|
)
|
31
35
|
end
|
36
|
+
end
|
32
37
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
)
|
38
|
+
sig { returns(String) }
|
39
|
+
def serialize
|
40
|
+
to_json
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
40
|
-
|
44
|
+
CacheShape = T.type_alias do
|
41
45
|
T::Hash[
|
42
46
|
String,
|
43
47
|
CacheContents
|
@@ -47,7 +51,7 @@ module Packwerk
|
|
47
51
|
sig { params(enable_cache: T::Boolean, cache_directory: Pathname, config_path: T.nilable(String)).void }
|
48
52
|
def initialize(enable_cache:, cache_directory:, config_path:)
|
49
53
|
@enable_cache = enable_cache
|
50
|
-
@cache = T.let({},
|
54
|
+
@cache = T.let({}, CacheShape)
|
51
55
|
@files_by_digest = T.let({}, T::Hash[String, String])
|
52
56
|
@config_path = config_path
|
53
57
|
@cache_directory = cache_directory
|
@@ -71,7 +75,7 @@ module Packwerk
|
|
71
75
|
).returns(T::Array[UnresolvedReference])
|
72
76
|
end
|
73
77
|
def with_cache(file_path, &block)
|
74
|
-
return
|
78
|
+
return yield unless @enable_cache
|
75
79
|
|
76
80
|
cache_location = @cache_directory.join(digest_for_string(file_path))
|
77
81
|
|
@@ -89,7 +93,7 @@ module Packwerk
|
|
89
93
|
else
|
90
94
|
Debug.out("Cache miss for #{file_path}")
|
91
95
|
|
92
|
-
unresolved_references =
|
96
|
+
unresolved_references = yield
|
93
97
|
|
94
98
|
cache_contents = CacheContents.new(
|
95
99
|
file_contents_digest: file_contents_digest,
|
@@ -116,6 +120,7 @@ module Packwerk
|
|
116
120
|
sig { void }
|
117
121
|
def bust_cache_if_packwerk_yml_has_changed!
|
118
122
|
return nil if @config_path.nil?
|
123
|
+
|
119
124
|
bust_cache_if_contents_have_changed(File.read(@config_path), :packwerk_yml)
|
120
125
|
end
|
121
126
|
|
@@ -155,14 +160,16 @@ module Packwerk
|
|
155
160
|
end
|
156
161
|
|
157
162
|
class Debug
|
158
|
-
|
163
|
+
class << self
|
164
|
+
extend T::Sig
|
159
165
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
166
|
+
sig { params(out: String).void }
|
167
|
+
def out(out)
|
168
|
+
if ENV["DEBUG_PACKWERK_CACHE"]
|
169
|
+
puts(out)
|
170
|
+
end
|
164
171
|
end
|
165
|
-
|
172
|
+
end
|
166
173
|
end
|
167
174
|
|
168
175
|
private_constant :Debug
|
data/lib/packwerk/cli.rb
CHANGED
@@ -32,8 +32,10 @@ module Packwerk
|
|
32
32
|
@style = style
|
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
|
-
@offenses_formatter = T.let(
|
36
|
-
OffensesFormatter)
|
35
|
+
@offenses_formatter = T.let(
|
36
|
+
offenses_formatter || Formatters::OffensesFormatter.new(style: @style),
|
37
|
+
OffensesFormatter
|
38
|
+
)
|
37
39
|
end
|
38
40
|
|
39
41
|
sig { params(args: T::Array[String]).returns(T.noreturn) }
|
@@ -65,7 +67,6 @@ module Packwerk
|
|
65
67
|
Subcommands:
|
66
68
|
init - set up packwerk
|
67
69
|
check - run all checks
|
68
|
-
update - update deprecated references (deprecated, use update-deprecations instead)
|
69
70
|
update-deprecations - update deprecated references
|
70
71
|
validate - verify integrity of packwerk and package configuration
|
71
72
|
help - display help information about packwerk
|
@@ -122,16 +123,21 @@ module Packwerk
|
|
122
123
|
result.status
|
123
124
|
end
|
124
125
|
|
125
|
-
sig
|
126
|
+
sig do
|
127
|
+
params(
|
128
|
+
relative_file_paths: T::Array[String],
|
129
|
+
ignore_nested_packages: T::Boolean
|
130
|
+
).returns(FilesForProcessing::RelativeFileSet)
|
131
|
+
end
|
126
132
|
def fetch_files_to_process(relative_file_paths, ignore_nested_packages)
|
127
|
-
|
133
|
+
relative_file_set = FilesForProcessing.fetch(
|
128
134
|
relative_file_paths: relative_file_paths,
|
129
135
|
ignore_nested_packages: ignore_nested_packages,
|
130
136
|
configuration: @configuration
|
131
137
|
)
|
132
138
|
abort("No files found or given. "\
|
133
|
-
"Specify files or check the include and exclude glob in the config file.") if
|
134
|
-
|
139
|
+
"Specify files or check the include and exclude glob in the config file.") if relative_file_set.empty?
|
140
|
+
relative_file_set
|
135
141
|
end
|
136
142
|
|
137
143
|
sig { params(_paths: T::Array[String]).returns(T::Boolean) }
|
@@ -183,7 +189,7 @@ module Packwerk
|
|
183
189
|
end
|
184
190
|
|
185
191
|
ParseRun.new(
|
186
|
-
|
192
|
+
relative_file_set: fetch_files_to_process(relative_file_paths, ignore_nested_packages),
|
187
193
|
configuration: @configuration,
|
188
194
|
progress_formatter: @progress_formatter,
|
189
195
|
offenses_formatter: @offenses_formatter
|
@@ -13,7 +13,8 @@ module Packwerk
|
|
13
13
|
.returns(T.nilable(String))
|
14
14
|
end
|
15
15
|
def constant_name_from_node(node, ancestors:)
|
16
|
-
return nil unless
|
16
|
+
return nil unless NodeHelpers.constant?(node)
|
17
|
+
|
17
18
|
parent = ancestors.first
|
18
19
|
|
19
20
|
# Only process the root `const` node for namespaced constant references. For example, in the
|
@@ -24,8 +25,8 @@ module Packwerk
|
|
24
25
|
fully_qualify_constant(ancestors)
|
25
26
|
else
|
26
27
|
begin
|
27
|
-
|
28
|
-
rescue
|
28
|
+
NodeHelpers.constant_name(node)
|
29
|
+
rescue NodeHelpers::TypeError
|
29
30
|
nil
|
30
31
|
end
|
31
32
|
end
|
@@ -35,20 +36,20 @@ module Packwerk
|
|
35
36
|
|
36
37
|
sig { params(parent: T.nilable(AST::Node)).returns(T::Boolean) }
|
37
38
|
def root_constant?(parent)
|
38
|
-
!(parent &&
|
39
|
+
!(parent && NodeHelpers.constant?(parent))
|
39
40
|
end
|
40
41
|
|
41
42
|
sig { params(node: AST::Node, parent: AST::Node).returns(T.nilable(T::Boolean)) }
|
42
43
|
def constant_in_module_or_class_definition?(node, parent:)
|
43
|
-
parent_name =
|
44
|
-
parent_name && parent_name ==
|
44
|
+
parent_name = NodeHelpers.module_name_from_definition(parent)
|
45
|
+
parent_name && parent_name == NodeHelpers.constant_name(node)
|
45
46
|
end
|
46
47
|
|
47
48
|
sig { params(ancestors: T::Array[AST::Node]).returns(String) }
|
48
49
|
def fully_qualify_constant(ancestors)
|
49
50
|
# We're defining a class with this name, in which case the constant is implicitly fully qualified by its
|
50
51
|
# enclosing namespace
|
51
|
-
"::" +
|
52
|
+
"::" + NodeHelpers.parent_module_name(ancestors: ancestors)
|
52
53
|
end
|
53
54
|
end
|
54
55
|
end
|
@@ -12,9 +12,9 @@ module Packwerk
|
|
12
12
|
interface!
|
13
13
|
|
14
14
|
sig do
|
15
|
-
|
15
|
+
abstract
|
16
|
+
.params(node: ::AST::Node, ancestors: T::Array[::AST::Node])
|
16
17
|
.returns(T.nilable(String))
|
17
|
-
.abstract
|
18
18
|
end
|
19
19
|
def constant_name_from_node(node, ancestors:); end
|
20
20
|
end
|
@@ -7,7 +7,7 @@ module Packwerk
|
|
7
7
|
class DeprecatedReferences
|
8
8
|
extend T::Sig
|
9
9
|
|
10
|
-
|
10
|
+
EntriesType = T.type_alias do
|
11
11
|
T::Hash[String, T.untyped]
|
12
12
|
end
|
13
13
|
|
@@ -15,8 +15,8 @@ module Packwerk
|
|
15
15
|
def initialize(package, filepath)
|
16
16
|
@package = package
|
17
17
|
@filepath = filepath
|
18
|
-
@new_entries = T.let({},
|
19
|
-
@deprecated_references = T.let(nil, T.nilable(
|
18
|
+
@new_entries = T.let({}, EntriesType)
|
19
|
+
@deprecated_references = T.let(nil, T.nilable(EntriesType))
|
20
20
|
end
|
21
21
|
|
22
22
|
sig do
|
@@ -36,28 +36,43 @@ module Packwerk
|
|
36
36
|
sig { params(reference: Packwerk::Reference, violation_type: Packwerk::ViolationType).returns(T::Boolean) }
|
37
37
|
def add_entries(reference, violation_type)
|
38
38
|
package_violations = @new_entries.fetch(reference.constant.package.name, {})
|
39
|
-
|
39
|
+
entries_for_constant = package_violations[reference.constant.name] ||= {}
|
40
40
|
|
41
|
-
|
42
|
-
|
41
|
+
entries_for_constant["violations"] ||= []
|
42
|
+
entries_for_constant["violations"] << violation_type.serialize
|
43
43
|
|
44
|
-
|
45
|
-
|
44
|
+
entries_for_constant["files"] ||= []
|
45
|
+
entries_for_constant["files"] << reference.relative_path.to_s
|
46
46
|
|
47
47
|
@new_entries[reference.constant.package.name] = package_violations
|
48
48
|
listed?(reference, violation_type: violation_type)
|
49
49
|
end
|
50
50
|
|
51
|
-
sig { returns(T::Boolean) }
|
52
|
-
def stale_violations?
|
51
|
+
sig { params(for_files: T::Set[String]).returns(T::Boolean) }
|
52
|
+
def stale_violations?(for_files)
|
53
53
|
prepare_entries_for_dump
|
54
|
+
|
54
55
|
deprecated_references.any? do |package, package_violations|
|
55
|
-
|
56
|
+
package_violations_for_files = {}
|
57
|
+
package_violations.each do |constant_name, entries_for_constant|
|
58
|
+
entries_for_files = for_files & entries_for_constant["files"]
|
59
|
+
next if entries_for_files.none?
|
60
|
+
|
61
|
+
package_violations_for_files[constant_name] = {
|
62
|
+
"violations" => entries_for_constant["violations"],
|
63
|
+
"files" => entries_for_files.to_a,
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
return true if package_violations_for_files.empty?
|
68
|
+
|
69
|
+
package_violations_for_files.any? do |constant_name, entries_for_constant|
|
56
70
|
new_entries_violation_types = @new_entries.dig(package, constant_name, "violations")
|
57
71
|
return true if new_entries_violation_types.nil?
|
58
|
-
|
72
|
+
|
73
|
+
if entries_for_constant["violations"].all? { |type| new_entries_violation_types.include?(type) }
|
59
74
|
stale_violations =
|
60
|
-
|
75
|
+
entries_for_constant["files"] - Array(@new_entries.dig(package, constant_name, "files"))
|
61
76
|
stale_violations.any?
|
62
77
|
else
|
63
78
|
return true
|
@@ -89,12 +104,12 @@ module Packwerk
|
|
89
104
|
|
90
105
|
private
|
91
106
|
|
92
|
-
sig { returns(
|
107
|
+
sig { returns(EntriesType) }
|
93
108
|
def prepare_entries_for_dump
|
94
109
|
@new_entries.each do |package_name, package_violations|
|
95
|
-
package_violations.each do |_,
|
96
|
-
|
97
|
-
|
110
|
+
package_violations.each do |_, entries_for_constant|
|
111
|
+
entries_for_constant["violations"].sort!.uniq!
|
112
|
+
entries_for_constant["files"].sort!.uniq!
|
98
113
|
end
|
99
114
|
@new_entries[package_name] = package_violations.sort.to_h
|
100
115
|
end
|
@@ -102,7 +117,7 @@ module Packwerk
|
|
102
117
|
@new_entries = @new_entries.sort.to_h
|
103
118
|
end
|
104
119
|
|
105
|
-
sig { returns(
|
120
|
+
sig { returns(EntriesType) }
|
106
121
|
def deprecated_references
|
107
122
|
@deprecated_references ||= if File.exist?(@filepath)
|
108
123
|
load_yaml(@filepath)
|
@@ -111,7 +126,7 @@ module Packwerk
|
|
111
126
|
end
|
112
127
|
end
|
113
128
|
|
114
|
-
sig { params(filepath: String).returns(
|
129
|
+
sig { params(filepath: String).returns(EntriesType) }
|
115
130
|
def load_yaml(filepath)
|
116
131
|
YAML.load_file(filepath) || {}
|
117
132
|
rescue Psych::Exception
|
@@ -29,49 +29,51 @@ module Packwerk
|
|
29
29
|
@parser_factory = T.let(parser_factory || Packwerk::Parsers::Factory.instance, Parsers::Factory)
|
30
30
|
end
|
31
31
|
|
32
|
+
class ProcessedFile < T::Struct
|
33
|
+
const :unresolved_references, T::Array[UnresolvedReference], default: []
|
34
|
+
const :offenses, T::Array[Offense], default: []
|
35
|
+
end
|
36
|
+
|
32
37
|
sig do
|
33
|
-
params(
|
34
|
-
T::Array[
|
35
|
-
T.any(
|
36
|
-
Packwerk::UnresolvedReference,
|
37
|
-
Packwerk::Offense,
|
38
|
-
)
|
39
|
-
]
|
40
|
-
)
|
38
|
+
params(relative_file: String).returns(ProcessedFile)
|
41
39
|
end
|
42
|
-
def call(
|
43
|
-
parser = parser_for(
|
44
|
-
|
40
|
+
def call(relative_file)
|
41
|
+
parser = parser_for(relative_file)
|
42
|
+
if parser.nil?
|
43
|
+
return ProcessedFile.new(offenses: [UnknownFileTypeResult.new(file: relative_file)])
|
44
|
+
end
|
45
45
|
|
46
|
-
@cache.with_cache(
|
47
|
-
node = parse_into_ast(
|
48
|
-
return
|
46
|
+
unresolved_references = @cache.with_cache(relative_file) do
|
47
|
+
node = parse_into_ast(relative_file, parser)
|
48
|
+
return ProcessedFile.new unless node
|
49
49
|
|
50
|
-
references_from_ast(node,
|
50
|
+
references_from_ast(node, relative_file)
|
51
51
|
end
|
52
|
+
|
53
|
+
ProcessedFile.new(unresolved_references: unresolved_references)
|
52
54
|
rescue Parsers::ParseError => e
|
53
|
-
[e.result]
|
55
|
+
ProcessedFile.new(offenses: [e.result])
|
54
56
|
end
|
55
57
|
|
56
58
|
private
|
57
59
|
|
58
60
|
sig do
|
59
|
-
params(node: Parser::AST::Node,
|
61
|
+
params(node: Parser::AST::Node, relative_file: String).returns(T::Array[UnresolvedReference])
|
60
62
|
end
|
61
|
-
def references_from_ast(node,
|
63
|
+
def references_from_ast(node, relative_file)
|
62
64
|
references = []
|
63
65
|
|
64
|
-
node_processor = @node_processor_factory.for(
|
66
|
+
node_processor = @node_processor_factory.for(relative_file: relative_file, node: node)
|
65
67
|
node_visitor = Packwerk::NodeVisitor.new(node_processor: node_processor)
|
66
68
|
node_visitor.visit(node, ancestors: [], result: references)
|
67
69
|
|
68
70
|
references
|
69
71
|
end
|
70
72
|
|
71
|
-
sig { params(
|
72
|
-
def parse_into_ast(
|
73
|
-
File.open(
|
74
|
-
parser.call(io: file, file_path:
|
73
|
+
sig { params(relative_file: String, parser: Parsers::ParserInterface).returns(T.untyped) }
|
74
|
+
def parse_into_ast(relative_file, parser)
|
75
|
+
File.open(relative_file, "r", nil, external_encoding: Encoding::UTF_8) do |file|
|
76
|
+
parser.call(io: file, file_path: relative_file)
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|