packwerk 2.0.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +26 -22
  3. data/README.md +13 -1
  4. data/USAGE.md +7 -0
  5. data/lib/packwerk/application_load_paths.rb +12 -18
  6. data/lib/packwerk/application_validator.rb +88 -40
  7. data/lib/packwerk/cache.rb +169 -0
  8. data/lib/packwerk/cli.rb +29 -13
  9. data/lib/packwerk/configuration.rb +17 -12
  10. data/lib/packwerk/constant_discovery.rb +20 -4
  11. data/lib/packwerk/constant_name_inspector.rb +1 -1
  12. data/lib/packwerk/deprecated_references.rb +1 -1
  13. data/lib/packwerk/file_processor.rb +43 -22
  14. data/lib/packwerk/files_for_processing.rb +55 -26
  15. data/lib/packwerk/generators/templates/packwerk.yml.erb +6 -0
  16. data/lib/packwerk/node.rb +2 -1
  17. data/lib/packwerk/node_processor.rb +6 -6
  18. data/lib/packwerk/node_processor_factory.rb +3 -4
  19. data/lib/packwerk/node_visitor.rb +3 -0
  20. data/lib/packwerk/offense.rb +10 -2
  21. data/lib/packwerk/package.rb +1 -1
  22. data/lib/packwerk/package_set.rb +4 -3
  23. data/lib/packwerk/parse_run.rb +37 -17
  24. data/lib/packwerk/parsed_constant_definitions.rb +4 -4
  25. data/lib/packwerk/parsers/erb.rb +2 -0
  26. data/lib/packwerk/parsers/factory.rb +2 -0
  27. data/lib/packwerk/parsers/parser_interface.rb +19 -0
  28. data/lib/packwerk/parsers/ruby.rb +2 -0
  29. data/lib/packwerk/parsers.rb +1 -0
  30. data/lib/packwerk/reference_checking/checkers/checker.rb +1 -1
  31. data/lib/packwerk/reference_checking/reference_checker.rb +3 -4
  32. data/lib/packwerk/reference_extractor.rb +72 -20
  33. data/lib/packwerk/reference_offense.rb +8 -3
  34. data/lib/packwerk/result.rb +2 -2
  35. data/lib/packwerk/run_context.rb +62 -36
  36. data/lib/packwerk/spring_command.rb +1 -1
  37. data/lib/packwerk/unresolved_reference.rb +10 -0
  38. data/lib/packwerk/version.rb +1 -1
  39. data/lib/packwerk.rb +2 -0
  40. data/packwerk.gemspec +4 -2
  41. data/sorbet/config +1 -0
  42. data/sorbet/rbi/gems/tapioca@0.4.19.rbi +1 -1
  43. data/sorbet/tapioca/require.rb +1 -1
  44. metadata +36 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a473db2c54adea132ca7e61a51072b570d7e37b7d63c021259eef3cc7625177b
4
- data.tar.gz: 1b8486e61969621fac562d2fb6ebe47dcf6f71fcd13f6d18524337a3ed831ac2
3
+ metadata.gz: dfc6290decd1cdaa95140a3d8495fb324c3c6d3cf01b4794e40068baff8eb663
4
+ data.tar.gz: cb01a5adb6f0118473a635b3d38416a4287207651262ee8a20b0f7833d8bfa37
5
5
  SHA512:
6
- metadata.gz: 8c05f0ce6e8b398a87e98453c721d00707628918ce8455600c1cb00fb2cb9c68efc6bea373323e889d36fc3658ff45db3508b0e6386b876da58ca50cdb53ab9f
7
- data.tar.gz: 51586b68f489ff1f32df06338d05a64cab753da56c11575b3c5c8fa55019e9a8527e0a20c54c8695e1432307227d26c10f01b0d6c610e41c55d84296be000bd8
6
+ metadata.gz: 85e01c90d7d35d169f773b4a9dd281b0cf563c5586d68e7592317d5747da81aba396b0330a9d77e776c01d098353fbc27c08d80f314ff6db88e5bf3117d06b41
7
+ data.tar.gz: 650a4a5852353ed999c54159c7951905c3984e1d21a17c99e51c94462733df2f20f55931b76ecab2590e179c41756da862df170263d7c6077e5976465e802fe9
data/Gemfile.lock CHANGED
@@ -87,15 +87,16 @@ GIT
87
87
  PATH
88
88
  remote: .
89
89
  specs:
