packwerk 1.4.0 → 2.1.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/Gemfile.lock +22 -19
  4. data/README.md +7 -2
  5. data/UPGRADING.md +54 -0
  6. data/USAGE.md +5 -40
  7. data/lib/packwerk/application_validator.rb +88 -93
  8. data/lib/packwerk/association_inspector.rb +1 -1
  9. data/lib/packwerk/cache.rb +169 -0
  10. data/lib/packwerk/cli.rb +32 -28
  11. data/lib/packwerk/configuration.rb +40 -5
  12. data/lib/packwerk/constant_discovery.rb +21 -5
  13. data/lib/packwerk/constant_name_inspector.rb +1 -1
  14. data/lib/packwerk/deprecated_references.rb +1 -1
  15. data/lib/packwerk/file_processor.rb +34 -15
  16. data/lib/packwerk/files_for_processing.rb +49 -22
  17. data/lib/packwerk/formatters/offenses_formatter.rb +1 -1
  18. data/lib/packwerk/formatters/progress_formatter.rb +1 -1
  19. data/lib/packwerk/generators/configuration_file.rb +4 -19
  20. data/lib/packwerk/generators/templates/packwerk.yml.erb +5 -5
  21. data/lib/packwerk/node.rb +2 -1
  22. data/lib/packwerk/node_processor.rb +6 -6
  23. data/lib/packwerk/node_processor_factory.rb +3 -4
  24. data/lib/packwerk/node_visitor.rb +3 -0
  25. data/lib/packwerk/offense.rb +10 -2
  26. data/lib/packwerk/package.rb +1 -1
  27. data/lib/packwerk/package_set.rb +3 -2
  28. data/lib/packwerk/parse_run.rb +37 -17
  29. data/lib/packwerk/parsed_constant_definitions.rb +4 -4
  30. data/lib/packwerk/parsers/erb.rb +2 -0
  31. data/lib/packwerk/parsers/factory.rb +2 -0
  32. data/lib/packwerk/parsers/parser_interface.rb +17 -0
  33. data/lib/packwerk/parsers/ruby.rb +2 -0
  34. data/lib/packwerk/parsers.rb +1 -0
  35. data/lib/packwerk/reference_checking/checkers/checker.rb +1 -1
  36. data/lib/packwerk/reference_checking/reference_checker.rb +2 -1
  37. data/lib/packwerk/reference_extractor.rb +78 -20
  38. data/lib/packwerk/reference_offense.rb +8 -3
  39. data/lib/packwerk/result.rb +2 -2
  40. data/lib/packwerk/run_context.rb +62 -38
  41. data/lib/packwerk/spring_command.rb +1 -1
  42. data/lib/packwerk/unresolved_reference.rb +10 -0
  43. data/lib/packwerk/version.rb +1 -1
  44. data/lib/packwerk.rb +5 -9
  45. data/packwerk.gemspec +1 -0
  46. data/sorbet/config +1 -0
  47. data/sorbet/rbi/gems/tapioca@0.4.19.rbi +1 -1
  48. data/sorbet/tapioca/require.rb +1 -1
  49. metadata +21 -7
  50. data/lib/packwerk/generators/inflections_file.rb +0 -43
  51. data/lib/packwerk/generators/templates/inflections.yml +0 -6
  52. data/lib/packwerk/inflections/custom.rb +0 -33
  53. data/lib/packwerk/inflections/default.rb +0 -73
  54. data/lib/packwerk/inflector.rb +0 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d150d89ede62e601246e2ff0290ecda4aab883a7c8b8be02feb86760ad26b72
4
- data.tar.gz: 33b088b3387b8fdfe694967f20529fb8795dec8572dc9e036cb4979402f19c5b
3
+ metadata.gz: e0270de859808f51bbfa45a7140813744da0555c57d9d60432ba4e166ab63c4a
4
+ data.tar.gz: 90465398a3e04f83ba4c0e3b290dca72677b166003640504c7470b181e556d73
5
5
  SHA512:
