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.
Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +29 -20
  3. data/.github/workflows/cla.yml +22 -0
  4. data/.rubocop.yml +48 -19
  5. data/Gemfile +7 -2
  6. data/Gemfile.lock +204 -177
  7. data/README.md +7 -2
  8. data/RESOLVING_VIOLATIONS.md +81 -0
  9. data/Rakefile +1 -1
  10. data/USAGE.md +14 -5
  11. data/bin/m +1 -1
  12. data/bin/rake +1 -1
  13. data/bin/rubocop +1 -1
  14. data/bin/srb +1 -1
  15. data/bin/tapioca +1 -1
  16. data/gemfiles/Gemfile-rails-6-0 +1 -1
  17. data/gemfiles/Gemfile-rails-6-1 +22 -0
  18. data/lib/packwerk/application_load_paths.rb +12 -18
  19. data/lib/packwerk/application_validator.rb +7 -6
  20. data/lib/packwerk/association_inspector.rb +17 -15
  21. data/lib/packwerk/cache.rb +36 -29
  22. data/lib/packwerk/cli.rb +14 -8
  23. data/lib/packwerk/const_node_inspector.rb +8 -7
  24. data/lib/packwerk/constant_name_inspector.rb +2 -2
  25. data/lib/packwerk/deprecated_references.rb +34 -19
  26. data/lib/packwerk/file_processor.rb +25 -23
  27. data/lib/packwerk/files_for_processing.rb +33 -35
  28. data/lib/packwerk/formatters/offenses_formatter.rb +3 -3
  29. data/lib/packwerk/formatters/progress_formatter.rb +2 -2
  30. data/lib/packwerk/node.rb +1 -294
  31. data/lib/packwerk/node_helpers.rb +335 -0
  32. data/lib/packwerk/node_processor.rb +6 -5
  33. data/lib/packwerk/node_processor_factory.rb +3 -3
  34. data/lib/packwerk/node_visitor.rb +1 -1
  35. data/lib/packwerk/offense_collection.rb +6 -3
  36. data/lib/packwerk/offenses_formatter.rb +2 -2
  37. data/lib/packwerk/package.rb +3 -0
  38. data/lib/packwerk/package_set.rb +3 -1
  39. data/lib/packwerk/parse_run.rb +15 -13
  40. data/lib/packwerk/parsed_constant_definitions.rb +23 -20
  41. data/lib/packwerk/parsers/erb.rb +3 -3
  42. data/lib/packwerk/parsers/parser_interface.rb +2 -0
  43. data/lib/packwerk/reference_checking/checkers/checker.rb +16 -3
  44. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +16 -0
  45. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +18 -0
  46. data/lib/packwerk/reference_checking/reference_checker.rb +4 -4
  47. data/lib/packwerk/reference_extractor.rb +51 -54
  48. data/lib/packwerk/reference_offense.rb +3 -27
  49. data/lib/packwerk/run_context.rb +9 -7
  50. data/lib/packwerk/spring_command.rb +1 -1
  51. data/lib/packwerk/version.rb +1 -1
  52. data/lib/packwerk.rb +1 -0
  53. data/packwerk.gemspec +4 -11
  54. data/sorbet/rbi/gems/actioncable@7.0.3.1.rbi +2754 -0
  55. data/sorbet/rbi/gems/actionmailbox@7.0.3.1.rbi +1496 -0
  56. data/sorbet/rbi/gems/actionmailer@7.0.3.1.rbi +2362 -0
  57. data/sorbet/rbi/gems/actionpack@7.0.3.1.rbi +19397 -0
  58. data/sorbet/rbi/gems/actiontext@7.0.3.1.rbi +1569 -0
  59. data/sorbet/rbi/gems/actionview@7.0.3.1.rbi +14907 -0
  60. data/sorbet/rbi/gems/activejob@7.0.3.1.rbi +2553 -0
  61. data/sorbet/rbi/gems/activemodel@7.0.3.1.rbi +5999 -0
  62. data/sorbet/rbi/gems/activerecord@7.0.3.1.rbi +37832 -0
  63. data/sorbet/rbi/gems/activestorage@7.0.3.1.rbi +2321 -0
  64. data/sorbet/rbi/gems/activesupport@7.0.3.1.rbi +18818 -0
  65. data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +11722 -0
  66. data/sorbet/rbi/gems/constant_resolver@0.2.0.rbi +90 -0
  67. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1079 -0
  68. data/sorbet/rbi/gems/digest@3.1.0.rbi +189 -0
  69. data/sorbet/rbi/gems/erubi@1.11.0.rbi +140 -0
  70. data/sorbet/rbi/gems/globalid@1.0.0.rbi +572 -0
  71. data/sorbet/rbi/gems/i18n@1.12.0.rbi +2296 -0
  72. data/sorbet/rbi/gems/json@2.6.2.rbi +1548 -0
  73. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +8 -0
  74. data/sorbet/rbi/gems/loofah@2.18.0.rbi +877 -0
  75. data/sorbet/rbi/gems/m@1.6.0.rbi +257 -0
  76. data/sorbet/rbi/gems/marcel@1.0.2.rbi +220 -0
  77. data/sorbet/rbi/gems/mini_mime@1.1.2.rbi +170 -0
  78. data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +8 -0
  79. data/sorbet/rbi/gems/minitest-focus@1.3.1.rbi +104 -0
  80. data/sorbet/rbi/gems/minitest@5.16.2.rbi +2136 -0
  81. data/sorbet/rbi/gems/mocha@1.14.0.rbi +4177 -0
  82. data/sorbet/rbi/gems/net-imap@0.2.3.rbi +2147 -0
  83. data/sorbet/rbi/gems/net-pop@0.1.1.rbi +926 -0
  84. data/sorbet/rbi/gems/net-protocol@0.1.3.rbi +11 -0
  85. data/sorbet/rbi/gems/net-smtp@0.3.1.rbi +1108 -0
  86. data/sorbet/rbi/gems/netrc@0.11.0.rbi +153 -0
  87. data/sorbet/rbi/gems/nio4r@2.5.8.rbi +292 -0
  88. data/sorbet/rbi/gems/nokogiri@1.13.8.rbi +6478 -0
  89. data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
  90. data/sorbet/rbi/gems/parser@3.1.2.1.rbi +9029 -0
  91. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +8 -0
  92. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  93. data/sorbet/rbi/gems/racc@1.6.0.rbi +152 -0
  94. data/sorbet/rbi/gems/rack-test@2.0.2.rbi +953 -0
  95. data/sorbet/rbi/gems/rack@2.2.4.rbi +5636 -0
  96. data/sorbet/rbi/gems/rails-html-sanitizer@1.4.3.rbi +688 -0
  97. data/sorbet/rbi/gems/rails@7.0.3.1.rbi +8 -0
  98. data/sorbet/rbi/gems/railties@7.0.3.1.rbi +3507 -0
  99. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +392 -0
  100. data/sorbet/rbi/gems/rake@13.0.6.rbi +2924 -0
  101. data/sorbet/rbi/gems/rbi@0.0.15.rbi +3007 -0
  102. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +3383 -0
  103. data/sorbet/rbi/gems/rexml@3.2.5.rbi +4714 -0
  104. data/sorbet/rbi/gems/rubocop-ast@1.21.0.rbi +6961 -0
  105. data/sorbet/rbi/gems/rubocop-performance@1.14.3.rbi +2986 -0
  106. data/sorbet/rbi/gems/{rubocop-shopify@2.0.1.rbi → rubocop-shopify@2.9.0.rbi} +4 -4
  107. data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +992 -0
  108. data/sorbet/rbi/gems/rubocop@1.34.1.rbi +51820 -0
  109. data/sorbet/rbi/gems/ruby-lsp@0.2.1.rbi +11 -0
  110. data/sorbet/rbi/gems/smart_properties@1.17.0.rbi +474 -0
  111. data/sorbet/rbi/gems/spoom@1.1.11.rbi +2181 -0
  112. data/sorbet/rbi/gems/spring@4.0.0.rbi +411 -0
  113. data/sorbet/rbi/gems/strscan@3.0.4.rbi +8 -0
  114. data/sorbet/rbi/gems/syntax_tree@3.3.0.rbi +8 -0
  115. data/sorbet/rbi/gems/tapioca@0.9.2.rbi +3181 -0
  116. data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
  117. data/sorbet/rbi/gems/timeout@0.3.0.rbi +142 -0
  118. data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +5896 -0
  119. data/sorbet/rbi/gems/unicode-display_width@2.2.0.rbi +48 -0
  120. data/sorbet/rbi/gems/unparser@0.6.5.rbi +4529 -0
  121. data/sorbet/rbi/gems/webrick@1.7.0.rbi +2582 -0
  122. data/sorbet/rbi/gems/websocket-driver@0.7.5.rbi +993 -0
  123. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +388 -0
  124. data/sorbet/rbi/gems/yard@0.9.28.rbi +18242 -0
  125. data/sorbet/rbi/gems/zeitwerk@2.6.0.rbi +867 -0
  126. data/sorbet/rbi/shims/psych.rbi +5 -0
  127. data/sorbet/tapioca/require.rb +2 -3
  128. metadata +88 -143
  129. data/.github/probots.yml +0 -2
  130. data/library.yml +0 -6
  131. data/service.yml +0 -1
  132. data/sorbet/rbi/gems/actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -860
  133. data/sorbet/rbi/gems/actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -568
  134. data/sorbet/rbi/gems/actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -587
  135. data/sorbet/rbi/gems/actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -5314
  136. data/sorbet/rbi/gems/actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -699
  137. data/sorbet/rbi/gems/actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -2515
  138. data/sorbet/rbi/gems/activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -624
  139. data/sorbet/rbi/gems/activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -1248
  140. data/sorbet/rbi/gems/activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8363
  141. data/sorbet/rbi/gems/activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -876
  142. data/sorbet/rbi/gems/activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -3987
  143. data/sorbet/rbi/gems/colorize@0.8.1.rbi +0 -40
  144. data/sorbet/rbi/gems/commander@4.5.2.rbi +0 -8
  145. data/sorbet/rbi/gems/concurrent-ruby@1.1.8.rbi +0 -1969
  146. data/sorbet/rbi/gems/constant_resolver@0.1.5.rbi +0 -26
  147. data/sorbet/rbi/gems/erubi@1.10.0.rbi +0 -41
  148. data/sorbet/rbi/gems/globalid@0.4.2.rbi +0 -178
  149. data/sorbet/rbi/gems/highline@2.0.3.rbi +0 -8
  150. data/sorbet/rbi/gems/i18n@1.8.10.rbi +0 -600
  151. data/sorbet/rbi/gems/loofah@2.9.0.rbi +0 -274
  152. data/sorbet/rbi/gems/m@1.5.1.rbi +0 -108
  153. data/sorbet/rbi/gems/marcel@1.0.0.rbi +0 -70
  154. data/sorbet/rbi/gems/mini_mime@1.0.3.rbi +0 -71
  155. data/sorbet/rbi/gems/minitest-focus@1.2.1.rbi +0 -8
  156. data/sorbet/rbi/gems/minitest@5.14.4.rbi +0 -544
  157. data/sorbet/rbi/gems/mocha@1.12.0.rbi +0 -953
  158. data/sorbet/rbi/gems/nio4r@2.5.7.rbi +0 -90
  159. data/sorbet/rbi/gems/nokogiri@1.11.2.rbi +0 -1647
  160. data/sorbet/rbi/gems/parallel@1.20.1.rbi +0 -117
  161. data/sorbet/rbi/gems/parlour@6.0.0.rbi +0 -1272
  162. data/sorbet/rbi/gems/parser@3.0.0.0.rbi +0 -1745
  163. data/sorbet/rbi/gems/pry@0.14.0.rbi +0 -8
  164. data/sorbet/rbi/gems/psych@3.3.2.rbi +0 -24
  165. data/sorbet/rbi/gems/racc@1.5.2.rbi +0 -57
  166. data/sorbet/rbi/gems/rack-test@1.1.0.rbi +0 -335
  167. data/sorbet/rbi/gems/rack@2.2.3.rbi +0 -1718
  168. data/sorbet/rbi/gems/rails-html-sanitizer@1.3.0.rbi +0 -213
  169. data/sorbet/rbi/gems/rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8
  170. data/sorbet/rbi/gems/railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -880
  171. data/sorbet/rbi/gems/rainbow@3.0.0.rbi +0 -155
  172. data/sorbet/rbi/gems/rake@13.0.3.rbi +0 -837
  173. data/sorbet/rbi/gems/regexp_parser@2.1.1.rbi +0 -8
  174. data/sorbet/rbi/gems/rexml@3.2.4.rbi +0 -8
  175. data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +0 -8
  176. data/sorbet/rbi/gems/rubocop-performance@1.10.2.rbi +0 -8
  177. data/sorbet/rbi/gems/rubocop-sorbet@0.6.1.rbi +0 -8
  178. data/sorbet/rbi/gems/rubocop@1.12.0.rbi +0 -8
  179. data/sorbet/rbi/gems/smart_properties@1.15.0.rbi +0 -168
  180. data/sorbet/rbi/gems/spoom@1.1.0.rbi +0 -1061
  181. data/sorbet/rbi/gems/spring@2.1.1.rbi +0 -160
  182. data/sorbet/rbi/gems/sprockets-rails@3.2.2.rbi +0 -451
  183. data/sorbet/rbi/gems/sprockets@4.0.2.rbi +0 -1133
  184. data/sorbet/rbi/gems/tapioca@0.4.19.rbi +0 -603
  185. data/sorbet/rbi/gems/thor@1.1.0.rbi +0 -893
  186. data/sorbet/rbi/gems/tzinfo@2.0.4.rbi +0 -566
  187. data/sorbet/rbi/gems/unicode-display_width@2.0.0.rbi +0 -8
  188. data/sorbet/rbi/gems/websocket-driver@0.7.3.rbi +0 -438
  189. 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 File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
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 File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
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.
@@ -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", '~> 6.0.0')
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::Array[String]) }
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::Array[String]) }
21
+ sig { returns(T::Hash[String, Module]) }
22
22
  def extract_application_autoload_paths
