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