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.
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