packwerk 3.0.0 → 3.2.2

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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/exe/packwerk +4 -1
  4. data/lib/packwerk/application_validator.rb +3 -0
  5. data/lib/packwerk/association_inspector.rb +17 -4
  6. data/lib/packwerk/checker.rb +16 -7
  7. data/lib/packwerk/cli.rb +14 -177
  8. data/lib/packwerk/commands/base_command.rb +69 -0
  9. data/lib/packwerk/commands/check_command.rb +62 -0
  10. data/lib/packwerk/commands/help_command.rb +33 -0
  11. data/lib/packwerk/commands/init_command.rb +42 -0
  12. data/lib/packwerk/commands/lazy_loaded_entry.rb +37 -0
  13. data/lib/packwerk/commands/update_todo_command.rb +60 -0
  14. data/lib/packwerk/commands/uses_parse_run.rb +92 -0
  15. data/lib/packwerk/commands/validate_command.rb +46 -0
  16. data/lib/packwerk/commands/version_command.rb +18 -0
  17. data/lib/packwerk/commands.rb +54 -0
  18. data/lib/packwerk/configuration.rb +8 -1
  19. data/lib/packwerk/const_node_inspector.rb +2 -2
  20. data/lib/packwerk/constant_name_inspector.rb +2 -2
  21. data/lib/packwerk/file_processor.rb +12 -1
  22. data/lib/packwerk/formatters/default_offenses_formatter.rb +3 -3
  23. data/lib/packwerk/formatters/progress_formatter.rb +11 -0
  24. data/lib/packwerk/generators/templates/package.yml +2 -2
  25. data/lib/packwerk/generators/templates/packwerk.yml.erb +1 -1
  26. data/lib/packwerk/offense_collection.rb +32 -12
  27. data/lib/packwerk/offenses_formatter.rb +14 -5
  28. data/lib/packwerk/package.rb +1 -1
  29. data/lib/packwerk/package_todo.rb +86 -69
  30. data/lib/packwerk/parse_run.rb +42 -82
  31. data/lib/packwerk/parsers/factory.rb +3 -3
  32. data/lib/packwerk/parsers/ruby.rb +9 -2
  33. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +3 -3
  34. data/lib/packwerk/reference_extractor.rb +29 -1
  35. data/lib/packwerk/reference_offense.rb +1 -1
  36. data/lib/packwerk/run_context.rb +15 -4
  37. data/lib/packwerk/spring_command.rb +0 -2
  38. data/lib/packwerk/validator.rb +19 -5
  39. data/lib/packwerk/version.rb +1 -1
  40. data/lib/packwerk.rb +5 -28
  41. data/sorbet/config +1 -0
  42. data/sorbet/rbi/gems/actionpack@7.0.3.1.rbi +3280 -3450
  43. data/sorbet/rbi/gems/actionview@7.0.3.1.rbi +2322 -1782
  44. data/sorbet/rbi/gems/activesupport@7.0.3.1.rbi +2654 -3268
  45. data/sorbet/rbi/gems/ast@2.4.2.rbi +535 -6
  46. data/sorbet/rbi/gems/better_html@2.0.1.rbi +529 -0
  47. data/sorbet/rbi/gems/builder@3.2.4.rbi +4 -4
  48. data/sorbet/rbi/gems/byebug@11.1.3.rbi +32 -4
  49. data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +1750 -1840
  50. data/sorbet/rbi/gems/constant_resolver@0.2.0.rbi +15 -15
  51. data/sorbet/rbi/gems/crass@1.0.6.rbi +489 -5
  52. data/sorbet/rbi/gems/erubi@1.11.0.rbi +24 -21
  53. data/sorbet/rbi/gems/i18n@1.12.0.rbi +395 -395
  54. data/sorbet/rbi/gems/json@2.6.2.rbi +70 -77
  55. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +1 -1
  56. data/sorbet/rbi/gems/loofah@2.18.0.rbi +134 -134
  57. data/sorbet/rbi/gems/m@1.6.0.rbi +60 -60
  58. data/sorbet/rbi/gems/method_source@1.1.0.rbi +303 -0
  59. data/sorbet/rbi/gems/minitest-focus@1.3.1.rbi +22 -28
  60. data/sorbet/rbi/gems/minitest@5.16.2.rbi +384 -396
  61. data/sorbet/rbi/gems/mocha@1.14.0.rbi +589 -589
  62. data/sorbet/rbi/gems/netrc@0.11.0.rbi +37 -32
  63. data/sorbet/rbi/gems/{nokogiri@1.13.8.rbi → nokogiri@1.15.3.rbi} +1869 -1030
  64. data/sorbet/rbi/gems/{parallel@1.22.1.rbi → parallel@1.24.0.rbi} +85 -82
  65. data/sorbet/rbi/gems/parser@3.3.1.0.rbi +7320 -0
  66. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +1 -1
  67. data/sorbet/rbi/gems/prism@0.27.0.rbi +36983 -0
  68. data/sorbet/rbi/gems/{racc@1.6.0.rbi → racc@1.7.1.rbi} +42 -33
  69. data/sorbet/rbi/gems/rack-test@2.0.2.rbi +148 -338
  70. data/sorbet/rbi/gems/rack@2.2.4.rbi +1079 -1130
  71. data/sorbet/rbi/gems/rails-dom-testing@2.0.3.rbi +354 -22
  72. data/sorbet/rbi/gems/rails-html-sanitizer@1.4.3.rbi +113 -259
  73. data/sorbet/rbi/gems/railties@7.0.3.1.rbi +642 -638
  74. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +109 -99
  75. data/sorbet/rbi/gems/rake@13.0.6.rbi +714 -599
  76. data/sorbet/rbi/gems/{rbi@0.0.15.rbi → rbi@0.1.12.rbi} +865 -801
  77. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +853 -870
  78. data/sorbet/rbi/gems/rexml@3.2.5.rbi +480 -477
  79. data/sorbet/rbi/gems/rubocop-ast@1.21.0.rbi +1621 -1622
  80. data/sorbet/rbi/gems/rubocop-performance@1.14.3.rbi +507 -526
  81. data/sorbet/rbi/gems/rubocop-shopify@2.9.0.rbi +1 -1
  82. data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +186 -203
  83. data/sorbet/rbi/gems/rubocop@1.34.1.rbi +8126 -8367
  84. data/sorbet/rbi/gems/{ruby-lsp@0.2.1.rbi → ruby-lsp@0.2.3.rbi} +2 -2
  85. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +1235 -4
  86. data/sorbet/rbi/gems/smart_properties@1.17.0.rbi +90 -90
  87. data/sorbet/rbi/gems/spoom@1.3.2.rbi +4420 -0
  88. data/sorbet/rbi/gems/spring@4.0.0.rbi +104 -104
  89. data/sorbet/rbi/gems/syntax_tree@3.3.0.rbi +1 -1
  90. data/sorbet/rbi/gems/{tapioca@0.9.2.rbi → tapioca@0.13.3.rbi} +1596 -1253
  91. data/sorbet/rbi/gems/{thor@1.2.1.rbi → thor@1.3.1.rbi} +1047 -652
  92. data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +531 -513
  93. data/sorbet/rbi/gems/unicode-display_width@2.2.0.rbi +13 -13
  94. data/sorbet/rbi/gems/{yard-sorbet@0.6.1.rbi → yard-sorbet@0.8.1.rbi} +132 -92
  95. data/sorbet/rbi/gems/{yard@0.9.28.rbi → yard@0.9.36.rbi} +3158 -3067
  96. data/sorbet/rbi/gems/zeitwerk@2.6.4.rbi +149 -145
  97. metadata +36 -84
  98. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
  99. data/.github/pull_request_template.md +0 -28
  100. data/.github/workflows/ci.yml +0 -65
  101. data/.github/workflows/cla.yml +0 -22
  102. data/.gitignore +0 -13
  103. data/.rubocop.yml +0 -75
  104. data/.ruby-version +0 -1
  105. data/CODEOWNERS +0 -1
  106. data/CODE_OF_CONDUCT.md +0 -76
  107. data/CONTRIBUTING.md +0 -17
  108. data/Gemfile +0 -27
  109. data/Gemfile.lock +0 -201
  110. data/RESOLVING_VIOLATIONS.md +0 -81
  111. data/Rakefile +0 -13
  112. data/TROUBLESHOOT.md +0 -45
  113. data/UPGRADING.md +0 -54
  114. data/USAGE.md +0 -367
  115. data/bin/console +0 -15
  116. data/bin/m +0 -29
  117. data/bin/rake +0 -29
  118. data/bin/rubocop +0 -29
  119. data/bin/setup +0 -8
  120. data/bin/srb +0 -29
  121. data/bin/tapioca +0 -29
  122. data/dev.yml +0 -32
  123. data/docs/cohesion.png +0 -0
  124. data/gemfiles/Gemfile-rails-6-0 +0 -22
  125. data/gemfiles/Gemfile-rails-6-1 +0 -22
  126. data/lib/packwerk/cli/result.rb +0 -11
  127. data/packwerk.gemspec +0 -58
  128. data/shipit.rubygems.yml +0 -5
  129. data/sorbet/rbi/gems/actioncable@7.0.3.1.rbi +0 -2754
  130. data/sorbet/rbi/gems/actionmailbox@7.0.3.1.rbi +0 -1496
  131. data/sorbet/rbi/gems/actionmailer@7.0.3.1.rbi +0 -2362
  132. data/sorbet/rbi/gems/actiontext@7.0.3.1.rbi +0 -1569
  133. data/sorbet/rbi/gems/activejob@7.0.3.1.rbi +0 -2553
  134. data/sorbet/rbi/gems/activemodel@7.0.3.1.rbi +0 -5999
  135. data/sorbet/rbi/gems/activerecord@7.0.3.1.rbi +0 -37832
  136. data/sorbet/rbi/gems/activestorage@7.0.3.1.rbi +0 -2321
  137. data/sorbet/rbi/gems/better_html@1.0.16.rbi +0 -317
  138. data/sorbet/rbi/gems/coderay@1.1.3.rbi +0 -8
  139. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +0 -1079
  140. data/sorbet/rbi/gems/digest@3.1.0.rbi +0 -189
  141. data/sorbet/rbi/gems/globalid@1.0.0.rbi +0 -572
  142. data/sorbet/rbi/gems/mail@2.7.1.rbi +0 -2490
  143. data/sorbet/rbi/gems/marcel@1.0.2.rbi +0 -220
  144. data/sorbet/rbi/gems/method_source@1.0.0.rbi +0 -76
  145. data/sorbet/rbi/gems/mini_mime@1.1.2.rbi +0 -170
  146. data/sorbet/rbi/gems/net-imap@0.2.3.rbi +0 -2147
  147. data/sorbet/rbi/gems/net-pop@0.1.1.rbi +0 -926
  148. data/sorbet/rbi/gems/net-protocol@0.1.3.rbi +0 -11
  149. data/sorbet/rbi/gems/net-smtp@0.3.1.rbi +0 -1108
  150. data/sorbet/rbi/gems/nio4r@2.5.8.rbi +0 -292
  151. data/sorbet/rbi/gems/parser@3.1.2.1.rbi +0 -9029
  152. data/sorbet/rbi/gems/pry@0.14.1.rbi +0 -8
  153. data/sorbet/rbi/gems/rails@7.0.3.1.rbi +0 -8
  154. data/sorbet/rbi/gems/spoom@1.1.11.rbi +0 -2181
  155. data/sorbet/rbi/gems/strscan@3.0.4.rbi +0 -8
  156. data/sorbet/rbi/gems/timeout@0.3.0.rbi +0 -142
  157. data/sorbet/rbi/gems/unparser@0.6.5.rbi +0 -4529
  158. data/sorbet/rbi/gems/webrick@1.7.0.rbi +0 -2582
  159. data/sorbet/rbi/gems/websocket-driver@0.7.5.rbi +0 -993
  160. data/sorbet/rbi/gems/websocket-extensions@0.1.5.rbi +0 -71
