packwerk 1.4.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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)