packwerk 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) 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/TROUBLESHOOT.md +0 -22
  8. data/USAGE.md +141 -51
  9. data/dev.yml +1 -1
  10. data/exe/packwerk +1 -0
  11. data/gemfiles/Gemfile-rails-6-1 +1 -1
  12. data/lib/packwerk/application_validator.rb +54 -285
  13. data/lib/packwerk/association_inspector.rb +2 -0
  14. data/lib/packwerk/cache.rb +6 -5
  15. data/lib/packwerk/checker.rb +54 -0
  16. data/lib/packwerk/cli/result.rb +11 -0
  17. data/lib/packwerk/cli.rb +55 -40
  18. data/lib/packwerk/configuration.rb +61 -40
  19. data/lib/packwerk/const_node_inspector.rb +2 -0
  20. data/lib/packwerk/constant_context.rb +8 -0
  21. data/lib/packwerk/constant_discovery.rb +5 -6
  22. data/lib/packwerk/constant_name_inspector.rb +2 -0
  23. data/lib/packwerk/disable_sorbet.rb +41 -0
  24. data/lib/packwerk/extension_loader.rb +24 -0
  25. data/lib/packwerk/file_processor.rb +3 -1
  26. data/lib/packwerk/files_for_processing.rb +25 -12
  27. data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
  28. data/lib/packwerk/formatters/progress_formatter.rb +31 -12
  29. data/lib/packwerk/generators/configuration_file.rb +7 -2
  30. data/lib/packwerk/generators/root_package.rb +5 -1
  31. data/lib/packwerk/generators/templates/package.yml +0 -10
  32. data/lib/packwerk/graph.rb +10 -2
  33. data/lib/packwerk/node.rb +1 -1
  34. data/lib/packwerk/node_helpers.rb +14 -7
  35. data/lib/packwerk/node_processor.rb +2 -0
  36. data/lib/packwerk/node_processor_factory.rb +6 -4
  37. data/lib/packwerk/node_visitor.rb +10 -1
  38. data/lib/packwerk/offense_collection.rb +26 -18
  39. data/lib/packwerk/offenses_formatter.rb +59 -2
  40. data/lib/packwerk/package.rb +7 -35
  41. data/lib/packwerk/package_set.rb +1 -1
  42. data/lib/packwerk/package_todo.rb +15 -9
  43. data/lib/packwerk/parse_run.rb +27 -34
  44. data/lib/packwerk/parsed_constant_definitions.rb +28 -5
  45. data/lib/packwerk/parsers/erb.rb +23 -4
  46. data/lib/packwerk/parsers/factory.rb +11 -2
  47. data/lib/packwerk/parsers/parser_interface.rb +1 -1
  48. data/lib/packwerk/parsers/ruby.rb +13 -3
  49. data/lib/packwerk/parsers.rb +6 -2
  50. data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
  51. data/lib/packwerk/reference.rb +7 -1
  52. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
  53. data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
  54. data/lib/packwerk/reference_extractor.rb +24 -12
  55. data/lib/packwerk/reference_offense.rb +2 -2
  56. data/lib/packwerk/run_context.rb +7 -10
  57. data/lib/packwerk/spring_command.rb +11 -2
  58. data/lib/packwerk/unresolved_reference.rb +9 -1
  59. data/lib/packwerk/validator/result.rb +18 -0
  60. data/lib/packwerk/validator.rb +90 -0
  61. data/lib/packwerk/validators/dependency_validator.rb +154 -0
  62. data/lib/packwerk/version.rb +1 -1
  63. data/lib/packwerk.rb +64 -26
  64. data/packwerk.gemspec +4 -2
  65. data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
  66. data/sorbet/rbi/shims/minitest/test.rb +8 -0
  67. data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
  68. data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
  69. data/sorbet/rbi/shims/parser.rbi +13 -0
  70. metadata +34 -15
  71. data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
  72. data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
  73. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
  74. data/lib/packwerk/result.rb +0 -9
  75. data/lib/packwerk/sanity_checker.rb +0 -8
  76. data/lib/packwerk/violation_type.rb +0 -11
  77. data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
  78. 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: 94a6e793dd2c071a7bfb9a739fb6aaf2e07b0d2183bf743afb38da5e3847f0e2
4
- data.tar.gz: dd779094dad5572746476d25d63e26f29819cf0b57519f73a4f693610cfd238e
3
+ metadata.gz: 11206a06e6ac99c1fb8ef4cdefbe334133317d0599fce6480d4dfe10f694c747
4
+ data.tar.gz: 606778530b8a872d331e5f331b9c6e14cb37ff6e0a5e13247bb0aebe1ca9e0cc
5
5
  SHA512:
6
- metadata.gz: 9ff4d2a2aa7b6cb34e59362321ea3fb22826d10430addeef1b6143408d1a1b510ff8915105f2e74e9173334dfcfbd0a76610ea00242a2720c6feb9c75b53556f
7
- data.tar.gz: 7ed4cbe3376821d54a5a58e2bc25ac3baf1f03a84928e027c0e1e1f39d52a14df4e18cc8d9ecfa50de3756507f46c0e9687d04464d3ec149037a7fdecb8eed0e
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.3.0)
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
 
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
 
@@ -22,26 +20,6 @@ _Note: You cannot specify folders or packages for `bin/packwerk validate` becaus
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)
23
22
  * [Understanding the package todo file](#understanding-the-package-todo-file)
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?
@@ -239,19 +210,12 @@ If so, you will want to stop the bleeding and prevent more violations from occur
239
210
 
240
211
  bin/packwerk update-todo
241
212
 
242
- Similar to `bin/packwerk check`, you may also run `bin/packwerk update-todo` on folders or packages:
243
-
244
- bin/packwerk update-todo components/your_package
245
-
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
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
 
254
-
255
219
  ### Understanding the package todo file
256
220
 
257
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.
@@ -275,3 +239,129 @@ Above is an example of a constant violation entry in `package_todo.yml`.
275
239
  * `components/merchant/app/public/merchant/generate_order.rb` - path to the file containing the violated constant
276
240
 
277
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)