@@ -7,16 +7,20 @@ module Packwerk
7
7
  class PackageTodo
8
8
  extend T::Sig
9
9
 
10
- EntriesType = T.type_alias do
11
- T::Hash[String, T.untyped]
10
+ PackageName = T.type_alias { String }
11
+ ConstantName = T.type_alias { String }
12
+ FilePath = T.type_alias { String }
13
+ Entry = T.type_alias { T::Hash[ConstantName, T::Hash[ConstantName, T::Array[FilePath]]] }
14
+ Entries = T.type_alias do
15
+ T::Hash[PackageName, Entry]
12
16
  end
13
17
 
14
- sig { params(package: Packwerk::Package, filepath: String).void }
15
- def initialize(package, filepath)
18
+ sig { params(package: Packwerk::Package, path: String).void }
19
+ def initialize(package, path)
16
20
  @package = package
17
- @filepath = filepath
18
- @new_entries = T.let({}, EntriesType)
19
- @todo_list = T.let(nil, T.nilable(EntriesType))
21
+ @path = path
22
+ @new_entries = T.let({}, Entries)
23
+ @old_entries = T.let(nil, T.nilable(Entries))
20
24
  end
21
25
 
22
26
  sig do
@@ -24,7 +28,7 @@ module Packwerk
24
28
  .returns(T::Boolean)
