packwerk 2.0.0 → 2.2.0
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/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 [](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
|