packwerk 3.0.0 → 3.2.2

Sign up to get free protection for your applications and to get access to all the features.
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