25
29
  end
26
30
  def listed?(reference, violation_type:)
27
- violated_constants_found = todo_list.dig(reference.constant.package.name, reference.constant.name)
31
+ violated_constants_found = old_entries.dig(reference.constant.package.name, reference.constant.name)
28
32
  return false unless violated_constants_found
29
33
 
30
34
  violated_constant_in_file = violated_constants_found.fetch("files", []).include?(reference.relative_path)
@@ -37,16 +41,16 @@ module Packwerk
37
41
  params(reference: Packwerk::Reference, violation_type: String).returns(T::Boolean)
38
42
  end
39
43
  def add_entries(reference, violation_type)
40
- package_violations = @new_entries.fetch(reference.constant.package.name, {})
44
+ package_violations = new_entries.fetch(reference.constant.package.name, {})
41
45
  entries_for_constant = package_violations[reference.constant.name] ||= {}
42
46
 
43
47
  entries_for_constant["violations"] ||= []
44
- entries_for_constant["violations"] << violation_type
48
+ entries_for_constant.fetch("violations") << violation_type
45
49
 
46
50
  entries_for_constant["files"] ||= []
47
- entries_for_constant["files"] << reference.relative_path.to_s
51
+ entries_for_constant.fetch("files") << reference.relative_path.to_s
48
52
 