6
- metadata.gz: 102154447670b85dc4e0825dcdf1ec11cd0fd291ffc1c7230b98da2208b0a39c38d2c8726a33226aeea68082e16a24917db7ad3380c871b9b02b0d76c8ac69fc
7
- data.tar.gz: 666e3745e9167028e5d3901d74400848c87c1b8a64a7487d3e4bfe13916e2902990633db7c448057b8106fd4b6b5c5de08c697c88151a305c92d34259266448f
6
+ metadata.gz: 86e386674412f99d605013799ebd09bbff4d9e0dd2718de534db33e71c148fd0b8aa2ec905ca96c34c48ce85e31e922eae41352c05676de8f4af6baea749c03e
7
+ data.tar.gz: 6406b7ddbcac8c91f3e51660d5c5e111145b6f8f01ef6ca2bfc1797cdec39b13cb0db1e3d8d04026bc59835bb67e9e8acb41a5edbd0cd207f609bbb532ffa067
data/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ See https://github.com/Shopify/packwerk/releases.
data/Gemfile.lock CHANGED
@@ -87,12 +87,13 @@ GIT
87
87
  PATH
88
88
  remote: .
89
89
  specs:
90
- packwerk (1.4.0)
90
+ packwerk (2.1.1)
91
91
  activesupport (>= 5.2)
92
92
  ast
93
93
  better_html
94
94
  bundler
95
95
  constant_resolver
96
+ digest
96
97
  parallel
97
98
  parser
98
99
  sorbet-runtime
@@ -118,6 +119,7 @@ GEM
118
119
  concurrent-ruby (1.1.8)
119
120
  constant_resolver (0.1.5)
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.3)
148
+ mini_portile2 (~> 2.8.0)
147
149
  racc (~> 1.4)
148
- nokogiri (1.12.5-x86_64-darwin)
150
+ nokogiri (1.13.3-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.9538)
198
+ sorbet-static (= 0.5.9538)
199
+ sorbet-runtime (0.5.9538)
200
+ sorbet-static (0.5.9538-universal-darwin-14)
201
+ sorbet-static (0.5.9538-universal-darwin-15)
202
+ sorbet-static (0.5.9538-universal-darwin-16)
203
+ sorbet-static (0.5.9538-universal-darwin-17)
204
+ sorbet-static (0.5.9538-universal-darwin-18)
205
+ sorbet-static (0.5.9538-universal-darwin-19)
206
+ sorbet-static (0.5.9538-universal-darwin-20)
207
+ sorbet-static (0.5.9538-universal-darwin-21)
208
+ sorbet-static (0.5.9538-x86_64-linux)
206
209
  spoom (1.1.0)
207
210
  colorize
208
211
  sorbet (>= 0.5.6347)
@@ -257,4 +260,4 @@ DEPENDENCIES
257
260
  tapioca
258
261
 
259
262
  BUNDLED WITH
