packwerk 1.0.1 → 1.1.3
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 +14 -5
- data/.ruby-version +1 -1
- data/Gemfile +2 -1
- data/Gemfile.lock +130 -110
- data/README.md +8 -1
- data/USAGE.md +23 -2
- data/dev.yml +1 -1
- data/exe/packwerk +1 -1
- data/gemfiles/Gemfile-rails-6-0 +22 -0
- data/lib/packwerk.rb +4 -2
- data/lib/packwerk/application_load_paths.rb +68 -0
- data/lib/packwerk/application_validator.rb +94 -74
- data/lib/packwerk/association_inspector.rb +24 -9
- data/lib/packwerk/cache_deprecated_references.rb +55 -0
- data/lib/packwerk/checker.rb +3 -0
- data/lib/packwerk/checking_deprecated_references.rb +5 -2
- data/lib/packwerk/cli.rb +56 -55
- 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 +5 -37
- data/lib/packwerk/const_node_inspector.rb +28 -17
- data/lib/packwerk/dependency_checker.rb +13 -5
- data/lib/packwerk/deprecated_references.rb +23 -0
- 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/inflector.rb +17 -8
- data/lib/packwerk/node.rb +61 -38
- data/lib/packwerk/node_processor.rb +4 -4
- data/lib/packwerk/node_processor_factory.rb +39 -0
- data/lib/packwerk/node_visitor.rb +1 -1
- 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_set.rb +9 -3
- 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 +23 -5
- data/lib/packwerk/run_context.rb +69 -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/library.yml +1 -1
- data/packwerk.gemspec +1 -1
- data/service.yml +2 -2
- data/shipit.rubygems.yml +5 -1
- data/sorbet/rbi/gems/{actioncable@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +56 -36
- data/sorbet/rbi/gems/{actionmailbox@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +25 -28
- data/sorbet/rbi/gems/{actionmailer@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +43 -24
- data/sorbet/rbi/gems/{actionpack@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +382 -284
- data/sorbet/rbi/gems/{actiontext@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +76 -40
- data/sorbet/rbi/gems/{actionview@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +206 -195
- data/sorbet/rbi/gems/{activejob@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +64 -75
- data/sorbet/rbi/gems/{activemodel@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +103 -56
- data/sorbet/rbi/gems/{activerecord@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1250 -898
- data/sorbet/rbi/gems/{activestorage@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +92 -120
- data/sorbet/rbi/gems/{activesupport@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +292 -193
- data/sorbet/rbi/gems/{ast@2.4.1.rbi → ast@2.4.2.rbi} +2 -1
- data/sorbet/rbi/gems/{better_html@1.0.15.rbi → better_html@1.0.16.rbi} +2 -2
- data/sorbet/rbi/gems/{concurrent-ruby@1.1.6.rbi → concurrent-ruby@1.1.8.rbi} +12 -9
- data/sorbet/rbi/gems/{erubi@1.9.0.rbi → erubi@1.10.0.rbi} +3 -1
- data/sorbet/rbi/gems/{i18n@1.8.2.rbi → i18n@1.8.10.rbi} +19 -52
- data/sorbet/rbi/gems/{loofah@2.5.0.rbi → loofah@2.9.0.rbi} +3 -1
- data/sorbet/rbi/gems/marcel@1.0.0.rbi +70 -0
- data/sorbet/rbi/gems/{mini_mime@1.0.2.rbi → mini_mime@1.0.3.rbi} +6 -6
- data/sorbet/rbi/gems/{mini_portile2@2.4.0.rbi → minitest-focus@1.2.1.rbi} +2 -2
- data/sorbet/rbi/gems/{minitest@5.14.0.rbi → minitest@5.14.4.rbi} +31 -29
- data/sorbet/rbi/gems/{mocha@1.11.2.rbi → mocha@1.12.0.rbi} +25 -36
- data/sorbet/rbi/gems/{nio4r@2.5.2.rbi → nio4r@2.5.7.rbi} +21 -20
- data/sorbet/rbi/gems/{nokogiri@1.10.9.rbi → nokogiri@1.11.2.rbi} +193 -154
- data/sorbet/rbi/gems/{parallel@1.19.1.rbi → parallel@1.20.1.rbi} +1 -1
- data/sorbet/rbi/gems/parlour@6.0.0.rbi +1272 -0
- data/sorbet/rbi/gems/{parser@2.7.1.4.rbi → parser@3.0.0.0.rbi} +287 -174
- data/sorbet/rbi/gems/{pry@0.13.1.rbi → pry@0.14.0.rbi} +1 -1
- data/sorbet/rbi/gems/racc@1.5.2.rbi +57 -0
- data/sorbet/rbi/gems/{rack@2.2.2.rbi → rack@2.2.3.rbi} +23 -35
- data/sorbet/rbi/gems/{rails@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1 -1
- data/sorbet/rbi/gems/{railties@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +132 -121
- data/sorbet/rbi/gems/{rake@13.0.1.rbi → rake@13.0.3.rbi} +16 -20
- data/sorbet/rbi/gems/regexp_parser@2.1.1.rbi +8 -0
- data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +8 -0
- data/sorbet/rbi/gems/{rubocop-performance@1.5.2.rbi → rubocop-performance@1.10.2.rbi} +1 -1
- data/sorbet/rbi/gems/{rubocop-shopify@1.0.2.rbi → rubocop-shopify@2.0.1.rbi} +1 -1
- data/sorbet/rbi/gems/{rubocop-sorbet@0.3.7.rbi → rubocop-sorbet@0.6.1.rbi} +1 -1
- data/sorbet/rbi/gems/{rubocop@0.82.0.rbi → rubocop@1.12.0.rbi} +1 -1
- data/sorbet/rbi/gems/{ruby-progressbar@1.10.1.rbi → ruby-progressbar@1.11.0.rbi} +1 -1
- data/sorbet/rbi/gems/spoom@1.1.0.rbi +1061 -0
- data/sorbet/rbi/gems/{spring@2.1.0.rbi → spring@2.1.1.rbi} +7 -7
- data/sorbet/rbi/gems/{sprockets-rails@3.2.1.rbi → sprockets-rails@3.2.2.rbi} +88 -68
- data/sorbet/rbi/gems/{sprockets@4.0.0.rbi → sprockets@4.0.2.rbi} +8 -7
- data/sorbet/rbi/gems/{tapioca@0.4.5.rbi → tapioca@0.4.19.rbi} +109 -24
- data/sorbet/rbi/gems/{thor@1.0.1.rbi → thor@1.1.0.rbi} +16 -15
- data/sorbet/rbi/gems/{tzinfo@2.0.2.rbi → tzinfo@2.0.4.rbi} +21 -2
- data/sorbet/rbi/gems/{unicode-display_width@1.7.0.rbi → unicode-display_width@2.0.0.rbi} +1 -1
- data/sorbet/rbi/gems/{websocket-driver@0.7.1.rbi → websocket-driver@0.7.3.rbi} +29 -29
- data/sorbet/rbi/gems/{websocket-extensions@0.1.4.rbi → websocket-extensions@0.1.5.rbi} +2 -2
- data/sorbet/rbi/gems/zeitwerk@2.4.2.rbi +177 -0
- metadata +66 -58
- data/lib/packwerk/output_styles.rb +0 -41
- data/sorbet/rbi/gems/jaro_winkler@1.5.4.rbi +0 -8
- data/sorbet/rbi/gems/marcel@0.3.3.rbi +0 -30
- data/sorbet/rbi/gems/mimemagic@0.3.5.rbi +0 -47
- data/sorbet/rbi/gems/parlour@4.0.1.rbi +0 -561
- data/sorbet/rbi/gems/spoom@1.0.4.rbi +0 -418
- data/sorbet/rbi/gems/zeitwerk@2.3.0.rbi +0 -8
- 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
data/README.md
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
# Packwerk [](https://github.com/Shopify/packwerk/actions?query=workflow%3ACI)
|
2
2
|
|
3
|
+
## NOTE: Packwerk is considered to be feature-complete for Shopify's uses. We are currently accepting bug fixes only, and it is not being actively developed. Please fork this project if you are interested in adding new features.
|
4
|
+
|
5
|
+
> "I know who you are and because of that I know what you do."
|
6
|
+
> This knowledge is a dependency that raises the cost of change.
|
7
|
+
|
8
|
+
-- _Sandi Metz, Practical Object-Oriented Design in Ruby_
|
9
|
+
|
3
10
|
Packwerk is a Ruby gem used to enforce boundaries and modularize Rails applications.
|
4
11
|
|
5
12
|
Packwerk can be used to:
|
6
|
-
* Combine
|
13
|
+
* Combine groups of files into packages
|
7
14
|
* Define package-level constant visibility (i.e. have publicly accessible constants)
|
8
15
|
* Enforce privacy (inbound) and dependency (outbound) boundaries between packages
|
9
16
|
* Help existing codebases to become more modular without obstructing development
|
data/USAGE.md
CHANGED
@@ -63,14 +63,35 @@ Packwerk reads from the `packwerk.yml` configuration file in the root directory.
|
|
63
63
|
|----------------------|-------------------------------------------|--------------|
|
64
64
|
| include | **/*.{rb,rake,erb} | list of patterns for folder paths to include |
|
65
65
|
| exclude | {bin,node_modules,script,tmp,vendor}/**/* | list of patterns for folder paths to exclude |
|
66
|
-
| package_paths | **/ | patterns to find package configuration files, see: Defining packages |
|
66
|
+
| package_paths | **/ | a single pattern or a list of patterns to find package configuration files, see: [Defining packages](#Defining-packages) |
|
67
67
|
| load_paths | All application autoload paths | list of load paths |
|
68
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
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
|
|
data/dev.yml
CHANGED
data/exe/packwerk
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source("https://rubygems.org")
|
4
|
+
|
5
|
+
gemspec path: ".."
|
6
|
+
|
7
|
+
# Specify the same dependency sources as the application Gemfile
|
8
|
+
|
9
|
+
gem("spring")
|
10
|
+
gem("rails", '~> 6.0.0')
|
11
|
+
gem("constant_resolver", require: false)
|
12
|
+
gem("sorbet-runtime", require: false)
|
13
|
+
gem("rubocop-performance", require: false)
|
14
|
+
gem("rubocop-sorbet", require: false)
|
15
|
+
gem("mocha", require: false)
|
16
|
+
gem("rubocop-shopify", require: false)
|
17
|
+
gem("tapioca", require: false)
|
18
|
+
|
19
|
+
group :development do
|
20
|
+
gem("byebug", require: false)
|
21
|
+
gem("minitest-focus", require: false)
|
22
|
+
end
|
data/lib/packwerk.rb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
require "sorbet-runtime"
|
5
5
|
require "active_support"
|
6
6
|
require "constant_resolver"
|
7
|
+
require "fileutils"
|
7
8
|
|
8
9
|
require "packwerk/offense"
|
9
10
|
|
@@ -14,7 +15,6 @@ require "packwerk/cli"
|
|
14
15
|
require "packwerk/configuration"
|
15
16
|
require "packwerk/const_node_inspector"
|
16
17
|
require "packwerk/constant_discovery"
|
17
|
-
require "packwerk/constant_name_inspector"
|
18
18
|
require "packwerk/dependency_checker"
|
19
19
|
require "packwerk/deprecated_references"
|
20
20
|
require "packwerk/files_for_processing"
|
@@ -28,7 +28,9 @@ require "packwerk/graph"
|
|
28
28
|
require "packwerk/inflector"
|
29
29
|
require "packwerk/node_processor"
|
30
30
|
require "packwerk/node_visitor"
|
31
|
-
require "packwerk/
|
31
|
+
require "packwerk/output_style"
|
32
|
+
require "packwerk/output_styles/plain"
|
33
|
+
require "packwerk/output_styles/coloured"
|
32
34
|
require "packwerk/package"
|
33
35
|
require "packwerk/package_set"
|
34
36
|
require "packwerk/parsers"
|
@@ -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,51 +56,37 @@ 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)
|
70
|
+
assert_constants_can_be_loaded(constants)
|
84
71
|
|
85
|
-
|
86
|
-
errors << "#{constant}, listed in #{filepath.inspect}, could not be resolved"
|
87
|
-
next
|
88
|
-
end
|
89
|
-
|
90
|
-
expected_filename = constant.underscore + ".rb"
|
72
|
+
constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
|
91
73
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
"It should be in something like #{expected_filename.inspect}"
|
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
|
|
@@ -124,26 +101,26 @@ module Packwerk
|
|
124
101
|
|
125
102
|
if hash.key?("enforce_privacy")
|
126
103
|
unless [TrueClass, FalseClass, Array].include?(hash["enforce_privacy"].class)
|
127
|
-
errors << "Invalid 'enforce_privacy' option in #{f.inspect}: #{hash[
|
104
|
+
errors << "Invalid 'enforce_privacy' option in #{f.inspect}: #{hash["enforce_privacy"].inspect}"
|
128
105
|
end
|
129
106
|
end
|
130
107
|
|
131
108
|
if hash.key?("enforce_dependencies")
|
132
109
|
unless [TrueClass, FalseClass].include?(hash["enforce_dependencies"].class)
|
133
|
-
errors << "Invalid 'enforce_dependencies' option in #{f.inspect}: #{hash[
|
110
|
+
errors << "Invalid 'enforce_dependencies' option in #{f.inspect}: #{hash["enforce_dependencies"].inspect}"
|
134
111
|
end
|
135
112
|
end
|
136
113
|
|
137
114
|
if hash.key?("public_path")
|
138
115
|
unless hash["public_path"].is_a?(String)
|
139
|
-
errors << "'public_path' option must be a string in #{f.inspect}: #{hash[
|
116
|
+
errors << "'public_path' option must be a string in #{f.inspect}: #{hash["public_path"].inspect}"
|
140
117
|
end
|
141
118
|
end
|
142
119
|
|
143
120
|
next unless hash.key?("dependencies")
|
144
121
|
next if hash["dependencies"].is_a?(Array)
|
145
122
|
|
146
|
-
errors << "Invalid 'dependencies' option in #{f.inspect}: #{hash[
|
123
|
+
errors << "Invalid 'dependencies' option in #{f.inspect}: #{hash["dependencies"].inspect}"
|
147
124
|
end
|
148
125
|
|
149
126
|
if errors.empty?
|
@@ -170,14 +147,12 @@ module Packwerk
|
|
170
147
|
def check_inflection_file
|
171
148
|
inflections_file = @configuration.inflections_file
|
172
149
|
|
173
|
-
|
174
|
-
|
175
|
-
Packwerk::Inflections::Default.apply_to(test_inflections)
|
176
|
-
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
|
177
152
|
|
178
153
|
results = %i(plurals singulars uncountables humans acronyms).map do |type|
|
179
|
-
expected =
|
180
|
-
actual =
|
154
|
+
expected = application_inflections.public_send(type).to_set
|
155
|
+
actual = packwerk_inflections.public_send(type).to_set
|
181
156
|
|
182
157
|
if expected == actual
|
183
158
|
Result.new(true)
|
@@ -195,24 +170,16 @@ module Packwerk
|
|
195
170
|
end
|
196
171
|
end
|
197
172
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
Result.new(
|
204
|
-
false,
|
205
|
-
"Inflections specified in #{inflections_file} don't line up with application!\n" +
|
206
|
-
errors.map(&:error_value).join("\n")
|
207
|
-
)
|
208
|
-
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
|
+
)
|
209
178
|
end
|
210
179
|
|
211
180
|
def check_acyclic_graph
|
212
|
-
|
213
|
-
|
214
|
-
edges = packages.flat_map do |package|
|
215
|
-
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)] }
|
216
183
|
end
|
217
184
|
dependency_graph = Packwerk::Graph.new(*edges)
|
218
185
|
|
@@ -227,7 +194,7 @@ module Packwerk
|
|
227
194
|
cycle_strings = dependency_graph.cycles.map do |cycle|
|
228
195
|
cycle_strings = cycle.map(&:to_s)
|
229
196
|
cycle_strings << cycle.first.to_s
|
230
|
-
"\t- #{cycle_strings.join(
|
197
|
+
"\t- #{cycle_strings.join(" → ")}"
|
231
198
|
end
|
232
199
|
|
233
200
|
if dependency_graph.acyclic?
|
@@ -299,7 +266,7 @@ module Packwerk
|
|
299
266
|
end
|
300
267
|
end
|
301
268
|
|
302
|
-
def
|
269
|
+
def check_root_package_exists
|
303
270
|
root_package_path = File.join(@configuration.root_path, "package.yml")
|
304
271
|
all_packages_manifests = package_manifests(package_glob)
|
305
272
|
|
@@ -318,8 +285,7 @@ module Packwerk
|
|
318
285
|
private
|
319
286
|
|
320
287
|
def package_manifests_settings_for(setting)
|
321
|
-
package_manifests(
|
322
|
-
.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
288
|
+
package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
323
289
|
end
|
324
290
|
|
325
291
|
def format_yaml_strings(list)
|
@@ -330,12 +296,17 @@ module Packwerk
|
|
330
296
|
@configuration.package_paths || "**"
|
331
297
|
end
|
332
298
|
|
333
|
-
def package_manifests(glob_pattern)
|
334
|
-
PackageSet.package_paths(@configuration.root_path, glob_pattern)
|
299
|
+
def package_manifests(glob_pattern = package_glob)
|
300
|
+
PackageSet.package_paths(@configuration.root_path, glob_pattern)
|
301
|
+
.map { |f| File.realpath(f) }
|
335
302
|
end
|
336
303
|
|
337
304
|
def relative_paths(paths)
|
338
|
-
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)
|
339
310
|
end
|
340
311
|
|
341
312
|
def invalid_package_path?(path)
|
@@ -345,5 +316,54 @@ module Packwerk
|
|
345
316
|
package_path = File.join(@configuration.root_path, path, Packwerk::PackageSet::PACKAGE_CONFIG_FILENAME)
|
346
317
|
!File.file?(package_path)
|
347
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
|
348
368
|
end
|
349
369
|
end
|