packwerk 2.1.1 → 2.2.1

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