49
- @new_entries[reference.constant.package.name] = package_violations
53
+ new_entries[reference.constant.package.name] = package_violations
50
54
  listed?(reference, violation_type: violation_type)
51
55
  end
52
56
 
@@ -54,102 +58,115 @@ module Packwerk
54
58
  def stale_violations?(for_files)
55
59
  prepare_entries_for_dump
56
60
 
57
- todo_list.any? do |package, package_violations|
58
- package_violations_for_files = {}
59
- package_violations.each do |constant_name, entries_for_constant|
60
- entries_for_files = for_files & entries_for_constant["files"]
61
- next if entries_for_files.none?
62
-
63
- package_violations_for_files[constant_name] = {
64
- "violations" => entries_for_constant["violations"],
65
- "files" => entries_for_files.to_a,
66
- }
67
- end
61
+ old_entries.any? do |package, violations|
62
+ files = for_files + deleted_files_for(package)
63
+ violations_for_files = package_violations_for(violations, files: files)
68
64
 
69
65
  # We `next false` because if we cannot find existing violations for `for_files` within
70
66
  # the `package_todo.yml` file, then there are no violations that
71
67
  # can be considered stale.
72
- next false if package_violations_for_files.empty?
73
-
74
- package_violations_for_files.any? do |constant_name, entries_for_constant|
75
- new_entries_violation_types = @new_entries.dig(package, constant_name, "violations")
76
- # If there are no NEW entries that match the old entries `for_files`,
77
- # @new_entries is from the list of violations we get when we check this file.
78
- # If this list is empty, we also must have stale violations.
79
- next true if new_entries_violation_types.nil?
80
-
81
- if entries_for_constant["violations"].all? { |type| new_entries_violation_types.include?(type) }
82
- stale_violations =
83
- entries_for_constant["files"] - Array(@new_entries.dig(package, constant_name, "files"))
84
- stale_violations.any?
85
- else
86
- return true
87
- end
88
- end
68
+ next false if violations_for_files.empty?
69
+
70
+ stale_violation_for_package?(package, violations: violations_for_files)
89
71
  end
90
72
  end
91
73
 
92
74
  sig { void }
93
75
  def dump
94
- delete_old_deprecated_references
95
-
96
- if @new_entries.empty?
76
+ if new_entries.empty?
97
77
  delete_if_exists
98
78
  else
99
79
  prepare_entries_for_dump
100
80
  message = <<~MESSAGE
101
- # This file contains a list of dependencies that are not part of the long term plan for #{@package.name}.
102
- # We should generally work to reduce this list, but not at the expense of actually getting work done.
81
+ # This file contains a list of dependencies that are not part of the long term plan for the
82
+ # '#{@package.name}' package.
83
+ # We should generally work to reduce this list over time.
103
84
  #
104
85
  # You can regenerate this file using the following command:
105
86
  #
106
- # bin/packwerk update-todo #{@package.name}
87
+ # bin/packwerk update-todo
107
88
  MESSAGE
108
- File.open(@filepath, "w") do |f|
89
+ File.open(@path, "w") do |f|
109
90
  f.write(message)
110
- f.write(@new_entries.to_yaml)
91
+ f.write(new_entries.to_yaml)
111
92
  end
112
93
  end
113
94
  end
114
95
 
115
96
  sig { void }
116
97
  def delete_if_exists
117
- File.delete(@filepath) if File.exist?(@filepath)
98
+ File.delete(@path) if File.exist?(@path)
118
99
  end
119
100
 
120
101
  private
121
102
 
122
- sig { void }
123
- def delete_old_deprecated_references
124
- deprecated_references_filepath = File.join(File.dirname(@filepath), "deprecated_references.yml")
125
- File.delete(deprecated_references_filepath) if File.exist?(deprecated_references_filepath)
103
+ sig { returns(Entries) }
104
+ attr_reader(:new_entries)
105
+
106
+ sig { params(package: String).returns(T::Array[String]) }
107
+ def deleted_files_for(package)
108
+ old_files = old_entries.fetch(package, {}).values.flat_map { |violation| violation.fetch("files") }
109
+ new_files = new_entries.fetch(package, {}).values.flat_map { |violation| violation.fetch("files") }
110
+ old_files - new_files
126
111
  end