90
- packwerk (2.0.0)
90
+ packwerk (2.2.0)
91
91
  activesupport (>= 5.2)
92
92
  ast
93
93
  better_html
94
94
  bundler
95
- constant_resolver
95
+ constant_resolver (>= 0.2.0)
96
+ digest
96
97
  parallel
97
98
  parser
98
- sorbet-runtime
99
+ sorbet-runtime (>= 0.5.9914)
99
100
 
100
101
  GEM
101
102
  remote: https://rubygems.org/
@@ -116,8 +117,9 @@ GEM
116
117
  commander (4.5.2)
117
118
  highline (~> 2.0.0)
118
119
  concurrent-ruby (1.1.8)
119
- constant_resolver (0.1.5)
120
+ constant_resolver (0.2.0)
120
121
  crass (1.0.6)
122
+ digest (3.1.0)
121
123
  erubi (1.10.0)
122
124
  globalid (0.4.2)
123
125
  activesupport (>= 4.2.0)
@@ -136,16 +138,16 @@ GEM
136
138
  marcel (1.0.0)
137
139
  method_source (1.0.0)
138
140
  mini_mime (1.0.3)
139
- mini_portile2 (2.6.1)
141
+ mini_portile2 (2.8.0)
140
142
  minitest (5.14.4)
141
143
  minitest-focus (1.2.1)
142
144
  minitest (>= 4, < 6)
143
145
  mocha (1.12.0)
144
146
  nio4r (2.5.7)
145
- nokogiri (1.12.5)
146
- mini_portile2 (~> 2.6.1)
147
+ nokogiri (1.13.4)
148
+ mini_portile2 (~> 2.8.0)
147
149
  racc (~> 1.4)
148
- nokogiri (1.12.5-x86_64-darwin)
150
+ nokogiri (1.13.4-x86_64-darwin)
149
151
  racc (~> 1.4)
150
152
  parallel (1.20.1)
151
153
  parlour (6.0.0)
@@ -159,7 +161,7 @@ GEM
159
161
  coderay (~> 1.1)
160
162
  method_source (~> 1.0)
161
163
  psych (3.3.2)
162
- racc (1.5.2)
164
+ racc (1.6.0)
163
165
  rack (2.2.3)
164
166
  rack-test (1.1.0)
165
167
  rack (>= 1.0, < 3)
@@ -191,18 +193,19 @@ GEM
191
193
  rubocop-sorbet (0.6.1)
192
194
  rubocop
193
195
  ruby-progressbar (1.11.0)
194
- smart_properties (1.16.3)
195
- sorbet (0.5.6360)
196
- sorbet-static (= 0.5.6360)
197
- sorbet-runtime (0.5.6360)
198
- sorbet-static (0.5.6360-universal-darwin-14)
199
- sorbet-static (0.5.6360-universal-darwin-15)
200
- sorbet-static (0.5.6360-universal-darwin-16)
201
- sorbet-static (0.5.6360-universal-darwin-17)
202
- sorbet-static (0.5.6360-universal-darwin-18)
203
- sorbet-static (0.5.6360-universal-darwin-19)
204
- sorbet-static (0.5.6360-universal-darwin-20)
205
- sorbet-static (0.5.6360-x86_64-linux)
196
+ smart_properties (1.17.0)
197
+ sorbet (0.5.9914)
198
+ sorbet-static (= 0.5.9914)
199
+ sorbet-runtime (0.5.9914)
200
+ sorbet-static (0.5.9914-universal-darwin-14)
201
+ sorbet-static (0.5.9914-universal-darwin-15)
202
+ sorbet-static (0.5.9914-universal-darwin-16)
203
+ sorbet-static (0.5.9914-universal-darwin-17)
204
+ sorbet-static (0.5.9914-universal-darwin-18)
205
+ sorbet-static (0.5.9914-universal-darwin-19)
206
+ sorbet-static (0.5.9914-universal-darwin-20)
207
+ sorbet-static (0.5.9914-universal-darwin-21)
208
+ sorbet-static (0.5.9914-x86_64-linux)
206
209
  spoom (1.1.0)
207
210
  colorize
208
211
  sorbet (>= 0.5.6347)
@@ -255,6 +258,7 @@ DEPENDENCIES
255
258
  sorbet-runtime
256
259
  spring
257
260
  tapioca
261
+ zeitwerk
258
262
 
259
263
  BUNDLED WITH
