packwerk 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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