127
112
 
128
- sig { returns(EntriesType) }
113
+ sig { params(package: String, violations: Entry).returns(T::Boolean) }
114
+ def stale_violation_for_package?(package, violations:)
115
+ violations.any? do |constant_name, entries_for_constant|
116
+ new_entries_violation_types = new_entries.dig(package, constant_name, "violations")
117
+ # If there are no NEW entries that match the old entries `for_files`,
118
+ # new_entries is from the list of violations we get when we check this file.
119
+ # If this list is empty, we also must have stale violations.
120
+ next true if new_entries_violation_types.nil?
121
+
122
+ if entries_for_constant.fetch("violations").all? { |type| new_entries_violation_types.include?(type) }
123
+ stale_violations =
124
+ entries_for_constant.fetch("files") - Array(new_entries.dig(package, constant_name, "files"))
125
+ stale_violations.any?
126
+ else
127
+ return true
128
+ end
129
+ end
130
+ end
131
+
132
+ sig { params(package_violations: Entry, files: T::Set[String]).returns(Entry) }
133
+ def package_violations_for(package_violations, files:)
134
+ {}.tap do |package_violations_for_files|
135
+ package_violations_for_files = T.cast(package_violations_for_files, Entry)
136
+
137
+ package_violations.each do |constant_name, entries_for_constant|
138
+ entries_for_files = files & entries_for_constant.fetch("files")
139
+ next if entries_for_files.none?
140
+
141
+ package_violations_for_files[constant_name] = {
142
+ "violations" => entries_for_constant["violations"],
143
+ "files" => entries_for_files.to_a,
144
+ }
145
+ end
146
+ end
147
+ end
148
+
149
+ sig { returns(Entries) }
129
150
  def prepare_entries_for_dump
130
- @new_entries.each do |package_name, package_violations|
151
+ new_entries.each do |package_name, package_violations|
131
152
  package_violations.each do |_, entries_for_constant|
132
- entries_for_constant["violations"].sort!.uniq!
133
- entries_for_constant["files"].sort!.uniq!
153
+ entries_for_constant.fetch("violations").sort!.uniq!
154
+ entries_for_constant.fetch("files").sort!.uniq!
134
155
  end
135
- @new_entries[package_name] = package_violations.sort.to_h
156
+ new_entries[package_name] = package_violations.sort.to_h
136
157
  end
137
158
 
138
- @new_entries = @new_entries.sort.to_h
159
+ @new_entries = new_entries.sort.to_h
139
160
  end
140
161
 
141
- sig { returns(EntriesType) }
142
- def todo_list
143
- @todo_list ||= if File.exist?(@filepath)
144
- load_yaml(@filepath)
145
- else
146
- {}
147
- end
162
+ sig { returns(Entries) }
163
+ def old_entries
164
+ @old_entries ||= load_yaml_file(@path)
148
165
  end
149
166
 
150
- sig { params(filepath: String).returns(EntriesType) }
151
- def load_yaml(filepath)
152
- YAML.load_file(filepath) || {}
167
+ sig { params(path: String).returns(Entries) }
168
+ def load_yaml_file(path)
169
+ File.exist?(path) && YAML.load_file(path) || {}
153
170
  rescue Psych::Exception
154
171
  {}
155
172
  end
@@ -14,93 +14,62 @@ module Packwerk
14
14
  sig do
15
15
  params(
16
16
  relative_file_set: FilesForProcessing::RelativeFileSet,
17
- configuration: Configuration,
18
- file_set_specified: T::Boolean,
19
- offenses_formatter: T.nilable(OffensesFormatter),
20
- progress_formatter: Formatters::ProgressFormatter,
17
+ parallel: T::Boolean,
21
18
  ).void
22
19
  end
23
- def initialize(
24
- relative_file_set:,
25
- configuration:,
26
- file_set_specified: false,
27
- offenses_formatter: nil,
28
- progress_formatter: Formatters::ProgressFormatter.new(StringIO.new)
29
- )
30
-
31
- @configuration = configuration
32
- @progress_formatter = progress_formatter
33
- @offenses_formatter = T.let(offenses_formatter || configuration.offenses_formatter, Packwerk::OffensesFormatter)
20
+ def initialize(relative_file_set:, parallel:)
34
21
  @relative_file_set = relative_file_set
35
- @file_set_specified = file_set_specified
22
+ @parallel = parallel
36
23
  end
37
24
 
