packwerk 1.0.0 → 1.1.2
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/.github/pull_request_template.md +8 -7
- data/.github/workflows/ci.yml +1 -1
- data/.gitignore +1 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +5 -2
- data/README.md +5 -3
- data/TROUBLESHOOT.md +1 -1
- data/USAGE.md +56 -19
- data/exe/packwerk +1 -1
- data/lib/packwerk.rb +3 -3
- data/lib/packwerk/application_load_paths.rb +68 -0
- data/lib/packwerk/application_validator.rb +96 -70
- data/lib/packwerk/association_inspector.rb +50 -20
- data/lib/packwerk/cache_deprecated_references.rb +55 -0
- data/lib/packwerk/checker.rb +23 -0
- data/lib/packwerk/checking_deprecated_references.rb +5 -2
- data/lib/packwerk/cli.rb +65 -56
- data/lib/packwerk/commands/detect_stale_violations_command.rb +60 -0
- data/lib/packwerk/commands/offense_progress_marker.rb +24 -0
- data/lib/packwerk/commands/result.rb +13 -0
- data/lib/packwerk/commands/update_deprecations_command.rb +81 -0
- data/lib/packwerk/configuration.rb +6 -34
- data/lib/packwerk/const_node_inspector.rb +28 -17
- data/lib/packwerk/dependency_checker.rb +16 -5
- data/lib/packwerk/deprecated_references.rb +24 -1
- data/lib/packwerk/detect_stale_deprecated_references.rb +14 -0
- data/lib/packwerk/file_processor.rb +4 -4
- data/lib/packwerk/formatters/offenses_formatter.rb +48 -0
- data/lib/packwerk/formatters/progress_formatter.rb +6 -2
- data/lib/packwerk/generators/application_validation.rb +2 -2
- data/lib/packwerk/generators/templates/package.yml +4 -0
- data/lib/packwerk/generators/templates/packwerk +2 -2
- data/lib/packwerk/generators/templates/packwerk.yml.erb +1 -1
- data/lib/packwerk/inflector.rb +17 -8
- data/lib/packwerk/node.rb +78 -39
- data/lib/packwerk/node_processor.rb +14 -3
- data/lib/packwerk/node_processor_factory.rb +39 -0
- data/lib/packwerk/offense.rb +4 -6
- data/lib/packwerk/output_style.rb +20 -0
- data/lib/packwerk/output_styles/coloured.rb +29 -0
- data/lib/packwerk/output_styles/plain.rb +26 -0
- data/lib/packwerk/package.rb +8 -1
- data/lib/packwerk/package_set.rb +13 -5
- data/lib/packwerk/parsed_constant_definitions.rb +4 -4
- data/lib/packwerk/parsers/erb.rb +4 -0
- data/lib/packwerk/parsers/factory.rb +10 -1
- data/lib/packwerk/privacy_checker.rb +26 -5
- data/lib/packwerk/run_context.rb +70 -46
- data/lib/packwerk/sanity_checker.rb +1 -1
- data/lib/packwerk/spring_command.rb +1 -1
- data/lib/packwerk/updating_deprecated_references.rb +2 -39
- data/lib/packwerk/version.rb +1 -1
- data/packwerk.gemspec +2 -2
- metadata +15 -8
- data/lib/packwerk/output_styles.rb +0 -41
- data/static/packwerk-check-demo.png +0 -0
- data/static/packwerk_check.gif +0 -0
- data/static/packwerk_check_violation.gif +0 -0
- data/static/packwerk_update.gif +0 -0
- data/static/packwerk_validate.gif +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd008f86d84fd0224aac9d8f92bc104123f23869e9381c947b3e51864523c947
|
4
|
+
data.tar.gz: debf7f89ce8f8419a02f2af84c3f1900c7bc2f2ac79cce535284e3e697133d74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2bf12776a5e5cd4f3f9d06f3bbabb3d9b0c6d873325dba9a7bc43dc49ffedbc119678b77a2087cface6428bf564e037b75386abb8fca2d06a313e617461db53a
|
7
|
+
data.tar.gz: d6ca7a42dea1c5338c40e89b6fe6b14baa43372cf44e22af3f19763ee1048c03629e34f4466b632e7f5b6d82932b8e05b2d02d5e26ebf91852334b9f2ca6d5b2
|
@@ -7,21 +7,22 @@
|
|
7
7
|
## What should reviewers focus on?
|
8
8
|
|
9
9
|
|
10
|
-
|
11
10
|
## Type of Change
|
12
11
|
|
13
|
-
- [ ]
|
14
|
-
- [ ] New feature
|
15
|
-
- [ ]
|
16
|
-
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
17
|
-
- [ ] This change requires a documentation update
|
12
|
+
- [ ] Bugfix
|
13
|
+
- [ ] New feature
|
14
|
+
- [ ] Non-breaking change (a change that doesn't alter functionality - i.e., code refactor, configs, etc.)
|
18
15
|
|
19
16
|
### Additional Release Notes
|
20
17
|
|
18
|
+
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
19
|
+
|
21
20
|
Include any notes here to include in the release description. For example, if you selected "breaking change" above, leave notes on how users can transition to this version.
|
22
21
|
|
23
22
|
If no additional notes are necessary, delete this section or leave it unchanged.
|
24
23
|
|
25
24
|
## Checklist
|
26
25
|
|
27
|
-
- [ ]
|
26
|
+
- [ ] I have updated the documentation accordingly.
|
27
|
+
- [ ] I have added tests to cover my changes.
|
28
|
+
- [ ] It is safe to rollback this change.
|
data/.github/workflows/ci.yml
CHANGED
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -85,7 +85,7 @@ GIT
|
|
85
85
|
PATH
|
86
86
|
remote: .
|
87
87
|
specs:
|
88
|
-
packwerk (1.
|
88
|
+
packwerk (1.1.2)
|
89
89
|
activesupport (>= 5.2)
|
90
90
|
ast
|
91
91
|
better_html
|
@@ -137,6 +137,8 @@ GEM
|
|
137
137
|
mini_mime (1.0.2)
|
138
138
|
mini_portile2 (2.4.0)
|
139
139
|
minitest (5.14.0)
|
140
|
+
minitest-focus (1.2.1)
|
141
|
+
minitest (>= 4, < 6)
|
140
142
|
mocha (1.11.2)
|
141
143
|
nio4r (2.5.2)
|
142
144
|
nokogiri (1.10.9)
|
@@ -180,7 +182,7 @@ GEM
|
|
180
182
|
smart_properties (1.15.0)
|
181
183
|
sorbet (0.5.5898)
|
182
184
|
sorbet-static (= 0.5.5898)
|
183
|
-
sorbet-runtime (0.5.
|
185
|
+
sorbet-runtime (0.5.6049)
|
184
186
|
sorbet-static (0.5.5898-universal-darwin-19)
|
185
187
|
spoom (1.0.4)
|
186
188
|
colorize
|
@@ -220,6 +222,7 @@ DEPENDENCIES
|
|
220
222
|
byebug
|
221
223
|
constant_resolver
|
222
224
|
m
|
225
|
+
minitest-focus
|
223
226
|
mocha
|
224
227
|
packwerk!
|
225
228
|
rails!
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
Packwerk is a Ruby gem used to enforce boundaries and modularize Rails applications.
|
4
4
|
|
5
5
|
Packwerk can be used to:
|
6
|
-
* Combine
|
6
|
+
* Combine groups of files into packages
|
7
7
|
* Define package-level constant visibility (i.e. have publicly accessible constants)
|
8
8
|
* Enforce privacy (inbound) and dependency (outbound) boundaries between packages
|
9
9
|
* Help existing codebases to become more modular without obstructing development
|
@@ -16,9 +16,9 @@ Packwerk supports MRI versions 2.6 and above.
|
|
16
16
|
|
17
17
|
## Demo
|
18
18
|
|
19
|
-
Watch a [1-minute video demo](https://
|
19
|
+
Watch a [1-minute video demo](https://www.youtube.com/watch?v=NwqlyBAxVpQ&feature=youtu.be) on how Packwerk works.
|
20
20
|
|
21
|
-
[](https://
|
21
|
+
[](https://www.youtube.com/watch?v=NwqlyBAxVpQ&feature=youtu.be)
|
22
22
|
|
23
23
|
## Installation
|
24
24
|
|
@@ -28,6 +28,8 @@ Watch a [1-minute video demo](https://drive.google.com/file/d/1D-t1nYduwgpHAP4DH
|
|
28
28
|
gem 'packwerk'
|
29
29
|
```
|
30
30
|
|
31
|
+
_Note: Packwerk has to be grouped in production environment within the Gemfile if your Rails app has custom inflections._
|
32
|
+
|
31
33
|
2. Install the gem
|
32
34
|
|
33
35
|
Execute:
|
data/TROUBLESHOOT.md
CHANGED
@@ -16,7 +16,7 @@ You can specify folders or packages in Packwerk commands for a shorter run time:
|
|
16
16
|
|
17
17
|
bundle exec packwerk check components/your_package
|
18
18
|
|
19
|
-
bundle exec packwerk update components/your_package
|
19
|
+
bundle exec packwerk update-deprecations components/your_package
|
20
20
|
|
21
21
|
_Note: You cannot specify folders or packages for `packwerk validate` because the command runs for the entire application._
|
22
22
|
|
data/USAGE.md
CHANGED
@@ -47,11 +47,11 @@ Here is a list of files generated:
|
|
47
47
|
|
48
48
|
| File | Location | Description |
|
49
49
|
|-----------------------------|--------------|------------|
|
50
|
-
| Packwerk configuration | packwerk.yml | See [Setting up configuration file](#Setting-up-configuration-file) |
|
50
|
+
| Packwerk configuration | packwerk.yml | See [Setting up the configuration file](#Setting-up-the-configuration-file) |
|
51
51
|
| Root package | package.yml | A package for the root folder |
|
52
52
|
| Bin script | bin/packwerk | For Rails applications to run Packwerk validation on CI, see [Validating the package system](#Validating-the-package-system) |
|
53
53
|
| Validation test | test/packwerk_validator_test.rb | For Ruby projects to run Packwerk validation using tests, see [Validating the package system](#Validating-the-package-system) |
|
54
|
-
| Custom inflections |
|
54
|
+
| Custom inflections | config/inflections.yml | A custom inflections file is only required if you have custom inflections in `inflections.rb`, see [Inflections](#Inflections) |
|
55
55
|
|
56
56
|
After that, you may begin creating packages for your application. See [Defining packages](#Defining-packages)
|
57
57
|
|
@@ -59,33 +59,58 @@ After that, you may begin creating packages for your application. See [Defining
|
|
59
59
|
|
60
60
|
Packwerk reads from the `packwerk.yml` configuration file in the root directory. Packwerk will run with the default configuration if any of these settings are not specified.
|
61
61
|
|
62
|
-
| Key | Default value
|
63
|
-
|
64
|
-
| include | **/*.{rb,rake,erb}
|
65
|
-
| exclude | {bin,node_modules,script,tmp}/**/* | list of patterns for folder paths to exclude |
|
66
|
-
| package_paths | **/
|
67
|
-
| load_paths | All application autoload paths
|
68
|
-
| custom_associations | N/A
|
62
|
+
| Key | Default value | Description |
|
63
|
+
|----------------------|-------------------------------------------|--------------|
|
64
|
+
| include | **/*.{rb,rake,erb} | list of patterns for folder paths to include |
|
65
|
+
| exclude | {bin,node_modules,script,tmp,vendor}/**/* | list of patterns for folder paths to exclude |
|
66
|
+
| package_paths | **/ | a single pattern or a list of patterns to find package configuration files, see: [Defining packages](#Defining-packages) |
|
67
|
+
| load_paths | All application autoload paths | list of load paths |
|
68
|
+
| custom_associations | N/A | list of custom associations, if any |
|
69
69
|
|
70
|
+
### Using a custom ERB parser
|
71
|
+
|
72
|
+
You can specify a custom ERB parser if needed. For example, if you're using `<%graphql>` tags from https://github.com/github/graphql-client in your ERBs, you can use a custom parser subclass to comment them out so that Packwerk can parse the rest of the file:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
class CustomParser < Packwerk::Parsers::Erb
|
76
|
+
def parse_buffer(buffer, file_path:)
|
77
|
+
preprocessed_source = buffer.source
|
78
|
+
|
79
|
+
# Comment out <%graphql ... %> tags. They won't contain any object
|
80
|
+
# references anyways.
|
81
|
+
preprocessed_source = preprocessed_source.gsub(/<%graphql/, "<%#")
|
82
|
+
|
83
|
+
preprocessed_buffer = Parser::Source::Buffer.new(file_path)
|
84
|
+
preprocessed_buffer.source = preprocessed_source
|
85
|
+
super(preprocessed_buffer, file_path: file_path)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
Packwerk::Parsers::Factory.instance.erb_parser_class = CustomParser
|
90
|
+
```
|
70
91
|
|
71
92
|
### Inflections
|
72
93
|
|
73
|
-
Packwerk requires custom inflections to be defined in `inflections.yml` instead of the traditional `inflections.rb`. This is because Packwerk accounts for custom inflections, such as acronyms, when resolving constants. Additionally, Packwerk interprets Active Record associations as references to constants. For example, `has_many :birds` is reference to the `
|
94
|
+
Packwerk requires custom inflections to be defined in `inflections.yml` instead of the traditional `inflections.rb`. This is because Packwerk accounts for custom inflections, such as acronyms, when resolving constants. Additionally, Packwerk interprets Active Record associations as references to constants. For example, `has_many :birds` is a reference to the `Bird` constant.
|
74
95
|
|
75
|
-
In order to make your custom inflections compatible with Active Support and Packwerk, you must create
|
96
|
+
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.
|
76
97
|
|
77
98
|
In `inflections.rb`, add:
|
78
99
|
|
79
100
|
```rb
|
101
|
+
require "packwerk/inflections/custom"
|
102
|
+
|
80
103
|
ActiveSupport::Inflector.inflections do |inflect|
|
81
104
|
# please add all custom inflections in the file below.
|
82
105
|
Packwerk::Inflections::Custom.new(
|
83
|
-
Rails.root.join("inflections.yml")
|
106
|
+
Rails.root.join("config", "inflections.yml")
|
84
107
|
).apply_to(inflect)
|
85
108
|
end
|
86
109
|
```
|
87
110
|
|
88
|
-
|
111
|
+
_Note: Packwerk has to be grouped in production environment within the Gemfile if you have custom inflections._
|
112
|
+
|
113
|
+
Next, move your existing custom inflections into `config/inflections.yml`:
|
89
114
|
|
90
115
|
```yaml
|
91
116
|
acronym:
|
@@ -97,9 +122,11 @@ irregular:
|
|
97
122
|
- ['reserve', 'reserves']
|
98
123
|
uncountable:
|
99
124
|
- 'payment_details'
|
125
|
+
singular:
|
126
|
+
- [!ruby/regexp /status$/, 'status']
|
100
127
|
```
|
101
128
|
|
102
|
-
Any new inflectors should be added to `inflections.yml`.
|
129
|
+
Any new inflectors should be added to `config/inflections.yml`.
|
103
130
|
|
104
131
|
## Validating the package system
|
105
132
|
|
@@ -168,7 +195,17 @@ enforce_privacy:
|
|
168
195
|
It will be a privacy violation when a file outside of the `components/merchandising` package tries to reference `Merchandising::Product`.
|
169
196
|
|
170
197
|
##### Using public folders
|
171
|
-
You may enforce privacy either way mentioned above and still expose a public API for your package by placing constants in the `app/public
|
198
|
+
You may enforce privacy either way mentioned above and still expose a public API for your package by placing constants in the public folder, which by default is `app/public`. The constants in the public folder will be made available for use by the rest of the application.
|
199
|
+
|
200
|
+
##### Defining your own public folder
|
201
|
+
|
202
|
+
You may prefer to override the default public folder, you can do so on a per-package basis by defining a `public_path`.
|
203
|
+
|
204
|
+
Example:
|
205
|
+
|
206
|
+
```yaml
|
207
|
+
public_path: my/custom/path/
|
208
|
+
```
|
172
209
|
|
173
210
|
#### Enforcing dependency boundary
|
174
211
|
A package's dependency boundary is violated whenever it references a constant in some package that has not been declared as a dependency.
|
@@ -210,17 +247,17 @@ For existing codebases, packages are likely to have existing boundary violations
|
|
210
247
|
|
211
248
|
If so, you will want to stop the bleeding and prevent more violations from occuring. The existing violations in the codebase can be recorded in a [deprecated references list](#Understanding_the_list_of_deprecated_references) by executing:
|
212
249
|
|
213
|
-
bundle exec packwerk update
|
250
|
+
bundle exec packwerk update-deprecations
|
214
251
|
|
215
|
-
Similar to `packwerk check`, you may also run `packwerk update` on folders or packages:
|
252
|
+
Similar to `packwerk check`, you may also run `packwerk update-deprecations` on folders or packages:
|
216
253
|
|
217
|
-
bundle exec packwerk update components/your_package
|
254
|
+
bundle exec packwerk update-deprecations components/your_package
|
218
255
|
|
219
256
|

|
220
257
|
|
221
258
|
_Note: Changing dependencies or enabling dependencies will not require a full update of the codebase, only the package that changed. On the other hand, changing or enabling privacy will require a full update of the codebase._
|
222
259
|
|
223
|
-
`packwerk update` should only be run to record existing violations and to remove deprecated references that have been worked off. Running `packwerk update` to resolve a violation should be the very last resort.
|
260
|
+
`packwerk update-deprecations` should only be run to record existing violations and to remove deprecated references that have been worked off. Running `packwerk update-deprecations` to resolve a violation should be the very last resort.
|
224
261
|
|
225
262
|
See: [TROUBLESHOOT.md - Troubleshooting violations](TROUBLESHOOT.md#Troubleshooting_violations)
|
226
263
|
|
data/exe/packwerk
CHANGED
data/lib/packwerk.rb
CHANGED
@@ -14,7 +14,6 @@ require "packwerk/cli"
|
|
14
14
|
require "packwerk/configuration"
|
15
15
|
require "packwerk/const_node_inspector"
|
16
16
|
require "packwerk/constant_discovery"
|
17
|
-
require "packwerk/constant_name_inspector"
|
18
17
|
require "packwerk/dependency_checker"
|
19
18
|
require "packwerk/deprecated_references"
|
20
19
|
require "packwerk/files_for_processing"
|
@@ -28,13 +27,14 @@ require "packwerk/graph"
|
|
28
27
|
require "packwerk/inflector"
|
29
28
|
require "packwerk/node_processor"
|
30
29
|
require "packwerk/node_visitor"
|
31
|
-
require "packwerk/
|
30
|
+
require "packwerk/output_style"
|
31
|
+
require "packwerk/output_styles/plain"
|
32
|
+
require "packwerk/output_styles/coloured"
|
32
33
|
require "packwerk/package"
|
33
34
|
require "packwerk/package_set"
|
34
35
|
require "packwerk/parsers"
|
35
36
|
require "packwerk/privacy_checker"
|
36
37
|
require "packwerk/reference_extractor"
|
37
|
-
require "packwerk/reference_lister"
|
38
38
|
require "packwerk/run_context"
|
39
39
|
require "packwerk/updating_deprecated_references"
|
40
40
|
require "packwerk/version"
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler"
|
5
|
+
|
6
|
+
module Packwerk
|
7
|
+
module ApplicationLoadPaths
|
8
|
+
class << self
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { returns(T::Array[String]) }
|
12
|
+
def extract_relevant_paths
|
13
|
+
assert_application_booted
|
14
|
+
all_paths = extract_application_autoload_paths
|
15
|
+
relevant_paths = filter_relevant_paths(all_paths)
|
16
|
+
assert_load_paths_present(relevant_paths)
|
17
|
+
relative_path_strings(relevant_paths)
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { void }
|
21
|
+
def assert_application_booted
|
22
|
+
raise "The application needs to be booted to extract load paths" unless defined?(::Rails)
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { returns(T::Array[String]) }
|
26
|
+
def extract_application_autoload_paths
|
27
|
+
Rails.application.railties
|
28
|
+
.select { |railtie| railtie.is_a?(Rails::Engine) }
|
29
|
+
.push(Rails.application)
|
30
|
+
.flat_map do |engine|
|
31
|
+
paths = (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths)
|
32
|
+
paths.map(&:to_s).uniq
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
sig do
|
37
|
+
params(all_paths: T::Array[String], bundle_path: Pathname, rails_root: Pathname)
|
38
|
+
.returns(T::Array[Pathname])
|
39
|
+
end
|
40
|
+
def filter_relevant_paths(all_paths, bundle_path: Bundler.bundle_path, rails_root: Rails.root)
|
41
|
+
bundle_path_match = bundle_path.join("**")
|
42
|
+
rails_root_match = rails_root.join("**")
|
43
|
+
|
44
|
+
all_paths
|
45
|
+
.map { |path| Pathname.new(path).expand_path }
|
46
|
+
.select { |path| path.fnmatch(rails_root_match.to_s) } # path needs to be in application directory
|
47
|
+
.reject { |path| path.fnmatch(bundle_path_match.to_s) } # reject paths from vendored gems
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { params(paths: T::Array[Pathname], rails_root: Pathname).returns(T::Array[String]) }
|
51
|
+
def relative_path_strings(paths, rails_root: Rails.root)
|
52
|
+
paths
|
53
|
+
.map { |path| path.relative_path_from(rails_root).to_s }
|
54
|
+
.uniq
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { params(paths: T::Array[T.untyped]).void }
|
58
|
+
def assert_load_paths_present(paths)
|
59
|
+
if paths.empty?
|
60
|
+
raise <<~EOS
|
61
|
+
We could not extract autoload paths from your Rails app. This is likely a configuration error.
|
62
|
+
Packwerk will not work correctly without any autoload paths.
|
63
|
+
EOS
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -9,15 +9,15 @@ require "yaml"
|
|
9
9
|
require "packwerk/package_set"
|
10
10
|
require "packwerk/graph"
|
11
11
|
require "packwerk/inflector"
|
12
|
+
require "packwerk/application_load_paths"
|
12
13
|
|
13
14
|
module Packwerk
|
14
15
|
class ApplicationValidator
|
15
|
-
def initialize(config_file_path:,
|
16
|
+
def initialize(config_file_path:, configuration:)
|
16
17
|
@config_file_path = config_file_path
|
17
18
|
@configuration = configuration
|
18
19
|
|
19
|
-
|
20
|
-
@application_load_paths = application_load_paths.sort.uniq
|
20
|
+
@application_load_paths = ApplicationLoadPaths.extract_relevant_paths
|
21
21
|
end
|
22
22
|
|
23
23
|
Result = Struct.new(:ok?, :error_value)
|
@@ -32,19 +32,10 @@ module Packwerk
|
|
32
32
|
check_acyclic_graph,
|
33
33
|
check_package_manifest_paths,
|
34
34
|
check_valid_package_dependencies,
|
35
|
-
|
35
|
+
check_root_package_exists,
|
36
36
|
]
|
37
37
|
|
38
|
-
results
|
39
|
-
|
40
|
-
if results.empty?
|
41
|
-
Result.new(true)
|
42
|
-
else
|
43
|
-
Result.new(
|
44
|
-
false,
|
45
|
-
results.map(&:error_value).join("\n===\n")
|
46
|
-
)
|
47
|
-
end
|
38
|
+
merge_results(results)
|
48
39
|
end
|
49
40
|
|
50
41
|
def check_autoload_path_cache
|
@@ -65,55 +56,41 @@ module Packwerk
|
|
65
56
|
def check_package_manifests_for_privacy
|
66
57
|
privacy_settings = package_manifests_settings_for("enforce_privacy")
|
67
58
|
|
68
|
-
autoload_paths = @configuration.load_paths
|
69
|
-
|
70
59
|
resolver = ConstantResolver.new(
|
71
60
|
root_path: @configuration.root_path,
|
72
|
-
load_paths:
|
61
|
+
load_paths: @configuration.load_paths
|
73
62
|
)
|
74
63
|
|
75
|
-
|
64
|
+
results = []
|
76
65
|
|
77
|
-
privacy_settings.each do |
|
66
|
+
privacy_settings.each do |config_file_path, setting|
|
78
67
|
next unless setting.is_a?(Array)
|
68
|
+
constants = setting
|
79
69
|
|
80
|
-
|
81
|
-
# make sure the constant can be loaded
|
82
|
-
constant.constantize # rubocop:disable Sorbet/ConstantsFromStrings
|
83
|
-
context = resolver.resolve(constant)
|
84
|
-
|
85
|
-
unless context
|
86
|
-
errors << "#{constant}, listed in #{filepath.inspect}, could not be resolved"
|
87
|
-
next
|
88
|
-
end
|
89
|
-
|
90
|
-
expected_filename = constant.underscore + ".rb"
|
70
|
+
assert_constants_can_be_loaded(constants)
|
91
71
|
|
92
|
-
|
93
|
-
# file that defines their parent namespace. This restriction makes sure that we don't.
|
94
|
-
next if context.location.end_with?(expected_filename)
|
72
|
+
constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
|
95
73
|
|
96
|
-
|
97
|
-
|
98
|
-
|
74
|
+
constant_locations.each do |name, location|
|
75
|
+
results << if location
|
76
|
+
check_private_constant_location(name, location, config_file_path)
|
77
|
+
else
|
78
|
+
private_constant_unresolvable(name, config_file_path)
|
79
|
+
end
|
99
80
|
end
|
100
81
|
end
|
101
82
|
|
102
|
-
|
103
|
-
Result.new(true)
|
104
|
-
else
|
105
|
-
Result.new(false, errors.join("\n---\n"))
|
106
|
-
end
|
83
|
+
merge_results(results, separator: "\n---\n")
|
107
84
|
end
|
108
85
|
|
109
86
|
def check_package_manifest_syntax
|
110
87
|
errors = []
|
111
88
|
|
112
|
-
package_manifests
|
89
|
+
package_manifests.each do |f|
|
113
90
|
hash = YAML.load_file(f)
|
114
91
|
next unless hash
|
115
92
|
|
116
|
-
known_keys = %w(enforce_privacy enforce_dependencies dependencies metadata)
|
93
|
+
known_keys = %w(enforce_privacy enforce_dependencies public_path dependencies metadata)
|
117
94
|
unknown_keys = hash.keys - known_keys
|
118
95
|
|
119
96
|
unless unknown_keys.empty?
|
@@ -134,6 +111,12 @@ module Packwerk
|
|
134
111
|
end
|
135
112
|
end
|
136
113
|
|
114
|
+
if hash.key?("public_path")
|
115
|
+
unless hash["public_path"].is_a?(String)
|
116
|
+
errors << "'public_path' option must be a string in #{f.inspect}: #{hash['public_path'].inspect}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
137
120
|
next unless hash.key?("dependencies")
|
138
121
|
next if hash["dependencies"].is_a?(Array)
|
139
122
|
|
@@ -164,14 +147,12 @@ module Packwerk
|
|
164
147
|
def check_inflection_file
|
165
148
|
inflections_file = @configuration.inflections_file
|
166
149
|
|
167
|
-
|
168
|
-
|
169
|
-
Packwerk::Inflections::Default.apply_to(test_inflections)
|
170
|
-
Packwerk::Inflections::Custom.new(inflections_file).apply_to(test_inflections)
|
150
|
+
application_inflections = ActiveSupport::Inflector.inflections
|
151
|
+
packwerk_inflections = Packwerk::Inflector.from_file(inflections_file).inflections
|
171
152
|
|
172
153
|
results = %i(plurals singulars uncountables humans acronyms).map do |type|
|
173
|
-
expected =
|
174
|
-
actual =
|
154
|
+
expected = application_inflections.public_send(type).to_set
|
155
|
+
actual = packwerk_inflections.public_send(type).to_set
|
175
156
|
|
176
157
|
if expected == actual
|
177
158
|
Result.new(true)
|
@@ -189,24 +170,16 @@ module Packwerk
|
|
189
170
|
end
|
190
171
|
end
|
191
172
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
Result.new(
|
198
|
-
false,
|
199
|
-
"Inflections specified in #{inflections_file} don't line up with application!\n" +
|
200
|
-
errors.map(&:error_value).join("\n")
|
201
|
-
)
|
202
|
-
end
|
173
|
+
merge_results(
|
174
|
+
results,
|
175
|
+
separator: "\n",
|
176
|
+
errors_headline: "Inflections specified in #{inflections_file} don't line up with application!\n"
|
177
|
+
)
|
203
178
|
end
|
204
179
|
|
205
180
|
def check_acyclic_graph
|
206
|
-
|
207
|
-
|
208
|
-
edges = packages.flat_map do |package|
|
209
|
-
package.dependencies.map { |dependency| [package, packages.fetch(dependency)] }
|
181
|
+
edges = package_set.flat_map do |package|
|
182
|
+
package.dependencies.map { |dependency| [package, package_set.fetch(dependency)] }
|
210
183
|
end
|
211
184
|
dependency_graph = Packwerk::Graph.new(*edges)
|
212
185
|
|
@@ -293,7 +266,7 @@ module Packwerk
|
|
293
266
|
end
|
294
267
|
end
|
295
268
|
|
296
|
-
def
|
269
|
+
def check_root_package_exists
|
297
270
|
root_package_path = File.join(@configuration.root_path, "package.yml")
|
298
271
|
all_packages_manifests = package_manifests(package_glob)
|
299
272
|
|
@@ -312,8 +285,7 @@ module Packwerk
|
|
312
285
|
private
|
313
286
|
|
314
287
|
def package_manifests_settings_for(setting)
|
315
|
-
package_manifests(
|
316
|
-
.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
288
|
+
package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
317
289
|
end
|
318
290
|
|
319
291
|
def format_yaml_strings(list)
|
@@ -324,12 +296,17 @@ module Packwerk
|
|
324
296
|
@configuration.package_paths || "**"
|
325
297
|
end
|
326
298
|
|
327
|
-
def package_manifests(glob_pattern)
|
328
|
-
|
299
|
+
def package_manifests(glob_pattern = package_glob)
|
300
|
+
PackageSet.package_paths(@configuration.root_path, glob_pattern)
|
301
|
+
.map { |f| File.realpath(f) }
|
329
302
|
end
|
330
303
|
|
331
304
|
def relative_paths(paths)
|
332
|
-
paths.map { |path|
|
305
|
+
paths.map { |path| relative_path(path) }
|
306
|
+
end
|
307
|
+
|
308
|
+
def relative_path(path)
|
309
|
+
Pathname.new(path).relative_path_from(@configuration.root_path)
|
333
310
|
end
|
334
311
|
|
335
312
|
def invalid_package_path?(path)
|
@@ -339,5 +316,54 @@ module Packwerk
|
|
339
316
|
package_path = File.join(@configuration.root_path, path, Packwerk::PackageSet::PACKAGE_CONFIG_FILENAME)
|
340
317
|
!File.file?(package_path)
|
341
318
|
end
|
319
|
+
|
320
|
+
def assert_constants_can_be_loaded(constants)
|
321
|
+
constants.each(&:constantize)
|
322
|
+
nil
|
323
|
+
end
|
324
|
+
|
325
|
+
def private_constant_unresolvable(name, config_file_path)
|
326
|
+
explicit_filepath = (name.start_with?("::") ? name[2..-1] : name).underscore + ".rb"
|
327
|
+
|
328
|
+
Result.new(
|
329
|
+
false,
|
330
|
+
"'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
|
331
|
+
"This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
|
332
|
+
"file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
|
333
|
+
"private. Add a #{explicit_filepath} file to explicitly define the constant."
|
334
|
+
)
|
335
|
+
end
|
336
|
+
|
337
|
+
def check_private_constant_location(name, location, config_file_path)
|
338
|
+
declared_package = package_set.package_from_path(relative_path(config_file_path))
|
339
|
+
constant_package = package_set.package_from_path(location)
|
340
|
+
|
341
|
+
if constant_package == declared_package
|
342
|
+
Result.new(true)
|
343
|
+
else
|
344
|
+
Result.new(
|
345
|
+
false,
|
346
|
+
"'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
|
347
|
+
"defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
|
348
|
+
)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def package_set
|
353
|
+
@package_set ||= Packwerk::PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob)
|
354
|
+
end
|
355
|
+
|
356
|
+
def merge_results(results, separator: "\n===\n", errors_headline: "")
|
357
|
+
results.reject!(&:ok?)
|
358
|
+
|
359
|
+
if results.empty?
|
360
|
+
Result.new(true)
|
361
|
+
else
|
362
|
+
Result.new(
|
363
|
+
false,
|
364
|
+
errors_headline + results.map(&:error_value).join(separator)
|
365
|
+
)
|
366
|
+
end
|
367
|
+
end
|
342
368
|
end
|
343
369
|
end
|