260
- 2.2.22
263
+ 2.3.4
data/README.md CHANGED
@@ -35,8 +35,6 @@ Watch a [1-minute video demo](https://www.youtube.com/watch?v=NwqlyBAxVpQ&featur
35
35
  gem 'packwerk'
36
36
  ```
37
37
 
38
- _Note: Packwerk has to be grouped in production environment within the Gemfile if your Rails app has custom inflections._
39
-
40
38
  2. Install the gem
41
39
 
42
40
  Execute:
@@ -58,6 +56,13 @@ Read [USAGE.md](USAGE.md) for usage once Packwerk is installed on your project.
58
56
 
59
57
  "Packwerk" is pronounced [[ˈpakvɛʁk]](https://cdn.shopify.com/s/files/1/0258/7469/files/packwerk.mp3).
60
58
 
59
+ ## Ecosystem
60
+
61
+ You can use these third party tools to enhance your packwerk experience:
62
+
63
+ - https://github.com/bellroy/graphwerk draws a graph of your package dependencies
64
+ - https://github.com/Gusto/packwerk-vscode integrates packwerk into Visual Studio Code so you can see violations right in your editor
65
+
61
66
  ## Development
62
67
 
63
68
  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/UPGRADING.md ADDED
@@ -0,0 +1,54 @@
1
+ # Upgrading from 1.x to 2.0
2
+
3
+ With Packwerk 2.0, we made a few changes to simplify the setup. Updating will require removing some previously necessary files and configuration.
4
+
5
+ ## Gem group
6
+
7
+ Because packwerk is no longer involved in specifying the application's inflections, it doesn't have to live in the `production` group in your `Gemfile` anymore. We recommend moving it to the `development` group.
8
+
9
+ ## Removing application config caches
10
+
11
+ ### Load paths
12
+ We no longer require the `load_paths` key in `packwerk.yml`. You can simply delete the load_paths key as it will not be read anymore. Instead, Packwerk will ask Rails for load paths. If you're using spring, make sure to properly set up spring (see [USAGE.md](USAGE.md#setting-up-spring)) to keep packwerk fast.
13
+
14
+ ### Inflections
15
+ We no longer require a custom `inflections.yml` file. Instead, you'll want to revert BACK to using the `inflections.rb` initializer as you would have done prior to adopting packwerk. To do this, you'll want to convert back to using the plain [ActiveSupport Inflections API](https://api.rubyonrails.org/classes/ActiveSupport/Inflector/Inflections.html).
16
+
17
+
18
+ Given the following example `inflections.yml`, here is an example `inflections.rb` that would follow. Tip: if you're using git, you can run `git log config/inflections.yml`, find the first commit that introduced `inflections.yml`, find the COMMIT_SHA, and then run `git show COMMIT_SHA` to see what your inflections file looked like before (note that you may have changed `inflections.yml` since then, though).
19
+
20
+ `config/inflections.yml`
21
+ ```yml
22
+ # List your inflections in this file instead of `inflections.rb`
23
+ # See steps to set up custom inflections:
24
+ # https://github.com/Shopify/packwerk/blob/main/USAGE.md#Inflections
25
+
26
+ acronym:
27
+ - 'HTML'
28
+ - 'API'
29
+
30
+ singular:
31
+ - ['oxen', 'oxen']
32
+
33
+ irregular:
34
+ - ['person', 'people']
35
+
36
+ uncountable:
37
+ - 'fish'
38
+ - 'sheep'
39
+ ```
40
+
41
+ `config/initializers/inflections.rb`
42
+ ```ruby
43
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
44
+ inflect.acronym('HTML')
45
+ inflect.acronym('API')
46
+
47
+ inflect.singular('oxen', 'oxen')
48
+
49
+ inflect.irregular('person', 'people')
50
+
51
+ inflect.uncountable('fish')
52
+ inflect.uncountable('sheep')
53
+ end
54
+ ```
data/USAGE.md CHANGED
@@ -9,7 +9,6 @@
9
9
  * [Setting up Spring](#setting-up-spring)
10
10
  * [Configuring Packwerk](#configuring-packwerk)
11
11
  * [Using a custom ERB parser](#using-a-custom-erb-parser)
12
- * [Inflections](#inflections)
13
12
  * [Validating the package system](#validating-the-package-system)
14
13
  * [Defining packages](#defining-packages)
15
14
  * [Package metadata](#package-metadata)
@@ -57,7 +56,6 @@ Here is a list of files generated:
57
56
  |-----------------------------|--------------|------------|
58
57
  | Packwerk configuration | packwerk.yml | See [Setting up the configuration file](#Setting-up-the-configuration-file) |
59
58
  | Root package | package.yml | A package for the root folder |
60
- | Custom inflections | config/inflections.yml | A custom inflections file is only required if you have custom inflections in `inflections.rb`, see [Inflections](#Inflections) |
61
59
 
62
60
  After that, you may begin creating packages for your application. See [Defining packages](#Defining-packages)
63
61
 
@@ -77,9 +75,10 @@ Packwerk reads from the `packwerk.yml` configuration file in the root directory.
77
75
  | include | **/*.{rb,rake,erb} | list of patterns for folder paths to include |
78
76
  | exclude | {bin,node_modules,script,tmp,vendor}/**/* | list of patterns for folder paths to exclude |
79
77
  | package_paths | **/ | a single pattern or a list of patterns to find package configuration files, see: [Defining packages](#Defining-packages) |
80
- | load_paths | All application autoload paths | list of load paths |
81
78
  | custom_associations | N/A | list of custom associations, if any |
82
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 |
83
82
 
84
83
  ### Using a custom ERB parser
85
84
 
@@ -103,44 +102,10 @@ end
103
102
  Packwerk::Parsers::Factory.instance.erb_parser_class = CustomParser
104
103
  ```
105
104
 
106
- ### Inflections
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
107
 
108
- Packwerk requires custom inflections to be defined in `inflections.yml` instead of the traditional `inflections.rb`. This is because Packwerk accounts for custom inflections, such as acronyms, when resolving constants. Additionally, Packwerk interprets Active Record associations as references to constants. For example, `has_many :birds` is a reference to the `Bird` constant.
109
-
110
- In order to make your custom inflections compatible with Active Support and Packwerk, you must create a `config/inflections.yml` file and point `ActiveSupport::Inflector` to that file.
111
-
112
- In `inflections.rb`, add:
113
-
114
- ```rb
115
- require "packwerk/inflections/custom"
116
-
117
- ActiveSupport::Inflector.inflections do |inflect|
118
- # please add all custom inflections in the file below.
119
- Packwerk::Inflections::Custom.new(
120
- Rails.root.join("config", "inflections.yml")
121
- ).apply_to(inflect)
122
- end
123
- ```
124
-
125
- _Note: Packwerk has to be grouped in production environment within the Gemfile if you have custom inflections._
126
-
127
- Next, move your existing custom inflections into `config/inflections.yml`:
128
-
129
- ```yaml
130
- acronym:
131
- - 'GraphQL'
132
- - 'MRuby'
133
- - 'TOS'
134
- irregular:
135
- - ['analysis', 'analyses']
136
- - ['reserve', 'reserves']
137
- uncountable:
138
- - 'payment_details'
139
- singular:
140
- - [!ruby/regexp /status$/, 'status']
141
- ```
142
-
143
- Any new inflectors should be added to `config/inflections.yml`.
108
+ This will write to `tmp/cache/packwerk`.
144
109
 
145
110
  ## Validating the package system
146
111
 
@@ -1,7 +1,6 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "active_support/inflector/inflections"
5
4
  require "constant_resolver"
6
5
  require "pathname"
7
6
  require "yaml"
@@ -10,23 +9,41 @@ module Packwerk
10
9
  # Checks the structure of the application and its packwerk configuration to make sure we can run a check and deliver
11
10
  # correct results.
12
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
13
21
  def initialize(config_file_path:, configuration:, environment:)
14
22
  @config_file_path = config_file_path
15
23
  @configuration = configuration
16
24
  @environment = environment
17
-
18
- @application_load_paths = ApplicationLoadPaths.extract_relevant_paths(configuration.root_path, environment)
25
+ @package_set = T.let(PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob),
26
+ PackageSet)
19
27
  end