23
- Rails.application.railties
24
- .select { |railtie| railtie.is_a?(Rails::Engine) }
25
- .push(Rails.application)
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::Array[String], bundle_path: Pathname, rails_root: Pathname)
34
- .returns(T::Array[Pathname])
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
- .map { |path| Pathname.new(path).expand_path }
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(paths: T::Array[Pathname], rails_root: Pathname).returns(T::Array[String]) }
47
- def relative_path_strings(paths, rails_root: Rails.root)
48
- paths
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::Array[T.untyped]).void }
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 = %w(enforce_privacy enforce_dependencies public_path dependencies metadata)
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
- "Private constants need to be prefixed with the top-level namespace operator `::`."
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
- "This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
328
- "file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
329
- "private. Add a #{explicit_filepath} file to explicitly define the constant."
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
- "defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
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
- %i(
14
- belongs_to
15
- has_many
16
- has_one
17
- has_and_belongs_to_many
18
- ).to_set,
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 Node.method_call?(node)
34
+ return unless NodeHelpers.method_call?(node)
35
35
  return unless association?(node)
36
36
 
37
- arguments = Node.method_arguments(node)
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 Node.string?(class_name_node)
42
- Node.literal_value(class_name_node)
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 = Node.method_name(node)
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| Node.hash?(n) }
59
+ association_options = arguments.detect { |n| NodeHelpers.hash?(n) }
59
60
  return unless association_options
