packwerk 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  3. data/.github/probots.yml +2 -0
  4. data/.github/pull_request_template.md +27 -0
  5. data/.github/workflows/ci.yml +50 -0
  6. data/.gitignore +12 -0
  7. data/.rubocop.yml +46 -0
  8. data/.ruby-version +1 -0
  9. data/CODEOWNERS +1 -0
  10. data/CODE_OF_CONDUCT.md +76 -0
  11. data/CONTRIBUTING.md +17 -0
  12. data/Gemfile +22 -0
  13. data/Gemfile.lock +236 -0
  14. data/LICENSE.md +7 -0
  15. data/README.md +73 -0
  16. data/Rakefile +13 -0
  17. data/TROUBLESHOOT.md +67 -0
  18. data/USAGE.md +250 -0
  19. data/bin/console +15 -0
  20. data/bin/setup +8 -0
  21. data/dev.yml +32 -0
  22. data/docs/cohesion.png +0 -0
  23. data/exe/packwerk +6 -0
  24. data/lib/packwerk.rb +44 -0
  25. data/lib/packwerk/application_validator.rb +343 -0
  26. data/lib/packwerk/association_inspector.rb +44 -0
  27. data/lib/packwerk/checking_deprecated_references.rb +40 -0
  28. data/lib/packwerk/cli.rb +238 -0
  29. data/lib/packwerk/configuration.rb +82 -0
  30. data/lib/packwerk/const_node_inspector.rb +44 -0
  31. data/lib/packwerk/constant_discovery.rb +60 -0
  32. data/lib/packwerk/constant_name_inspector.rb +22 -0
  33. data/lib/packwerk/dependency_checker.rb +28 -0
  34. data/lib/packwerk/deprecated_references.rb +92 -0
  35. data/lib/packwerk/file_processor.rb +43 -0
  36. data/lib/packwerk/files_for_processing.rb +67 -0
  37. data/lib/packwerk/formatters/progress_formatter.rb +46 -0
  38. data/lib/packwerk/generators/application_validation.rb +62 -0
  39. data/lib/packwerk/generators/configuration_file.rb +69 -0
  40. data/lib/packwerk/generators/inflections_file.rb +43 -0
  41. data/lib/packwerk/generators/root_package.rb +37 -0
  42. data/lib/packwerk/generators/templates/inflections.yml +6 -0
  43. data/lib/packwerk/generators/templates/package.yml +17 -0
  44. data/lib/packwerk/generators/templates/packwerk +23 -0
  45. data/lib/packwerk/generators/templates/packwerk.yml.erb +23 -0
  46. data/lib/packwerk/generators/templates/packwerk_validator_test.rb +11 -0
  47. data/lib/packwerk/graph.rb +74 -0
  48. data/lib/packwerk/inflections/custom.rb +33 -0
  49. data/lib/packwerk/inflections/default.rb +73 -0
  50. data/lib/packwerk/inflector.rb +41 -0
  51. data/lib/packwerk/node.rb +259 -0
  52. data/lib/packwerk/node_processor.rb +49 -0
  53. data/lib/packwerk/node_visitor.rb +22 -0
  54. data/lib/packwerk/offense.rb +44 -0
  55. data/lib/packwerk/output_styles.rb +41 -0
  56. data/lib/packwerk/package.rb +56 -0
  57. data/lib/packwerk/package_set.rb +59 -0
  58. data/lib/packwerk/parsed_constant_definitions.rb +62 -0
  59. data/lib/packwerk/parsers.rb +23 -0
  60. data/lib/packwerk/parsers/erb.rb +66 -0
  61. data/lib/packwerk/parsers/factory.rb +34 -0
  62. data/lib/packwerk/parsers/ruby.rb +42 -0
  63. data/lib/packwerk/privacy_checker.rb +45 -0
  64. data/lib/packwerk/reference.rb +6 -0
  65. data/lib/packwerk/reference_extractor.rb +81 -0
  66. data/lib/packwerk/reference_lister.rb +23 -0
  67. data/lib/packwerk/run_context.rb +103 -0
  68. data/lib/packwerk/sanity_checker.rb +10 -0
  69. data/lib/packwerk/spring_command.rb +28 -0
  70. data/lib/packwerk/updating_deprecated_references.rb +51 -0
  71. data/lib/packwerk/version.rb +6 -0
  72. data/lib/packwerk/violation_type.rb +13 -0
  73. data/library.yml +6 -0
  74. data/packwerk.gemspec +58 -0
  75. data/service.yml +6 -0
  76. data/shipit.rubygems.yml +1 -0
  77. data/sorbet/config +2 -0
  78. data/sorbet/rbi/gems/actioncable@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +840 -0
  79. data/sorbet/rbi/gems/actionmailbox@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +571 -0
  80. data/sorbet/rbi/gems/actionmailer@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +568 -0
  81. data/sorbet/rbi/gems/actionpack@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +5216 -0
  82. data/sorbet/rbi/gems/actiontext@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +663 -0
  83. data/sorbet/rbi/gems/actionview@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +2504 -0
  84. data/sorbet/rbi/gems/activejob@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +635 -0
  85. data/sorbet/rbi/gems/activemodel@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +1201 -0
  86. data/sorbet/rbi/gems/activerecord@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +8011 -0
  87. data/sorbet/rbi/gems/activestorage@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +904 -0
  88. data/sorbet/rbi/gems/activesupport@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +3888 -0
  89. data/sorbet/rbi/gems/ast@2.4.1.rbi +54 -0
  90. data/sorbet/rbi/gems/better_html@1.0.15.rbi +317 -0
  91. data/sorbet/rbi/gems/builder@3.2.4.rbi +8 -0
  92. data/sorbet/rbi/gems/byebug@11.1.3.rbi +8 -0
  93. data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
  94. data/sorbet/rbi/gems/colorize@0.8.1.rbi +40 -0
  95. data/sorbet/rbi/gems/commander@4.5.2.rbi +8 -0
  96. data/sorbet/rbi/gems/concurrent-ruby@1.1.6.rbi +1966 -0
  97. data/sorbet/rbi/gems/constant_resolver@0.1.5.rbi +26 -0
  98. data/sorbet/rbi/gems/crass@1.0.6.rbi +138 -0
  99. data/sorbet/rbi/gems/erubi@1.9.0.rbi +39 -0
  100. data/sorbet/rbi/gems/globalid@0.4.2.rbi +178 -0
  101. data/sorbet/rbi/gems/highline@2.0.3.rbi +8 -0
  102. data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +46 -0
  103. data/sorbet/rbi/gems/i18n@1.8.2.rbi +633 -0
  104. data/sorbet/rbi/gems/jaro_winkler@1.5.4.rbi +8 -0
  105. data/sorbet/rbi/gems/loofah@2.5.0.rbi +272 -0
  106. data/sorbet/rbi/gems/m@1.5.1.rbi +108 -0
  107. data/sorbet/rbi/gems/mail@2.7.1.rbi +2490 -0
  108. data/sorbet/rbi/gems/marcel@0.3.3.rbi +30 -0
  109. data/sorbet/rbi/gems/method_source@1.0.0.rbi +76 -0
  110. data/sorbet/rbi/gems/mimemagic@0.3.5.rbi +47 -0
  111. data/sorbet/rbi/gems/mini_mime@1.0.2.rbi +71 -0
  112. data/sorbet/rbi/gems/mini_portile2@2.4.0.rbi +8 -0
  113. data/sorbet/rbi/gems/minitest@5.14.0.rbi +542 -0
  114. data/sorbet/rbi/gems/mocha@1.11.2.rbi +964 -0
  115. data/sorbet/rbi/gems/nio4r@2.5.2.rbi +89 -0
  116. data/sorbet/rbi/gems/nokogiri@1.10.9.rbi +1608 -0
  117. data/sorbet/rbi/gems/parallel@1.19.1.rbi +8 -0
  118. data/sorbet/rbi/gems/parlour@4.0.1.rbi +561 -0
  119. data/sorbet/rbi/gems/parser@2.7.1.4.rbi +1632 -0
  120. data/sorbet/rbi/gems/pry@0.13.1.rbi +8 -0
  121. data/sorbet/rbi/gems/rack-test@1.1.0.rbi +335 -0
  122. data/sorbet/rbi/gems/rack@2.2.2.rbi +1730 -0
  123. data/sorbet/rbi/gems/rails-dom-testing@2.0.3.rbi +123 -0
  124. data/sorbet/rbi/gems/rails-html-sanitizer@1.3.0.rbi +213 -0
  125. data/sorbet/rbi/gems/rails@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +8 -0
  126. data/sorbet/rbi/gems/railties@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +869 -0
  127. data/sorbet/rbi/gems/rainbow@3.0.0.rbi +155 -0
  128. data/sorbet/rbi/gems/rake@13.0.1.rbi +841 -0
  129. data/sorbet/rbi/gems/rexml@3.2.4.rbi +8 -0
  130. data/sorbet/rbi/gems/rubocop-performance@1.5.2.rbi +8 -0
  131. data/sorbet/rbi/gems/rubocop-shopify@1.0.2.rbi +8 -0
  132. data/sorbet/rbi/gems/rubocop-sorbet@0.3.7.rbi +8 -0
  133. data/sorbet/rbi/gems/rubocop@0.82.0.rbi +8 -0
  134. data/sorbet/rbi/gems/ruby-progressbar@1.10.1.rbi +8 -0
  135. data/sorbet/rbi/gems/smart_properties@1.15.0.rbi +168 -0
  136. data/sorbet/rbi/gems/spoom@1.0.4.rbi +418 -0
  137. data/sorbet/rbi/gems/spring@2.1.0.rbi +160 -0
  138. data/sorbet/rbi/gems/sprockets-rails@3.2.1.rbi +431 -0
  139. data/sorbet/rbi/gems/sprockets@4.0.0.rbi +1132 -0
  140. data/sorbet/rbi/gems/tapioca@0.4.5.rbi +518 -0
  141. data/sorbet/rbi/gems/thor@1.0.1.rbi +892 -0
  142. data/sorbet/rbi/gems/tzinfo@2.0.2.rbi +547 -0
  143. data/sorbet/rbi/gems/unicode-display_width@1.7.0.rbi +8 -0
  144. data/sorbet/rbi/gems/websocket-driver@0.7.1.rbi +438 -0
  145. data/sorbet/rbi/gems/websocket-extensions@0.1.4.rbi +71 -0
  146. data/sorbet/rbi/gems/zeitwerk@2.3.0.rbi +8 -0
  147. data/sorbet/tapioca/require.rb +25 -0
  148. data/static/packwerk-check-demo.png +0 -0
  149. data/static/packwerk_check.gif +0 -0
  150. data/static/packwerk_check_violation.gif +0 -0
  151. data/static/packwerk_update.gif +0 -0
  152. data/static/packwerk_validate.gif +0 -0
  153. metadata +341 -0
Binary file
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "packwerk"
5
+
6
+ Packwerk::Cli.new(style: Packwerk::OutputStyles::Coloured).run(ARGV.dup)
@@ -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