38
- sig { returns(Cli::Result) }
39
- def update_todo
40
- if @file_set_specified
41
- message = <<~MSG.squish
42
- ⚠️ update-todo must be called without any file arguments.
43
- MSG
44
-
45
- return Cli::Result.new(message: message, status: false)
46
- end
47
-
48
- run_context = RunContext.from_configuration(@configuration)
49
- offense_collection = find_offenses(run_context)
50
- offense_collection.persist_package_todo_files(run_context.package_set)
51
-
52
- message = <<~EOS
53
- #{@offenses_formatter.show_offenses(offense_collection.errors)}
54
- ✅ `package_todo.yml` has been updated.
55
- EOS
56
-
57
- Cli::Result.new(message: message, status: offense_collection.errors.empty?)
25
+ sig do
26
+ params(
27
+ run_context: RunContext,
28
+ on_interrupt: T.nilable(T.proc.void),
29
+ block: T.nilable(T.proc.params(
30
+ offenses: T::Array[Packwerk::Offense],
31
+ ).void)
32
+ ).returns(T::Array[Offense])
58
33
  end
34
+ def find_offenses(run_context, on_interrupt: nil, &block)
35
+ process_file_proc = process_file_proc(run_context, &block)
59
36
 
60
- sig { returns(Cli::Result) }
61
- def check
62
- run_context = RunContext.from_configuration(@configuration)
63
- offense_collection = find_offenses(run_context, show_errors: true)
64
-
65
- messages = [
66
- @offenses_formatter.show_offenses(offense_collection.outstanding_offenses),
67
- @offenses_formatter.show_stale_violations(offense_collection, @relative_file_set),
68
- @offenses_formatter.show_strict_mode_violations(offense_collection.strict_mode_violations),
69
- ]
70
-
71
- result_status = offense_collection.outstanding_offenses.empty? &&
72
- !offense_collection.stale_violations?(@relative_file_set) && offense_collection.strict_mode_violations.empty?
37
+ offenses = if @parallel
38
+ Parallel.flat_map(@relative_file_set, &process_file_proc)
39
+ else
40
+ serial_find_offenses(on_interrupt: on_interrupt, &process_file_proc)
41
+ end
73
42
 
74
- Cli::Result.new(message: messages.select(&:present?).join("\n") + "\n", status: result_status)
43
+ offenses
75
44
  end
76
45
 
77
46
  private
78
47
 
79
- sig { params(run_context: RunContext, show_errors: T::Boolean).returns(OffenseCollection) }
80
- def find_offenses(run_context, show_errors: false)
81
- offense_collection = OffenseCollection.new(@configuration.root_path)
82
- all_offenses = T.let([], T::Array[Offense])
83
- process_file = T.let(->(relative_file) do
84
- run_context.process_file(relative_file: relative_file).tap do |offenses|
85
- failed = show_errors && offenses.any? { |offense| !offense_collection.listed?(offense) }
86
- update_progress(failed: failed)
87
- end
88
- end, ProcessFileProc)
89
-
90
- @progress_formatter.started_inspection(@relative_file_set) do
91
- all_offenses = if @configuration.parallel?
92
- Parallel.flat_map(@relative_file_set, &process_file)
93
- else
94
- serial_find_offenses(&process_file)
95
- end
48
+ sig do
49
+ params(
50
+ run_context: RunContext,
51
+ block: T.nilable(T.proc.params(offenses: T::Array[Offense]).void)
52
+ ).returns(ProcessFileProc)
53
+ end
54
+ def process_file_proc(run_context, &block)
55
+ if block
56
+ T.let(proc do |relative_file|
57
+ run_context.process_file(relative_file: relative_file).tap(&block)
58
+ end, ProcessFileProc)
59
+ else
60
+ T.let(proc do |relative_file|
61
+ run_context.process_file(relative_file: relative_file)
62
+ end, ProcessFileProc)
96
63
  end
97
-
98
- all_offenses.each { |offense| offense_collection.add_offense(offense) }
99
- offense_collection
100
64
  end
101
65
 
102
- sig { params(block: ProcessFileProc).returns(T::Array[Offense]) }
103
- def serial_find_offenses(&block)
66
+ sig do
67
+ params(
68
+ on_interrupt: T.nilable(T.proc.void),
69
+ block: ProcessFileProc
70
+ ).returns(T::Array[Offense])
71
+ end
72
+ def serial_find_offenses(on_interrupt: nil, &block)
104
73
  all_offenses = T.let([], T::Array[Offense])
105
74
  begin
106
75
  @relative_file_set.each do |relative_file|
@@ -108,20 +77,11 @@ module Packwerk
108
77
  all_offenses.concat(offenses)
109
78
  end
110
79
  rescue Interrupt
111
- @progress_formatter.interrupted
80
+ on_interrupt&.call
112
81
  all_offenses
113
82
  end
114
83
  all_offenses
