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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -0
- data/Gemfile.lock +22 -19
- data/README.md +7 -2
- data/UPGRADING.md +54 -0
- data/USAGE.md +5 -40
- data/lib/packwerk/application_validator.rb +88 -93
- data/lib/packwerk/association_inspector.rb +1 -1
- data/lib/packwerk/cache.rb +169 -0
- data/lib/packwerk/cli.rb +32 -28
- data/lib/packwerk/configuration.rb +40 -5
- data/lib/packwerk/constant_discovery.rb +21 -5
- data/lib/packwerk/constant_name_inspector.rb +1 -1
- data/lib/packwerk/deprecated_references.rb +1 -1
- data/lib/packwerk/file_processor.rb +34 -15
- data/lib/packwerk/files_for_processing.rb +49 -22
- data/lib/packwerk/formatters/offenses_formatter.rb +1 -1
- data/lib/packwerk/formatters/progress_formatter.rb +1 -1
- data/lib/packwerk/generators/configuration_file.rb +4 -19
- data/lib/packwerk/generators/templates/packwerk.yml.erb +5 -5
- data/lib/packwerk/node.rb +2 -1
- data/lib/packwerk/node_processor.rb +6 -6
- data/lib/packwerk/node_processor_factory.rb +3 -4
- data/lib/packwerk/node_visitor.rb +3 -0
- data/lib/packwerk/offense.rb +10 -2
- data/lib/packwerk/package.rb +1 -1
- data/lib/packwerk/package_set.rb +3 -2
- data/lib/packwerk/parse_run.rb +37 -17
- data/lib/packwerk/parsed_constant_definitions.rb +4 -4
- data/lib/packwerk/parsers/erb.rb +2 -0
- data/lib/packwerk/parsers/factory.rb +2 -0
- data/lib/packwerk/parsers/parser_interface.rb +17 -0
- data/lib/packwerk/parsers/ruby.rb +2 -0
- data/lib/packwerk/parsers.rb +1 -0
- data/lib/packwerk/reference_checking/checkers/checker.rb +1 -1
- data/lib/packwerk/reference_checking/reference_checker.rb +2 -1
- data/lib/packwerk/reference_extractor.rb +78 -20
- data/lib/packwerk/reference_offense.rb +8 -3
- data/lib/packwerk/result.rb +2 -2
- data/lib/packwerk/run_context.rb +62 -38
- data/lib/packwerk/spring_command.rb +1 -1
- data/lib/packwerk/unresolved_reference.rb +10 -0
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +5 -9
- data/packwerk.gemspec +1 -0
- data/sorbet/config +1 -0
- data/sorbet/rbi/gems/tapioca@0.4.19.rbi +1 -1
- data/sorbet/tapioca/require.rb +1 -1
- metadata +21 -7
- data/lib/packwerk/generators/inflections_file.rb +0 -43
- data/lib/packwerk/generators/templates/inflections.yml +0 -6
- data/lib/packwerk/inflections/custom.rb +0 -33
- data/lib/packwerk/inflections/default.rb +0 -73
- data/lib/packwerk/inflector.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0270de859808f51bbfa45a7140813744da0555c57d9d60432ba4e166ab63c4a
|
4
|
+
data.tar.gz: 90465398a3e04f83ba4c0e3b290dca72677b166003640504c7470b181e556d73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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.
|
146
|
-
mini_portile2 (~> 2.
|
147
|
+
nokogiri (1.13.3)
|
148
|
+
mini_portile2 (~> 2.8.0)
|
147
149
|
racc (~> 1.4)
|
148
|
-
nokogiri (1.
|
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.
|
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.
|
195
|
-
sorbet (0.5.
|
196
|
-
sorbet-static (= 0.5.
|
197
|
-
sorbet-runtime (0.5.
|
198
|
-
sorbet-static (0.5.
|
199
|
-
sorbet-static (0.5.
|
200
|
-
sorbet-static (0.5.
|
201
|
-
sorbet-static (0.5.
|
202
|
-
sorbet-static (0.5.
|
203
|
-
sorbet-static (0.5.
|
204
|
-
sorbet-static (0.5.
|
205
|
-
sorbet-static (0.5.
|
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.
|
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
|
-
|
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
|
-
|
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:
|
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
|
-
|
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
|
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
|
-
|
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 =
|
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,
|
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
|
-
|
322
|
-
|
323
|
-
|
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
|
-
|
354
|
-
|
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)
|