packwerk 2.2.0 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) 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 +202 -175
  7. data/README.md +1 -1
  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 +1 -1
  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 +24 -20
  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 +40 -20
  26. data/lib/packwerk/file_processor.rb +14 -14
  27. data/lib/packwerk/files_for_processing.rb +27 -31
  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 +27 -8
  36. data/lib/packwerk/offenses_formatter.rb +2 -2
  37. data/lib/packwerk/package.rb +3 -0
  38. data/lib/packwerk/package_set.rb +2 -0
  39. data/lib/packwerk/parse_run.rb +29 -20
  40. data/lib/packwerk/parsed_constant_definitions.rb +23 -20
  41. data/lib/packwerk/parsers/erb.rb +3 -3
  42. data/lib/packwerk/reference_checking/checkers/checker.rb +16 -3
  43. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +16 -0
  44. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +18 -0
  45. data/lib/packwerk/reference_checking/reference_checker.rb +3 -1
  46. data/lib/packwerk/reference_extractor.rb +51 -48
  47. data/lib/packwerk/reference_offense.rb +3 -27
  48. data/lib/packwerk/run_context.rb +9 -8
  49. data/lib/packwerk/spring_command.rb +1 -1
  50. data/lib/packwerk/version.rb +1 -1
  51. data/lib/packwerk.rb +1 -0
  52. data/packwerk.gemspec +5 -12
  53. data/sorbet/rbi/gems/actioncable@7.0.3.1.rbi +2754 -0
  54. data/sorbet/rbi/gems/actionmailbox@7.0.3.1.rbi +1496 -0
  55. data/sorbet/rbi/gems/actionmailer@7.0.3.1.rbi +2362 -0
  56. data/sorbet/rbi/gems/actionpack@7.0.3.1.rbi +19397 -0
  57. data/sorbet/rbi/gems/actiontext@7.0.3.1.rbi +1569 -0
  58. data/sorbet/rbi/gems/actionview@7.0.3.1.rbi +14907 -0
  59. data/sorbet/rbi/gems/activejob@7.0.3.1.rbi +2553 -0
  60. data/sorbet/rbi/gems/activemodel@7.0.3.1.rbi +5999 -0
  61. data/sorbet/rbi/gems/activerecord@7.0.3.1.rbi +37832 -0
  62. data/sorbet/rbi/gems/activestorage@7.0.3.1.rbi +2321 -0
  63. data/sorbet/rbi/gems/activesupport@7.0.3.1.rbi +18818 -0
  64. data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +11722 -0
  65. data/sorbet/rbi/gems/constant_resolver@0.2.0.rbi +90 -0
  66. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1079 -0
  67. data/sorbet/rbi/gems/digest@3.1.0.rbi +189 -0
  68. data/sorbet/rbi/gems/erubi@1.11.0.rbi +140 -0
  69. data/sorbet/rbi/gems/globalid@1.0.0.rbi +572 -0
  70. data/sorbet/rbi/gems/i18n@1.12.0.rbi +2296 -0
  71. data/sorbet/rbi/gems/json@2.6.2.rbi +1548 -0
  72. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +8 -0
  73. data/sorbet/rbi/gems/loofah@2.18.0.rbi +877 -0
  74. data/sorbet/rbi/gems/m@1.6.0.rbi +257 -0
  75. data/sorbet/rbi/gems/marcel@1.0.2.rbi +220 -0
  76. data/sorbet/rbi/gems/mini_mime@1.1.2.rbi +170 -0
  77. data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +8 -0
  78. data/sorbet/rbi/gems/minitest-focus@1.3.1.rbi +104 -0
  79. data/sorbet/rbi/gems/minitest@5.16.2.rbi +2136 -0
  80. data/sorbet/rbi/gems/mocha@1.14.0.rbi +4177 -0
  81. data/sorbet/rbi/gems/net-imap@0.2.3.rbi +2147 -0
  82. data/sorbet/rbi/gems/net-pop@0.1.1.rbi +926 -0
  83. data/sorbet/rbi/gems/net-protocol@0.1.3.rbi +11 -0
  84. data/sorbet/rbi/gems/net-smtp@0.3.1.rbi +1108 -0
  85. data/sorbet/rbi/gems/netrc@0.11.0.rbi +153 -0
  86. data/sorbet/rbi/gems/nio4r@2.5.8.rbi +292 -0
  87. data/sorbet/rbi/gems/nokogiri@1.13.8.rbi +6478 -0
  88. data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
  89. data/sorbet/rbi/gems/parser@3.1.2.1.rbi +9029 -0
  90. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +8 -0
  91. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  92. data/sorbet/rbi/gems/racc@1.6.0.rbi +152 -0
  93. data/sorbet/rbi/gems/rack-test@2.0.2.rbi +953 -0
  94. data/sorbet/rbi/gems/rack@2.2.4.rbi +5636 -0
  95. data/sorbet/rbi/gems/rails-html-sanitizer@1.4.3.rbi +688 -0
  96. data/sorbet/rbi/gems/rails@7.0.3.1.rbi +8 -0
  97. data/sorbet/rbi/gems/railties@7.0.3.1.rbi +3507 -0
  98. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +392 -0
  99. data/sorbet/rbi/gems/rake@13.0.6.rbi +2924 -0
  100. data/sorbet/rbi/gems/rbi@0.0.15.rbi +3007 -0
  101. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +3383 -0
  102. data/sorbet/rbi/gems/rexml@3.2.5.rbi +4714 -0
  103. data/sorbet/rbi/gems/rubocop-ast@1.21.0.rbi +6961 -0
  104. data/sorbet/rbi/gems/rubocop-performance@1.14.3.rbi +2986 -0
  105. data/sorbet/rbi/gems/{rubocop-shopify@2.0.1.rbi → rubocop-shopify@2.9.0.rbi} +4 -4
  106. data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +992 -0
  107. data/sorbet/rbi/gems/rubocop@1.34.1.rbi +51820 -0
  108. data/sorbet/rbi/gems/ruby-lsp@0.2.1.rbi +11 -0
  109. data/sorbet/rbi/gems/smart_properties@1.17.0.rbi +474 -0
  110. data/sorbet/rbi/gems/spoom@1.1.11.rbi +2181 -0
  111. data/sorbet/rbi/gems/spring@4.0.0.rbi +411 -0
  112. data/sorbet/rbi/gems/strscan@3.0.4.rbi +8 -0
  113. data/sorbet/rbi/gems/syntax_tree@3.3.0.rbi +8 -0
  114. data/sorbet/rbi/gems/tapioca@0.9.2.rbi +3181 -0
  115. data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
  116. data/sorbet/rbi/gems/timeout@0.3.0.rbi +142 -0
  117. data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +5896 -0
  118. data/sorbet/rbi/gems/unicode-display_width@2.2.0.rbi +48 -0
  119. data/sorbet/rbi/gems/unparser@0.6.5.rbi +4529 -0
  120. data/sorbet/rbi/gems/webrick@1.7.0.rbi +2582 -0
  121. data/sorbet/rbi/gems/websocket-driver@0.7.5.rbi +993 -0
  122. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +388 -0
  123. data/sorbet/rbi/gems/yard@0.9.28.rbi +18242 -0
  124. data/sorbet/rbi/gems/zeitwerk@2.6.0.rbi +867 -0
  125. data/sorbet/rbi/shims/psych.rbi +5 -0
  126. data/sorbet/tapioca/require.rb +2 -3
  127. metadata +91 -146
  128. data/.github/probots.yml +0 -2
  129. data/library.yml +0 -6
  130. data/service.yml +0 -1
  131. data/sorbet/rbi/gems/actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -860
  132. data/sorbet/rbi/gems/actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -568
  133. data/sorbet/rbi/gems/actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -587
  134. data/sorbet/rbi/gems/actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -5314
  135. data/sorbet/rbi/gems/actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -699
  136. data/sorbet/rbi/gems/actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -2515
  137. data/sorbet/rbi/gems/activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -624
  138. data/sorbet/rbi/gems/activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -1248
  139. data/sorbet/rbi/gems/activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8363
  140. data/sorbet/rbi/gems/activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -876
  141. data/sorbet/rbi/gems/activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -3987
  142. data/sorbet/rbi/gems/colorize@0.8.1.rbi +0 -40
  143. data/sorbet/rbi/gems/commander@4.5.2.rbi +0 -8
  144. data/sorbet/rbi/gems/concurrent-ruby@1.1.8.rbi +0 -1969
  145. data/sorbet/rbi/gems/constant_resolver@0.1.5.rbi +0 -26
  146. data/sorbet/rbi/gems/erubi@1.10.0.rbi +0 -41
  147. data/sorbet/rbi/gems/globalid@0.4.2.rbi +0 -178
  148. data/sorbet/rbi/gems/highline@2.0.3.rbi +0 -8
  149. data/sorbet/rbi/gems/i18n@1.8.10.rbi +0 -600
  150. data/sorbet/rbi/gems/loofah@2.9.0.rbi +0 -274
  151. data/sorbet/rbi/gems/m@1.5.1.rbi +0 -108
  152. data/sorbet/rbi/gems/marcel@1.0.0.rbi +0 -70
  153. data/sorbet/rbi/gems/mini_mime@1.0.3.rbi +0 -71
  154. data/sorbet/rbi/gems/minitest-focus@1.2.1.rbi +0 -8
  155. data/sorbet/rbi/gems/minitest@5.14.4.rbi +0 -544
  156. data/sorbet/rbi/gems/mocha@1.12.0.rbi +0 -953
  157. data/sorbet/rbi/gems/nio4r@2.5.7.rbi +0 -90
  158. data/sorbet/rbi/gems/nokogiri@1.11.2.rbi +0 -1647
  159. data/sorbet/rbi/gems/parallel@1.20.1.rbi +0 -117
  160. data/sorbet/rbi/gems/parlour@6.0.0.rbi +0 -1272
  161. data/sorbet/rbi/gems/parser@3.0.0.0.rbi +0 -1745
  162. data/sorbet/rbi/gems/pry@0.14.0.rbi +0 -8
  163. data/sorbet/rbi/gems/psych@3.3.2.rbi +0 -24
  164. data/sorbet/rbi/gems/racc@1.5.2.rbi +0 -57
  165. data/sorbet/rbi/gems/rack-test@1.1.0.rbi +0 -335
  166. data/sorbet/rbi/gems/rack@2.2.3.rbi +0 -1718
  167. data/sorbet/rbi/gems/rails-html-sanitizer@1.3.0.rbi +0 -213
  168. data/sorbet/rbi/gems/rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8
  169. data/sorbet/rbi/gems/railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -880
  170. data/sorbet/rbi/gems/rainbow@3.0.0.rbi +0 -155
  171. data/sorbet/rbi/gems/rake@13.0.3.rbi +0 -837
  172. data/sorbet/rbi/gems/regexp_parser@2.1.1.rbi +0 -8
  173. data/sorbet/rbi/gems/rexml@3.2.4.rbi +0 -8
  174. data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +0 -8
  175. data/sorbet/rbi/gems/rubocop-performance@1.10.2.rbi +0 -8
  176. data/sorbet/rbi/gems/rubocop-sorbet@0.6.1.rbi +0 -8
  177. data/sorbet/rbi/gems/rubocop@1.12.0.rbi +0 -8
  178. data/sorbet/rbi/gems/smart_properties@1.15.0.rbi +0 -168
  179. data/sorbet/rbi/gems/spoom@1.1.0.rbi +0 -1061
  180. data/sorbet/rbi/gems/spring@2.1.1.rbi +0 -160
  181. data/sorbet/rbi/gems/sprockets-rails@3.2.2.rbi +0 -451
  182. data/sorbet/rbi/gems/sprockets@4.0.2.rbi +0 -1133
  183. data/sorbet/rbi/gems/tapioca@0.4.19.rbi +0 -603
  184. data/sorbet/rbi/gems/thor@1.1.0.rbi +0 -893
  185. data/sorbet/rbi/gems/tzinfo@2.0.4.rbi +0 -566
  186. data/sorbet/rbi/gems/unicode-display_width@2.0.0.rbi +0 -8
  187. data/sorbet/rbi/gems/websocket-driver@0.7.3.rbi +0 -438
  188. data/sorbet/rbi/gems/zeitwerk@2.4.2.rbi +0 -177