115
84
  end
116
-
117
- sig { params(failed: T::Boolean).void }
118
- def update_progress(failed: false)
119
- if failed
120
- @progress_formatter.mark_as_failed
121
- else
122
- @progress_formatter.mark_as_inspected
123
- end
124
- end
125
85
  end
126
86
 
127
87
  private_constant :ParseRun
@@ -24,7 +24,7 @@ module Packwerk
24
24
  def initialize
25
25
  @ruby_parser = T.let(nil, T.nilable(ParserInterface))
26
26
  @erb_parser = T.let(nil, T.nilable(ParserInterface))
27
- @erb_parser_class = T.let(nil, T.nilable(Class))
27
+ @erb_parser_class = T.let(nil, T.nilable(T::Class[T.anything]))
28
28
  end
29
29
 
30
30
  sig { params(path: String).returns(T.nilable(ParserInterface)) }
@@ -37,12 +37,12 @@ module Packwerk
37
37
  end
38
38
  end
39
39
 
40
- sig { returns(Class) }
40
+ sig { returns(T::Class[T.anything]) }
41
41
  def erb_parser_class
42
42
  @erb_parser_class ||= Erb
43
43
  end
44
44
 
45
- sig { params(klass: T.nilable(Class)).void }
45
+ sig { params(klass: T.nilable(T::Class[T.anything])).void }
46
46
  def erb_parser_class=(klass)
47
47
  @erb_parser_class = klass
48
48
  @erb_parser = nil
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "parser"
5
- require "parser/current"
5
+ require "prism"
6
6
 
7
7
  module Packwerk
8
8
  module Parsers
@@ -11,7 +11,7 @@ module Packwerk
11
11
 
12
12
  include ParserInterface
13
13
 
14
- class RaiseExceptionsParser < Parser::CurrentRuby
14
+ class RaiseExceptionsParser < Prism::Translation::Parser
15
15
  extend T::Sig
16
16
 
17
17
  sig { params(builder: T.untyped).void }
@@ -19,6 +19,13 @@ module Packwerk
19
19
  super(builder)
20
20
  super.diagnostics.all_errors_are_fatal = true
21
21
  end
22
+
23
+ private
24
+
25
+ sig { params(error: Prism::ParseError).returns(T::Boolean) }
26
+ def valid_error?(error)
27
+ error.type != :invalid_yield
28
+ end
22
29
  end
23
30
 
24
31
  class TolerateInvalidUtf8Builder < Parser::Builders::Default
@@ -47,9 +47,9 @@ module Packwerk
47
47
  EOS
48
48
  end
49
49
 
50
- sig { override.params(listed_offense: ReferenceOffense).returns(T::Boolean) }
51
- def strict_mode_violation?(listed_offense)
52
- referencing_package = listed_offense.reference.package
50
+ sig { override.params(offense: ReferenceOffense).returns(T::Boolean) }
51
+ def strict_mode_violation?(offense)
52
+ referencing_package = offense.reference.package
53
53
  referencing_package.config["enforce_dependencies"] == "strict"
54
54
  end
55
55
 
@@ -80,7 +80,12 @@ module Packwerk
80
80
  constant_name = T.let(nil, T.nilable(String))
81
81
 
82
82
  @constant_name_inspectors.each do |inspector|
83
- constant_name = inspector.constant_name_from_node(node, ancestors: ancestors)
83
+ constant_name = inspect_node(
84
+ inspector,
85
+ node: node,
86
+ ancestors: ancestors,
87
+ relative_file: relative_file
88
+ )
84
89
 
85
90
  break if constant_name
86
91
  end
@@ -97,6 +102,29 @@ module Packwerk
97
102
 
98
103
  private
99
104
 
105
+ sig do
106
+ params(
107
+ inspector: ConstantNameInspector,
108
+ node: Parser::AST::Node,
109
+ ancestors: T::Array[Parser::AST::Node],
110
+ relative_file: String
111
+ ).returns(T.nilable(String))
112
+ end
113
+ def inspect_node(inspector, node:, ancestors:, relative_file:)
114
+ inspector.constant_name_from_node(node, ancestors: ancestors, relative_file: relative_file)
115
+ rescue ArgumentError => error
116
+ if error.message == "unknown keyword: :relative_file"
117
+ T.unsafe(inspector).constant_name_from_node(node, ancestors: ancestors).tap do
118
+ warn(<<~MSG.squish)
119
+ #{T.cast(inspector, Object).class}#reference_from_node without a relative_file: keyword
120
+ argument is deprecated and will be required in Packwerk 3.1.1.
121
+ MSG
122
+ end
123
+ else
124
+ raise
125
+ end
126
+ end
127
+
100
128
  sig do
