packwerk 2.2.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -5
  3. data/.ruby-version +1 -1
  4. data/Gemfile +0 -1
  5. data/Gemfile.lock +5 -95
  6. data/README.md +2 -7
  7. data/RESOLVING_VIOLATIONS.md +7 -7
  8. data/TROUBLESHOOT.md +1 -23
  9. data/USAGE.md +149 -59
  10. data/dev.yml +1 -1
  11. data/exe/packwerk +1 -0
  12. data/gemfiles/Gemfile-rails-6-1 +1 -1
  13. data/lib/packwerk/application_validator.rb +54 -285
  14. data/lib/packwerk/association_inspector.rb +2 -0
  15. data/lib/packwerk/cache.rb +6 -5
  16. data/lib/packwerk/checker.rb +54 -0
  17. data/lib/packwerk/cli/result.rb +11 -0
  18. data/lib/packwerk/cli.rb +56 -31
  19. data/lib/packwerk/configuration.rb +61 -40
  20. data/lib/packwerk/const_node_inspector.rb +2 -0
  21. data/lib/packwerk/constant_context.rb +8 -0
  22. data/lib/packwerk/constant_discovery.rb +5 -6
  23. data/lib/packwerk/constant_name_inspector.rb +2 -0
  24. data/lib/packwerk/disable_sorbet.rb +41 -0
  25. data/lib/packwerk/extension_loader.rb +24 -0
  26. data/lib/packwerk/file_processor.rb +3 -1
  27. data/lib/packwerk/files_for_processing.rb +25 -12
  28. data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
  29. data/lib/packwerk/formatters/progress_formatter.rb +31 -12
  30. data/lib/packwerk/generators/configuration_file.rb +7 -2
  31. data/lib/packwerk/generators/root_package.rb +5 -1
  32. data/lib/packwerk/generators/templates/package.yml +0 -10
  33. data/lib/packwerk/graph.rb +10 -2
  34. data/lib/packwerk/node.rb +1 -1
  35. data/lib/packwerk/node_helpers.rb +14 -7
  36. data/lib/packwerk/node_processor.rb +2 -0
  37. data/lib/packwerk/node_processor_factory.rb +6 -4
  38. data/lib/packwerk/node_visitor.rb +10 -1
  39. data/lib/packwerk/offense_collection.rb +43 -23
  40. data/lib/packwerk/offenses_formatter.rb +59 -2
  41. data/lib/packwerk/package.rb +7 -35
  42. data/lib/packwerk/package_set.rb +1 -1
  43. data/lib/packwerk/{deprecated_references.rb → package_todo.rb} +29 -13
  44. data/lib/packwerk/parse_run.rb +29 -36
  45. data/lib/packwerk/parsed_constant_definitions.rb +28 -5
  46. data/lib/packwerk/parsers/erb.rb +23 -4
  47. data/lib/packwerk/parsers/factory.rb +11 -2
  48. data/lib/packwerk/parsers/parser_interface.rb +1 -1
  49. data/lib/packwerk/parsers/ruby.rb +13 -3
  50. data/lib/packwerk/parsers.rb +6 -2
  51. data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
  52. data/lib/packwerk/reference.rb +7 -1
  53. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
  54. data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
  55. data/lib/packwerk/reference_extractor.rb +24 -12
  56. data/lib/packwerk/reference_offense.rb +2 -2
  57. data/lib/packwerk/run_context.rb +7 -10
  58. data/lib/packwerk/spring_command.rb +11 -2
  59. data/lib/packwerk/unresolved_reference.rb +9 -1
  60. data/lib/packwerk/validator/result.rb +18 -0
  61. data/lib/packwerk/validator.rb +90 -0
  62. data/lib/packwerk/validators/dependency_validator.rb +154 -0
  63. data/lib/packwerk/version.rb +1 -1
  64. data/lib/packwerk.rb +64 -26
  65. data/packwerk.gemspec +4 -2
  66. data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
  67. data/sorbet/rbi/shims/minitest/test.rb +8 -0
  68. data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
  69. data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
  70. data/sorbet/rbi/shims/parser.rbi +13 -0
  71. metadata +35 -16
  72. data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
  73. data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
  74. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
  75. data/lib/packwerk/result.rb +0 -9
  76. data/lib/packwerk/sanity_checker.rb +0 -8
  77. data/lib/packwerk/violation_type.rb +0 -11
  78. data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
  79. data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e1c2cdab1129d016428019597dc9f19dbf0836d0c545a13ce780486bf9fbba1
