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
@@ -0,0 +1,92 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "optparse"
5
+
6
+ module Packwerk
7
+ module Commands
8
+ module UsesParseRun
9
+ extend T::Sig
10
+ extend T::Helpers
11
+
12
+ requires_ancestor { BaseCommand }
13
+
14
+ sig do
15
+ params(
16
+ args: T::Array[String],
17
+ configuration: Configuration,
18
+ out: T.any(StringIO, IO),
19
+ err_out: T.any(StringIO, IO),
20
+ progress_formatter: Formatters::ProgressFormatter,
21
+ offenses_formatter: OffensesFormatter,
22
+ ).void
23
+ end
24
+ def initialize(args, configuration:, out:, err_out:, progress_formatter:, offenses_formatter:)
25
+ super
26
+ @files_for_processing = T.let(fetch_files_to_process, FilesForProcessing)
27
+ @offenses_formatter = T.let(offenses_formatter_from_options || @offenses_formatter, OffensesFormatter)
28
+ configuration.parallel = parsed_options[:parallel]
29
+ end
30
+
31
+ private
32
+
33
+ sig { returns(FilesForProcessing) }
34
+ def fetch_files_to_process
35
+ FilesForProcessing.fetch(
36
+ relative_file_paths: parsed_options[:relative_file_paths],
37
+ ignore_nested_packages: parsed_options[:ignore_nested_packages],
38
+ configuration: configuration
39
+ )
40
+ end
41
+
42
+ sig { returns(T.nilable(OffensesFormatter)) }
43
+ def offenses_formatter_from_options
44
+ OffensesFormatter.find(parsed_options[:formatter_name]) if parsed_options[:formatter_name]
45
+ end
46
+
47
+ sig { returns(ParseRun) }
48
+ def parse_run
49
+ ParseRun.new(
50
+ relative_file_set: @files_for_processing.files,
51
+ parallel: configuration.parallel?,
52
+ )
53
+ end
54
+
55
+ sig { returns(T::Hash[Symbol, T.untyped]) }
56
+ def parsed_options
57
+ return @parsed_options if @parsed_options
58
+
59
+ @parsed_options = T.let(nil, T.nilable(T::Hash[Symbol, T.untyped]))
60
+
61
+ @parsed_options = {
62
+ relative_file_paths: T.let([], T::Array[String]),
63
+ ignore_nested_packages: T.let(false, T::Boolean),
64
+ formatter_name: T.let(nil, T.nilable(String)),
65
+ parallel: T.let(configuration.parallel?, T::Boolean),
66
+ }
67
+
68
+ OptionParser.new do |parser|
69
+ parser.on("--packages=PACKAGESLIST", Array, "package names, comma separated") do |p|
70
+ @parsed_options[:relative_file_paths] = p
71
+ @parsed_options[:ignore_nested_packages] = true
72
+ end
73
+
74
+ parser.on("--offenses-formatter=FORMATTER", String,
75
+ "identifier of offenses formatter to use") do |formatter_name|
76
+ @parsed_options[:formatter_name] = formatter_name
77
+ end
78
+
79
+ parser.on("--[no-]parallel", TrueClass, "parallel processing") do |parallel|
80
+ @parsed_options[:parallel] = parallel
81
+ end
82
+ end.parse!(args)
83
+
84
+ @parsed_options[:relative_file_paths] = args if @parsed_options[:relative_file_paths].empty?
85
+
86
+ @parsed_options
87
+ end
88
+ end
89
+
90
+ private_constant :UsesParseRun
91
+ end
92
+ end
@@ -0,0 +1,46 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Commands
6
+ class ValidateCommand < BaseCommand
7
+ extend T::Sig
8
+
9
+ description "verify integrity of packwerk and package configuration"
10
+
11
+ sig { override.returns(T::Boolean) }
12
+ def run
13
+ validator_result = T.let(nil, T.nilable(Validator::Result))
14
+
15
+ progress_formatter.started_validation do
16
+ validator_result = validator.check_all(package_set, configuration)
17
+ end
18
+
19
+ validator_result = T.must(validator_result)
20
+
21
+ if validator_result.ok?
22
+ out.puts("Validation successful 🎉")
23
+ else
24
+ out.puts("Validation failed ❗\n\n#{validator_result.error_value}")
25
+ end
26
+
27
+ validator_result.ok?
28
+ end
29
+
30
+ private
31
+
32
+ sig { returns(ApplicationValidator) }
33
+ def validator
34
+ ApplicationValidator.new
35
+ end
36
+
37
+ sig { returns(PackageSet) }
38
+ def package_set
39
+ PackageSet.load_all_from(
40
+ configuration.root_path,
41
+ package_pathspec: configuration.package_paths
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Commands
6
+ class VersionCommand < BaseCommand
7
+ extend T::Sig
8
+
9
+ description "output packwerk version"
10
+
11
+ sig { override.returns(T::Boolean) }
12
+ def run
13
+ out.puts(Packwerk::VERSION)
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,54 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Commands
6
+ extend T::Sig
7
+ extend ActiveSupport::Autoload
8
+
9
+ autoload :BaseCommand
10
+ autoload :CheckCommand
11
+ autoload :HelpCommand
12
+ autoload :InitCommand
13
+ autoload :LazyLoadedEntry
14
+ autoload :UpdateTodoCommand
15
+ autoload :UsesParseRun
16
+ autoload :ValidateCommand
17
+ autoload :VersionCommand
18
+
19
+ class << self
20
+ extend T::Sig
21
+
22
+ sig { params(name: String, aliases: T::Array[String]).void }
23
+ def register(name, aliases: [])
24
+ registry << LazyLoadedEntry.new(name, aliases: aliases)
25
+ end
26
+
27
+ sig { params(name_or_alias: String).returns(T.nilable(T.class_of(BaseCommand))) }
28
+ def for(name_or_alias)
29
+ registry
30
+ .find { |command| command.matches_command?(name_or_alias) }
31
+ &.command_class
32
+ end
33
+
34
+ sig { returns(T::Array[LazyLoadedEntry]) }
35
+ def all
36
+ registry.dup
37
+ end
38
+
39
+ private
40
+
41
+ sig { returns(T::Array[LazyLoadedEntry]) }
42
+ def registry
43
+ @registry ||= T.let([], T.nilable(T::Array[LazyLoadedEntry]))
44
+ end
45
+ end
46
+
47
+ register("init")
48
+ register("check")
49
+ register("update-todo", aliases: ["update"])
50
+ register("validate")
51
+ register("version")
52
+ register("help")
53
+ end
54
+ end
@@ -54,12 +54,18 @@ module Packwerk
54
54
  sig { returns(T::Array[Symbol]) }
55
55
  attr_reader(:custom_associations)
56
56
 
57
+ sig { returns(T::Array[String]) }
58
+ attr_reader(:associations_exclude)
59
+
57
60
  sig { returns(T.nilable(String)) }
58
61
  attr_reader(:config_path)
59
62
 
60
63
  sig { returns(Pathname) }
61
64
  attr_reader(:cache_directory)
62
65
 
66
+ sig { params(parallel: T::Boolean).returns(T::Boolean) }
67
+ attr_writer(:parallel)
68
+
63
69
  sig do
64
70
  params(
65
71
  configs: T::Hash[String, T.untyped],
@@ -72,7 +78,8 @@ module Packwerk
72
78
  root = config_path ? File.dirname(config_path) : "."
73
79
  @root_path = T.let(File.expand_path(root), String)
74
80
  @package_paths = T.let(configs["package_paths"] || "**/", T.any(String, T::Array[String]))
75
- @custom_associations = T.let(configs["custom_associations"] || [], T::Array[Symbol])
81
+ @custom_associations = T.let((configs["custom_associations"] || []).map(&:to_sym), T::Array[Symbol])
82
+ @associations_exclude = T.let(configs["associations_exclude"] || [], T::Array[String])
76
83
  @parallel = T.let(configs.key?("parallel") ? configs["parallel"] : true, T::Boolean)
77
84
  @cache_enabled = T.let(configs.key?("cache") ? configs["cache"] : false, T::Boolean)
78
85
  @cache_directory = T.let(Pathname.new(configs["cache_directory"] || "tmp/cache/packwerk"), Pathname)
@@ -9,10 +9,10 @@ module Packwerk
9
9
 
10
10
  sig do
11
11
  override
12
- .params(node: AST::Node, ancestors: T::Array[AST::Node])
12
+ .params(node: AST::Node, ancestors: T::Array[AST::Node], relative_file: String)
13
13
  .returns(T.nilable(String))
14
14
  end
15
- def constant_name_from_node(node, ancestors:)
15
+ def constant_name_from_node(node, ancestors:, relative_file:)
16
16
  return nil unless NodeHelpers.constant?(node)
17
17
 
18
18
  parent = ancestors.first
@@ -13,10 +13,10 @@ module Packwerk
13
13
 
14
14
  sig do
15
15
  abstract
16
- .params(node: ::AST::Node, ancestors: T::Array[::AST::Node])
16
+ .params(node: ::AST::Node, ancestors: T::Array[::AST::Node], relative_file: String)
17
17
  .returns(T.nilable(String))
18
18
  end
19
- def constant_name_from_node(node, ancestors:); end
19
+ def constant_name_from_node(node, ancestors:, relative_file:); end
20
20
  end
21
21
 
22
22
  private_constant :ConstantNameInspector
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "ast/node"
4
+ require "parser/ast/node"
5
5
 
6
6
  module Packwerk
7
7
  class FileProcessor
@@ -53,6 +53,17 @@ module Packwerk
53
53
  ProcessedFile.new(unresolved_references: unresolved_references)
54
54
  rescue Parsers::ParseError => e
55
55
  ProcessedFile.new(offenses: [e.result])
56
+ rescue StandardError => e
57
+ message = <<~MSG
58
+ Packwerk encountered an internal error.
59
+ For now, you can add this file to `packwerk.yml` `exclude` list.
60
+ Please file an issue and include this error message and stacktrace:
61
+
62
+ #{e.message} #{e.backtrace&.join("\n")}"
63
+ MSG
64
+
65
+ offense = Parsers::ParseResult.new(file: relative_file, message: message)
66
+ ProcessedFile.new(offenses: [offense])
56
67
  end
57
68
 
58
69
  private
@@ -20,9 +20,9 @@ module Packwerk
20
20
  EOS
21
21
  end
22
22
 
23
- sig { override.params(offense_collection: OffenseCollection, fileset: T::Set[String]).returns(String) }
24
- def show_stale_violations(offense_collection, fileset)
25
- if offense_collection.stale_violations?(fileset)
23
+ sig { override.params(offense_collection: OffenseCollection, file_set: T::Set[String]).returns(String) }
24
+ def show_stale_violations(offense_collection, file_set)
25
+ if offense_collection.stale_violations?(file_set)
26
26
  "There were stale violations found, please run `packwerk update-todo`"
27
27
  else
28
28
  "No stale violations detected"
@@ -30,6 +30,15 @@ module Packwerk
30
30
  finished(execution_time)
31
31
  end
32
32
 
33
+ sig { params(failed: T::Boolean).void }
34
+ def increment_progress(failed = false)
35
+ if failed
36
+ mark_as_failed
37
+ else
38
+ mark_as_inspected
39
+ end
40
+ end
41
+
33
42
  sig { void }
34
43
  def mark_as_inspected
35
44
  @out.print(".")
@@ -44,6 +53,7 @@ module Packwerk
44
53
  def interrupted
45
54
  @out.puts
46
55
  @out.puts("Manually interrupted. Violations caught so far are listed below:")
56
+ @out.puts
47
57
  end
48
58
 
49
59
  private
@@ -52,6 +62,7 @@ module Packwerk
52
62
  def finished(execution_time)
53
63
  @out.puts
54
64
  @out.puts("📦 Finished in #{execution_time.round(2)} seconds")
65
+ @out.puts
55
66
  end
56
67
 
57
68
  sig { void }
@@ -2,8 +2,8 @@
2
2
  # Please validate the configuration using `packwerk validate` (for Rails applications) or running the auto generated
3
3
  # test case (for non-Rails projects). You can then use `packwerk check` to check your code.
4
4
 
5
- # Turn on dependency checks for this package
6
- enforce_dependencies: true
5
+ # Change to `true` to turn on dependency checks for this package
6
+ enforce_dependencies: false
7
7
 
8
8
  # A list of this package's dependencies
9
9
  # Note that packages in this list require their own `package.yml` file
@@ -1,5 +1,5 @@
1
1
  # See: Setting up the configuration file
2
- # https://github.com/Shopify/packwerk/blob/main/USAGE.md#setting-up-the-configuration-file
2
+ # https://github.com/Shopify/packwerk/blob/main/USAGE.md#configuring-packwerk
3
3
 
4
4
  # List of patterns for folder paths to include
5
5
  # include:
@@ -11,12 +11,12 @@ module Packwerk
11
11
  sig do
12
12
  params(
13
13
  root_path: String,
14
- package_todo: T::Hash[Packwerk::Package, Packwerk::PackageTodo]
14
+ package_todos: T::Hash[Packwerk::Package, Packwerk::PackageTodo]
15
15
  ).void
16
16
  end
17
- def initialize(root_path, package_todo = {})
17
+ def initialize(root_path, package_todos = {})
18
18
  @root_path = root_path
19
- @package_todo = T.let(package_todo, T::Hash[Packwerk::Package, Packwerk::PackageTodo])
19
+ @package_todos = T.let(package_todos, T::Hash[Packwerk::Package, Packwerk::PackageTodo])
20
20
  @new_violations = T.let([], T::Array[Packwerk::ReferenceOffense])
21
21
  @strict_mode_violations = T.let([], T::Array[Packwerk::ReferenceOffense])
22
22
  @errors = T.let([], T::Array[Packwerk::Offense])
@@ -38,8 +38,12 @@ module Packwerk
38
38
  def listed?(offense)
39
39
  return false unless offense.is_a?(ReferenceOffense)
40
40
 
41
- reference = offense.reference
42
- package_todo_for(reference.package).listed?(reference, violation_type: offense.violation_type)
41
+ already_listed?(offense)
42
+ end
43
+
44
+ sig { params(offenses: T::Array[Offense]).void }
45
+ def add_offenses(offenses)
46
+ offenses.each { |offense| add_offense(offense) }
43
47
  end
44
48
 
45
49
  sig do
@@ -51,16 +55,21 @@ module Packwerk
51
55
  return
52
56
  end
53
57
 
54
- if !already_listed?(offense)
55
- new_violations << offense
56
- elsif strict_mode_violation?(offense)
58
+ already_listed = already_listed?(offense)
59
+
60
+ new_violations << offense unless already_listed
61
+
62
+ if strict_mode_violation?(offense)
63
+ add_to_package_todo(offense) if already_listed
57
64
  strict_mode_violations << offense
65
+ else
66
+ add_to_package_todo(offense)
58
67
  end
59
68
  end
60
69
 
61
70
  sig { params(for_files: T::Set[String]).returns(T::Boolean) }
62
71
  def stale_violations?(for_files)
63
- @package_todo.values.any? do |package_todo|
72
+ @package_todos.values.any? do |package_todo|
64
73
  package_todo.stale_violations?(for_files)
65
74
  end
66
75
  end
@@ -76,10 +85,21 @@ module Packwerk
76
85
  errors + new_violations
77
86
  end
78
87
 
88
+ sig { returns(T::Array[Packwerk::ReferenceOffense]) }
89
+ def unlisted_strict_mode_violations
90
+ strict_mode_violations.reject { |offense| already_listed?(offense) }
91
+ end
92
+
79
93
  private
80
94
 
81
95
  sig { params(offense: ReferenceOffense).returns(T::Boolean) }
82
96
  def already_listed?(offense)
97
+ package_todo_for(offense.reference.package).listed?(offense.reference,
98
+ violation_type: offense.violation_type)
99
+ end
100
+
101
+ sig { params(offense: ReferenceOffense).returns(T::Boolean) }
102
+ def add_to_package_todo(offense)
83
103
  package_todo_for(offense.reference.package).add_entries(offense.reference,
84
104
  offense.violation_type)
85
105
  end
@@ -92,7 +112,7 @@ module Packwerk
92
112
 
93
113
  sig { params(package_set: Packwerk::PackageSet).void }
94
114
  def cleanup_extra_package_todo_files(package_set)
95
- packages_without_todos = (package_set.packages.values - @package_todo.keys)
115
+ packages_without_todos = (package_set.packages.values - @package_todos.keys)
96
116
 
97
117
  packages_without_todos.each do |package|
98
118
  Packwerk::PackageTodo.new(
@@ -104,12 +124,12 @@ module Packwerk
104
124
 
105
125
  sig { void }
106
126
  def dump_package_todo_files
107
- @package_todo.each_value(&:dump)
127
+ @package_todos.each_value(&:dump)
108
128
  end
109
129
 
110
130
  sig { params(package: Packwerk::Package).returns(Packwerk::PackageTodo) }
111
131
  def package_todo_for(package)
112
- @package_todo[package] ||= Packwerk::PackageTodo.new(
132
+ @package_todos[package] ||= Packwerk::PackageTodo.new(
113
133
  package,
114
134
  package_todo_file_for(package),
115
135
  )
@@ -20,16 +20,15 @@ module Packwerk
20
20
  class << self
21
21
  extend T::Sig
22
22
 
23
- sig { params(base: Class).void }
23
+ sig { params(base: T::Class[T.anything]).void }
24
24
  def included(base)
25
- @offenses_formatters ||= T.let(@offenses_formatters, T.nilable(T::Array[Class]))
26
- @offenses_formatters ||= []
27
- @offenses_formatters << base
25
+ offenses_formatters << base
28
26
  end
29
27
 
30
28
  sig { returns(T::Array[OffensesFormatter]) }
31
29
  def all
32
- T.unsafe(@offenses_formatters).map(&:new)
30
+ load_defaults
31
+ T.cast(offenses_formatters.map(&:new), T::Array[OffensesFormatter])
33
32
  end
34
33
 
35
34
  sig { params(identifier: String).returns(OffensesFormatter) }
@@ -39,6 +38,16 @@ module Packwerk
39
38
 
40
39
  private
41
40
 
41
+ sig { void }
42
+ def load_defaults
43
+ require("packwerk/formatters/default_offenses_formatter")
44
+ end
45
+
46
+ sig { returns(T::Array[T::Class[T.anything]]) }
47
+ def offenses_formatters
48
+ @offenses_formatters ||= T.let([], T.nilable(T::Array[T::Class[T.anything]]))
49
+ end
50
+
42
51
  sig { params(name: String).returns(OffensesFormatter) }
43
52
  def formatter_by_identifier(name)
44
53
  @formatter_by_identifier ||= T.let(nil, T.nilable(T::Hash[String, T.nilable(OffensesFormatter)]))
@@ -42,7 +42,7 @@ module Packwerk
42
42
  def package_path?(path)
43
43
  return true if root?
44
44
 
45
- path.start_with?(@name)
45
+ path.start_with?(@name + "/")
46
46
  end
47
47
 
48
48
  sig { params(other: T.untyped).returns(T.nilable(Integer)) }