packwerk 1.1.0 → 1.3.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/.github/workflows/ci.yml +17 -8
- data/.ruby-version +1 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +129 -111
- data/README.md +10 -3
- data/TROUBLESHOOT.md +2 -2
- data/USAGE.md +30 -30
- data/bin/m +29 -0
- data/bin/rake +29 -0
- data/bin/rubocop +29 -0
- data/bin/srb +29 -0
- data/bin/tapioca +29 -0
- data/dev.yml +7 -7
- data/exe/packwerk +1 -1
- data/gemfiles/Gemfile-rails-6-0 +22 -0
- data/lib/packwerk.rb +72 -34
- data/lib/packwerk/application_load_paths.rb +21 -10
- data/lib/packwerk/application_validator.rb +104 -84
- data/lib/packwerk/association_inspector.rb +23 -11
- data/lib/packwerk/checker.rb +4 -7
- data/lib/packwerk/cli.rb +36 -129
- data/lib/packwerk/configuration.rb +10 -2
- data/lib/packwerk/const_node_inspector.rb +13 -14
- data/lib/packwerk/constant_discovery.rb +2 -0
- data/lib/packwerk/constant_name_inspector.rb +0 -1
- data/lib/packwerk/dependency_checker.rb +12 -17
- data/lib/packwerk/deprecated_references.rb +8 -10
- data/lib/packwerk/file_processor.rb +0 -4
- data/lib/packwerk/formatters/offenses_formatter.rb +52 -0
- data/lib/packwerk/formatters/progress_formatter.rb +9 -4
- data/lib/packwerk/generators/configuration_file.rb +0 -1
- data/lib/packwerk/inflector.rb +0 -2
- data/lib/packwerk/node.rb +9 -2
- data/lib/packwerk/node_processor.rb +15 -32
- data/lib/packwerk/node_processor_factory.rb +0 -5
- data/lib/packwerk/node_visitor.rb +1 -4
- data/lib/packwerk/offense.rb +2 -8
- data/lib/packwerk/offense_collection.rb +84 -0
- data/lib/packwerk/offenses_formatter.rb +19 -0
- 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 +17 -1
- data/lib/packwerk/package_set.rb +2 -3
- data/lib/packwerk/parse_run.rb +106 -0
- data/lib/packwerk/parsed_constant_definitions.rb +2 -4
- data/lib/packwerk/parsers.rb +0 -2
- data/lib/packwerk/parsers/erb.rb +0 -2
- data/lib/packwerk/parsers/factory.rb +1 -3
- data/lib/packwerk/privacy_checker.rb +22 -17
- data/lib/packwerk/reference_extractor.rb +0 -8
- data/lib/packwerk/reference_offense.rb +49 -0
- data/lib/packwerk/result.rb +9 -0
- data/lib/packwerk/run_context.rb +4 -21
- data/lib/packwerk/sanity_checker.rb +1 -3
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk/violation_type.rb +0 -2
- data/library.yml +1 -1
- data/packwerk.gemspec +1 -0
- data/service.yml +1 -4
- 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.20.1.rbi +117 -0
- 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/{parallel@1.19.1.rbi → regexp_parser@2.1.1.rbi} +2 -2
- 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
- data/sorbet/tapioca/require.rb +1 -0
- metadata +83 -65
- data/lib/packwerk/cache_deprecated_references.rb +0 -47
- data/lib/packwerk/checking_deprecated_references.rb +0 -40
- data/lib/packwerk/commands/detect_stale_violations_command.rb +0 -63
- data/lib/packwerk/commands/offense_progress_marker.rb +0 -24
- data/lib/packwerk/detect_stale_deprecated_references.rb +0 -14
- data/lib/packwerk/generators/application_validation.rb +0 -62
- data/lib/packwerk/generators/templates/packwerk +0 -23
- data/lib/packwerk/generators/templates/packwerk_validator_test.rb +0 -11
- data/lib/packwerk/output_styles.rb +0 -41
- data/lib/packwerk/reference_lister.rb +0 -23
- data/lib/packwerk/spring_command.rb +0 -28
- data/lib/packwerk/updating_deprecated_references.rb +0 -14
- 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
@@ -8,28 +8,24 @@ module Packwerk
|
|
8
8
|
class << self
|
9
9
|
extend T::Sig
|
10
10
|
|
11
|
-
sig { returns(T::Array[String]) }
|
12
|
-
def extract_relevant_paths
|
13
|
-
|
11
|
+
sig { params(root: String, environment: String).returns(T::Array[String]) }
|
12
|
+
def extract_relevant_paths(root, environment)
|
13
|
+
require_application(root, environment)
|
14
14
|
all_paths = extract_application_autoload_paths
|
15
15
|
relevant_paths = filter_relevant_paths(all_paths)
|
16
16
|
assert_load_paths_present(relevant_paths)
|
17
17
|
relative_path_strings(relevant_paths)
|
18
18
|
end
|
19
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
20
|
sig { returns(T::Array[String]) }
|
26
21
|
def extract_application_autoload_paths
|
27
22
|
Rails.application.railties
|
28
23
|
.select { |railtie| railtie.is_a?(Rails::Engine) }
|
29
24
|
.push(Rails.application)
|
30
25
|
.flat_map do |engine|
|
31
|
-
|
32
|
-
|
26
|
+
paths = (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths)
|
27
|
+
paths.map(&:to_s).uniq
|
28
|
+
end
|
33
29
|
end
|
34
30
|
|
35
31
|
sig do
|
@@ -53,6 +49,21 @@ module Packwerk
|
|
53
49
|
.uniq
|
54
50
|
end
|
55
51
|
|
52
|
+
private
|
53
|
+
|
54
|
+
sig { params(root: String, environment: String).void }
|
55
|
+
def require_application(root, environment)
|
56
|
+
environment_file = "#{root}/config/environment"
|
57
|
+
|
58
|
+
if File.file?("#{environment_file}.rb")
|
59
|
+
ENV["RAILS_ENV"] ||= environment
|
60
|
+
|
61
|
+
require environment_file
|
62
|
+
else
|
63
|
+
raise "A Rails application could not be found in #{root}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
56
67
|
sig { params(paths: T::Array[T.untyped]).void }
|
57
68
|
def assert_load_paths_present(paths)
|
58
69
|
if paths.empty?
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "active_support/inflector/inflections"
|
@@ -6,18 +6,14 @@ require "constant_resolver"
|
|
6
6
|
require "pathname"
|
7
7
|
require "yaml"
|
8
8
|
|
9
|
-
require "packwerk/package_set"
|
10
|
-
require "packwerk/graph"
|
11
|
-
require "packwerk/inflector"
|
12
|
-
require "packwerk/application_load_paths"
|
13
|
-
|
14
9
|
module Packwerk
|
15
10
|
class ApplicationValidator
|
16
|
-
def initialize(config_file_path:, configuration:)
|
11
|
+
def initialize(config_file_path:, configuration:, environment:)
|
17
12
|
@config_file_path = config_file_path
|
18
13
|
@configuration = configuration
|
14
|
+
@environment = environment
|
19
15
|
|
20
|
-
@application_load_paths = ApplicationLoadPaths.extract_relevant_paths
|
16
|
+
@application_load_paths = ApplicationLoadPaths.extract_relevant_paths(configuration.root_path, environment)
|
21
17
|
end
|
22
18
|
|
23
19
|
Result = Struct.new(:ok?, :error_value)
|
@@ -35,16 +31,7 @@ module Packwerk
|
|
35
31
|
check_root_package_exists,
|
36
32
|
]
|
37
33
|
|
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
|
34
|
+
merge_results(results)
|
48
35
|
end
|
49
36
|
|
50
37
|
def check_autoload_path_cache
|
@@ -65,51 +52,37 @@ module Packwerk
|
|
65
52
|
def check_package_manifests_for_privacy
|
66
53
|
privacy_settings = package_manifests_settings_for("enforce_privacy")
|
67
54
|
|
68
|
-
autoload_paths = @configuration.load_paths
|
69
|
-
|
70
55
|
resolver = ConstantResolver.new(
|
71
56
|
root_path: @configuration.root_path,
|
72
|
-
load_paths:
|
57
|
+
load_paths: @configuration.load_paths
|
73
58
|
)
|
74
59
|
|
75
|
-
|
60
|
+
results = []
|
76
61
|
|
77
|
-
privacy_settings.each do |
|
62
|
+
privacy_settings.each do |config_file_path, setting|
|
78
63
|
next unless setting.is_a?(Array)
|
64
|
+
constants = setting
|
79
65
|
|
80
|
-
|
81
|
-
# make sure the constant can be loaded
|
82
|
-
constant.constantize # rubocop:disable Sorbet/ConstantsFromStrings
|
83
|
-
context = resolver.resolve(constant)
|
66
|
+
assert_constants_can_be_loaded(constants)
|
84
67
|
|
85
|
-
|
86
|
-
errors << "#{constant}, listed in #{filepath.inspect}, could not be resolved"
|
87
|
-
next
|
88
|
-
end
|
68
|
+
constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
|
89
69
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
errors << "Explicitly private constants need to have their own files.\n"\
|
97
|
-
"#{constant}, listed in #{filepath.inspect}, was resolved to #{context.location.inspect}.\n"\
|
98
|
-
"It should be in something like #{expected_filename.inspect}"
|
70
|
+
constant_locations.each do |name, location|
|
71
|
+
results << if location
|
72
|
+
check_private_constant_location(name, location, config_file_path)
|
73
|
+
else
|
74
|
+
private_constant_unresolvable(name, config_file_path)
|
75
|
+
end
|
99
76
|
end
|
100
77
|
end
|
101
78
|
|
102
|
-
|
103
|
-
Result.new(true)
|
104
|
-
else
|
105
|
-
Result.new(false, errors.join("\n---\n"))
|
106
|
-
end
|
79
|
+
merge_results(results, separator: "\n---\n")
|
107
80
|
end
|
108
81
|
|
109
82
|
def check_package_manifest_syntax
|
110
83
|
errors = []
|
111
84
|
|
112
|
-
package_manifests
|
85
|
+
package_manifests.each do |f|
|
113
86
|
hash = YAML.load_file(f)
|
114
87
|
next unless hash
|
115
88
|
|
@@ -124,26 +97,26 @@ module Packwerk
|
|
124
97
|
|
125
98
|
if hash.key?("enforce_privacy")
|
126
99
|
unless [TrueClass, FalseClass, Array].include?(hash["enforce_privacy"].class)
|
127
|
-
errors << "Invalid 'enforce_privacy' option in #{f.inspect}: #{hash[
|
100
|
+
errors << "Invalid 'enforce_privacy' option in #{f.inspect}: #{hash["enforce_privacy"].inspect}"
|
128
101
|
end
|
129
102
|
end
|
130
103
|
|
131
104
|
if hash.key?("enforce_dependencies")
|
132
105
|
unless [TrueClass, FalseClass].include?(hash["enforce_dependencies"].class)
|
133
|
-
errors << "Invalid 'enforce_dependencies' option in #{f.inspect}: #{hash[
|
106
|
+
errors << "Invalid 'enforce_dependencies' option in #{f.inspect}: #{hash["enforce_dependencies"].inspect}"
|
134
107
|
end
|
135
108
|
end
|
136
109
|
|
137
110
|
if hash.key?("public_path")
|
138
111
|
unless hash["public_path"].is_a?(String)
|
139
|
-
errors << "'public_path' option must be a string in #{f.inspect}: #{hash[
|
112
|
+
errors << "'public_path' option must be a string in #{f.inspect}: #{hash["public_path"].inspect}"
|
140
113
|
end
|
141
114
|
end
|
142
115
|
|
143
116
|
next unless hash.key?("dependencies")
|
144
117
|
next if hash["dependencies"].is_a?(Array)
|
145
118
|
|
146
|
-
errors << "Invalid 'dependencies' option in #{f.inspect}: #{hash[
|
119
|
+
errors << "Invalid 'dependencies' option in #{f.inspect}: #{hash["dependencies"].inspect}"
|
147
120
|
end
|
148
121
|
|
149
122
|
if errors.empty?
|
@@ -193,40 +166,20 @@ module Packwerk
|
|
193
166
|
end
|
194
167
|
end
|
195
168
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
Result.new(
|
202
|
-
false,
|
203
|
-
"Inflections specified in #{inflections_file} don't line up with application!\n" +
|
204
|
-
errors.map(&:error_value).join("\n")
|
205
|
-
)
|
206
|
-
end
|
169
|
+
merge_results(
|
170
|
+
results,
|
171
|
+
separator: "\n",
|
172
|
+
errors_headline: "Inflections specified in #{inflections_file} don't line up with application!\n"
|
173
|
+
)
|
207
174
|
end
|
208
175
|
|
209
176
|
def check_acyclic_graph
|
210
|
-
|
211
|
-
|
212
|
-
edges = packages.flat_map do |package|
|
213
|
-
package.dependencies.map { |dependency| [package, packages.fetch(dependency)] }
|
214
|
-
end
|
215
|
-
dependency_graph = Packwerk::Graph.new(*edges)
|
216
|
-
|
217
|
-
# Convert the cycle
|
218
|
-
#
|
219
|
-
# [a, b, c]
|
220
|
-
#
|
221
|
-
# to the string
|
222
|
-
#
|
223
|
-
# a -> b -> c -> a
|
224
|
-
#
|
225
|
-
cycle_strings = dependency_graph.cycles.map do |cycle|
|
226
|
-
cycle_strings = cycle.map(&:to_s)
|
227
|
-
cycle_strings << cycle.first.to_s
|
228
|
-
"\t- #{cycle_strings.join(' → ')}"
|
177
|
+
edges = package_set.flat_map do |package|
|
178
|
+
package.dependencies.map { |dependency| [package, package_set.fetch(dependency)] }
|
229
179
|
end
|
180
|
+
dependency_graph = Packwerk::Graph.new(*T.unsafe(edges))
|
181
|
+
|
182
|
+
cycle_strings = build_cycle_strings(dependency_graph.cycles)
|
230
183
|
|
231
184
|
if dependency_graph.acyclic?
|
232
185
|
Result.new(true)
|
@@ -315,9 +268,23 @@ module Packwerk
|
|
315
268
|
|
316
269
|
private
|
317
270
|
|
271
|
+
# Convert the cycles:
|
272
|
+
#
|
273
|
+
# [[a, b, c], [b, c]]
|
274
|
+
#
|
275
|
+
# to the string:
|
276
|
+
#
|
277
|
+
# ["a -> b -> c -> a", "b -> c -> b"]
|
278
|
+
def build_cycle_strings(cycles)
|
279
|
+
cycles.map do |cycle|
|
280
|
+
cycle_strings = cycle.map(&:to_s)
|
281
|
+
cycle_strings << cycle.first.to_s
|
282
|
+
"\t- #{cycle_strings.join(" → ")}"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
318
286
|
def package_manifests_settings_for(setting)
|
319
|
-
package_manifests(
|
320
|
-
.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
287
|
+
package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
321
288
|
end
|
322
289
|
|
323
290
|
def format_yaml_strings(list)
|
@@ -328,13 +295,17 @@ module Packwerk
|
|
328
295
|
@configuration.package_paths || "**"
|
329
296
|
end
|
330
297
|
|
331
|
-
def package_manifests(glob_pattern)
|
298
|
+
def package_manifests(glob_pattern = package_glob)
|
332
299
|
PackageSet.package_paths(@configuration.root_path, glob_pattern)
|
333
300
|
.map { |f| File.realpath(f) }
|
334
301
|
end
|
335
302
|
|
336
303
|
def relative_paths(paths)
|
337
|
-
paths.map { |path|
|
304
|
+
paths.map { |path| relative_path(path) }
|
305
|
+
end
|
306
|
+
|
307
|
+
def relative_path(path)
|
308
|
+
Pathname.new(path).relative_path_from(@configuration.root_path)
|
338
309
|
end
|
339
310
|
|
340
311
|
def invalid_package_path?(path)
|
@@ -344,5 +315,54 @@ module Packwerk
|
|
344
315
|
package_path = File.join(@configuration.root_path, path, Packwerk::PackageSet::PACKAGE_CONFIG_FILENAME)
|
345
316
|
!File.file?(package_path)
|
346
317
|
end
|
318
|
+
|
319
|
+
def assert_constants_can_be_loaded(constants)
|
320
|
+
constants.each(&:constantize)
|
321
|
+
nil
|
322
|
+
end
|
323
|
+
|
324
|
+
def private_constant_unresolvable(name, config_file_path)
|
325
|
+
explicit_filepath = (name.start_with?("::") ? name[2..-1] : name).underscore + ".rb"
|
326
|
+
|
327
|
+
Result.new(
|
328
|
+
false,
|
329
|
+
"'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
|
330
|
+
"This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
|
331
|
+
"file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
|
332
|
+
"private. Add a #{explicit_filepath} file to explicitly define the constant."
|
333
|
+
)
|
334
|
+
end
|
335
|
+
|
336
|
+
def check_private_constant_location(name, location, config_file_path)
|
337
|
+
declared_package = package_set.package_from_path(relative_path(config_file_path))
|
338
|
+
constant_package = package_set.package_from_path(location)
|
339
|
+
|
340
|
+
if constant_package == declared_package
|
341
|
+
Result.new(true)
|
342
|
+
else
|
343
|
+
Result.new(
|
344
|
+
false,
|
345
|
+
"'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
|
346
|
+
"defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
|
347
|
+
)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def package_set
|
352
|
+
@package_set ||= Packwerk::PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob)
|
353
|
+
end
|
354
|
+
|
355
|
+
def merge_results(results, separator: "\n===\n", errors_headline: "")
|
356
|
+
results.reject!(&:ok?)
|
357
|
+
|
358
|
+
if results.empty?
|
359
|
+
Result.new(true)
|
360
|
+
else
|
361
|
+
Result.new(
|
362
|
+
false,
|
363
|
+
errors_headline + results.map(&:error_value).join(separator)
|
364
|
+
)
|
365
|
+
end
|
366
|
+
end
|
347
367
|
end
|
348
368
|
end
|
@@ -1,26 +1,35 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "packwerk/constant_name_inspector"
|
5
|
-
require "packwerk/node"
|
6
|
-
|
7
4
|
module Packwerk
|
8
5
|
# Extracts the implicit constant reference from an active record association
|
9
6
|
class AssociationInspector
|
7
|
+
extend T::Sig
|
10
8
|
include ConstantNameInspector
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
CustomAssociations = T.type_alias { T.any(T::Array[Symbol], T::Set[Symbol]) }
|
11
|
+
|
12
|
+
RAILS_ASSOCIATIONS = T.let(
|
13
|
+
%i(
|
14
|
+
belongs_to
|
15
|
+
has_many
|
16
|
+
has_one
|
17
|
+
has_and_belongs_to_many
|
18
|
+
).to_set,
|
19
|
+
CustomAssociations
|
20
|
+
)
|
18
21
|
|
22
|
+
sig { params(inflector: Inflector, custom_associations: CustomAssociations).void }
|
19
23
|
def initialize(inflector:, custom_associations: Set.new)
|
20
24
|
@inflector = inflector
|
21
|
-
@associations = RAILS_ASSOCIATIONS + custom_associations
|
25
|
+
@associations = T.let(RAILS_ASSOCIATIONS + custom_associations, CustomAssociations)
|
22
26
|
end
|
23
27
|
|
28
|
+
sig do
|
29
|
+
override
|
30
|
+
.params(node: AST::Node, ancestors: T::Array[AST::Node])
|
31
|
+
.returns(T.nilable(String))
|
32
|
+
end
|
24
33
|
def constant_name_from_node(node, ancestors:)
|
25
34
|
return unless Node.method_call?(node)
|
26
35
|
return unless association?(node)
|
@@ -38,11 +47,13 @@ module Packwerk
|
|
38
47
|
|
39
48
|
private
|
40
49
|
|
50
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
41
51
|
def association?(node)
|
42
52
|
method_name = Node.method_name(node)
|
43
53
|
@associations.include?(method_name)
|
44
54
|
end
|
45
55
|
|
56
|
+
sig { params(arguments: T::Array[AST::Node]).returns(T.nilable(AST::Node)) }
|
46
57
|
def custom_class_name(arguments)
|
47
58
|
association_options = arguments.detect { |n| Node.hash?(n) }
|
48
59
|
return unless association_options
|
@@ -50,6 +61,7 @@ module Packwerk
|
|
50
61
|
Node.value_from_hash(association_options, :class_name)
|
51
62
|
end
|
52
63
|
|
64
|
+
sig { params(arguments: T::Array[AST::Node]).returns(T.any(T.nilable(Symbol), T.nilable(String))) }
|
53
65
|
def association_name(arguments)
|
54
66
|
return unless Node.symbol?(arguments[0])
|
55
67
|
|
data/lib/packwerk/checker.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "sorbet-runtime"
|
5
|
-
require "packwerk/reference_lister"
|
6
|
-
|
7
4
|
module Packwerk
|
8
5
|
module Checker
|
9
6
|
extend T::Sig
|
@@ -11,10 +8,10 @@ module Packwerk
|
|
11
8
|
|
12
9
|
interface!
|
13
10
|
|
14
|
-
sig {
|
15
|
-
def
|
11
|
+
sig { returns(ViolationType).abstract }
|
12
|
+
def violation_type; end
|
16
13
|
|
17
|
-
sig { params(reference: Reference).returns(
|
18
|
-
def
|
14
|
+
sig { params(reference: Reference).returns(T::Boolean).abstract }
|
15
|
+
def invalid_reference?(reference); end
|
19
16
|
end
|
20
17
|
end
|
data/lib/packwerk/cli.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
|
-
require "benchmark"
|
4
|
-
require "sorbet-runtime"
|
5
|
-
|
6
|
-
require "packwerk/application_validator"
|
7
|
-
require "packwerk/configuration"
|
8
|
-
require "packwerk/files_for_processing"
|
9
|
-
require "packwerk/formatters/progress_formatter"
|
10
|
-
require "packwerk/inflector"
|
11
|
-
require "packwerk/output_styles"
|
12
|
-
require "packwerk/run_context"
|
13
|
-
require "packwerk/updating_deprecated_references"
|
14
|
-
require "packwerk/checking_deprecated_references"
|
15
|
-
require "packwerk/commands/detect_stale_violations_command"
|
16
|
-
require "packwerk/commands/offense_progress_marker"
|
17
3
|
|
18
4
|
module Packwerk
|
19
5
|
class Cli
|
20
6
|
extend T::Sig
|
21
|
-
include OffenseProgressMarker
|
22
7
|
|
23
|
-
|
8
|
+
sig do
|
9
|
+
params(
|
10
|
+
configuration: T.nilable(Configuration),
|
11
|
+
out: T.any(StringIO, IO),
|
12
|
+
err_out: T.any(StringIO, IO),
|
13
|
+
environment: String,
|
14
|
+
style: Packwerk::OutputStyle,
|
15
|
+
offenses_formatter: T.nilable(Packwerk::OffensesFormatter)
|
16
|
+
).void
|
17
|
+
end
|
18
|
+
def initialize(
|
19
|
+
configuration: nil,
|
20
|
+
out: $stdout,
|
21
|
+
err_out: $stderr,
|
22
|
+
environment: "test",
|
23
|
+
style: OutputStyles::Plain.new,
|
24
|
+
offenses_formatter: nil
|
25
|
+
)
|
24
26
|
@out = out
|
25
27
|
@err_out = err_out
|
28
|
+
@environment = environment
|
26
29
|
@style = style
|
27
30
|
@configuration = configuration || Configuration.from_path
|
28
|
-
@run_context = run_context || Packwerk::RunContext.from_configuration(
|
29
|
-
@configuration,
|
30
|
-
reference_lister: ::Packwerk::CheckingDeprecatedReferences.new(@configuration.root_path),
|
31
|
-
)
|
32
31
|
@progress_formatter = Formatters::ProgressFormatter.new(@out, style: style)
|
32
|
+
@offenses_formatter = offenses_formatter || Formatters::OffensesFormatter.new(style: @style)
|
33
33
|
end
|
34
34
|
|
35
35
|
sig { params(args: T::Array[String]).returns(T.noreturn) }
|
@@ -47,13 +47,13 @@ module Packwerk
|
|
47
47
|
when "generate_configs"
|
48
48
|
generate_configs
|
49
49
|
when "check"
|
50
|
-
|
50
|
+
output_result(parse_run(args).check)
|
51
51
|
when "detect-stale-violations"
|
52
|
-
|
52
|
+
output_result(parse_run(args).detect_stale_violations)
|
53
53
|
when "update"
|
54
54
|
update(args)
|
55
55
|
when "update-deprecations"
|
56
|
-
|
56
|
+
output_result(parse_run(args).update_deprecations)
|
57
57
|
when "validate"
|
58
58
|
validate(args)
|
59
59
|
when nil, "help"
|
@@ -80,28 +80,12 @@ module Packwerk
|
|
80
80
|
def init
|
81
81
|
@out.puts("📦 Initializing Packwerk...")
|
82
82
|
|
83
|
-
|
84
|
-
for_rails_app: rails_app?,
|
85
|
-
root: @configuration.root_path,
|
86
|
-
out: @out
|
87
|
-
)
|
88
|
-
|
89
|
-
if application_validation
|
90
|
-
if rails_app?
|
91
|
-
# To run in the same space as the Rails process,
|
92
|
-
# in order to fetch load paths for the configuration generator
|
93
|
-
exec("bin/packwerk", "generate_configs")
|
94
|
-
else
|
95
|
-
generate_configurations = generate_configs
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
application_validation && generate_configurations
|
83
|
+
generate_configs
|
100
84
|
end
|
101
85
|
|
102
86
|
def generate_configs
|
103
87
|
configuration_file = Packwerk::Generators::ConfigurationFile.generate(
|
104
|
-
load_paths: @configuration.
|
88
|
+
load_paths: Packwerk::ApplicationLoadPaths.extract_relevant_paths(@configuration.root_path, @environment),
|
105
89
|
root: @configuration.root_path,
|
106
90
|
out: @out
|
107
91
|
)
|
@@ -130,71 +114,10 @@ module Packwerk
|
|
130
114
|
|
131
115
|
def update(paths)
|
132
116
|
warn("`packwerk update` is deprecated in favor of `packwerk update-deprecations`.")
|
133
|
-
|
117
|
+
output_result(parse_run(paths).update_deprecations)
|
134
118
|
end
|
135
119
|
|
136
|
-
def
|
137
|
-
updating_deprecated_references = ::Packwerk::UpdatingDeprecatedReferences.new(@configuration.root_path)
|
138
|
-
@run_context = Packwerk::RunContext.from_configuration(
|
139
|
-
@configuration,
|
140
|
-
reference_lister: updating_deprecated_references
|
141
|
-
)
|
142
|
-
|
143
|
-
files = fetch_files_to_process(paths)
|
144
|
-
|
145
|
-
@progress_formatter.started(files)
|
146
|
-
|
147
|
-
all_offenses = T.let([], T.untyped)
|
148
|
-
execution_time = Benchmark.realtime do
|
149
|
-
all_offenses = files.flat_map do |path|
|
150
|
-
@run_context.process_file(file: path).tap do |offenses|
|
151
|
-
mark_progress(offenses: offenses, progress_formatter: @progress_formatter)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
updating_deprecated_references.dump_deprecated_references_files
|
156
|
-
end
|
157
|
-
|
158
|
-
@out.puts # put a new line after the progress dots
|
159
|
-
show_offenses(all_offenses)
|
160
|
-
@progress_formatter.finished(execution_time)
|
161
|
-
@out.puts("✅ `deprecated_references.yml` has been updated.")
|
162
|
-
|
163
|
-
all_offenses.empty?
|
164
|
-
end
|
165
|
-
|
166
|
-
def check(paths)
|
167
|
-
files = fetch_files_to_process(paths)
|
168
|
-
|
169
|
-
@progress_formatter.started(files)
|
170
|
-
|
171
|
-
all_offenses = T.let([], T.untyped)
|
172
|
-
execution_time = Benchmark.realtime do
|
173
|
-
files.each do |path|
|
174
|
-
@run_context.process_file(file: path).tap do |offenses|
|
175
|
-
mark_progress(offenses: offenses, progress_formatter: @progress_formatter)
|
176
|
-
all_offenses.concat(offenses)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
rescue Interrupt
|
180
|
-
@out.puts
|
181
|
-
@out.puts("Manually interrupted. Violations caught so far are listed below:")
|
182
|
-
end
|
183
|
-
|
184
|
-
@out.puts # put a new line after the progress dots
|
185
|
-
show_offenses(all_offenses)
|
186
|
-
@progress_formatter.finished(execution_time)
|
187
|
-
|
188
|
-
all_offenses.empty?
|
189
|
-
end
|
190
|
-
|
191
|
-
def detect_stale_violations(paths)
|
192
|
-
detect_stale_violations = DetectStaleViolationsCommand.new(
|
193
|
-
files: fetch_files_to_process(paths),
|
194
|
-
configuration: @configuration,
|
195
|
-
progress_formatter: @progress_formatter
|
196
|
-
)
|
197
|
-
result = detect_stale_violations.run
|
120
|
+
def output_result(result)
|
198
121
|
@out.puts
|
199
122
|
@out.puts(result.message)
|
200
123
|
result.status
|
@@ -208,14 +131,11 @@ module Packwerk
|
|
208
131
|
end
|
209
132
|
|
210
133
|
def validate(_paths)
|
211
|
-
warn("`packwerk validate` should be run within the application. "\
|
212
|
-
"Generate the bin script using `packwerk init` and"\
|
213
|
-
" use `bin/packwerk validate` instead.") unless defined?(::Rails)
|
214
|
-
|
215
134
|
@progress_formatter.started_validation do
|
216
135
|
checker = Packwerk::ApplicationValidator.new(
|
217
136
|
config_file_path: @configuration.config_path,
|
218
|
-
configuration: @configuration
|
137
|
+
configuration: @configuration,
|
138
|
+
environment: @environment,
|
219
139
|
)
|
220
140
|
result = checker.check_all
|
221
141
|
|
@@ -225,19 +145,6 @@ module Packwerk
|
|
225
145
|
end
|
226
146
|
end
|
227
147
|
|
228
|
-
def show_offenses(offenses)
|
229
|
-
if offenses.empty?
|
230
|
-
@out.puts("No offenses detected 🎉")
|
231
|
-
else
|
232
|
-
offenses.each do |offense|
|
233
|
-
@out.puts(offense.to_s(@style))
|
234
|
-
end
|
235
|
-
|
236
|
-
offenses_string = Inflector.default.pluralize("offense", offenses.length)
|
237
|
-
@out.puts("#{offenses.length} #{offenses_string} detected")
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
148
|
def list_validation_errors(result)
|
242
149
|
@out.puts
|
243
150
|
if result.ok?
|
@@ -248,13 +155,13 @@ module Packwerk
|
|
248
155
|
end
|
249
156
|
end
|
250
157
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
158
|
+
def parse_run(paths)
|
159
|
+
ParseRun.new(
|
160
|
+
files: fetch_files_to_process(paths),
|
161
|
+
configuration: @configuration,
|
162
|
+
progress_formatter: @progress_formatter,
|
163
|
+
offenses_formatter: @offenses_formatter
|
164
|
+
)
|
258
165
|
end
|
259
166
|
end
|
260
167
|
end
|