20
28
 
21
- 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)
22
34
 
35
+ sig { returns(T::Boolean) }
36
+ def ok?
37
+ ok
38
+ end
39
+ end
40
+
41
+ sig { returns(Result) }
23
42
  def check_all
24
43
  results = [
25
- check_autoload_path_cache,
26
44
  check_package_manifests_for_privacy,
27
45
  check_package_manifest_syntax,
28
46
  check_application_structure,
29
- check_inflection_file,
30
47
  check_acyclic_graph,
31
48
  check_package_manifest_paths,
32
49
  check_valid_package_dependencies,
@@ -36,21 +53,7 @@ module Packwerk
36
53
  merge_results(results)
37
54
  end
38
55
 
39
- def check_autoload_path_cache
40
- expected = Set.new(@application_load_paths)
41
- actual = Set.new(@configuration.load_paths)
42
- if expected == actual
43
- Result.new(true)
44
- else
45
- Result.new(
46
- false,
47
- "Load path cache in #{@config_file_path} incorrect!\n"\
48
- "Paths missing from file:\n#{format_yaml_strings(expected - actual)}\n"\
49
- "Extraneous load paths in file:\n#{format_yaml_strings(actual - expected)}"
50
- )
51
- end
52
- end
53
-
56
+ sig { returns(Result) }
54
57
  def check_package_manifests_for_privacy
55
58
  privacy_settings = package_manifests_settings_for("enforce_privacy")
56
59
 
@@ -59,13 +62,13 @@ module Packwerk
59
62
  load_paths: @configuration.load_paths
60
63
  )
