packwerk 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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
|
|