packwerk 1.0.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 +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- data/.github/probots.yml +2 -0
- data/.github/pull_request_template.md +27 -0
- data/.github/workflows/ci.yml +50 -0
- data/.gitignore +12 -0
- data/.rubocop.yml +46 -0
- data/.ruby-version +1 -0
- data/CODEOWNERS +1 -0
- data/CODE_OF_CONDUCT.md +76 -0
- data/CONTRIBUTING.md +17 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +236 -0
- data/LICENSE.md +7 -0
- data/README.md +73 -0
- data/Rakefile +13 -0
- data/TROUBLESHOOT.md +67 -0
- data/USAGE.md +250 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/dev.yml +32 -0
- data/docs/cohesion.png +0 -0
- data/exe/packwerk +6 -0
- data/lib/packwerk.rb +44 -0
- data/lib/packwerk/application_validator.rb +343 -0
- data/lib/packwerk/association_inspector.rb +44 -0
- data/lib/packwerk/checking_deprecated_references.rb +40 -0
- data/lib/packwerk/cli.rb +238 -0
- data/lib/packwerk/configuration.rb +82 -0
- data/lib/packwerk/const_node_inspector.rb +44 -0
- data/lib/packwerk/constant_discovery.rb +60 -0
- data/lib/packwerk/constant_name_inspector.rb +22 -0
- data/lib/packwerk/dependency_checker.rb +28 -0
- data/lib/packwerk/deprecated_references.rb +92 -0
- data/lib/packwerk/file_processor.rb +43 -0
- data/lib/packwerk/files_for_processing.rb +67 -0
- data/lib/packwerk/formatters/progress_formatter.rb +46 -0
- data/lib/packwerk/generators/application_validation.rb +62 -0
- data/lib/packwerk/generators/configuration_file.rb +69 -0
- data/lib/packwerk/generators/inflections_file.rb +43 -0
- data/lib/packwerk/generators/root_package.rb +37 -0
- data/lib/packwerk/generators/templates/inflections.yml +6 -0
- data/lib/packwerk/generators/templates/package.yml +17 -0
- data/lib/packwerk/generators/templates/packwerk +23 -0
- data/lib/packwerk/generators/templates/packwerk.yml.erb +23 -0
- data/lib/packwerk/generators/templates/packwerk_validator_test.rb +11 -0
- data/lib/packwerk/graph.rb +74 -0
- data/lib/packwerk/inflections/custom.rb +33 -0
- data/lib/packwerk/inflections/default.rb +73 -0
- data/lib/packwerk/inflector.rb +41 -0
- data/lib/packwerk/node.rb +259 -0
- data/lib/packwerk/node_processor.rb +49 -0
- data/lib/packwerk/node_visitor.rb +22 -0
- data/lib/packwerk/offense.rb +44 -0
- data/lib/packwerk/output_styles.rb +41 -0
- data/lib/packwerk/package.rb +56 -0
- data/lib/packwerk/package_set.rb +59 -0
- data/lib/packwerk/parsed_constant_definitions.rb +62 -0
- data/lib/packwerk/parsers.rb +23 -0
- data/lib/packwerk/parsers/erb.rb +66 -0
- data/lib/packwerk/parsers/factory.rb +34 -0
- data/lib/packwerk/parsers/ruby.rb +42 -0
- data/lib/packwerk/privacy_checker.rb +45 -0
- data/lib/packwerk/reference.rb +6 -0
- data/lib/packwerk/reference_extractor.rb +81 -0
- data/lib/packwerk/reference_lister.rb +23 -0
- data/lib/packwerk/run_context.rb +103 -0
- data/lib/packwerk/sanity_checker.rb +10 -0
- data/lib/packwerk/spring_command.rb +28 -0
- data/lib/packwerk/updating_deprecated_references.rb +51 -0
- data/lib/packwerk/version.rb +6 -0
- data/lib/packwerk/violation_type.rb +13 -0
- data/library.yml +6 -0
- data/packwerk.gemspec +58 -0
- data/service.yml +6 -0
- data/shipit.rubygems.yml +1 -0
- data/sorbet/config +2 -0
- data/sorbet/rbi/gems/actioncable@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +840 -0
- data/sorbet/rbi/gems/actionmailbox@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +571 -0
- data/sorbet/rbi/gems/actionmailer@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +568 -0
- data/sorbet/rbi/gems/actionpack@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +5216 -0
- data/sorbet/rbi/gems/actiontext@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +663 -0
- data/sorbet/rbi/gems/actionview@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +2504 -0
- data/sorbet/rbi/gems/activejob@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +635 -0
- data/sorbet/rbi/gems/activemodel@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +1201 -0
- data/sorbet/rbi/gems/activerecord@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +8011 -0
- data/sorbet/rbi/gems/activestorage@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +904 -0
- data/sorbet/rbi/gems/activesupport@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +3888 -0
- data/sorbet/rbi/gems/ast@2.4.1.rbi +54 -0
- data/sorbet/rbi/gems/better_html@1.0.15.rbi +317 -0
- data/sorbet/rbi/gems/builder@3.2.4.rbi +8 -0
- data/sorbet/rbi/gems/byebug@11.1.3.rbi +8 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
- data/sorbet/rbi/gems/colorize@0.8.1.rbi +40 -0
- data/sorbet/rbi/gems/commander@4.5.2.rbi +8 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.1.6.rbi +1966 -0
- data/sorbet/rbi/gems/constant_resolver@0.1.5.rbi +26 -0
- data/sorbet/rbi/gems/crass@1.0.6.rbi +138 -0
- data/sorbet/rbi/gems/erubi@1.9.0.rbi +39 -0
- data/sorbet/rbi/gems/globalid@0.4.2.rbi +178 -0
- data/sorbet/rbi/gems/highline@2.0.3.rbi +8 -0
- data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +46 -0
- data/sorbet/rbi/gems/i18n@1.8.2.rbi +633 -0
- data/sorbet/rbi/gems/jaro_winkler@1.5.4.rbi +8 -0
- data/sorbet/rbi/gems/loofah@2.5.0.rbi +272 -0
- data/sorbet/rbi/gems/m@1.5.1.rbi +108 -0
- data/sorbet/rbi/gems/mail@2.7.1.rbi +2490 -0
- data/sorbet/rbi/gems/marcel@0.3.3.rbi +30 -0
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +76 -0
- data/sorbet/rbi/gems/mimemagic@0.3.5.rbi +47 -0
- data/sorbet/rbi/gems/mini_mime@1.0.2.rbi +71 -0
- data/sorbet/rbi/gems/mini_portile2@2.4.0.rbi +8 -0
- data/sorbet/rbi/gems/minitest@5.14.0.rbi +542 -0
- data/sorbet/rbi/gems/mocha@1.11.2.rbi +964 -0
- data/sorbet/rbi/gems/nio4r@2.5.2.rbi +89 -0
- data/sorbet/rbi/gems/nokogiri@1.10.9.rbi +1608 -0
- data/sorbet/rbi/gems/parallel@1.19.1.rbi +8 -0
- data/sorbet/rbi/gems/parlour@4.0.1.rbi +561 -0
- data/sorbet/rbi/gems/parser@2.7.1.4.rbi +1632 -0
- data/sorbet/rbi/gems/pry@0.13.1.rbi +8 -0
- data/sorbet/rbi/gems/rack-test@1.1.0.rbi +335 -0
- data/sorbet/rbi/gems/rack@2.2.2.rbi +1730 -0
- data/sorbet/rbi/gems/rails-dom-testing@2.0.3.rbi +123 -0
- data/sorbet/rbi/gems/rails-html-sanitizer@1.3.0.rbi +213 -0
- data/sorbet/rbi/gems/rails@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +8 -0
- data/sorbet/rbi/gems/railties@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +869 -0
- data/sorbet/rbi/gems/rainbow@3.0.0.rbi +155 -0
- data/sorbet/rbi/gems/rake@13.0.1.rbi +841 -0
- data/sorbet/rbi/gems/rexml@3.2.4.rbi +8 -0
- data/sorbet/rbi/gems/rubocop-performance@1.5.2.rbi +8 -0
- data/sorbet/rbi/gems/rubocop-shopify@1.0.2.rbi +8 -0
- data/sorbet/rbi/gems/rubocop-sorbet@0.3.7.rbi +8 -0
- data/sorbet/rbi/gems/rubocop@0.82.0.rbi +8 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.10.1.rbi +8 -0
- data/sorbet/rbi/gems/smart_properties@1.15.0.rbi +168 -0
- data/sorbet/rbi/gems/spoom@1.0.4.rbi +418 -0
- data/sorbet/rbi/gems/spring@2.1.0.rbi +160 -0
- data/sorbet/rbi/gems/sprockets-rails@3.2.1.rbi +431 -0
- data/sorbet/rbi/gems/sprockets@4.0.0.rbi +1132 -0
- data/sorbet/rbi/gems/tapioca@0.4.5.rbi +518 -0
- data/sorbet/rbi/gems/thor@1.0.1.rbi +892 -0
- data/sorbet/rbi/gems/tzinfo@2.0.2.rbi +547 -0
- data/sorbet/rbi/gems/unicode-display_width@1.7.0.rbi +8 -0
- data/sorbet/rbi/gems/websocket-driver@0.7.1.rbi +438 -0
- data/sorbet/rbi/gems/websocket-extensions@0.1.4.rbi +71 -0
- data/sorbet/rbi/gems/zeitwerk@2.3.0.rbi +8 -0
- data/sorbet/tapioca/require.rb +25 -0
- 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
- metadata +341 -0
data/docs/cohesion.png
ADDED
Binary file
|
data/exe/packwerk
ADDED
data/lib/packwerk.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
require "active_support"
|
6
|
+
require "constant_resolver"
|
7
|
+
|
8
|
+
require "packwerk/offense"
|
9
|
+
|
10
|
+
require "packwerk/application_validator"
|
11
|
+
require "packwerk/association_inspector"
|
12
|
+
require "packwerk/checking_deprecated_references"
|
13
|
+
require "packwerk/cli"
|
14
|
+
require "packwerk/configuration"
|
15
|
+
require "packwerk/const_node_inspector"
|
16
|
+
require "packwerk/constant_discovery"
|
17
|
+
require "packwerk/constant_name_inspector"
|
18
|
+
require "packwerk/dependency_checker"
|
19
|
+
require "packwerk/deprecated_references"
|
20
|
+
require "packwerk/files_for_processing"
|
21
|
+
require "packwerk/file_processor"
|
22
|
+
require "packwerk/formatters/progress_formatter"
|
23
|
+
require "packwerk/generators/application_validation"
|
24
|
+
require "packwerk/generators/configuration_file"
|
25
|
+
require "packwerk/generators/inflections_file"
|
26
|
+
require "packwerk/generators/root_package"
|
27
|
+
require "packwerk/graph"
|
28
|
+
require "packwerk/inflector"
|
29
|
+
require "packwerk/node_processor"
|
30
|
+
require "packwerk/node_visitor"
|
31
|
+
require "packwerk/output_styles"
|
32
|
+
require "packwerk/package"
|
33
|
+
require "packwerk/package_set"
|
34
|
+
require "packwerk/parsers"
|
35
|
+
require "packwerk/privacy_checker"
|
36
|
+
require "packwerk/reference_extractor"
|
37
|
+
require "packwerk/reference_lister"
|
38
|
+
require "packwerk/run_context"
|
39
|
+
require "packwerk/updating_deprecated_references"
|
40
|
+
require "packwerk/version"
|
41
|
+
require "packwerk/violation_type"
|
42
|
+
|
43
|
+
module Packwerk
|
44
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "active_support/inflector/inflections"
|
5
|
+
require "constant_resolver"
|
6
|
+
require "pathname"
|
7
|
+
require "yaml"
|
8
|
+
|
9
|
+
require "packwerk/package_set"
|
10
|
+
require "packwerk/graph"
|
11
|
+
require "packwerk/inflector"
|
12
|
+
|
13
|
+
module Packwerk
|
14
|
+
class ApplicationValidator
|
15
|
+
def initialize(config_file_path:, application_load_paths:, configuration:)
|
16
|
+
@config_file_path = config_file_path
|
17
|
+
@configuration = configuration
|
18
|
+
|
19
|
+
# Load paths should be from the application
|
20
|
+
@application_load_paths = application_load_paths.sort.uniq
|
21
|
+
end
|
22
|
+
|
23
|
+
Result = Struct.new(:ok?, :error_value)
|
24
|
+
|
25
|
+
def check_all
|
26
|
+
results = [
|
27
|
+
check_autoload_path_cache,
|
28
|
+
check_package_manifests_for_privacy,
|
29
|
+
check_package_manifest_syntax,
|
30
|
+
check_application_structure,
|
31
|
+
check_inflection_file,
|
32
|
+
check_acyclic_graph,
|
33
|
+
check_package_manifest_paths,
|
34
|
+
check_valid_package_dependencies,
|
35
|
+
check_root_package_exist,
|
36
|
+
]
|
37
|
+
|
38
|
+
results.reject!(&:ok?)
|
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
|
48
|
+
end
|
49
|
+
|
50
|
+
def check_autoload_path_cache
|
51
|
+
expected = @application_load_paths
|
52
|
+
actual = @configuration.load_paths
|
53
|
+
if expected.sort == actual.sort
|
54
|
+
Result.new(true)
|
55
|
+
else
|
56
|
+
Result.new(
|
57
|
+
false,
|
58
|
+
"Load path cache in #{@config_file_path} incorrect!\n"\
|
59
|
+
"Paths missing from file:\n#{format_yaml_strings(expected - actual)}\n"\
|
60
|
+
"Extraneous load paths in file:\n#{format_yaml_strings(actual - expected)}"
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def check_package_manifests_for_privacy
|
66
|
+
privacy_settings = package_manifests_settings_for("enforce_privacy")
|
67
|
+
|
68
|
+
autoload_paths = @configuration.load_paths
|
69
|
+
|
70
|
+
resolver = ConstantResolver.new(
|
71
|
+
root_path: @configuration.root_path,
|
72
|
+
load_paths: autoload_paths
|
73
|
+
)
|
74
|
+
|
75
|
+
errors = []
|
76
|
+
|
77
|
+
privacy_settings.each do |filepath, setting|
|
78
|
+
next unless setting.is_a?(Array)
|
79
|
+
|
80
|
+
setting.each do |constant|
|
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"
|
91
|
+
|
92
|
+
# We don't support all custom inflections yet, so we may accidentally resolve constants to the
|
93
|
+
# file that defines their parent namespace. This restriction makes sure that we don't.
|
94
|
+
next if context.location.end_with?(expected_filename)
|
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}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
if errors.empty?
|
103
|
+
Result.new(true)
|
104
|
+
else
|
105
|
+
Result.new(false, errors.join("\n---\n"))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def check_package_manifest_syntax
|
110
|
+
errors = []
|
111
|
+
|
112
|
+
package_manifests(package_glob).each do |f|
|
113
|
+
hash = YAML.load_file(f)
|
114
|
+
next unless hash
|
115
|
+
|
116
|
+
known_keys = %w(enforce_privacy enforce_dependencies dependencies metadata)
|
117
|
+
unknown_keys = hash.keys - known_keys
|
118
|
+
|
119
|
+
unless unknown_keys.empty?
|
120
|
+
errors << "Unknown keys in #{f}: #{unknown_keys.inspect}\n"\
|
121
|
+
"If you think a key should be included in your package.yml, please "\
|
122
|
+
"open an issue in https://github.com/Shopify/packwerk"
|
123
|
+
end
|
124
|
+
|
125
|
+
if hash.key?("enforce_privacy")
|
126
|
+
unless [TrueClass, FalseClass, Array].include?(hash["enforce_privacy"].class)
|
127
|
+
errors << "Invalid 'enforce_privacy' option in #{f.inspect}: #{hash['enforce_privacy'].inspect}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
if hash.key?("enforce_dependencies")
|
132
|
+
unless [TrueClass, FalseClass].include?(hash["enforce_dependencies"].class)
|
133
|
+
errors << "Invalid 'enforce_dependencies' option in #{f.inspect}: #{hash['enforce_dependencies'].inspect}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
next unless hash.key?("dependencies")
|
138
|
+
next if hash["dependencies"].is_a?(Array)
|
139
|
+
|
140
|
+
errors << "Invalid 'dependencies' option in #{f.inspect}: #{hash['dependencies'].inspect}"
|
141
|
+
end
|
142
|
+
|
143
|
+
if errors.empty?
|
144
|
+
Result.new(true)
|
145
|
+
else
|
146
|
+
Result.new(false, errors.join("\n---\n"))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def check_application_structure
|
151
|
+
resolver = ConstantResolver.new(
|
152
|
+
root_path: @configuration.root_path.to_s,
|
153
|
+
load_paths: @configuration.load_paths
|
154
|
+
)
|
155
|
+
|
156
|
+
begin
|
157
|
+
resolver.file_map
|
158
|
+
Result.new(true)
|
159
|
+
rescue => e
|
160
|
+
Result.new(false, e.message)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def check_inflection_file
|
165
|
+
inflections_file = @configuration.inflections_file
|
166
|
+
|
167
|
+
test_inflections = ActiveSupport::Inflector::Inflections.new
|
168
|
+
|
169
|
+
Packwerk::Inflections::Default.apply_to(test_inflections)
|
170
|
+
Packwerk::Inflections::Custom.new(inflections_file).apply_to(test_inflections)
|
171
|
+
|
172
|
+
results = %i(plurals singulars uncountables humans acronyms).map do |type|
|
173
|
+
expected = ActiveSupport::Inflector.inflections.public_send(type).to_a
|
174
|
+
actual = test_inflections.public_send(type).to_a
|
175
|
+
|
176
|
+
if expected == actual
|
177
|
+
Result.new(true)
|
178
|
+
else
|
179
|
+
missing_msg = unless (expected - actual).empty?
|
180
|
+
"Expected #{type} to be specified in file: #{expected - actual}"
|
181
|
+
end
|
182
|
+
extraneous_msg = unless (actual - expected).empty?
|
183
|
+
"Extraneous #{type} was specified in file: #{actual - expected}"
|
184
|
+
end
|
185
|
+
Result.new(
|
186
|
+
false,
|
187
|
+
[missing_msg, extraneous_msg].join("\n")
|
188
|
+
)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
errors = results.reject(&:ok?)
|
193
|
+
|
194
|
+
if errors.empty?
|
195
|
+
Result.new(true)
|
196
|
+
else
|
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
|
203
|
+
end
|
204
|
+
|
205
|
+
def check_acyclic_graph
|
206
|
+
packages = Packwerk::PackageSet.load_all_from(".")
|
207
|
+
|
208
|
+
edges = packages.flat_map do |package|
|
209
|
+
package.dependencies.map { |dependency| [package, packages.fetch(dependency)] }
|
210
|
+
end
|
211
|
+
dependency_graph = Packwerk::Graph.new(*edges)
|
212
|
+
|
213
|
+
# Convert the cycle
|
214
|
+
#
|
215
|
+
# [a, b, c]
|
216
|
+
#
|
217
|
+
# to the string
|
218
|
+
#
|
219
|
+
# a -> b -> c -> a
|
220
|
+
#
|
221
|
+
cycle_strings = dependency_graph.cycles.map do |cycle|
|
222
|
+
cycle_strings = cycle.map(&:to_s)
|
223
|
+
cycle_strings << cycle.first.to_s
|
224
|
+
"\t- #{cycle_strings.join(' → ')}"
|
225
|
+
end
|
226
|
+
|
227
|
+
if dependency_graph.acyclic?
|
228
|
+
Result.new(true)
|
229
|
+
else
|
230
|
+
Result.new(
|
231
|
+
false,
|
232
|
+
<<~EOS
|
233
|
+
Expected the package dependency graph to be acyclic, but it contains the following cycles:
|
234
|
+
|
235
|
+
#{cycle_strings.join("\n")}
|
236
|
+
EOS
|
237
|
+
)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def check_package_manifest_paths
|
242
|
+
all_package_manifests = package_manifests("**/")
|
243
|
+
package_paths_package_manifests = package_manifests(package_glob)
|
244
|
+
|
245
|
+
difference = all_package_manifests - package_paths_package_manifests
|
246
|
+
|
247
|
+
if difference.empty?
|
248
|
+
Result.new(true)
|
249
|
+
else
|
250
|
+
Result.new(
|
251
|
+
false,
|
252
|
+
<<~EOS
|
253
|
+
Expected package paths for all package.ymls to be specified, but paths were missing for the following manifests:
|
254
|
+
|
255
|
+
#{relative_paths(difference).join("\n")}
|
256
|
+
EOS
|
257
|
+
)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def check_valid_package_dependencies
|
262
|
+
packages_dependencies = package_manifests_settings_for("dependencies")
|
263
|
+
.delete_if { |_, deps| deps.nil? }
|
264
|
+
|
265
|
+
packages_with_invalid_dependencies =
|
266
|
+
packages_dependencies.each_with_object([]) do |(package, dependencies), invalid_packages|
|
267
|
+
invalid_dependencies = dependencies.filter { |path| invalid_package_path?(path) }
|
268
|
+
invalid_packages << [package, invalid_dependencies] if invalid_dependencies.any?
|
269
|
+
end
|
270
|
+
|
271
|
+
if packages_with_invalid_dependencies.empty?
|
272
|
+
Result.new(true)
|
273
|
+
else
|
274
|
+
error_locations = packages_with_invalid_dependencies.map do |package, invalid_dependencies|
|
275
|
+
package ||= @configuration.root_path
|
276
|
+
package_path = Pathname.new(package).relative_path_from(@configuration.root_path)
|
277
|
+
all_invalid_dependencies = invalid_dependencies.map { |d| " - #{d}" }
|
278
|
+
|
279
|
+
<<~EOS
|
280
|
+
#{package_path}:
|
281
|
+
#{all_invalid_dependencies.join("\n")}
|
282
|
+
EOS
|
283
|
+
end
|
284
|
+
|
285
|
+
Result.new(
|
286
|
+
false,
|
287
|
+
<<~EOS
|
288
|
+
These dependencies do not point to valid packages:
|
289
|
+
|
290
|
+
#{error_locations.join("\n")}
|
291
|
+
EOS
|
292
|
+
)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def check_root_package_exist
|
297
|
+
root_package_path = File.join(@configuration.root_path, "package.yml")
|
298
|
+
all_packages_manifests = package_manifests(package_glob)
|
299
|
+
|
300
|
+
if all_packages_manifests.include?(root_package_path)
|
301
|
+
Result.new(true)
|
302
|
+
else
|
303
|
+
Result.new(
|
304
|
+
false,
|
305
|
+
<<~EOS
|
306
|
+
A root package does not exist. Create an empty `package.yml` at the root directory.
|
307
|
+
EOS
|
308
|
+
)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
private
|
313
|
+
|
314
|
+
def package_manifests_settings_for(setting)
|
315
|
+
package_manifests(package_glob)
|
316
|
+
.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
|
317
|
+
end
|
318
|
+
|
319
|
+
def format_yaml_strings(list)
|
320
|
+
list.sort.map { |p| "- \"#{p}\"" }.join("\n")
|
321
|
+
end
|
322
|
+
|
323
|
+
def package_glob
|
324
|
+
@configuration.package_paths || "**"
|
325
|
+
end
|
326
|
+
|
327
|
+
def package_manifests(glob_pattern)
|
328
|
+
Dir.glob(File.join(glob_pattern, Packwerk::PackageSet::PACKAGE_CONFIG_FILENAME)).map { |f| File.realpath(f) }
|
329
|
+
end
|
330
|
+
|
331
|
+
def relative_paths(paths)
|
332
|
+
paths.map { |path| Pathname.new(path).relative_path_from(@configuration.root_path) }
|
333
|
+
end
|
334
|
+
|
335
|
+
def invalid_package_path?(path)
|
336
|
+
# Packages at the root can be implicitly specified as "."
|
337
|
+
return false if path == "."
|
338
|
+
|
339
|
+
package_path = File.join(@configuration.root_path, path, Packwerk::PackageSet::PACKAGE_CONFIG_FILENAME)
|
340
|
+
!File.file?(package_path)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "packwerk/constant_name_inspector"
|
5
|
+
require "packwerk/node"
|
6
|
+
|
7
|
+
module Packwerk
|
8
|
+
# Extracts the implicit constant reference from an active record association
|
9
|
+
class AssociationInspector
|
10
|
+
include ConstantNameInspector
|
11
|
+
|
12
|
+
RAILS_ASSOCIATIONS = %i(
|
13
|
+
belongs_to
|
14
|
+
has_many
|
15
|
+
has_one
|
16
|
+
has_and_belongs_to_many
|
17
|
+
).to_set
|
18
|
+
|
19
|
+
def initialize(inflector: Inflector.new, custom_associations: Set.new)
|
20
|
+
@inflector = inflector
|
21
|
+
@associations = RAILS_ASSOCIATIONS + custom_associations
|
22
|
+
end
|
23
|
+
|
24
|
+
def constant_name_from_node(node, ancestors:)
|
25
|
+
return unless Node.type(node) == Node::METHOD_CALL
|
26
|
+
|
27
|
+
method_name = Node.method_name(node)
|
28
|
+
return nil unless @associations.include?(method_name)
|
29
|
+
|
30
|
+
arguments = Node.method_arguments(node)
|
31
|
+
association_name = Node.literal_value(arguments[0]) if Node.type(arguments[0]) == Node::SYMBOL
|
32
|
+
return nil unless association_name
|
33
|
+
|
34
|
+
association_options = arguments.detect { |n| Node.type(n) == Node::HASH }
|
35
|
+
class_name_node = Node.value_from_hash(association_options, :class_name) if association_options
|
36
|
+
|
37
|
+
if class_name_node
|
38
|
+
Node.literal_value(class_name_node) if Node.type(class_name_node) == Node::STRING
|
39
|
+
else
|
40
|
+
@inflector.classify(association_name.to_s)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
|
6
|
+
require "packwerk/reference_lister"
|
7
|
+
|
8
|
+
module Packwerk
|
9
|
+
class CheckingDeprecatedReferences
|
10
|
+
extend T::Sig
|
11
|
+
include ReferenceLister
|
12
|
+
|
13
|
+
def initialize(root_path)
|
14
|
+
@root_path = root_path
|
15
|
+
@deprecated_references = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
sig do
|
19
|
+
params(reference: Packwerk::Reference, violation_type: ViolationType)
|
20
|
+
.returns(T::Boolean)
|
21
|
+
.override
|
22
|
+
end
|
23
|
+
def listed?(reference, violation_type:)
|
24
|
+
deprecated_references_for(reference.source_package).listed?(reference, violation_type: violation_type)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def deprecated_references_for(source_package)
|
30
|
+
@deprecated_references[source_package] ||= Packwerk::DeprecatedReferences.new(
|
31
|
+
source_package,
|
32
|
+
deprecated_references_file_for(source_package),
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def deprecated_references_file_for(package)
|
37
|
+
File.join(@root_path, package.name, "deprecated_references.yml")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|