packwerk 1.0.2 → 1.2.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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +14 -5
  3. data/.ruby-version +1 -1
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +129 -111
  6. data/README.md +8 -1
  7. data/USAGE.md +39 -17
  8. data/dev.yml +1 -1
  9. data/exe/packwerk +1 -1
  10. data/gemfiles/Gemfile-rails-6-0 +22 -0
  11. data/lib/packwerk.rb +73 -34
  12. data/lib/packwerk/application_load_paths.rb +3 -2
  13. data/lib/packwerk/application_validator.rb +85 -69
  14. data/lib/packwerk/association_inspector.rb +23 -11
  15. data/lib/packwerk/checker.rb +4 -7
  16. data/lib/packwerk/cli.rb +36 -93
  17. data/lib/packwerk/configuration.rb +10 -2
  18. data/lib/packwerk/const_node_inspector.rb +13 -14
  19. data/lib/packwerk/constant_discovery.rb +2 -0
  20. data/lib/packwerk/constant_name_inspector.rb +0 -1
  21. data/lib/packwerk/dependency_checker.rb +12 -17
  22. data/lib/packwerk/deprecated_references.rb +25 -8
  23. data/lib/packwerk/file_processor.rb +0 -4
  24. data/lib/packwerk/formatters/offenses_formatter.rb +43 -0
  25. data/lib/packwerk/formatters/progress_formatter.rb +9 -4
  26. data/lib/packwerk/generators/configuration_file.rb +0 -1
  27. data/lib/packwerk/inflector.rb +0 -2
  28. data/lib/packwerk/node.rb +9 -2
  29. data/lib/packwerk/node_processor.rb +15 -32
  30. data/lib/packwerk/node_processor_factory.rb +0 -5
  31. data/lib/packwerk/node_visitor.rb +1 -4
  32. data/lib/packwerk/offense.rb +2 -8
  33. data/lib/packwerk/offense_collection.rb +84 -0
  34. data/lib/packwerk/offenses_formatter.rb +15 -0
  35. data/lib/packwerk/output_style.rb +20 -0
  36. data/lib/packwerk/output_styles/coloured.rb +29 -0
  37. data/lib/packwerk/output_styles/plain.rb +26 -0
  38. data/lib/packwerk/package.rb +8 -0
  39. data/lib/packwerk/package_set.rb +8 -5
  40. data/lib/packwerk/parse_run.rb +104 -0
  41. data/lib/packwerk/parsed_constant_definitions.rb +2 -4
  42. data/lib/packwerk/parsers.rb +0 -2
  43. data/lib/packwerk/parsers/erb.rb +4 -2
  44. data/lib/packwerk/parsers/factory.rb +10 -3
  45. data/lib/packwerk/privacy_checker.rb +22 -17
  46. data/lib/packwerk/reference_extractor.rb +0 -8
  47. data/lib/packwerk/reference_offense.rb +49 -0
  48. data/lib/packwerk/result.rb +9 -0
  49. data/lib/packwerk/run_context.rb +4 -20
  50. data/lib/packwerk/sanity_checker.rb +1 -3
  51. data/lib/packwerk/spring_command.rb +1 -1
  52. data/lib/packwerk/version.rb +1 -1
  53. data/lib/packwerk/violation_type.rb +0 -2
  54. data/library.yml +1 -1
  55. data/packwerk.gemspec +1 -0
  56. data/service.yml +1 -4
  57. data/shipit.rubygems.yml +5 -1
  58. data/sorbet/rbi/gems/{actioncable@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +56 -36
  59. data/sorbet/rbi/gems/{actionmailbox@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +25 -28
  60. data/sorbet/rbi/gems/{actionmailer@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +43 -24
  61. data/sorbet/rbi/gems/{actionpack@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +382 -284
  62. data/sorbet/rbi/gems/{actiontext@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +76 -40
  63. data/sorbet/rbi/gems/{actionview@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +206 -195
  64. data/sorbet/rbi/gems/{activejob@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +64 -75
  65. data/sorbet/rbi/gems/{activemodel@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +103 -56
  66. data/sorbet/rbi/gems/{activerecord@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1250 -898
  67. data/sorbet/rbi/gems/{activestorage@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +92 -120
  68. data/sorbet/rbi/gems/{activesupport@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +292 -193
  69. data/sorbet/rbi/gems/{ast@2.4.1.rbi → ast@2.4.2.rbi} +2 -1
  70. data/sorbet/rbi/gems/{better_html@1.0.15.rbi → better_html@1.0.16.rbi} +2 -2
  71. data/sorbet/rbi/gems/{concurrent-ruby@1.1.6.rbi → concurrent-ruby@1.1.8.rbi} +12 -9
  72. data/sorbet/rbi/gems/{erubi@1.9.0.rbi → erubi@1.10.0.rbi} +3 -1
  73. data/sorbet/rbi/gems/{i18n@1.8.2.rbi → i18n@1.8.10.rbi} +19 -52
  74. data/sorbet/rbi/gems/{loofah@2.5.0.rbi → loofah@2.9.0.rbi} +3 -1
  75. data/sorbet/rbi/gems/marcel@1.0.0.rbi +70 -0
  76. data/sorbet/rbi/gems/{mini_mime@1.0.2.rbi → mini_mime@1.0.3.rbi} +6 -6
  77. data/sorbet/rbi/gems/{mini_portile2@2.4.0.rbi → minitest-focus@1.2.1.rbi} +2 -2
  78. data/sorbet/rbi/gems/{minitest@5.14.0.rbi → minitest@5.14.4.rbi} +31 -29
  79. data/sorbet/rbi/gems/{mocha@1.11.2.rbi → mocha@1.12.0.rbi} +25 -36
  80. data/sorbet/rbi/gems/{nio4r@2.5.2.rbi → nio4r@2.5.7.rbi} +21 -20
  81. data/sorbet/rbi/gems/{nokogiri@1.10.9.rbi → nokogiri@1.11.2.rbi} +193 -154
  82. data/sorbet/rbi/gems/parallel@1.20.1.rbi +117 -0
  83. data/sorbet/rbi/gems/parlour@6.0.0.rbi +1272 -0
  84. data/sorbet/rbi/gems/{parser@2.7.1.4.rbi → parser@3.0.0.0.rbi} +287 -174
  85. data/sorbet/rbi/gems/{pry@0.13.1.rbi → pry@0.14.0.rbi} +1 -1
  86. data/sorbet/rbi/gems/racc@1.5.2.rbi +57 -0
  87. data/sorbet/rbi/gems/{rack@2.2.2.rbi → rack@2.2.3.rbi} +23 -35
  88. data/sorbet/rbi/gems/{rails@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1 -1
  89. data/sorbet/rbi/gems/{railties@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +132 -121
  90. data/sorbet/rbi/gems/{rake@13.0.1.rbi → rake@13.0.3.rbi} +16 -20
  91. data/sorbet/rbi/gems/{parallel@1.19.1.rbi → regexp_parser@2.1.1.rbi} +2 -2
  92. data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +8 -0
  93. data/sorbet/rbi/gems/{rubocop-performance@1.5.2.rbi → rubocop-performance@1.10.2.rbi} +1 -1
  94. data/sorbet/rbi/gems/{rubocop-shopify@1.0.2.rbi → rubocop-shopify@2.0.1.rbi} +1 -1
  95. data/sorbet/rbi/gems/{rubocop-sorbet@0.3.7.rbi → rubocop-sorbet@0.6.1.rbi} +1 -1
  96. data/sorbet/rbi/gems/{rubocop@0.82.0.rbi → rubocop@1.12.0.rbi} +1 -1
  97. data/sorbet/rbi/gems/{ruby-progressbar@1.10.1.rbi → ruby-progressbar@1.11.0.rbi} +1 -1
  98. data/sorbet/rbi/gems/spoom@1.1.0.rbi +1061 -0
  99. data/sorbet/rbi/gems/{spring@2.1.0.rbi → spring@2.1.1.rbi} +7 -7
  100. data/sorbet/rbi/gems/{sprockets-rails@3.2.1.rbi → sprockets-rails@3.2.2.rbi} +88 -68
  101. data/sorbet/rbi/gems/{sprockets@4.0.0.rbi → sprockets@4.0.2.rbi} +8 -7
  102. data/sorbet/rbi/gems/{tapioca@0.4.5.rbi → tapioca@0.4.19.rbi} +109 -24
  103. data/sorbet/rbi/gems/{thor@1.0.1.rbi → thor@1.1.0.rbi} +16 -15
  104. data/sorbet/rbi/gems/{tzinfo@2.0.2.rbi → tzinfo@2.0.4.rbi} +21 -2
  105. data/sorbet/rbi/gems/{unicode-display_width@1.7.0.rbi → unicode-display_width@2.0.0.rbi} +1 -1
  106. data/sorbet/rbi/gems/{websocket-driver@0.7.1.rbi → websocket-driver@0.7.3.rbi} +29 -29
  107. data/sorbet/rbi/gems/{websocket-extensions@0.1.4.rbi → websocket-extensions@0.1.5.rbi} +2 -2
  108. data/sorbet/rbi/gems/zeitwerk@2.4.2.rbi +177 -0
  109. data/sorbet/tapioca/require.rb +1 -0
  110. metadata +78 -57
  111. data/lib/packwerk/checking_deprecated_references.rb +0 -40
  112. data/lib/packwerk/output_styles.rb +0 -41
  113. data/lib/packwerk/reference_lister.rb +0 -23
  114. data/lib/packwerk/updating_deprecated_references.rb +0 -51
  115. data/sorbet/rbi/gems/jaro_winkler@1.5.4.rbi +0 -8
  116. data/sorbet/rbi/gems/marcel@0.3.3.rbi +0 -30
  117. data/sorbet/rbi/gems/mimemagic@0.3.5.rbi +0 -47
  118. data/sorbet/rbi/gems/parlour@4.0.1.rbi +0 -561
  119. data/sorbet/rbi/gems/spoom@1.0.4.rbi +0 -418
  120. data/sorbet/rbi/gems/zeitwerk@2.3.0.rbi +0 -8
data/dev.yml CHANGED
@@ -3,7 +3,7 @@ name: packwerk
3
3
  type: ruby
4
4
 
5
5
  up:
6
- - ruby: 2.6.6
6
+ - ruby: 3.0.0
7
7
  - bundler
8
8
 
9
9
  commands:
data/exe/packwerk CHANGED
@@ -3,4 +3,4 @@
3
3
 
4
4
  require "packwerk"
5
5
 
6
- Packwerk::Cli.new(style: Packwerk::OutputStyles::Coloured).run(ARGV.dup)
6
+ Packwerk::Cli.new(style: Packwerk::OutputStyles::Coloured.new).run(ARGV.dup)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ source("https://rubygems.org")
4
+
5
+ gemspec path: ".."
6
+
7
+ # Specify the same dependency sources as the application Gemfile
8
+
9
+ gem("spring")
10
+ gem("rails", '~> 6.0.0')
11
+ gem("constant_resolver", require: false)
12
+ gem("sorbet-runtime", require: false)
13
+ gem("rubocop-performance", require: false)
14
+ gem("rubocop-sorbet", require: false)
15
+ gem("mocha", require: false)
16
+ gem("rubocop-shopify", require: false)
17
+ gem("tapioca", require: false)
18
+
19
+ group :development do
20
+ gem("byebug", require: false)
21
+ gem("minitest-focus", require: false)
22
+ end
data/lib/packwerk.rb CHANGED
@@ -3,40 +3,79 @@
3
3
 
4
4
  require "sorbet-runtime"
5
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/dependency_checker"
18
- require "packwerk/deprecated_references"
19
- require "packwerk/files_for_processing"
20
- require "packwerk/file_processor"
21
- require "packwerk/formatters/progress_formatter"
22
- require "packwerk/generators/application_validation"
23
- require "packwerk/generators/configuration_file"
24
- require "packwerk/generators/inflections_file"
25
- require "packwerk/generators/root_package"
26
- require "packwerk/graph"
27
- require "packwerk/inflector"
28
- require "packwerk/node_processor"
29
- require "packwerk/node_visitor"
30
- require "packwerk/output_styles"
31
- require "packwerk/package"
32
- require "packwerk/package_set"
33
- require "packwerk/parsers"
34
- require "packwerk/privacy_checker"
35
- require "packwerk/reference_extractor"
36
- require "packwerk/run_context"
37
- require "packwerk/updating_deprecated_references"
38
- require "packwerk/version"
39
- require "packwerk/violation_type"
6
+ require "fileutils"
40
7
 
41
8
  module Packwerk
9
+ extend ActiveSupport::Autoload
10
+
11
+ autoload :ApplicationLoadPaths
12
+ autoload :ApplicationValidator
13
+ autoload :AssociationInspector
14
+ autoload :OffenseCollection
15
+ autoload :Checker
16
+ autoload :Cli
17
+ autoload :Configuration
18
+ autoload :ConstantDiscovery
19
+ autoload :ConstantNameInspector
20
+ autoload :ConstNodeInspector
21
+ autoload :DependencyChecker
22
+ autoload :DeprecatedReferences
23
+ autoload :FileProcessor
24
+ autoload :FilesForProcessing
25
+ autoload :Graph
26
+ autoload :Inflector
27
+ autoload :Node
28
+ autoload :NodeProcessor
29
+ autoload :NodeProcessorFactory
30
+ autoload :NodeVisitor
31
+ autoload :Offense
32
+ autoload :OffensesFormatter
33
+ autoload :OutputStyle
34
+ autoload :Package
35
+ autoload :PackageSet
36
+ autoload :ParsedConstantDefinitions
37
+ autoload :Parsers
38
+ autoload :ParseRun
39
+ autoload :PrivacyChecker
40
+ autoload :Reference
41
+ autoload :ReferenceExtractor
42
+ autoload :ReferenceOffense
43
+ autoload :Result
44
+ autoload :RunContext
45
+ autoload :Version
46
+ autoload :ViolationType
47
+
48
+ module Inflections
49
+ extend ActiveSupport::Autoload
50
+
51
+ autoload :Custom
52
+ autoload :Default
53
+ end
54
+
55
+ module OutputStyles
56
+ extend ActiveSupport::Autoload
57
+
58
+ autoload :Coloured
59
+ autoload :Plain
60
+ end
61
+
62
+ autoload_under "commands" do
63
+ autoload :OffenseProgressMarker
64
+ end
65
+
66
+ module Formatters
67
+ extend ActiveSupport::Autoload
68
+
69
+ autoload :OffensesFormatter
70
+ autoload :ProgressFormatter
71
+ end
72
+
73
+ module Generators
74
+ extend ActiveSupport::Autoload
75
+
76
+ autoload :ApplicationValidation
77
+ autoload :ConfigurationFile
78
+ autoload :InflectionsFile
79
+ autoload :RootPackage
80
+ end
42
81
  end
@@ -28,8 +28,9 @@ module Packwerk
28
28
  .select { |railtie| railtie.is_a?(Rails::Engine) }
29
29
  .push(Rails.application)
30
30
  .flat_map do |engine|
31
- (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths).uniq
32
- end
31
+ paths = (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths)
32
+ paths.map(&:to_s).uniq
33
+ end
33
34
  end
34
35
 
35
36
  sig do
@@ -6,11 +6,6 @@ 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
11
  def initialize(config_file_path:, configuration:)
@@ -32,19 +27,10 @@ module Packwerk
32
27
  check_acyclic_graph,
33
28
  check_package_manifest_paths,
34
29
  check_valid_package_dependencies,
35
- check_root_package_exist,
30
+ check_root_package_exists,
36
31
  ]
37
32
 
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
33
+ merge_results(results)
48
34
  end
49
35
 
50
36
  def check_autoload_path_cache
@@ -65,51 +51,37 @@ module Packwerk
65
51
  def check_package_manifests_for_privacy
66
52
  privacy_settings = package_manifests_settings_for("enforce_privacy")
67
53
 
68
- autoload_paths = @configuration.load_paths
69
-
70
54
  resolver = ConstantResolver.new(
71
55
  root_path: @configuration.root_path,
72
- load_paths: autoload_paths
56
+ load_paths: @configuration.load_paths
73
57
  )
74
58
 
75
- errors = []
59
+ results = []
76
60
 
77
- privacy_settings.each do |filepath, setting|
61
+ privacy_settings.each do |config_file_path, setting|
78
62
  next unless setting.is_a?(Array)
63
+ constants = setting
79
64
 
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"
65
+ assert_constants_can_be_loaded(constants)
91
66
 
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)
67
+ constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
95
68
 
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}"
69
+ constant_locations.each do |name, location|
70
+ results << if location
71
+ check_private_constant_location(name, location, config_file_path)
72
+ else
73
+ private_constant_unresolvable(name, config_file_path)
74
+ end
99
75
  end
100
76
  end
101
77
 
102
- if errors.empty?
103
- Result.new(true)
104
- else
105
- Result.new(false, errors.join("\n---\n"))
106
- end
78
+ merge_results(results, separator: "\n---\n")
107
79
  end
108
80
 
109
81
  def check_package_manifest_syntax
110
82
  errors = []
111
83
 
112
- package_manifests(package_glob).each do |f|
84
+ package_manifests.each do |f|
113
85
  hash = YAML.load_file(f)
114
86
  next unless hash
115
87
 
@@ -124,26 +96,26 @@ module Packwerk
124
96
 
125
97
  if hash.key?("enforce_privacy")
126
98
  unless [TrueClass, FalseClass, Array].include?(hash["enforce_privacy"].class)
127
- errors << "Invalid 'enforce_privacy' option in #{f.inspect}: #{hash['enforce_privacy'].inspect}"
99
+ errors << "Invalid 'enforce_privacy' option in #{f.inspect}: #{hash["enforce_privacy"].inspect}"
128
100
  end
129
101
  end
130
102
 
131
103
  if hash.key?("enforce_dependencies")
132
104
  unless [TrueClass, FalseClass].include?(hash["enforce_dependencies"].class)
133
- errors << "Invalid 'enforce_dependencies' option in #{f.inspect}: #{hash['enforce_dependencies'].inspect}"
105
+ errors << "Invalid 'enforce_dependencies' option in #{f.inspect}: #{hash["enforce_dependencies"].inspect}"
134
106
  end
135
107
  end
136
108
 
137
109
  if hash.key?("public_path")
138
110
  unless hash["public_path"].is_a?(String)
139
- errors << "'public_path' option must be a string in #{f.inspect}: #{hash['public_path'].inspect}"
111
+ errors << "'public_path' option must be a string in #{f.inspect}: #{hash["public_path"].inspect}"
140
112
  end
141
113
  end
142
114
 
143
115
  next unless hash.key?("dependencies")
144
116
  next if hash["dependencies"].is_a?(Array)
145
117
 
146
- errors << "Invalid 'dependencies' option in #{f.inspect}: #{hash['dependencies'].inspect}"
118
+ errors << "Invalid 'dependencies' option in #{f.inspect}: #{hash["dependencies"].inspect}"
147
119
  end
148
120
 
149
121
  if errors.empty?
@@ -193,24 +165,16 @@ module Packwerk
193
165
  end
194
166
  end
195
167
 
196
- errors = results.reject(&:ok?)
197
-
198
- if errors.empty?
199
- Result.new(true)
200
- else
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
168
+ merge_results(
169
+ results,
170
+ separator: "\n",
171
+ errors_headline: "Inflections specified in #{inflections_file} don't line up with application!\n"
172
+ )
207
173
  end
208
174
 
209
175
  def check_acyclic_graph
210
- packages = Packwerk::PackageSet.load_all_from(".")
211
-
212
- edges = packages.flat_map do |package|
213
- package.dependencies.map { |dependency| [package, packages.fetch(dependency)] }
176
+ edges = package_set.flat_map do |package|
177
+ package.dependencies.map { |dependency| [package, package_set.fetch(dependency)] }
214
178
  end
215
179
  dependency_graph = Packwerk::Graph.new(*edges)
216
180
 
@@ -225,7 +189,7 @@ module Packwerk
225
189
  cycle_strings = dependency_graph.cycles.map do |cycle|
226
190
  cycle_strings = cycle.map(&:to_s)
227
191
  cycle_strings << cycle.first.to_s
228
- "\t- #{cycle_strings.join('')}"
192
+ "\t- #{cycle_strings.join("")}"
229
193
  end
230
194
 
231
195
  if dependency_graph.acyclic?
@@ -297,7 +261,7 @@ module Packwerk
297
261
  end
298
262
  end
299
263
 
300
- def check_root_package_exist
264
+ def check_root_package_exists
301
265
  root_package_path = File.join(@configuration.root_path, "package.yml")
302
266
  all_packages_manifests = package_manifests(package_glob)
303
267
 
@@ -316,8 +280,7 @@ module Packwerk
316
280
  private
317
281
 
318
282
  def package_manifests_settings_for(setting)
319
- package_manifests(package_glob)
320
- .map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
283
+ package_manifests.map { |f| [f, (YAML.load_file(File.join(f)) || {})[setting]] }
321
284
  end
322
285
 
323
286
  def format_yaml_strings(list)
@@ -328,13 +291,17 @@ module Packwerk
328
291
  @configuration.package_paths || "**"
329
292
  end
330
293
 
331
- def package_manifests(glob_pattern)
294
+ def package_manifests(glob_pattern = package_glob)
332
295
  PackageSet.package_paths(@configuration.root_path, glob_pattern)
333
296
  .map { |f| File.realpath(f) }
334
297
  end
335
298
 
336
299
  def relative_paths(paths)
337
- paths.map { |path| Pathname.new(path).relative_path_from(@configuration.root_path) }
300
+ paths.map { |path| relative_path(path) }
301
+ end
302
+
303
+ def relative_path(path)
304
+ Pathname.new(path).relative_path_from(@configuration.root_path)
338
305
  end
339
306
 
340
307
  def invalid_package_path?(path)
@@ -344,5 +311,54 @@ module Packwerk
344
311
  package_path = File.join(@configuration.root_path, path, Packwerk::PackageSet::PACKAGE_CONFIG_FILENAME)
345
312
  !File.file?(package_path)
346
313
  end
314
+
315
+ def assert_constants_can_be_loaded(constants)
316
+ constants.each(&:constantize)
317
+ nil
318
+ end
319
+
320
+ def private_constant_unresolvable(name, config_file_path)
321
+ explicit_filepath = (name.start_with?("::") ? name[2..-1] : name).underscore + ".rb"
322
+
323
+ Result.new(
324
+ false,
325
+ "'#{name}', listed in #{config_file_path}, could not be resolved.\n"\
326
+ "This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n"\
327
+ "file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n"\
328
+ "private. Add a #{explicit_filepath} file to explicitly define the constant."
329
+ )
330
+ end
331
+
332
+ def check_private_constant_location(name, location, config_file_path)
333
+ declared_package = package_set.package_from_path(relative_path(config_file_path))
334
+ constant_package = package_set.package_from_path(location)
335
+
336
+ if constant_package == declared_package
337
+ Result.new(true)
338
+ else
339
+ Result.new(
340
+ false,
341
+ "'#{name}' is declared as private in the '#{declared_package}' package but appears to be "\
342
+ "defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
343
+ )
344
+ end
345
+ end
346
+
347
+ def package_set
348
+ @package_set ||= Packwerk::PackageSet.load_all_from(@configuration.root_path, package_pathspec: package_glob)
349
+ end
350
+
351
+ def merge_results(results, separator: "\n===\n", errors_headline: "")
352
+ results.reject!(&:ok?)
353
+
354
+ if results.empty?
355
+ Result.new(true)
356
+ else
357
+ Result.new(
358
+ false,
359
+ errors_headline + results.map(&:error_value).join(separator)
360
+ )
361
+ end
362
+ end
347
363
  end
348
364
  end
@@ -1,26 +1,35 @@
1
- # typed: true
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
- RAILS_ASSOCIATIONS = %i(
13
- belongs_to
14
- has_many
15
- has_one
16
- has_and_belongs_to_many
17
- ).to_set
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