60
61
 
61
- Node.value_from_hash(association_options, :class_name)
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
- return unless Node.symbol?(arguments[0])
67
+ association_name_node = T.must(arguments[0])
68
+ return unless NodeHelpers.symbol?(association_name_node)
67
69
 
68
- Node.literal_value(arguments[0])
70
+ NodeHelpers.literal_value(association_name_node)
69
71
  end
70
72
  end
71
73
  end
@@ -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
- sig { returns(String) }
17
- def serialize
18
- to_json
19
- end
20
-
21
- sig { params(serialized_cache_contents: String).returns(CacheContents) }
22
- def self.deserialize(serialized_cache_contents)
23
- cache_contents_json = JSON.parse(serialized_cache_contents)
24
- unresolved_references = cache_contents_json["unresolved_references"].map do |json|
25
- UnresolvedReference.new(
26
- json["constant_name"],
27
- json["namespace_path"],
28
- json["relative_path"],
29
- Node::Location.new(json["source_location"]["line"], json["source_location"]["column"],)
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
- CacheContents.new(
34
- file_contents_digest: cache_contents_json["file_contents_digest"],
35
- unresolved_references: unresolved_references,
36
- )
38
+ sig { returns(String) }
39
+ def serialize
40
+ to_json
37
41
  end
38
42
  end
39
43
 
40
- CACHE_SHAPE = T.type_alias do
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({}, CACHE_SHAPE)
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 block.call unless @enable_cache
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 = block.call
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
- extend T::Sig
163
+ class << self
164
+ extend T::Sig
159
165
 
160
- sig { params(out: String).void }
161
- def self.out(out)
162
- if ENV["DEBUG_PACKWERK_CACHE"]
163
- puts(out)
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
- end
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(offenses_formatter || Formatters::OffensesFormatter.new(style: @style),
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 { params(relative_file_paths: T::Array[String], ignore_nested_packages: T::Boolean).returns(T::Array[String]) }
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
- absolute_files = FilesForProcessing.fetch(
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 absolute_files.empty?
134
- absolute_files
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
- absolute_files: fetch_files_to_process(relative_file_paths, ignore_nested_packages),
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 Node.constant?(node)
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
- Node.constant_name(node)
28
- rescue Node::TypeError
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 && Node.constant?(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 = Node.module_name_from_definition(parent)
44
- parent_name && parent_name == Node.constant_name(node)
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
- "::" + Node.parent_module_name(ancestors: ancestors)
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
- params(node: ::AST::Node, ancestors: T::Array[::AST::Node])
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
- ENTRIES_TYPE = T.type_alias do
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({}, ENTRIES_TYPE)
19
- @deprecated_references = T.let(nil, T.nilable(ENTRIES_TYPE))
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
- entries_for_file = package_violations[reference.constant.name] ||= {}
39
+ entries_for_constant = package_violations[reference.constant.name] ||= {}
40
40
 
41
- entries_for_file["violations"] ||= []
42
- entries_for_file["violations"] << violation_type.serialize
41
+ entries_for_constant["violations"] ||= []
42
+ entries_for_constant["violations"] << violation_type.serialize
43
43
 
44
- entries_for_file["files"] ||= []
45
- entries_for_file["files"] << reference.relative_path.to_s
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
- package_violations.any? do |constant_name, entries_for_file|
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
- if entries_for_file["violations"].all? { |type| new_entries_violation_types.include?(type) }
72
+
73
+ if entries_for_constant["violations"].all? { |type| new_entries_violation_types.include?(type) }
59
74
  stale_violations =
60
- entries_for_file["files"] - Array(@new_entries.dig(package, constant_name, "files"))
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(ENTRIES_TYPE) }
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 |_, entries_for_file|
96
- entries_for_file["violations"].sort!.uniq!
97
- entries_for_file["files"].sort!.uniq!
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(ENTRIES_TYPE) }
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(ENTRIES_TYPE) }
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(absolute_file: String).returns(
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(absolute_file)
43
- parser = parser_for(absolute_file)
44
- return [UnknownFileTypeResult.new(file: absolute_file)] if T.unsafe(parser).nil?
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(absolute_file) do
47
- node = parse_into_ast(absolute_file, T.must(parser))
48
- return [] unless node
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, absolute_file)
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, absolute_file: String).returns(T::Array[UnresolvedReference])
61
+ params(node: Parser::AST::Node, relative_file: String).returns(T::Array[UnresolvedReference])
60
62
  end
61
- def references_from_ast(node, absolute_file)
63
+ def references_from_ast(node, relative_file)
62
64
  references = []
63
65
 
64
- node_processor = @node_processor_factory.for(absolute_file: absolute_file, node: node)
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(absolute_file: String, parser: Parsers::ParserInterface).returns(T.untyped) }
72
- def parse_into_ast(absolute_file, parser)
73
- File.open(absolute_file, "r", nil, external_encoding: Encoding::UTF_8) do |file|
74
- parser.call(io: file, file_path: absolute_file)
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