61
64
 
62
- results = []
65
+ results = T.let([], T::Array[Result])
63
66
 
64
67
  privacy_settings.each do |config_file_path, setting|
65
68
  next unless setting.is_a?(Array)
66
69
  constants = setting
67
70
 
68
- assert_constants_can_be_loaded(constants)
71
+ results += assert_constants_can_be_loaded(constants, config_file_path)
69
72
 
70
73
  constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
71
74
 
@@ -81,6 +84,7 @@ module Packwerk
81
84
  merge_results(results, separator: "\n---\n")
82
85
  end
83
86
 
87
+ sig { returns(Result) }
84
88
  def check_package_manifest_syntax
85
89
  errors = []
86
90
 
@@ -122,12 +126,13 @@ module Packwerk
122
126
  end
123
127
 
124
128
  if errors.empty?
125
- Result.new(true)
129
+ Result.new(ok: true)
126
130
  else
127
- Result.new(false, errors.join("\n---\n"))
131
+ Result.new(ok: false, error_value: errors.join("\n---\n"))
128
132
  end
129
133
  end
130
134
 
135
+ sig { returns(Result) }
131
136
  def check_application_structure
132
137
  resolver = ConstantResolver.new(
133
138
  root_path: @configuration.root_path.to_s,
@@ -136,59 +141,27 @@ module Packwerk
136
141
 
137
142
  begin
138
143
  resolver.file_map
139
- Result.new(true)
144
+ Result.new(ok: true)
140
145
  rescue => e
141
- Result.new(false, e.message)
142
- end
143
- end
144
-
145
- def check_inflection_file
146
- inflections_file = @configuration.inflections_file
147
-
148
- application_inflections = ActiveSupport::Inflector.inflections
149
- packwerk_inflections = Packwerk::Inflector.from_file(inflections_file).inflections
150
-
151
- results = %i(plurals singulars uncountables humans acronyms).map do |type|
152
- expected = application_inflections.public_send(type).to_set
153
- actual = packwerk_inflections.public_send(type).to_set
154
-
155
- if expected == actual
156
- Result.new(true)
157
- else
158
- missing_msg = unless (expected - actual).empty?
159
- "Expected #{type} to be specified in file: #{expected - actual}"
160
- end
161
- extraneous_msg = unless (actual - expected).empty?
162
- "Extraneous #{type} was specified in file: #{actual - expected}"
163
- end
164
- Result.new(
165
- false,
166
- [missing_msg, extraneous_msg].join("\n")
167
- )
168
- end
146
+ Result.new(ok: false, error_value: e.message)
169
147
  end
170
-
171
- merge_results(
172
- results,
173
- separator: "\n",
174
- errors_headline: "Inflections specified in #{inflections_file} don't line up with application!\n"
175
- )
176
148
  end
177
149
 
150
+ sig { returns(Result) }
178
151
  def check_acyclic_graph
179
- edges = package_set.flat_map do |package|
180
- 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)] }
181
154
  end