4
- data.tar.gz: d9556ef773c776283a30f8e22308f0068e4d1a4ea918e81af44a0e126c0ca62e
3
+ metadata.gz: 11206a06e6ac99c1fb8ef4cdefbe334133317d0599fce6480d4dfe10f694c747
4
+ data.tar.gz: 606778530b8a872d331e5f331b9c6e14cb37ff6e0a5e13247bb0aebe1ca9e0cc
5
5
  SHA512:
6
- metadata.gz: 74cb6f2c95a7d51dc8e5199a47601b53897c29cfd6dde5d0c0ada40566d19bf0f9d0f0fcf9209dbe5b9e0ccbbcfcac9c1282fa74377af0b8e6cee63e75204948
7
- data.tar.gz: d03e35ab0975d457a9202d2df2f697372f22f5a4072880dc7d308b87581ffd5a30446b8b235b221da0655cd4b31be58552b0a3f658e15a8c48d78cdee23f9794
6
+ metadata.gz: 66166a9c683483fda99341adc79c807c40f8190c349c8650b8646810e06d11f9b4728e2e938ef0abf640967d0764d78b66e956aba3bdaef830c4a15253711542
7
+ data.tar.gz: c1cce5df16246f1561cd0954abca4685e21adcaf52d3f6d164acd776f9466a0ad276a65b9a2f5cc9e358b058fcdc2cef265c2b3948043f9f3e35b1d4798ec5e9
@@ -1,6 +1,6 @@
1
1
  name: CI
2
2
 
3
- on: [push]
3
+ on: [push, pull_request]
4
4
 
5
5
  jobs:
6
6
  tests:
@@ -12,13 +12,10 @@ jobs:
12
12
  - gemfiles/Gemfile-rails-6-0
13
13
  - gemfiles/Gemfile-rails-6-1
14
14
  ruby:
15
- - 2.6
16
15
  - 2.7
17
16
  - 3.0
18
17
  - 3.1
19
- exclude:
20
- - ruby: 2.6
21
- gemfile: Gemfile
18
+ - 3.2
22
19
  env:
23
20
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
24
21
  name: "Tests: Ruby ${{ matrix.ruby }} ${{ matrix.gemfile }}"
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.0.0
1
+ 3.2.1
data/Gemfile CHANGED
@@ -8,7 +8,6 @@ gemspec
8
8
  # Specify the same dependency sources as the application Gemfile
9
9
 
10
10
  gem("spring")
11
- gem("rails")
12
11
  gem("constant_resolver", require: false)
13
12
  gem("rubocop-performance", require: false)
14
13
  gem("rubocop-sorbet", require: false)
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- packwerk (2.2.2)
5
- activesupport (>= 5.2)
4
+ packwerk (3.0.0)
5
+ activesupport (>= 6.0)
6
6
  ast
7
7
  better_html
8
8
  bundler
@@ -15,31 +15,6 @@ PATH
15
15
  GEM
16
16
  remote: https://rubygems.org/
17
17
  specs:
18
- actioncable (7.0.3.1)
19
- actionpack (= 7.0.3.1)
20
- activesupport (= 7.0.3.1)
21
- nio4r (~> 2.0)
22
- websocket-driver (>= 0.6.1)
23
- actionmailbox (7.0.3.1)
24
- actionpack (= 7.0.3.1)
25
- activejob (= 7.0.3.1)
26
- activerecord (= 7.0.3.1)
27
- activestorage (= 7.0.3.1)
28
- activesupport (= 7.0.3.1)
29
- mail (>= 2.7.1)
30
- net-imap
31
- net-pop
32
- net-smtp
33
- actionmailer (7.0.3.1)
34
- actionpack (= 7.0.3.1)
35
- actionview (= 7.0.3.1)
36
- activejob (= 7.0.3.1)
37
- activesupport (= 7.0.3.1)
38
- mail (~> 2.5, >= 2.5.4)
39
- net-imap
40
- net-pop
41
- net-smtp
42
- rails-dom-testing (~> 2.0)
43
18
  actionpack (7.0.3.1)
44
19
  actionview (= 7.0.3.1)
45
20
  activesupport (= 7.0.3.1)
@@ -47,34 +22,12 @@ GEM
47
22
  rack-test (>= 0.6.3)
48
23
  rails-dom-testing (~> 2.0)
49
24
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
50
- actiontext (7.0.3.1)
51
- actionpack (= 7.0.3.1)
52
- activerecord (= 7.0.3.1)
53
- activestorage (= 7.0.3.1)
54
- activesupport (= 7.0.3.1)
55
- globalid (>= 0.6.0)
56
- nokogiri (>= 1.8.5)
57
25
  actionview (7.0.3.1)