data/README.md CHANGED
@@ -66,7 +66,7 @@ Various third parties have built tooling on top of packwerk. Here's a selection
66
66
  - https://github.com/bellroy/graphwerk draws a graph of your package dependencies
67
67
  - https://github.com/Gusto/packwerk-vscode integrates packwerk into Visual Studio Code so you can see violations right in your editor
68
68
  - https://github.com/Gusto/stimpack sets up Rails autoloading, as well as `rspec` and `FactoryBot` integration, for packages arranged in a flat list. Stimpack is quite convenient, but for autoloading we recommend to use `Rails::Engine`s instead.
69
- - https://github.com/BigRails/danger-packwerk integrates packwerk with [danger.systems](https://danger.systems) to provide packwerk feedback as Github inline PR comments
69
+ - https://github.com/rubyatscale/danger-packwerk integrates packwerk with [danger.systems](https://danger.systems) to provide packwerk feedback as Github inline PR comments
70
70
 
71
71
  ## Development
72
72
 
@@ -0,0 +1,81 @@
1
+ # Resolving Violations
2
+
3
+ Violations can be [recorded as a deprecation](#recording-violations) or (better!) [eliminated](#eliminating-violations).
4
+
5
+ ## Recording Violations
6
+ 💡 New privacy and dependency violations are never hard-blocked. There are many very valid reasons to run `bin/packwerk update-deprecations`, adding new violations to `deprecated_references.yml` files. Even if you feel your reason might not be "valid," if your judgement says adding the violation and shipping your change will produce positive impact, trust your gut.
7
+
8
+ ### Emergency Fixes
9
+ ❔ Is it a revert or is there a lot of urgency because you are fixing a production bug impacting customers?
10
+
11
+ ➡️ Simply run `bin/packwerk update-deprecations`, and address the violation when the customer issue is resolved.
12
+
13
+ ### Improving System Design
14
+ ❔ Are you improving system boundaries by renaming or moving a file, class, constant,` or module?
15
+
16
+ ➡️ Simply run `bin/packwerk update-deprecations`. We've improved how our system is organized, so the new violations are natural.
17
+
18
+ ### Making Things Explicit
19
+ ❔ Are you making something that was implicit (hidden to Packwerk) explicit, such as adding a Sorbet signature, using a class instead of a method call, or something similar?
20
+
21
+ ➡️ Simply run `bin/packwerk update-deprecations`. Making something implicit explicit and capturing that as a new violation is a strict improvement.
22
+
23
+ ### Temporary State
24
+ ❔ Is the violation temporary because you will soon delete the code or is it part of a refactor to improve system boundaries and reduce violations overall?
25
+
26
+ ➡️ Simply run `bin/packwerk update-deprecations`. Sometimes things get "worse" before they get better.
27
+
28
+ ### Delivering Features
29
+ ❔ Are you in a rush to get a feature out and product, your manager, or an internal sense of urgency has made you feel like you can't resolve system design issues?
30
+
31
+ ➡️ Stay strong. Eliminate the violation. It is important to build a sustainable business that optimizes for the long-term. Look for advocates such as from mentors or within your team who can help you justify improving system design.
32
+
33
+ ## Eliminating Violations
34
+ 💡 Dependency and privacy violations are Packwerk's signal that what we've stated about the desired system design (what
35
+ packages exist and what lives in them, the dependencies in a package.yml, and the public interface in the package's public folder) doesn't match the reality of our system.
36
+ If what we've stated about our system design doesn't feel right, then the violations won't make sense either! Make sure to think through system design before addressing a violation.
37
+
38
+ ### Moving Things Around
39
+ ❔ Does the code you're writing (or changing) live in the right package? Does the code you're referencing live in the right package?
40
+
41
+ If not, find a better place for it to live.
42
+
43
+ Otherwise, follow the guide for eliminating [Privacy Violations](#privacy-violations) or [Dependency Violations](#dependency-violations).
44
+
45
+ ### Privacy Violations
46
+ 💡 Packwerk thinks something is a privacy violation if you're referencing a constant, class, or module defined in the private implementation (i.e. not the public folder) of another package. We care about these because we want to make sure we only use parts of a package that have been exposed as public API.
47
+
48
+ An explicit and implementation-hiding public API is a cornerstone of well-modularized code.
49
+
50
+ #### Use Existing Public Interface
51
+ ❔ Does the package you're using expose public API in its public folder that supports your use case?
52
+
53
+ ➡️ Use that public API instead!
54
+
55
+ #### Change The Public Interface
56
+ ❔ Can we work with the package's owner to create and use a public API? Or should the thing we're using already be public?
57
+
58
+ ➡️ Work together on a new public API and use that instead! If the thing we're using should be public, move it to the public folder to make it public!
59
+
60
+ ⛈️ If working with the package's owner to improve the API is not possible, run `bin/packwerk update-deprecations`. Add some context to your PR about why it's not possible.
61
+
62
+ ### Dependency Violations
63
+ 💡 Packwerk thinks something is a dependency violation if you're referencing a constant, class, module defined ANYWHERE but your package doesn't list it as an explicit dependency in its `package.yml`. We care about these because it allows us to be systematically intentional about what our code needs to run and helps us untangle and remove dependency cycles from our system.
64
+
65
+ Thoughtful dependency management is another cornerstone of well-modularized code.
66
+
67
+ #### Adding Explicit Dependencies
68
+ ❔ Do we actually want to depend on the other package? Work with your team to help answer this question!
69
+
70
+ ➡️ Add the other package to your package's `package.yml` `dependencies` key.
71
+
72
+ ⁉️ Did you get a cyclic dependency when CI ran `bin/packwerk validate`?
73
+
74
+ ➡️ Work with your team to think through what link in the cycle we don't want. Remove that link and rerun `bin/packwerk validate`.
75
+
76
+ #### Changing The System Design
77
+ ❔ Can we spend some time to think through changes to the system design that don't require the dependency?
78
+
79
+ ➡️ Work with the owners of the relevant packages, as well as your team, to think through a design that doesn't include the unwanted dependency.
80
+
81
+ ⛈️ If this is not possible within the scope of your changes (think hard about this one!), run `bin/packwerk update-deprecations`. Add some context to your PR about why it's not possible, and any additional context you may have, such as a possible solution.
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.libs << "test"
8
8
  t.libs << "lib"
9
9
  t.test_files = FileList["test/**/*_test.rb"]
10
- t.warning = true
10
+ t.warning = false
11
11
  end
12
12
 
13
13
  task(default: :test)
data/USAGE.md CHANGED
@@ -17,6 +17,8 @@
17
17
  * [Using public folders](#using-public-folders)
18
18
  * [Enforcing dependency boundary](#enforcing-dependency-boundary)
19
19
  * [Checking for violations](#checking-for-violations)
20
+ * [Resolving new violations](#resolving-new-violations)
21
+ * [Understanding how to respond to new violations](#understanding-how-to-respond-to-new-violations)
20
22
  * [Recording existing violations](#recording-existing-violations)
21
23
  * [Understanding the list of deprecated references](#understanding-the-list-of-deprecated-references)
22
24
 
@@ -54,7 +56,7 @@ Here is a list of files generated:
54
56
 
55
57
  | File | Location | Description |
56
58
  |-----------------------------|--------------|------------|
57
- | Packwerk configuration | packwerk.yml | See [Setting up the configuration file](#Setting-up-the-configuration-file) |
59
+ | Packwerk configuration | packwerk.yml | See [Setting up the configuration file](#configuring-packwerk) |
58
60
  | Root package | package.yml | A package for the root folder |
59
61
 
60
62
  After that, you may begin creating packages for your application. See [Defining packages](#Defining-packages)
@@ -115,7 +117,7 @@ We recommend setting up the package system validation for your Rails application
115
117
 
116
118
  Use the following command to validate the application:
117
119
 
118
- packwerk validate
120
+ bin/packwerk validate
119
121
 
120
122
  ![](static/packwerk_validate.gif)
121
123
 
@@ -204,17 +206,17 @@ It will be a dependency violation when `components/shop_identity` tries to refer
204
206
 
205
207
  After enforcing the boundary checks for a package, you may execute:
206
208
 
207
- packwerk check
209
+ bin/packwerk check
208
210
 
209
211
  Packwerk will check the entire codebase for any new or stale violations.
210
212
 
211
213
  You can also specify folders for a shorter run time. When checking against folders all subfolders will be analyzed, irrespective of nested package boundaries.
212
214
 
213
- packwerk check components/your_package
215
+ bin/packwerk check components/your_package
214
216
 
215
217
  You can also specify packages for a shorter run time. When checking against packages any packages nested underneath the specified packages will not be checked. This can be helpful to test packages like the root package, which can have many nested packages.
216
218
 
217
- packwerk check --packages=components/your_package,components/your_other_package
219
+ bin/packwerk check --packages=components/your_package,components/your_other_package
218
220
 
219
221
  ![](static/packwerk_check.gif)
220
222
 
@@ -222,6 +224,13 @@ In order to keep the package system valid at each version of the application, we
222
224
 
223
225
  See: [TROUBLESHOOT.md - Sample violations](TROUBLESHOOT.md#Sample-violations)
224
226
 
227
+ ## Resolving new violations
228
+ ### Understanding how to respond to new violations
229
+
230
+ When you have a new dependency or privacy violation, what do you do?
231
+
232
+ See: [RESOLVING_VIOLATIONS.md](RESOLVING_VIOLATIONS.md)
233
+
225
234
  ## Recording existing violations
226
235
 
227
236
  For existing codebases, packages are likely to have existing boundary violations.
data/bin/m 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/rake 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/rubocop 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/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
@@ -21,7 +21,7 @@ module Packwerk
21
21
  sig { returns(T::Hash[String, Module]) }
22
22
  def extract_application_autoload_paths
23
23
  Rails.autoloaders.inject({}) do |h, loader|
24
- h.merge(loader.root_dirs)
24
+ h.merge(loader.dirs(namespaces: true))
25
25
  end
26
26
  end
27
27
 
@@ -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
@@ -50,8 +50,6 @@ module Packwerk
50
50
  case subcommand
51
51
  when "init"
52
52
  init
53
- when "generate_configs"
54
- generate_configs
55
53
  when "check"
56
54
  output_result(parse_run(args).check)
57
55
  when "detect-stale-violations"
@@ -61,20 +59,11 @@ module Packwerk
61
59
  when "validate"
62
60
  validate(args)
63
61
  when nil, "help"
64
- @err_out.puts(<<~USAGE)
65
- Usage: #{$PROGRAM_NAME} <subcommand>
66
-
67
- Subcommands:
68
- init - set up packwerk
69
- check - run all checks
70
- update - update deprecated references (deprecated, use update-deprecations instead)
71
- update-deprecations - update deprecated references
72
- validate - verify integrity of packwerk and package configuration
73
- help - display help information about packwerk
74
- USAGE
75
- true
62
+ usage
76
63
  else
77
- @err_out.puts("'#{subcommand}' is not a packwerk command. See `packwerk help`.")
64
+ @err_out.puts(
65
+ "'#{subcommand}' is not a packwerk command. See `packwerk help`."
66
+ )
78
67
  false
79
68
  end
80
69
  end
@@ -117,6 +106,21 @@ module Packwerk
117
106
  success
118
107
  end
119
108
 
109
+ sig { returns(T::Boolean) }
110
+ def usage
111
+ @err_out.puts(<<~USAGE)
112
+ Usage: #{$PROGRAM_NAME} <subcommand>
113
+
114
+ Subcommands:
115
+ init - set up packwerk
116
+ check - run all checks
117
+ update-deprecations - update deprecated_references.yml files
118
+ validate - verify integrity of packwerk and package configuration
119
+ help - display help information about packwerk
120
+ USAGE
121
+ true
122
+ end
123
+
120
124
  sig { params(result: Result).returns(T::Boolean) }
121
125
  def output_result(result)
122
126
  @out.puts
@@ -128,17 +132,17 @@ module Packwerk
128
132
  params(
129
133
  relative_file_paths: T::Array[String],
130
134
  ignore_nested_packages: T::Boolean
131
- ).returns(FilesForProcessing::AbsoluteFileSet)
135
+ ).returns(FilesForProcessing::RelativeFileSet)
132
136
  end
133
137
  def fetch_files_to_process(relative_file_paths, ignore_nested_packages)
134
- absolute_file_set = FilesForProcessing.fetch(
138
+ relative_file_set = FilesForProcessing.fetch(
135
139
  relative_file_paths: relative_file_paths,
136
140
  ignore_nested_packages: ignore_nested_packages,
137
141
  configuration: @configuration
138
142
  )
139
143
  abort("No files found or given. "\
140
- "Specify files or check the include and exclude glob in the config file.") if absolute_file_set.empty?
141
- absolute_file_set
144
+ "Specify files or check the include and exclude glob in the config file.") if relative_file_set.empty?
145
+ relative_file_set
142
146
  end
143
147
 
144
148
  sig { params(_paths: T::Array[String]).returns(T::Boolean) }
@@ -190,7 +194,7 @@ module Packwerk
190
194
  end
191
195
 
192
196
  ParseRun.new(
193
- absolute_file_set: fetch_files_to_process(relative_file_paths, ignore_nested_packages),
197
+ relative_file_set: fetch_files_to_process(relative_file_paths, ignore_nested_packages),
194
198
  configuration: @configuration,
195
199
  progress_formatter: @progress_formatter,
196
200
  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