182
- dependency_graph = Packwerk::Graph.new(*T.unsafe(edges))
155
+ dependency_graph = Graph.new(*T.unsafe(edges))
183
156
 
184
157
  cycle_strings = build_cycle_strings(dependency_graph.cycles)
185
158
 
186
159
  if dependency_graph.acyclic?
187
- Result.new(true)
160
+ Result.new(ok: true)
188
161
  else
189
162
  Result.new(
190
- false,
191
- <<~EOS
163
+ ok: false,
164
+ error_value: <<~EOS
192
165
  Expected the package dependency graph to be acyclic, but it contains the following cycles:
193
166
 
194
167
  #{cycle_strings.join("\n")}
@@ -197,6 +170,7 @@ module Packwerk
197
170
  end
198
171
  end
199
172
 
173
+ sig { returns(Result) }
200
174
  def check_package_manifest_paths
201
175
  all_package_manifests = package_manifests("**/")
202
176
  package_paths_package_manifests = package_manifests(package_glob)
@@ -204,11 +178,11 @@ module Packwerk
204
178
  difference = all_package_manifests - package_paths_package_manifests
205
179
 
206
180
  if difference.empty?
207
- Result.new(true)
181
+ Result.new(ok: true)
208
182
  else
209
183
  Result.new(
210
- false,
211
- <<~EOS
184
+ ok: false,
185
+ error_value: <<~EOS
212
186
  Expected package paths for all package.ymls to be specified, but paths were missing for the following manifests:
213
187
 
214
188
  #{relative_paths(difference).join("\n")}
@@ -217,6 +191,7 @@ module Packwerk
217
191
  end
218
192
  end
219
193
 
194
+ sig { returns(Result) }
220
195
  def check_valid_package_dependencies
221
196
  packages_dependencies = package_manifests_settings_for("dependencies")
222
197
  .delete_if { |_, deps| deps.nil? }
@@ -228,7 +203,7 @@ module Packwerk
228
203
  end
229
204
 
230
205
  if packages_with_invalid_dependencies.empty?
231
- Result.new(true)
206
+ Result.new(ok: true)
232
207
  else
233
208
  error_locations = packages_with_invalid_dependencies.map do |package, invalid_dependencies|
234
209
  package ||= @configuration.root_path
@@ -242,8 +217,8 @@ module Packwerk
242
217
  end
243
218
 
244
219
  Result.new(
245
- false,
246
- <<~EOS
220
+ ok: false,
221
+ error_value: <<~EOS
247
222
  These dependencies do not point to valid packages:
248
223
 
249
224
  #{error_locations.join("\n")}
@@ -252,16 +227,17 @@ module Packwerk
252
227
  end
253
228
  end
254
229
 
230
+ sig { returns(Result) }
255
231
  def check_root_package_exists
256
232
  root_package_path = File.join(@configuration.root_path, "package.yml")
257
233
  all_packages_manifests = package_manifests(package_glob)
258
234
 
259
235
  if all_packages_manifests.include?(root_package_path)
260
- Result.new(true)
236
+ Result.new(ok: true)
261
237
  else
262
238
  Result.new(
263
- false,
264
- <<~EOS
239
+ ok: false,
240
+ error_value: <<~EOS
265
241
  A root package does not exist. Create an empty `package.yml` at the root directory.
266
242
  EOS
267
243
  )
@@ -277,6 +253,7 @@ module Packwerk
277
253
  # to the string:
278
254
  #
279
255
  # ["a -> b -> c -> a", "b -> c -> b"]
256
+ sig { params(cycles: T.untyped).returns(T::Array[String]) }
280
257
  def build_cycle_strings(cycles)
281
258
  cycles.map do |cycle|
282
259
  cycle_strings = cycle.map(&:to_s)
@@ -285,84 +262,102 @@ module Packwerk
285
262
  end
286
263
  end
287
264
 
265
+ sig { params(setting: T.untyped).returns(T.untyped) }
288
266
  def package_manifests_settings_for(setting)
289
267
  package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
290
268
  end
291
269
 
270
+ sig { params(list: T.untyped).returns(T.untyped) }
292
271
  def format_yaml_strings(list)
293
272
  list.sort.map { |p| "- \"#{p}\"" }.join("\n")
294
273
  end
295
274
 
275
+ sig { returns(T.any(T::Array[String], String)) }
296
276
  def package_glob
297
277
  @configuration.package_paths || "**"
298
278
  end
299
279
 
280
+ sig { params(glob_pattern: T.any(T::Array[String], String)).returns(T::Array[String]) }
300
281
  def package_manifests(glob_pattern = package_glob)
301
282
  PackageSet.package_paths(@configuration.root_path, glob_pattern, @configuration.exclude)
302
283
  .map { |f| File.realpath(f) }
303
284
  end
304
285
 
286
+ sig { params(paths: T::Array[String]).returns(T::Array[Pathname]) }
305
287
  def relative_paths(paths)
306
288
  paths.map { |path| relative_path(path) }
307
289
  end
308
290
 
291
+ sig { params(path: String).returns(Pathname) }
309
292
  def relative_path(path)
310
293
  Pathname.new(path).relative_path_from(@configuration.root_path)
311
294
  end
312
295
 
296
+ sig { params(path: T.untyped).returns(T::Boolean) }
313
297
  def invalid_package_path?(path)
314
298
  # Packages at the root can be implicitly specified as "."
315
299
  return false if path == "."
316
300
 
317
- 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)
318
302
  !File.file?(package_path)
319
303
  end
320
304
 
321
- def assert_constants_can_be_loaded(constants)
322
- constants.each(&:constantize)
323
- 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
324
318
  end
325
319
 
320
+ sig { params(name: T.untyped, config_file_path: T.untyped).returns(Result) }
326
321
  def private_constant_unresolvable(name, config_file_path)
327
322
  explicit_filepath = (name.start_with?("::") ? name[2..-1] : name).underscore + ".rb"
328
323
 
329
324
  Result.new(
330
- false,
331
- "'#{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"\
332
327
  "This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
333
328
  "file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
334
329
  "private. Add a #{explicit_filepath} file to explicitly define the constant."
335
330
  )
336
331
  end
337
332
 
333
+ sig { params(name: T.untyped, location: T.untyped, config_file_path: T.untyped).returns(Result) }
338
334
  def check_private_constant_location(name, location, config_file_path)
339
- declared_package = package_set.package_from_path(relative_path(config_file_path))
340
- 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)
341
337
 
342
338
  if constant_package == declared_package
343
- Result.new(true)
339
+ Result.new(ok: true)
344
340
  else
345
341
  Result.new(
346
- false,
347
- "'#{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 "\
348
344
  "defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
349
345
  )
350
346
  end
351
347
  end
352
348
 
353
- def package_set
354
- @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)
355
351
  end
356
-
357
352
  def merge_results(results, separator: "\n===\n", errors_headline: "")
358
353
  results.reject!(&:ok?)
359
354
 
360
355
  if results.empty?
361
- Result.new(true)
356
+ Result.new(ok: true)
362
357
  else
363
358
  Result.new(
364
- false,
365
- errors_headline + results.map(&:error_value).join(separator)
359
+ ok: false,
360
+ error_value: errors_headline + results.map(&:error_value).join(separator)
366
361
  )
367
362
  end
368
363
  end
@@ -19,7 +19,7 @@ module Packwerk
19
19
  CustomAssociations
20
20
  )
21
21
 
22
- sig { params(inflector: Inflector, custom_associations: CustomAssociations).void }
22
+ sig { params(inflector: T.class_of(ActiveSupport::Inflector), custom_associations: CustomAssociations).void }
23
23
  def initialize(inflector:, custom_associations: Set.new)
24
24
  @inflector = inflector
25
25
  @associations = T.let(RAILS_ASSOCIATIONS + custom_associations, CustomAssociations)