58
26
  activesupport (= 7.0.3.1)
59
27
  builder (~> 3.1)
60
28
  erubi (~> 1.4)
61
29
  rails-dom-testing (~> 2.0)
62
30
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
63
- activejob (7.0.3.1)
64
- activesupport (= 7.0.3.1)
65
- globalid (>= 0.3.6)
66
- activemodel (7.0.3.1)
67
- activesupport (= 7.0.3.1)
68
- activerecord (7.0.3.1)
69
- activemodel (= 7.0.3.1)
70
- activesupport (= 7.0.3.1)
71
- activestorage (7.0.3.1)
72
- actionpack (= 7.0.3.1)
73
- activejob (= 7.0.3.1)
74
- activerecord (= 7.0.3.1)
75
- activesupport (= 7.0.3.1)
76
- marcel (~> 1.0)
77
- mini_mime (>= 1.1.0)
78
31
  activesupport (7.0.3.1)
79
32
  concurrent-ruby (~> 1.0, >= 1.0.2)
80
33
  i18n (>= 1.6, < 2)
@@ -95,10 +48,7 @@ GEM
95
48
  constant_resolver (0.2.0)
96
49
  crass (1.0.6)
97
50
  diff-lcs (1.5.0)
98
- digest (3.1.0)
99
51
  erubi (1.11.0)
100
- globalid (1.0.0)
101
- activesupport (>= 5.0)
102
52
  i18n (1.12.0)
103
53
  concurrent-ruby (~> 1.0)
104
54
  json (2.6.2)
@@ -109,37 +59,16 @@ GEM
109
59
  m (1.6.0)
110
60
  method_source (>= 0.6.7)
111
61
  rake (>= 0.9.2.2)
112
- mail (2.7.1)
113
- mini_mime (>= 0.1.1)
114
- marcel (1.0.2)
115
62
  method_source (1.0.0)
116
- mini_mime (1.1.2)
117
63
  mini_portile2 (2.8.0)
118
64
  minitest (5.16.2)
119
65
  minitest-focus (1.3.1)
120
66
  minitest (>= 4, < 6)
121
67
  mocha (1.14.0)
122
- net-imap (0.2.3)
123
- digest
124
- net-protocol
125
- strscan
126
- net-pop (0.1.1)
127
- digest
128
- net-protocol
129
- timeout
130
- net-protocol (0.1.3)
131
- timeout
132
- net-smtp (0.3.1)
133
- digest
134
- net-protocol
135
- timeout
136
68
  netrc (0.11.0)
137
- nio4r (2.5.8)
138
69
  nokogiri (1.13.8)
139
70
  mini_portile2 (~> 2.8.0)
140
71
  racc (~> 1.4)
141
- nokogiri (1.13.8-x86_64-darwin)
142
- racc (~> 1.4)
143
72
  parallel (1.22.1)
144
73
  parser (3.1.2.1)
145
74
  ast (~> 2.4.1)
@@ -151,20 +80,6 @@ GEM
151
80
  rack (2.2.4)
152
81
  rack-test (2.0.2)
153
82
  rack (>= 1.3)
154
- rails (7.0.3.1)
155
- actioncable (= 7.0.3.1)
156
- actionmailbox (= 7.0.3.1)
157
- actionmailer (= 7.0.3.1)
158
- actionpack (= 7.0.3.1)
159
- actiontext (= 7.0.3.1)
160
- actionview (= 7.0.3.1)
161
- activejob (= 7.0.3.1)
162
- activemodel (= 7.0.3.1)
163
- activerecord (= 7.0.3.1)
164
- activestorage (= 7.0.3.1)
165
- activesupport (= 7.0.3.1)
166
- bundler (>= 1.15.0)
167
- railties (= 7.0.3.1)
168
83
  rails-dom-testing (2.0.3)
169
84
  activesupport (>= 4.2.0)
170
85
  nokogiri (>= 1.6)
@@ -232,7 +147,6 @@ GEM
232
147
  sorbet-runtime (>= 0.5.9204)
233
148
  thor (>= 0.19.2)
234
149
  spring (4.0.0)
235
- strscan (3.0.4)
236
150
  syntax_tree (3.3.0)
237
151
  prettier_print
238
152
  tapioca (0.9.2)
@@ -246,7 +160,6 @@ GEM
246
160
  thor (>= 1.2.0)
247
161
  yard-sorbet
248
162
  thor (1.2.1)
249
- timeout (0.3.0)
250
163
  tzinfo (2.0.5)