101
129
  params(
102
130
  constant_name: String,
@@ -23,7 +23,7 @@ module Packwerk
23
23
  .void
24
24
  end
25
25
  def initialize(reference:, violation_type:, message:, location: nil)
26
- super(file: reference.relative_path, message: message, location: location)
26
+ super(file: T.must(reference.relative_path), message: message, location: location)
27
27
  @reference = reference
28
28
  @violation_type = violation_type
29
29
  end
@@ -15,14 +15,13 @@ module Packwerk
15
15
  params(configuration: Configuration).returns(RunContext)
16
16
  end
17
17
  def from_configuration(configuration)
18
- inflector = ActiveSupport::Inflector
19
-
20
18
  new(
21
19
  root_path: configuration.root_path,
22
20
  load_paths: configuration.load_paths,
23
21
  package_paths: configuration.package_paths,
24
- inflector: inflector,
22
+ inflector: ActiveSupport::Inflector,
25
23
  custom_associations: configuration.custom_associations,
24
+ associations_exclude: configuration.associations_exclude,
26
25
  cache_enabled: configuration.cache_enabled?,
27
26
  cache_directory: configuration.cache_directory,
28
27
  config_path: configuration.config_path,
@@ -39,6 +38,7 @@ module Packwerk
39
38
  config_path: T.nilable(String),
40
39
  package_paths: T.nilable(T.any(T::Array[String], String)),
41
40
  custom_associations: AssociationInspector::CustomAssociations,
41
+ associations_exclude: T::Array[String],
42
42
  checkers: T::Array[Checker],
43
43
  cache_enabled: T::Boolean,
44
44
  ).void
@@ -51,6 +51,7 @@ module Packwerk
51
51
  config_path: nil,
52
52
  package_paths: nil,
53
53
  custom_associations: [],
54
+ associations_exclude: [],
54
55
  checkers: Checker.all,
55
56
  cache_enabled: false
56
57
  )
@@ -59,6 +60,7 @@ module Packwerk
59
60
  @package_paths = package_paths
60
61
  @inflector = inflector
61
62
  @custom_associations = custom_associations
63
+ @associations_exclude = associations_exclude
62
64
  @checkers = checkers
63
65
  @cache_enabled = cache_enabled
64
66
  @cache_directory = cache_directory
@@ -128,9 +130,18 @@ module Packwerk
128
130
  def constant_name_inspectors
129
131
  [
130
132
  ConstNodeInspector.new,
131
- AssociationInspector.new(inflector: @inflector, custom_associations: @custom_associations),
133
+ AssociationInspector.new(
134
+ inflector: @inflector,
135
+ custom_associations: @custom_associations,
136
+ excluded_files: relative_files_for_globs(@associations_exclude),
137
+ ),
132
138
  ]
133
139
  end
140
+
141
+ sig { params(relative_globs: T::Array[String]).returns(FilesForProcessing::RelativeFileSet) }
142
+ def relative_files_for_globs(relative_globs)
143
+ Set.new(relative_globs.flat_map { |glob| Dir[glob] })
144
+ end
134
145
  end
135
146
 
136
147
  private_constant :RunContext
@@ -33,5 +33,3 @@ module Packwerk
33
33
 
34
34
  Spring.register_command("packwerk", SpringCommand.new)
35
35
  end
36
-
37
- require "packwerk/disable_sorbet"
@@ -9,22 +9,36 @@ module Packwerk
9
9
  module Validator
10
10
  extend T::Sig
11
11
  extend T::Helpers
12
+ extend ActiveSupport::Autoload
13
+
14
+ autoload :Result
12
15
 
13
16
  abstract!
14
17
 
15
18
  class << self
16
19
  extend T::Sig
17
20
 
18
- sig { params(base: Class).void }
21
+ sig { params(base: T::Class[T.anything]).void }
19
22
  def included(base)
20
- @validators ||= T.let(@validators, T.nilable(T::Array[Class]))
21
- @validators ||= []
22
- @validators << base
23
+ validators << base
23
24
  end
24
25
 
25
26
  sig { returns(T::Array[Validator]) }
26
27
  def all
27
- T.unsafe(@validators).map(&:new)
28
+ load_defaults
29
+ T.cast(validators.map(&:new), T::Array[Validator])
30
+ end
31
+
32
+ private
33
+
34
+ sig { void }
35
+ def load_defaults
36
+ require("packwerk/validators/dependency_validator")
37
+ end
38
+
39
+ sig { returns(T::Array[T::Class[T.anything]]) }
40
+ def validators
41
+ @validators ||= T.let([], T.nilable(T::Array[T::Class[T.anything]]))
28
42
  end
29
43
  end
30
44
 
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
- VERSION = "3.0.0"
5
+ VERSION = "3.2.2"
6
6
  end