260
- 2.2.22
264
+ 2.3.5
data/README.md CHANGED
@@ -1,6 +1,9 @@
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
- ## NOTE: Packwerk is considered to be feature-complete for Shopify's uses. We are currently accepting bug fixes only, and it is not being actively developed. Please fork this project if you are interested in adding new features.
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
+ ---
4
7
 
5
8
  > "I know who you are and because of that I know what you do."
6
9
  > This knowledge is a dependency that raises the cost of change.
@@ -56,6 +59,15 @@ Read [USAGE.md](USAGE.md) for usage once Packwerk is installed on your project.
56
59
 
57
60
  "Packwerk" is pronounced [[ˈpakvɛʁk]](https://cdn.shopify.com/s/files/1/0258/7469/files/packwerk.mp3).
58
61
 
62
+ ## Ecosystem
63
+
64
+ Various third parties have built tooling on top of packwerk. Here's a selection of some that might prove useful:
65
+
66
+ - https://github.com/bellroy/graphwerk draws a graph of your package dependencies
67
+ - https://github.com/Gusto/packwerk-vscode integrates packwerk into Visual Studio Code so you can see violations right in your editor
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
70
+
59
71
  ## Development
60
72
 
61
73
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/USAGE.md CHANGED
@@ -77,6 +77,8 @@ Packwerk reads from the `packwerk.yml` configuration file in the root directory.
77
77
  | package_paths | **/ | a single pattern or a list of patterns to find package configuration files, see: [Defining packages](#Defining-packages) |
78
78
  | custom_associations | N/A | list of custom associations, if any |
79
79
  | parallel | true | when true, fork code parsing out to subprocesses |
80
+ | cache | false | when true, caches the results of parsing files |
81
+ | cache_directory | tmp/cache/packwerk | the directory that will hold the packwerk cache |
80
82
 
81
83
  ### Using a custom ERB parser
82
84
 
@@ -100,6 +102,11 @@ end
100
102
  Packwerk::Parsers::Factory.instance.erb_parser_class = CustomParser
101
103
  ```
102
104
 
105
+ ## Using the cache
106
+ Packwerk ships with an cache to help speed up file parsing. You can turn this on by setting `cache: true` in `packwerk.yml`.
107
+
108
+ This will write to `tmp/cache/packwerk`.
109
+
103
110
  ## Validating the package system
104
111
 
105
112
  There are some criteria that an application must meet in order to have a valid package system. These criteria include having a valid autoload path cache, package definition files, and application folder structure. The dependency graph within the package system also has to be acyclic.
@@ -9,7 +9,7 @@ module Packwerk
9
9
  class << self
10
10
  extend T::Sig
11
11
 
12
- sig { params(root: String, environment: String).returns(T::Array[String]) }
12
+ sig { params(root: String, environment: String).returns(T::Hash[String, Module]) }
13
13
  def extract_relevant_paths(root, environment)
14
14
  require_application(root, environment)
15
15
  all_paths = extract_application_autoload_paths
@@ -18,36 +18,30 @@ module Packwerk
18
18
  relative_path_strings(relevant_paths)
19
19
  end
20
20
 
21
- sig { returns(T::Array[String]) }
21
+ sig { returns(T::Hash[String, Module]) }
22
22
  def extract_application_autoload_paths
23
- Rails.application.railties
24
- .select { |railtie| railtie.is_a?(Rails::Engine) }
25
- .push(Rails.application)
26
- .flat_map do |engine|
27
- paths = (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths)
28
- paths.map(&:to_s).uniq
29
- end
23
+ Rails.autoloaders.inject({}) do |h, loader|
24
+ h.merge(loader.root_dirs)
25
+ end
30
26
  end
31
27
 
32
28
  sig do
33
- params(all_paths: T::Array[String], bundle_path: Pathname, rails_root: Pathname)
34
- .returns(T::Array[Pathname])
29
+ params(all_paths: T::Hash[String, Module], bundle_path: Pathname, rails_root: Pathname)
30
+ .returns(T::Hash[Pathname, Module])
35
31
  end
36
32
  def filter_relevant_paths(all_paths, bundle_path: Bundler.bundle_path, rails_root: Rails.root)
37
33
  bundle_path_match = bundle_path.join("**")
38
34
  rails_root_match = rails_root.join("**")
39
35
 
40
36
  all_paths
41
- .map { |path| Pathname.new(path).expand_path }
37
+ .transform_keys { |path| Pathname.new(path).expand_path }
42
38
  .select { |path| path.fnmatch(rails_root_match.to_s) } # path needs to be in application directory
43
39
  .reject { |path| path.fnmatch(bundle_path_match.to_s) } # reject paths from vendored gems
44
40
  end
45
41
 
46
- sig { params(paths: T::Array[Pathname], rails_root: Pathname).returns(T::Array[String]) }
47
- def relative_path_strings(paths, rails_root: Rails.root)
48
- paths
49
- .map { |path| path.relative_path_from(rails_root).to_s }
50
- .uniq
42
+ sig { params(load_paths: T::Hash[Pathname, Module], rails_root: Pathname).returns(T::Hash[String, Module]) }
43
+ def relative_path_strings(load_paths, rails_root: Rails.root)
44
+ load_paths.transform_keys { |path| Pathname.new(path).relative_path_from(rails_root).to_s }
51
45
  end
52
46
 
53
47
  private
@@ -65,7 +59,7 @@ module Packwerk
65
59
  end
66
60
  end
67
61
 
68
- sig { params(paths: T::Array[T.untyped]).void }
62
+ sig { params(paths: T::Hash[T.untyped, Module]).void }
69
63
  def assert_load_paths_present(paths)
70
64
  if paths.empty?
71
65
  raise <<~EOS
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "constant_resolver"
@@ -9,14 +9,36 @@ module Packwerk
9
9
  # Checks the structure of the application and its packwerk configuration to make sure we can run a check and deliver
10
10
  # correct results.
11
11
  class ApplicationValidator
12
+ extend T::Sig
13
+
14
+ sig do
15
+ params(
16
+ config_file_path: String,
17
+ configuration: Configuration,
18
+ environment: String
19
+ ).void
20
+ end
12
21
  def initialize(config_file_path:, configuration:, environment:)
13
22
  @config_file_path = config_file_path
14
23
  @configuration = configuration
15
24
  @environment = environment
25
+ @package_set = T.let(PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob),
26
+ PackageSet)
16
27
  end
17
28
 
18
- Result = Struct.new(:ok?, :error_value)
29
+ class Result < T::Struct
30
+ extend T::Sig
31
+
32
+ const :ok, T::Boolean
33
+ const :error_value, T.nilable(String)
34
+
35
+ sig { returns(T::Boolean) }
36
+ def ok?
37
+ ok
38
+ end
39
+ end
19
40
 
41
+ sig { returns(Result) }
20
42
  def check_all
21
43
  results = [
22
44
  check_package_manifests_for_privacy,
@@ -31,6 +53,7 @@ module Packwerk
31
53
  merge_results(results)
32
54
  end
33
55
 
56
+ sig { returns(Result) }
34
57
  def check_package_manifests_for_privacy
35
58
  privacy_settings = package_manifests_settings_for("enforce_privacy")
36
59
 
@@ -39,13 +62,13 @@ module Packwerk
39
62
  load_paths: @configuration.load_paths
40
63
  )
41
64
 
42
- results = []
65
+ results = T.let([], T::Array[Result])
43
66
 
44
67
  privacy_settings.each do |config_file_path, setting|
45
68
  next unless setting.is_a?(Array)
46
69
  constants = setting
47
70
 
48
- assert_constants_can_be_loaded(constants)
71
+ results += assert_constants_can_be_loaded(constants, config_file_path)
49
72
 
50
73
  constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
51
74
 
@@ -61,6 +84,7 @@ module Packwerk
61
84
  merge_results(results, separator: "\n---\n")
62
85
  end
63
86
 
87
+ sig { returns(Result) }
64
88
  def check_package_manifest_syntax
65
89
  errors = []
66
90
 
@@ -102,12 +126,13 @@ module Packwerk
102
126
  end
103
127
 
104
128
  if errors.empty?
105
- Result.new(true)
129
+ Result.new(ok: true)
106
130
  else
107
- Result.new(false, errors.join("\n---\n"))
131
+ Result.new(ok: false, error_value: errors.join("\n---\n"))
108
132
  end
109
133
  end
110
134
 
135
+ sig { returns(Result) }
111
136
  def check_application_structure
112
137
  resolver = ConstantResolver.new(
113
138
  root_path: @configuration.root_path.to_s,
@@ -116,26 +141,27 @@ module Packwerk
116
141
 
117
142
  begin
118
143
  resolver.file_map
119
- Result.new(true)
144
+ Result.new(ok: true)
120
145
  rescue => e
121
- Result.new(false, e.message)
146
+ Result.new(ok: false, error_value: e.message)
122
147
  end
123
148
  end
124
149
 
150
+ sig { returns(Result) }
125
151
  def check_acyclic_graph
126
- edges = package_set.flat_map do |package|
127
- package.dependencies.map { |dependency| [package, package_set.fetch(dependency)] }
152
+ edges = @package_set.flat_map do |package|
153
+ package.dependencies.map { |dependency| [package, @package_set.fetch(dependency)] }
128
154
  end
129
- dependency_graph = Packwerk::Graph.new(*T.unsafe(edges))
155
+ dependency_graph = Graph.new(*T.unsafe(edges))
130
156
 
131
157
  cycle_strings = build_cycle_strings(dependency_graph.cycles)
132
158
 
133
159
  if dependency_graph.acyclic?
134
- Result.new(true)
160
+ Result.new(ok: true)
135
161
  else
136
162
  Result.new(
137
- false,
138
- <<~EOS
163
+ ok: false,
164
+ error_value: <<~EOS
139
165
  Expected the package dependency graph to be acyclic, but it contains the following cycles:
140
166
 
141
167
  #{cycle_strings.join("\n")}
@@ -144,6 +170,7 @@ module Packwerk
144
170
  end
145
171
  end
146
172
 
173
+ sig { returns(Result) }
147
174
  def check_package_manifest_paths
148
175
  all_package_manifests = package_manifests("**/")
149
176
  package_paths_package_manifests = package_manifests(package_glob)
@@ -151,11 +178,11 @@ module Packwerk
151
178
  difference = all_package_manifests - package_paths_package_manifests
152
179
 
153
180
  if difference.empty?
154
- Result.new(true)
181
+ Result.new(ok: true)
155
182
  else
156
183
  Result.new(
157
- false,
158
- <<~EOS
184
+ ok: false,
185
+ error_value: <<~EOS
159
186
  Expected package paths for all package.ymls to be specified, but paths were missing for the following manifests:
160
187
 
161
188
  #{relative_paths(difference).join("\n")}
@@ -164,6 +191,7 @@ module Packwerk
164
191
  end
165
192
  end
166
193
 
194
+ sig { returns(Result) }
167
195
  def check_valid_package_dependencies
168
196
  packages_dependencies = package_manifests_settings_for("dependencies")
169
197
  .delete_if { |_, deps| deps.nil? }
@@ -175,7 +203,7 @@ module Packwerk
175
203
  end
176
204
 
177
205
  if packages_with_invalid_dependencies.empty?
178
- Result.new(true)
206
+ Result.new(ok: true)
179
207
  else
180
208
  error_locations = packages_with_invalid_dependencies.map do |package, invalid_dependencies|
181
209
  package ||= @configuration.root_path
@@ -189,8 +217,8 @@ module Packwerk
189
217
  end
190
218
 
191
219
  Result.new(
192
- false,
193
- <<~EOS
220
+ ok: false,
221
+ error_value: <<~EOS
194
222
  These dependencies do not point to valid packages:
195
223
 
196
224
  #{error_locations.join("\n")}
@@ -199,16 +227,17 @@ module Packwerk
199
227
  end
200
228
  end
201
229
 
230
+ sig { returns(Result) }
202
231
  def check_root_package_exists
203
232
  root_package_path = File.join(@configuration.root_path, "package.yml")
204
233
  all_packages_manifests = package_manifests(package_glob)
205
234
 
206
235
  if all_packages_manifests.include?(root_package_path)
207
- Result.new(true)
236
+ Result.new(ok: true)
208
237
  else
209
238
  Result.new(
210
- false,
211
- <<~EOS
239
+ ok: false,
240
+ error_value: <<~EOS
212
241
  A root package does not exist. Create an empty `package.yml` at the root directory.
213
242
  EOS
214
243
  )
@@ -224,6 +253,7 @@ module Packwerk
224
253
  # to the string:
225
254
  #
226
255
  # ["a -> b -> c -> a", "b -> c -> b"]
256
+ sig { params(cycles: T.untyped).returns(T::Array[String]) }
227
257
  def build_cycle_strings(cycles)
228
258
  cycles.map do |cycle|
229
259
  cycle_strings = cycle.map(&:to_s)
@@ -232,84 +262,102 @@ module Packwerk
232
262
  end
233
263
  end
234
264
 
265
+ sig { params(setting: T.untyped).returns(T.untyped) }
235
266
  def package_manifests_settings_for(setting)
236
267
  package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
237
268
  end
238
269
 
270
+ sig { params(list: T.untyped).returns(T.untyped) }
239
271
  def format_yaml_strings(list)
240
272
  list.sort.map { |p| "- \"#{p}\"" }.join("\n")
241
273
  end
242
274
 
275
+ sig { returns(T.any(T::Array[String], String)) }
243
276
  def package_glob
244
277
  @configuration.package_paths || "**"
245
278
  end
246
279
 
280
+ sig { params(glob_pattern: T.any(T::Array[String], String)).returns(T::Array[String]) }
247
281
  def package_manifests(glob_pattern = package_glob)
248
282
  PackageSet.package_paths(@configuration.root_path, glob_pattern, @configuration.exclude)
249
283
  .map { |f| File.realpath(f) }
250
284
  end
251
285
 
286
+ sig { params(paths: T::Array[String]).returns(T::Array[Pathname]) }
252
287
  def relative_paths(paths)
253
288
  paths.map { |path| relative_path(path) }
254
289
  end
255
290
 
291
+ sig { params(path: String).returns(Pathname) }
256
292
  def relative_path(path)
257
293
  Pathname.new(path).relative_path_from(@configuration.root_path)
258
294
  end
259
295
 
296
+ sig { params(path: T.untyped).returns(T::Boolean) }
260
297
  def invalid_package_path?(path)
261
298
  # Packages at the root can be implicitly specified as "."
262
299
  return false if path == "."
263
300
 
264
- package_path = File.join(@configuration.root_path, path, Packwerk::PackageSet::PACKAGE_CONFIG_FILENAME)
301
+ package_path = File.join(@configuration.root_path, path, PackageSet::PACKAGE_CONFIG_FILENAME)
265
302
  !File.file?(package_path)
266
303
  end
267
304
 
268
- def assert_constants_can_be_loaded(constants)
269
- constants.each(&:constantize)
270
- nil
305
+ sig { params(constants: T.untyped, config_file_path: String).returns(T::Array[Result]) }
306
+ def assert_constants_can_be_loaded(constants, config_file_path)
307
+ constants.map do |constant|
308
+ if !constant.start_with?("::")
309
+ Result.new(
310
+ ok: false,
311
+ 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
+ )
314
+ else
315
+ constant.try(&:constantize) && Result.new(ok: true)
316
+ end
317
+ end
271
318
  end
272
319
 
320
+ sig { params(name: T.untyped, config_file_path: T.untyped).returns(Result) }
273
321
  def private_constant_unresolvable(name, config_file_path)
274
322
  explicit_filepath = (name.start_with?("::") ? name[2..-1] : name).underscore + ".rb"
275
323
 
276
324
  Result.new(
277
- false,
278
- "'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
325
+ ok: false,
326
+ error_value: "'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
279
327
  "This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
280
328
  "file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
281
329
  "private. Add a #{explicit_filepath} file to explicitly define the constant."
282
330
  )
283
331
  end
284
332
 
333
+ sig { params(name: T.untyped, location: T.untyped, config_file_path: T.untyped).returns(Result) }
285
334
  def check_private_constant_location(name, location, config_file_path)
286
- declared_package = package_set.package_from_path(relative_path(config_file_path))
287
- constant_package = package_set.package_from_path(location)
335
+ declared_package = @package_set.package_from_path(relative_path(config_file_path))
336
+ constant_package = @package_set.package_from_path(location)
288
337
 
289
338
  if constant_package == declared_package
290
- Result.new(true)
339
+ Result.new(ok: true)
291
340
  else
292
341
  Result.new(
293
- false,
294
- "'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
342
+ ok: false,
343
+ error_value: "'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
295
344
  "defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
296
345
  )
297
346
  end
298
347
  end
299
348
 
300
- def package_set
301
- @package_set ||= Packwerk::PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob)
349
+ sig do
350
+ params(results: T::Array[Result], separator: String, errors_headline: String).returns(Result)
302
351
  end
303
-
304
352
  def merge_results(results, separator: "\n===\n", errors_headline: "")
305
353
  results.reject!(&:ok?)
306
354
 
307
355
  if results.empty?
308
- Result.new(true)
356
+ Result.new(ok: true)
309
357
  else
310
358
  Result.new(
311
- false,
312
- errors_headline + results.map(&:error_value).join(separator)
359
+ ok: false,
360
+ error_value: errors_headline + results.map(&:error_value).join(separator)
313
361
  )
314
362
  end
315
363
  end