251
164
  concurrent-ruby (~> 1.0)
252
165
  unicode-display_width (2.2.0)
@@ -254,15 +167,12 @@ GEM
254
167
  diff-lcs (~> 1.3)
255
168
  parser (>= 3.1.0)
256
169
  webrick (1.7.0)
257
- websocket-driver (0.7.5)
258
- websocket-extensions (>= 0.1.0)
259
- websocket-extensions (0.1.5)
260
170
  yard (0.9.28)
261
171
  webrick (~> 1.7.0)
262
172
  yard-sorbet (0.6.1)
263
173
  sorbet-runtime (>= 0.5)
264
174
  yard (>= 0.9)
265
- zeitwerk (2.6.6)
175
+ zeitwerk (2.6.4)
266
176
 
267
177
  PLATFORMS
268
178
  ruby
@@ -276,7 +186,7 @@ DEPENDENCIES
276
186
  minitest-focus
277
187
  mocha
278
188
  packwerk!
279
- rails
189
+ railties
280
190
  rake
281
191
  rubocop-performance
282
192
  rubocop-shopify
@@ -288,4 +198,4 @@ DEPENDENCIES
288
198
  zeitwerk
289
199
 
290
200
  BUNDLED WITH
291
- 2.3.5
201
+ 2.4.7
data/README.md CHANGED
@@ -1,10 +1,5 @@
1
1
  # Packwerk [![Build Status](https://github.com/Shopify/packwerk/workflows/CI/badge.svg)](https://github.com/Shopify/packwerk/actions?query=workflow%3ACI)
2
2
 
3
- ### ⚠️ While Shopify is actively using `packwerk`, we consider it feature complete.
4
- We are keeping `packwerk` compatible with current versions of Ruby and Rails, but will accept feature requests only in rare cases. Please submit bug fixes though!
5
-
6
- ---
7
-
8
3
  > "I know who you are and because of that I know what you do."
9
4
  > This knowledge is a dependency that raises the cost of change.
10
5
 
@@ -15,14 +10,13 @@ Packwerk is a Ruby gem used to enforce boundaries and modularize Rails applicati
15
10
  Packwerk can be used to:
16
11
  * Combine groups of files into packages
17
12
  * Define package-level constant visibility (i.e. have publicly accessible constants)
18
- * Enforce privacy (inbound) and dependency (outbound) boundaries between packages
19
13
  * Help existing codebases to become more modular without obstructing development
20
14
 
21
15
  ## Prerequisites
22
16
 
