packwerk 2.0.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +26 -22
- data/README.md +13 -1
- data/USAGE.md +7 -0
- data/lib/packwerk/application_load_paths.rb +12 -18
- data/lib/packwerk/application_validator.rb +88 -40
- data/lib/packwerk/cache.rb +169 -0
- data/lib/packwerk/cli.rb +29 -13
- data/lib/packwerk/configuration.rb +17 -12
- data/lib/packwerk/constant_discovery.rb +20 -4
- data/lib/packwerk/constant_name_inspector.rb +1 -1
- data/lib/packwerk/deprecated_references.rb +1 -1
- data/lib/packwerk/file_processor.rb +43 -22
- data/lib/packwerk/files_for_processing.rb +55 -26
- data/lib/packwerk/generators/templates/packwerk.yml.erb +6 -0
- 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 +4 -3
- 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 +19 -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 +3 -4
- data/lib/packwerk/reference_extractor.rb +72 -20
- data/lib/packwerk/reference_offense.rb +8 -3
- data/lib/packwerk/result.rb +2 -2
- data/lib/packwerk/run_context.rb +62 -36
- 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 +2 -0
- data/packwerk.gemspec +4 -2
- data/sorbet/config +1 -0
- data/sorbet/rbi/gems/tapioca@0.4.19.rbi +1 -1
- data/sorbet/tapioca/require.rb +1 -1
- metadata +36 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dfc6290decd1cdaa95140a3d8495fb324c3c6d3cf01b4794e40068baff8eb663
|
4
|
+
data.tar.gz: cb01a5adb6f0118473a635b3d38416a4287207651262ee8a20b0f7833d8bfa37
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85e01c90d7d35d169f773b4a9dd281b0cf563c5586d68e7592317d5747da81aba396b0330a9d77e776c01d098353fbc27c08d80f314ff6db88e5bf3117d06b41
|
7
|
+
data.tar.gz: 650a4a5852353ed999c54159c7951905c3984e1d21a17c99e51c94462733df2f20f55931b76ecab2590e179c41756da862df170263d7c6077e5976465e802fe9
|
data/Gemfile.lock
CHANGED
@@ -87,15 +87,16 @@ GIT
|
|
87
87
|
PATH
|
88
88
|
remote: .
|
89
89
|
specs:
|
90
|
-
packwerk (2.
|
90
|
+
packwerk (2.2.0)
|
91
91
|
activesupport (>= 5.2)
|
92
92
|
ast
|
93
93
|
better_html
|
94
94
|
bundler
|
95
|
-
constant_resolver
|
95
|
+
constant_resolver (>= 0.2.0)
|
96
|
+
digest
|
96
97
|
parallel
|
97
98
|
parser
|
98
|
-
sorbet-runtime
|
99
|
+
sorbet-runtime (>= 0.5.9914)
|
99
100
|
|
100
101
|
GEM
|
101
102
|
remote: https://rubygems.org/
|
@@ -116,8 +117,9 @@ GEM
|
|
116
117
|
commander (4.5.2)
|
117
118
|
highline (~> 2.0.0)
|
118
119
|
concurrent-ruby (1.1.8)
|
119
|
-
constant_resolver (0.
|
120
|
+
constant_resolver (0.2.0)
|
120
121
|
crass (1.0.6)
|
122
|
+
digest (3.1.0)
|
121
123
|
erubi (1.10.0)
|
122
124
|
globalid (0.4.2)
|
123
125
|
activesupport (>= 4.2.0)
|
@@ -136,16 +138,16 @@ GEM
|
|
136
138
|
marcel (1.0.0)
|
137
139
|
method_source (1.0.0)
|
138
140
|
mini_mime (1.0.3)
|
139
|
-
mini_portile2 (2.
|
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.4)
|
148
|
+
mini_portile2 (~> 2.8.0)
|
147
149
|
racc (~> 1.4)
|
148
|
-
nokogiri (1.
|
150
|
+
nokogiri (1.13.4-x86_64-darwin)
|
149
151
|
racc (~> 1.4)
|
150
152
|
parallel (1.20.1)
|
151
153
|
parlour (6.0.0)
|
@@ -159,7 +161,7 @@ GEM
|
|
159
161
|
coderay (~> 1.1)
|
160
162
|
method_source (~> 1.0)
|
161
163
|
psych (3.3.2)
|
162
|
-
racc (1.
|
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.9914)
|
198
|
+
sorbet-static (= 0.5.9914)
|
199
|
+
sorbet-runtime (0.5.9914)
|
200
|
+
sorbet-static (0.5.9914-universal-darwin-14)
|
201
|
+
sorbet-static (0.5.9914-universal-darwin-15)
|
202
|
+
sorbet-static (0.5.9914-universal-darwin-16)
|
203
|
+
sorbet-static (0.5.9914-universal-darwin-17)
|
204
|
+
sorbet-static (0.5.9914-universal-darwin-18)
|
205
|
+
sorbet-static (0.5.9914-universal-darwin-19)
|
206
|
+
sorbet-static (0.5.9914-universal-darwin-20)
|
207
|
+
sorbet-static (0.5.9914-universal-darwin-21)
|
208
|
+
sorbet-static (0.5.9914-x86_64-linux)
|
206
209
|
spoom (1.1.0)
|
207
210
|
colorize
|
208
211
|
sorbet (>= 0.5.6347)
|
@@ -255,6 +258,7 @@ DEPENDENCIES
|
|
255
258
|
sorbet-runtime
|
256
259
|
spring
|
257
260
|
tapioca
|
261
|
+
zeitwerk
|
258
262
|
|
259
263
|
BUNDLED WITH
|
260
|
-
2.
|
264
|
+
2.3.5
|
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Packwerk [![Build Status](https://github.com/Shopify/packwerk/workflows/CI/badge.svg)](https://github.com/Shopify/packwerk/actions?query=workflow%3ACI)
|
2
2
|
|
3
|
-
|
3
|
+
### ⚠️ While Shopify is actively using `packwerk`, we consider it feature complete.
|
4
|
+
We are keeping `packwerk` compatible with current versions of Ruby and Rails, but will accept feature requests only in rare cases. Please submit bug fixes though!
|
5
|
+
|
6
|
+
---
|
4
7
|
|
5
8
|
> "I know who you are and because of that I know what you do."
|
6
9
|
> This knowledge is a dependency that raises the cost of change.
|
@@ -56,6 +59,15 @@ Read [USAGE.md](USAGE.md) for usage once Packwerk is installed on your project.
|
|
56
59
|
|
57
60
|
"Packwerk" is pronounced [[ˈpakvɛʁk]](https://cdn.shopify.com/s/files/1/0258/7469/files/packwerk.mp3).
|
58
61
|
|
62
|
+
## Ecosystem
|
63
|
+
|
64
|
+
Various third parties have built tooling on top of packwerk. Here's a selection of some that might prove useful:
|
65
|
+
|
66
|
+
- https://github.com/bellroy/graphwerk draws a graph of your package dependencies
|
67
|
+
- https://github.com/Gusto/packwerk-vscode integrates packwerk into Visual Studio Code so you can see violations right in your editor
|
68
|
+
- https://github.com/Gusto/stimpack sets up Rails autoloading, as well as `rspec` and `FactoryBot` integration, for packages arranged in a flat list. Stimpack is quite convenient, but for autoloading we recommend to use `Rails::Engine`s instead.
|
69
|
+
- https://github.com/BigRails/danger-packwerk integrates packwerk with [danger.systems](https://danger.systems) to provide packwerk feedback as Github inline PR comments
|
70
|
+
|
59
71
|
## Development
|
60
72
|
|
61
73
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/USAGE.md
CHANGED
@@ -77,6 +77,8 @@ Packwerk reads from the `packwerk.yml` configuration file in the root directory.
|
|
77
77
|
| package_paths | **/ | a single pattern or a list of patterns to find package configuration files, see: [Defining packages](#Defining-packages) |
|
78
78
|
| custom_associations | N/A | list of custom associations, if any |
|
79
79
|
| parallel | true | when true, fork code parsing out to subprocesses |
|
80
|
+
| cache | false | when true, caches the results of parsing files |
|
81
|
+
| cache_directory | tmp/cache/packwerk | the directory that will hold the packwerk cache |
|
80
82
|
|
81
83
|
### Using a custom ERB parser
|
82
84
|
|
@@ -100,6 +102,11 @@ end
|
|
100
102
|
Packwerk::Parsers::Factory.instance.erb_parser_class = CustomParser
|
101
103
|
```
|
102
104
|
|
105
|
+
## Using the cache
|
106
|
+
Packwerk ships with an cache to help speed up file parsing. You can turn this on by setting `cache: true` in `packwerk.yml`.
|
107
|
+
|
108
|
+
This will write to `tmp/cache/packwerk`.
|
109
|
+
|
103
110
|
## Validating the package system
|
104
111
|
|
105
112
|
There are some criteria that an application must meet in order to have a valid package system. These criteria include having a valid autoload path cache, package definition files, and application folder structure. The dependency graph within the package system also has to be acyclic.
|
@@ -9,7 +9,7 @@ module Packwerk
|
|
9
9
|
class << self
|
10
10
|
extend T::Sig
|
11
11
|
|
12
|
-
sig { params(root: String, environment: String).returns(T::
|
12
|
+
sig { params(root: String, environment: String).returns(T::Hash[String, Module]) }
|
13
13
|
def extract_relevant_paths(root, environment)
|
14
14
|
require_application(root, environment)
|
15
15
|
all_paths = extract_application_autoload_paths
|
@@ -18,36 +18,30 @@ module Packwerk
|
|
18
18
|
relative_path_strings(relevant_paths)
|
19
19
|
end
|
20
20
|
|
21
|
-
sig { returns(T::
|
21
|
+
sig { returns(T::Hash[String, Module]) }
|
22
22
|
def extract_application_autoload_paths
|
23
|
-
Rails.
|
24
|
-
.
|
25
|
-
|
26
|
-
.flat_map do |engine|
|
27
|
-
paths = (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths)
|
28
|
-
paths.map(&:to_s).uniq
|
29
|
-
end
|
23
|
+
Rails.autoloaders.inject({}) do |h, loader|
|
24
|
+
h.merge(loader.root_dirs)
|
25
|
+
end
|
30
26
|
end
|
31
27
|
|
32
28
|
sig do
|
33
|
-
params(all_paths: T::
|
34
|
-
.returns(T::
|
29
|
+
params(all_paths: T::Hash[String, Module], bundle_path: Pathname, rails_root: Pathname)
|
30
|
+
.returns(T::Hash[Pathname, Module])
|
35
31
|
end
|
36
32
|
def filter_relevant_paths(all_paths, bundle_path: Bundler.bundle_path, rails_root: Rails.root)
|
37
33
|
bundle_path_match = bundle_path.join("**")
|
38
34
|
rails_root_match = rails_root.join("**")
|
39
35
|
|
40
36
|
all_paths
|
41
|
-
.
|
37
|
+
.transform_keys { |path| Pathname.new(path).expand_path }
|
42
38
|
.select { |path| path.fnmatch(rails_root_match.to_s) } # path needs to be in application directory
|
43
39
|
.reject { |path| path.fnmatch(bundle_path_match.to_s) } # reject paths from vendored gems
|
44
40
|
end
|
45
41
|
|
46
|
-
sig { params(
|
47
|
-
def relative_path_strings(
|
48
|
-
|
49
|
-
.map { |path| path.relative_path_from(rails_root).to_s }
|
50
|
-
.uniq
|
42
|
+
sig { params(load_paths: T::Hash[Pathname, Module], rails_root: Pathname).returns(T::Hash[String, Module]) }
|
43
|
+
def relative_path_strings(load_paths, rails_root: Rails.root)
|
44
|
+
load_paths.transform_keys { |path| Pathname.new(path).relative_path_from(rails_root).to_s }
|
51
45
|
end
|
52
46
|
|
53
47
|
private
|
@@ -65,7 +59,7 @@ module Packwerk
|
|
65
59
|
end
|
66
60
|
end
|
67
61
|
|
68
|
-
sig { params(paths: T::
|
62
|
+
sig { params(paths: T::Hash[T.untyped, Module]).void }
|
69
63
|
def assert_load_paths_present(paths)
|
70
64
|
if paths.empty?
|
71
65
|
raise <<~EOS
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "constant_resolver"
|
@@ -9,14 +9,36 @@ module Packwerk
|
|
9
9
|
# Checks the structure of the application and its packwerk configuration to make sure we can run a check and deliver
|
10
10
|
# correct results.
|
11
11
|
class ApplicationValidator
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig do
|
15
|
+
params(
|
16
|
+
config_file_path: String,
|
17
|
+
configuration: Configuration,
|
18
|
+
environment: String
|
19
|
+
).void
|
20
|
+
end
|
12
21
|
def initialize(config_file_path:, configuration:, environment:)
|
13
22
|
@config_file_path = config_file_path
|
14
23
|
@configuration = configuration
|
15
24
|
@environment = environment
|
25
|
+
@package_set = T.let(PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob),
|
26
|
+
PackageSet)
|
16
27
|
end
|
17
28
|
|
18
|
-
Result
|
29
|
+
class Result < T::Struct
|
30
|
+
extend T::Sig
|
31
|
+
|
32
|
+
const :ok, T::Boolean
|
33
|
+
const :error_value, T.nilable(String)
|
34
|
+
|
35
|
+
sig { returns(T::Boolean) }
|
36
|
+
def ok?
|
37
|
+
ok
|
38
|
+
end
|
39
|
+
end
|
19
40
|
|
41
|
+
sig { returns(Result) }
|
20
42
|
def check_all
|
21
43
|
results = [
|
22
44
|
check_package_manifests_for_privacy,
|
@@ -31,6 +53,7 @@ module Packwerk
|
|
31
53
|
merge_results(results)
|
32
54
|
end
|
33
55
|
|
56
|
+
sig { returns(Result) }
|
34
57
|
def check_package_manifests_for_privacy
|
35
58
|
privacy_settings = package_manifests_settings_for("enforce_privacy")
|
36
59
|
|
@@ -39,13 +62,13 @@ module Packwerk
|
|
39
62
|
load_paths: @configuration.load_paths
|
40
63
|
)
|
41
64
|
|
42
|
-
results = []
|
65
|
+
results = T.let([], T::Array[Result])
|
43
66
|
|
44
67
|
privacy_settings.each do |config_file_path, setting|
|
45
68
|
next unless setting.is_a?(Array)
|
46
69
|
constants = setting
|
47
70
|
|
48
|
-
assert_constants_can_be_loaded(constants)
|
71
|
+
results += assert_constants_can_be_loaded(constants, config_file_path)
|
49
72
|
|
50
73
|
constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
|
51
74
|
|
@@ -61,6 +84,7 @@ module Packwerk
|
|
61
84
|
merge_results(results, separator: "\n---\n")
|
62
85
|
end
|
63
86
|
|
87
|
+
sig { returns(Result) }
|
64
88
|
def check_package_manifest_syntax
|
65
89
|
errors = []
|
66
90
|
|
@@ -102,12 +126,13 @@ module Packwerk
|
|
102
126
|
end
|
103
127
|
|
104
128
|
if errors.empty?
|
105
|
-
Result.new(true)
|
129
|
+
Result.new(ok: true)
|
106
130
|
else
|
107
|
-
Result.new(false, errors.join("\n---\n"))
|
131
|
+
Result.new(ok: false, error_value: errors.join("\n---\n"))
|
108
132
|
end
|
109
133
|
end
|
110
134
|
|
135
|
+
sig { returns(Result) }
|
111
136
|
def check_application_structure
|
112
137
|
resolver = ConstantResolver.new(
|
113
138
|
root_path: @configuration.root_path.to_s,
|
@@ -116,26 +141,27 @@ module Packwerk
|
|
116
141
|
|
117
142
|
begin
|
118
143
|
resolver.file_map
|
119
|
-
Result.new(true)
|
144
|
+
Result.new(ok: true)
|
120
145
|
rescue => e
|
121
|
-
Result.new(false, e.message)
|
146
|
+
Result.new(ok: false, error_value: e.message)
|
122
147
|
end
|
123
148
|
end
|
124
149
|
|
150
|
+
sig { returns(Result) }
|
125
151
|
def check_acyclic_graph
|
126
|
-
edges = package_set.flat_map do |package|
|
127
|
-
package.dependencies.map { |dependency| [package, package_set.fetch(dependency)] }
|
152
|
+
edges = @package_set.flat_map do |package|
|
153
|
+
package.dependencies.map { |dependency| [package, @package_set.fetch(dependency)] }
|
128
154
|
end
|
129
|
-
dependency_graph =
|
155
|
+
dependency_graph = Graph.new(*T.unsafe(edges))
|
130
156
|
|
131
157
|
cycle_strings = build_cycle_strings(dependency_graph.cycles)
|
132
158
|
|
133
159
|
if dependency_graph.acyclic?
|
134
|
-
Result.new(true)
|
160
|
+
Result.new(ok: true)
|
135
161
|
else
|
136
162
|
Result.new(
|
137
|
-
false,
|
138
|
-
<<~EOS
|
163
|
+
ok: false,
|
164
|
+
error_value: <<~EOS
|
139
165
|
Expected the package dependency graph to be acyclic, but it contains the following cycles:
|
140
166
|
|
141
167
|
#{cycle_strings.join("\n")}
|
@@ -144,6 +170,7 @@ module Packwerk
|
|
144
170
|
end
|
145
171
|
end
|
146
172
|
|
173
|
+
sig { returns(Result) }
|
147
174
|
def check_package_manifest_paths
|
148
175
|
all_package_manifests = package_manifests("**/")
|
149
176
|
package_paths_package_manifests = package_manifests(package_glob)
|
@@ -151,11 +178,11 @@ module Packwerk
|
|
151
178
|
difference = all_package_manifests - package_paths_package_manifests
|
152
179
|
|
153
180
|
if difference.empty?
|
154
|
-
Result.new(true)
|
181
|
+
Result.new(ok: true)
|
155
182
|
else
|
156
183
|
Result.new(
|
157
|
-
false,
|
158
|
-
<<~EOS
|
184
|
+
ok: false,
|
185
|
+
error_value: <<~EOS
|
159
186
|
Expected package paths for all package.ymls to be specified, but paths were missing for the following manifests:
|
160
187
|
|
161
188
|
#{relative_paths(difference).join("\n")}
|
@@ -164,6 +191,7 @@ module Packwerk
|
|
164
191
|
end
|
165
192
|
end
|
166
193
|
|
194
|
+
sig { returns(Result) }
|
167
195
|
def check_valid_package_dependencies
|
168
196
|
packages_dependencies = package_manifests_settings_for("dependencies")
|
169
197
|
.delete_if { |_, deps| deps.nil? }
|
@@ -175,7 +203,7 @@ module Packwerk
|
|
175
203
|
end
|
176
204
|
|
177
205
|
if packages_with_invalid_dependencies.empty?
|
178
|
-
Result.new(true)
|
206
|
+
Result.new(ok: true)
|
179
207
|
else
|
180
208
|
error_locations = packages_with_invalid_dependencies.map do |package, invalid_dependencies|
|
181
209
|
package ||= @configuration.root_path
|
@@ -189,8 +217,8 @@ module Packwerk
|
|
189
217
|
end
|
190
218
|
|
191
219
|
Result.new(
|
192
|
-
false,
|
193
|
-
<<~EOS
|
220
|
+
ok: false,
|
221
|
+
error_value: <<~EOS
|
194
222
|
These dependencies do not point to valid packages:
|
195
223
|
|
196
224
|
#{error_locations.join("\n")}
|
@@ -199,16 +227,17 @@ module Packwerk
|
|
199
227
|
end
|
200
228
|
end
|
201
229
|
|
230
|
+
sig { returns(Result) }
|
202
231
|
def check_root_package_exists
|
203
232
|
root_package_path = File.join(@configuration.root_path, "package.yml")
|
204
233
|
all_packages_manifests = package_manifests(package_glob)
|
205
234
|
|
206
235
|
if all_packages_manifests.include?(root_package_path)
|
207
|
-
Result.new(true)
|
236
|
+
Result.new(ok: true)
|
208
237
|
else
|
209
238
|
Result.new(
|
210
|
-
false,
|
211
|
-
<<~EOS
|
239
|
+
ok: false,
|
240
|
+
error_value: <<~EOS
|
212
241
|
A root package does not exist. Create an empty `package.yml` at the root directory.
|
213
242
|
EOS
|
214
243
|
)
|
@@ -224,6 +253,7 @@ module Packwerk
|
|
224
253
|
# to the string:
|
225
254
|
#
|
226
255
|
# ["a -> b -> c -> a", "b -> c -> b"]
|
256
|
+
sig { params(cycles: T.untyped).returns(T::Array[String]) }
|
227
257
|
def build_cycle_strings(cycles)
|
228
258
|
cycles.map do |cycle|
|
229
259
|
cycle_strings = cycle.map(&:to_s)
|
@@ -232,84 +262,102 @@ module Packwerk
|
|
232
262
|
end
|
233
263
|
end
|
234
264
|
|
265
|
+
sig { params(setting: T.untyped).returns(T.untyped) }
|
235
266
|
def package_manifests_settings_for(setting)
|
236
267
|
package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
237
268
|
end
|
238
269
|
|
270
|
+
sig { params(list: T.untyped).returns(T.untyped) }
|
239
271
|
def format_yaml_strings(list)
|
240
272
|
list.sort.map { |p| "- \"#{p}\"" }.join("\n")
|
241
273
|
end
|
242
274
|
|
275
|
+
sig { returns(T.any(T::Array[String], String)) }
|
243
276
|
def package_glob
|
244
277
|
@configuration.package_paths || "**"
|
245
278
|
end
|
246
279
|
|
280
|
+
sig { params(glob_pattern: T.any(T::Array[String], String)).returns(T::Array[String]) }
|
247
281
|
def package_manifests(glob_pattern = package_glob)
|
248
282
|
PackageSet.package_paths(@configuration.root_path, glob_pattern, @configuration.exclude)
|
249
283
|
.map { |f| File.realpath(f) }
|
250
284
|
end
|
251
285
|
|
286
|
+
sig { params(paths: T::Array[String]).returns(T::Array[Pathname]) }
|
252
287
|
def relative_paths(paths)
|
253
288
|
paths.map { |path| relative_path(path) }
|
254
289
|
end
|
255
290
|
|
291
|
+
sig { params(path: String).returns(Pathname) }
|
256
292
|
def relative_path(path)
|
257
293
|
Pathname.new(path).relative_path_from(@configuration.root_path)
|
258
294
|
end
|
259
295
|
|
296
|
+
sig { params(path: T.untyped).returns(T::Boolean) }
|
260
297
|
def invalid_package_path?(path)
|
261
298
|
# Packages at the root can be implicitly specified as "."
|
262
299
|
return false if path == "."
|
263
300
|
|
264
|
-
package_path = File.join(@configuration.root_path, path,
|
301
|
+
package_path = File.join(@configuration.root_path, path, PackageSet::PACKAGE_CONFIG_FILENAME)
|
265
302
|
!File.file?(package_path)
|
266
303
|
end
|
267
304
|
|
268
|
-
|
269
|
-
|
270
|
-
|
305
|
+
sig { params(constants: T.untyped, config_file_path: String).returns(T::Array[Result]) }
|
306
|
+
def assert_constants_can_be_loaded(constants, config_file_path)
|
307
|
+
constants.map do |constant|
|
308
|
+
if !constant.start_with?("::")
|
309
|
+
Result.new(
|
310
|
+
ok: false,
|
311
|
+
error_value: "'#{constant}', listed in the 'enforce_privacy' option in #{config_file_path}, is invalid.\n"\
|
312
|
+
"Private constants need to be prefixed with the top-level namespace operator `::`."
|
313
|
+
)
|
314
|
+
else
|
315
|
+
constant.try(&:constantize) && Result.new(ok: true)
|
316
|
+
end
|
317
|
+
end
|
271
318
|
end
|
272
319
|
|
320
|
+
sig { params(name: T.untyped, config_file_path: T.untyped).returns(Result) }
|
273
321
|
def private_constant_unresolvable(name, config_file_path)
|
274
322
|
explicit_filepath = (name.start_with?("::") ? name[2..-1] : name).underscore + ".rb"
|
275
323
|
|
276
324
|
Result.new(
|
277
|
-
false,
|
278
|
-
"'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
|
325
|
+
ok: false,
|
326
|
+
error_value: "'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
|
279
327
|
"This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
|
280
328
|
"file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
|
281
329
|
"private. Add a #{explicit_filepath} file to explicitly define the constant."
|
282
330
|
)
|
283
331
|
end
|
284
332
|
|
333
|
+
sig { params(name: T.untyped, location: T.untyped, config_file_path: T.untyped).returns(Result) }
|
285
334
|
def check_private_constant_location(name, location, config_file_path)
|
286
|
-
declared_package = package_set.package_from_path(relative_path(config_file_path))
|
287
|
-
constant_package = package_set.package_from_path(location)
|
335
|
+
declared_package = @package_set.package_from_path(relative_path(config_file_path))
|
336
|
+
constant_package = @package_set.package_from_path(location)
|
288
337
|
|
289
338
|
if constant_package == declared_package
|
290
|
-
Result.new(true)
|
339
|
+
Result.new(ok: true)
|
291
340
|
else
|
292
341
|
Result.new(
|
293
|
-
false,
|
294
|
-
"'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
|
342
|
+
ok: false,
|
343
|
+
error_value: "'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
|
295
344
|
"defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
|
296
345
|
)
|
297
346
|
end
|
298
347
|
end
|
299
348
|
|
300
|
-
|
301
|
-
|
349
|
+
sig do
|
350
|
+
params(results: T::Array[Result], separator: String, errors_headline: String).returns(Result)
|
302
351
|
end
|
303
|
-
|
304
352
|
def merge_results(results, separator: "\n===\n", errors_headline: "")
|
305
353
|
results.reject!(&:ok?)
|
306
354
|
|
307
355
|
if results.empty?
|
308
|
-
Result.new(true)
|
356
|
+
Result.new(ok: true)
|
309
357
|
else
|
310
358
|
Result.new(
|
311
|
-
false,
|
312
|
-
errors_headline + results.map(&:error_value).join(separator)
|
359
|
+
ok: false,
|
360
|
+
error_value: errors_headline + results.map(&:error_value).join(separator)
|
313
361
|
)
|
314
362
|
end
|
315
363
|
end
|