23
17
  Packwerk needs [Zeitwerk](https://github.com/fxn/zeitwerk) enabled, which comes with Rails 6.
24
18
 
25
- Packwerk supports MRI versions 2.6 and above.
19
+ Packwerk supports MRI versions 2.7 and above.
26
20
 
27
21
  ## Demo
28
22
 
@@ -67,6 +61,7 @@ Various third parties have built tooling on top of packwerk. Here's a selection
67
61
  - https://github.com/Gusto/packwerk-vscode integrates packwerk into Visual Studio Code so you can see violations right in your editor
68
62
  - 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
63
  - https://github.com/rubyatscale/danger-packwerk integrates packwerk with [danger.systems](https://danger.systems) to provide packwerk feedback as Github inline PR comments
64
+ - https://github.com/rubyatscale/packwerk-extensions contains extensions for packwerk, including a checker for packwerk that allows you to enforce public API boundaries. This was originally extracted from `packwerk` itself.
70
65
 
71
66
  ## Development
72
67
 
@@ -3,27 +3,27 @@
3
3
  Violations can be [recorded as a deprecation](#recording-violations) or (better!) [eliminated](#eliminating-violations).
4
4
 
5
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.
6
+ 💡 New privacy and dependency violations are never hard-blocked. There are many very valid reasons to run `bin/packwerk update-todo`, adding new violations to `package_todo.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
7
 
8
8
  ### Emergency Fixes
9
9
  ❔ Is it a revert or is there a lot of urgency because you are fixing a production bug impacting customers?
10
10
 
11
- ➡️ Simply run `bin/packwerk update-deprecations`, and address the violation when the customer issue is resolved.
11
+ ➡️ Simply run `bin/packwerk update-todo`, and address the violation when the customer issue is resolved.
12
12
 
13
13
  ### Improving System Design
14
14
  ❔ Are you improving system boundaries by renaming or moving a file, class, constant,` or module?
15
15
 
16
- ➡️ Simply run `bin/packwerk update-deprecations`. We've improved how our system is organized, so the new violations are natural.
16
+ ➡️ Simply run `bin/packwerk update-todo`. We've improved how our system is organized, so the new violations are natural.
17
17
 
18
18
  ### Making Things Explicit
19
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
20
 
21
- ➡️ Simply run `bin/packwerk update-deprecations`. Making something implicit explicit and capturing that as a new violation is a strict improvement.
21
+ ➡️ Simply run `bin/packwerk update-todo`. Making something implicit explicit and capturing that as a new violation is a strict improvement.
22
22
 
23
23
  ### Temporary State
24
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
25
 
26
- ➡️ Simply run `bin/packwerk update-deprecations`. Sometimes things get "worse" before they get better.
26
+ ➡️ Simply run `bin/packwerk update-todo`. Sometimes things get "worse" before they get better.
27
27
 
28
28
  ### Delivering Features
29
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?
@@ -57,7 +57,7 @@ An explicit and implementation-hiding public API is a cornerstone of well-modula
57
57
 
58
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
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.
60
+ ⛈️ If working with the package's owner to improve the API is not possible, run `bin/packwerk update-todo`. Add some context to your PR about why it's not possible.
61
61
 
62
62
  ### Dependency Violations
63
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.
@@ -78,4 +78,4 @@ Thoughtful dependency management is another cornerstone of well-modularized code
78
78
 
79
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
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.
81
+ ⛈️ If this is not possible within the scope of your changes (think hard about this one!), run `bin/packwerk update-todo`. 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/TROUBLESHOOT.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  * [Troubleshooting violations](#Troubleshooting-violations)
4
4
  * [Feedback loop](#Feedback-loop)
5
- * [Package Privacy violation](#Package-Privacy-violation)
6
- * [Interpreting Privacy violation](#Interpreting-Privacy-violation)
7
5
  * [Package Dependency violation](#Package-Dependency-violation)
8
6
  * [Interpreting Dependency violation](#Interpreting-Dependency-violation)
9
7
 
@@ -16,32 +14,12 @@ You can specify folders or packages in Packwerk commands for a shorter run time:
16
14
 
17
15
  bin/packwerk check components/your_package
18
16
 
19
- bin/packwerk update-deprecations components/your_package
17
+ bin/packwerk update-todo components/your_package
20
18
 
21
19
  _Note: You cannot specify folders or packages for `bin/packwerk validate` because the command runs for the entire application._
22
20
 
23
21
  ![](static/packwerk_check_violation.gif)
24
22
 
25
- ### Package Privacy violation
26
- A constant that is private to its package has been referenced from outside of the package. Constants are declared private in their package’s `package.yml`.
27
-
28
- See: [USAGE.md - Enforcing privacy boundary](USAGE.md#Enforcing-privacy-boundary)
29
-
30
- #### Interpreting Privacy violation
31
-
32
- > /Users/JaneDoe/src/github.com/sample-project/user/app/controllers/labels_controller.rb:170:30
33
- > Privacy violation: '::Billing::CarrierInvoiceTransaction' is private to 'billing' but referenced from 'user'.
34
- > Is there a public entrypoint in 'billing/app/public/' that you can use instead?
35
- >
36
- > Inference details: 'Billing::CarrierInvoiceTransaction' refers to ::Billing::CarrierInvoiceTransaction which seems to be defined in billing/app/models/billing/carrier_invoice_transaction.rb.
37
-
38
- There has been a privacy violation of the package `billing` in the package `user`, through the use of the constant `Billing::CarrierInvoiceTransaction` in the file `user/app/controllers/labels_controller.rb`.
39
-
40
- ##### Suggestions
41
- You may be accessing the implementation of a piece of functionality that is supposed to be accessed through a public interface on the package. Try to use the public interface instead. A package’s public interface should be defined in its `app/public` folder and documented.
42
-
43
- The functionality you’re looking for may not be intended to be reused across packages at all. If there is no public interface for it but you have a good reason to use it from outside of its package, find the people responsible for the package and discuss a solution with them.
44
-
45
23
  ### Package Dependency violation
46
24
  A constant defined in a package A is referenced from a package B that doesn’t define a dependency on A. Packages define their dependencies in their `package.yml`.
47
25
 
data/USAGE.md CHANGED
@@ -13,14 +13,15 @@
13
13
  * [Defining packages](#defining-packages)
14
14
  * [Package metadata](#package-metadata)
15
15
  * [Types of boundary checks](#types-of-boundary-checks)
16
- * [Enforcing privacy boundary](#enforcing-privacy-boundary)
17
- * [Using public folders](#using-public-folders)
18
16
  * [Enforcing dependency boundary](#enforcing-dependency-boundary)
17
+ * [Using strict mode](#using-strict-mode)
19
18
  * [Checking for violations](#checking-for-violations)
20
19
  * [Resolving new violations](#resolving-new-violations)
21
20
  * [Understanding how to respond to new violations](#understanding-how-to-respond-to-new-violations)
22
21
  * [Recording existing violations](#recording-existing-violations)
22
+ * [Understanding the package todo file](#understanding-the-package-todo-file)
23
23
  * [Understanding the list of deprecated references](#understanding-the-list-of-deprecated-references)
24
+ * [Loading extensions](#loading-extensions)
24
25
 
25
26
  ## What problem does Packwerk solve?
26
27
 
@@ -143,50 +144,10 @@ Example:
143
144
 
144
145
  ## Types of boundary checks
145
146
 
146
- Packwerk can perform two types of boundary checks: privacy and dependency.
147
-
148
- #### Enforcing privacy boundary
149
-
150
- A package's privacy boundary is violated when there is a reference to the package's private constants from a source outside the package.
151
-
152
- There are two ways you can enforce privacy for your package:
153
-
154
- 1. Enforce privacy for all external sources
155
-
156
- ```yaml
157
- # components/merchandising/package.yml
158
- enforce_privacy: true # will make everything private that is not in
159
- # the components/merchandising/app/public folder
160
- ```
161
-
162
- Setting `enforce_privacy` to true will make all references to private constants in your package a violation.
163
-
164
- 2. Enforce privacy for specific constants
165
-
166
- ```yaml
167
- # components/merchandising/package.yml
168
- enforce_privacy:
169
- - "::Merchandising::Product"
170
- - "::SomeNamespace" # enforces privacy for the namespace and
171
- # everything nested in it
172
- ```
173
-
174
- It will be a privacy violation when a file outside of the `components/merchandising` package tries to reference `Merchandising::Product`.
175
-
176
- ##### Using public folders
177
- You may enforce privacy either way mentioned above and still expose a public API for your package by placing constants in the public folder, which by default is `app/public`. The constants in the public folder will be made available for use by the rest of the application.
178
-
179
- ##### Defining your own public folder
180
-
181
- You may prefer to override the default public folder, you can do so on a per-package basis by defining a `public_path`.
182
-
183
- Example:
184
-
185
- ```yaml
186
- public_path: my/custom/path/
187
- ```
147
+ Packwerk ships with dependency boundary checking only. See [`packwerk-extensions`](https://github.com/rubyatscale/packwerk-extensions) to incorporate privacy checks into your use of `packwerk`.
188
148
 
189
149
  #### Enforcing dependency boundary
150
+
190
151
  A package's dependency boundary is violated whenever it references a constant in some package that has not been declared as a dependency.
191
152
 
192
153
  Specify `enforce_dependencies: true` to start enforcing the dependencies of a package. The intentional dependencies of the package are specified as a list under a `dependencies:` key.
@@ -202,6 +163,15 @@ dependencies:
202
163
 
203
164
  It will be a dependency violation when `components/shop_identity` tries to reference a constant that is not within `components/platform` or itself.
204
165
 
166
+ #### Using strict mode
167
+
168
+ Once there are no more violations in a package, you can turn on `strict` mode, which will prevent new violations from being added to the package's `package_todo.yml`. To use this, simply change `enforce_dependencies: true` to `enforce_dependencies: strict` in your `package.yml`.
169
+
170
+ Then, when you run `bin/packwerk check`, new violations will cause the following error to be displayed:
171
+ ```
172
+ packs/referencing_package cannot have dependency violations on packs/defining_package because strict mode is enabled for dependency violations in packs/referencing_package/package.yml
173
+ ```
174
+
205
175
  ## Checking for violations
206
176
 
207
177
  After enforcing the boundary checks for a package, you may execute:
@@ -225,6 +195,7 @@ In order to keep the package system valid at each version of the application, we
225
195
  See: [TROUBLESHOOT.md - Sample violations](TROUBLESHOOT.md#Sample-violations)
226
196
 
227
197
  ## Resolving new violations
198
+
228
199
  ### Understanding how to respond to new violations
229
200
 
230
201
  When you have a new dependency or privacy violation, what do you do?
@@ -235,28 +206,21 @@ See: [RESOLVING_VIOLATIONS.md](RESOLVING_VIOLATIONS.md)
235
206
 
236
207
  For existing codebases, packages are likely to have existing boundary violations.
237
208
 
238
- If so, you will want to stop the bleeding and prevent more violations from occuring. The existing violations in the codebase can be recorded in a [deprecated references list](#Understanding_the_list_of_deprecated_references) by executing:
239
-
240
- bin/packwerk update-deprecations
241
-
242
- Similar to `bin/packwerk check`, you may also run `bin/packwerk update-deprecations` on folders or packages:
209
+ If so, you will want to stop the bleeding and prevent more violations from occuring. The existing violations in the codebase can be recorded in a [todo list](#understanding-the-package-todo-file) by executing:
243
210
 
244
- bin/packwerk update-deprecations components/your_package
211
+ bin/packwerk update-todo
245
212
 
246
213
  ![](static/packwerk_update.gif)
247
214
 
248
- _Note: Changing dependencies or enabling dependencies will not require a full update of the codebase, only the package that changed. On the other hand, changing or enabling privacy will require a full update of the codebase._
249
-
250
- `bin/packwerk update-deprecations` should only be run to record existing violations and to remove deprecated references that have been worked off. Running `bin/packwerk update-deprecations` to resolve a violation should be the very last resort.
215
+ `bin/packwerk update-todo` should only be run to record existing violations and to remove violations that have been worked off. Running `bin/packwerk update-todo` to resolve a violation should be the very last resort.
251
216
 
252
217
  See: [TROUBLESHOOT.md - Troubleshooting violations](TROUBLESHOOT.md#Troubleshooting_violations)
253
218
 
219
+ ### Understanding the package todo file
254
220
 
255
- ### Understanding the list of deprecated references
256
-
257
- The deprecated references list is called `deprecated_references.yml` and can be found in the package folder. The list outlines the constant violations of the package, where the violation is located, and the file defining the violation.
221
+ The package TODO list is called `package_todo.yml` and can be found in the package folder. The list outlines the constant violations of the package, where the violation is located, and the file defining the violation.
258
222
 
259
- The deprecated references list should not be added to, but worked off over time.
223
+ The package TODO list should not be added to, but worked off over time.
260
224
 
261
225
  ```yaml
262
226
  components/merchant:
@@ -267,11 +231,137 @@ components/merchant:
267
231
  - components/merchant/app/public/merchant/generate_order.rb
268
232
  ```
269
233
 
270
- Above is an example of a constant violation entry in `deprecated_references.yml`.
234
+ Above is an example of a constant violation entry in `package_todo.yml`.
271
235
 
272
236
  * `components/merchant` - package where the constant violation is found
273
237
  * `::Checkouts::Core::CheckoutId` - violated constant in question
274
238
  * `dependency` - type of violation, either dependency or privacy
275
239
  * `components/merchant/app/public/merchant/generate_order.rb` - path to the file containing the violated constant
276
240
 
277
- Violations exist within the package that makes a violating reference. This means privacy violations of your package can be found listed in `deprecated_references.yml` files in the packages with the reference to a private constant.
241
+ Violations exist within the package that makes a violating reference. This means privacy violations of your package can be found listed in `package_todo.yml` files in the packages with the reference to a private constant.
242
+
243
+ # Loading Extensions
244
+
245
+ You can optionally specify ruby files that you'd like to be loaded with `packwerk` by specifying a `require` directive in `packwerk.yml`:
246
+ ```yml
247
+ require:
248
+ - ./path/to/file.rb
249
+ - my_gem
250
+ ```
251
+
252
+ `packwerk` will directly call `require` with these paths.
253
+ You can prefix local files with a dot to define them relative to `packwerk.yml`, or you can use absolute paths.
254
+ You can also reference the name of a gem.
255
+
256
+ ## Examples
257
+
258
+ ### Custom Offense Formatter
259
+
260
+ While `packwerk` ships with its own offense formatter, you may specify a custom one in your configuration file via the `offenses_formatter:` key. Your custom formatter will be used when `bin/packwerk check` is run.
261
+
262
+ Firstly, you'll need to create an `OffensesFormatter` class that includes `Packwerk::OffensesFormatter`. You can use [`Packwerk::Formatters::OffensesFormatter`](lib/packwerk/formatters/offenses_formatter.rb) as a point of reference for this. Then, in the `require` directive described above, you'll want to tell `packwerk` about it:
263
+ ```ruby
264
+ # ./path/to/file.rb
265
+ class MyOffensesFormatter
266
+ include Packwerk::OffensesFormatter
267
+ # implement the `OffensesFormatter` interface
268
+
269
+ def identifier
270
+ 'my_offenses_formatter'
271
+ end
272
+ end
273
+ ```
274
+
275
+ Then in `packwerk.yml`, you can set the `formatter` to the identifier for your class:
276
+ ```yml
277
+ offenses_formatter: my_offenses_formatter
278
+ ```
279
+
280
+ You can also pass in a formatter on the command line:
281
+ ```
282
+ bin/packwerk check --offenses-formatter=my_offenses_formatter
283
+ ```
284
+
285
+ ### Custom Checkers
286
+
287
+ Packwerk ships with a way to analyze dependencies and also supports custom checkers, such as the privacy checker listed below.
288
+
289
+ Custom checkers will allow references to constants to be analyzed in new ways, and for those invalid references to show up as violations in `package_todo.yml`.
290
+
291
+ To create a custom checker, you'll first need to create a checker class that includes `Packwerk::Checker`. You can use [`Packwerk::ReferenceChecking::Checkers::DependencyChecker`](lib/packwerk/reference_checking/checkers/dependency_checker.rb) as a point of reference for this. Here is an example:
292
+
293
+ ```ruby
294
+ # ./path/to/file.rb
295
+ class MyChecker
296
+ include Packwerk::Checker
297
+ # implement the `Checker` interface
298
+
299
+ sig { override.returns(String) }
300
+ def violation_type
301
+ 'my_custom_violation_type'
302
+ end
303
+
304
+ sig { override.params(listed_offense: ReferenceOffense).returns(T::Boolean) }
305
+ def strict_mode_violation?(listed_offense)
306
+ # This will allow "strict mode" to be supported in your checker
307
+ referencing_package = listed_offense.reference.package
308
+ referencing_package.config["enforce_custom"] == "strict"
309
+ end
310
+
311
+ sig { override.params(reference: Reference).returns(T::Boolean) }
312
+ def invalid_reference?(reference)
313
+ # your logic here
314
+ end
315
+
316
+ sig { override.params(reference: Reference).returns(String) }
317
+ def message(reference)
318
+ # your message here
319
+ end
320
+ end
321
+ ```
322
+
323
+ Then, in the `require` directive described above, you'll want to tell `packwerk` about it:
324
+
325
+ ```yml
326
+ require:
327
+ - ./path/to/file.rb
328
+ ```
329
+
330
+ #### Privacy Checker
331
+
332
+ [`packwerk-extensions`](https://github.com/rubyatscale/packwerk-extensions) (originally extracted from `packwerk`) can be used to help define and enforce public API boundaries of a package. See the README.md for more details. To use this, add it to your `Gemfile` and then require it via `packwerk.yml`:
333
+ ```yml
334
+ require:
335
+ - packwerk-extensions
336
+ ```
337
+
338
+ ### Custom Validators
339
+
340
+ Similar to checkers, you can define your own validator to be executed when `bin/packwerk validate` is invoked. This can be used to support your custom checker (by specifying permitted keys) or to provide any other validations you want to impose on packages.
341
+
342
+ To create a custom validator, you'll first need to create a validator class that includes `Packwerk::Validator`. You can use [`Packwerk::Validators::DependencyValidator`](lib/packwerk/validators/dependency_validator.rb) as a point of reference for this. Here is an example:
343
+
344
+ ```ruby
345
+ # ./path/to/file.rb
346
+ class MyValidator
347
+ include Packwerk::Validator
348
+ # implement the `Validator` interface
349
+
350
+ sig { override.returns(T::Array[String]) }
351
+ def permitted_keys
352
+ ['enforce_my_custom_checker']
353
+ end
354
+
355
+ sig { override.params(package_set: PackageSet, configuration: Configuration).returns(ApplicationValidator::Result) }
356
+ def call(package_set, configuration)
357
+ # your logic here
358
+ end
359
+ end
360
+ ```
361
+
362
+ Then, in the `require` directive described above, you'll want to tell `packwerk` about it:
363
+
364
+ ```yml
365
+ require:
366
+ - ./path/to/file.rb
367
+ ```
data/dev.yml CHANGED
@@ -3,7 +3,7 @@ name: packwerk
3
3
  type: ruby
4
4
 
5
5
  up:
6
- - ruby: 3.0.0
6
+ - ruby: 3.2.1
7
7
  - bundler
8
8
 
9
9
  commands:
data/exe/packwerk CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "packwerk/disable_sorbet"
4
5
  require "packwerk"
5
6
 
6
7
  # Needs to be run in test environment in order to have test helper paths available in the autoload paths
@@ -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.1.0")
10
+ gem("rails", "~> 6.1")
11
11
  gem("constant_resolver", require: false)
12
12
  gem("sorbet-runtime", require: false)
13
13
  gem("rubocop-performance", require: false)