packwerk 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -5
  3. data/.ruby-version +1 -1
  4. data/Gemfile +0 -1
  5. data/Gemfile.lock +5 -95
  6. data/README.md +2 -7
  7. data/TROUBLESHOOT.md +0 -22
  8. data/USAGE.md +141 -51
  9. data/dev.yml +1 -1
  10. data/exe/packwerk +1 -0
  11. data/gemfiles/Gemfile-rails-6-1 +1 -1
  12. data/lib/packwerk/application_validator.rb +54 -285
  13. data/lib/packwerk/association_inspector.rb +2 -0
  14. data/lib/packwerk/cache.rb +6 -5
  15. data/lib/packwerk/checker.rb +54 -0
  16. data/lib/packwerk/cli/result.rb +11 -0
  17. data/lib/packwerk/cli.rb +55 -40
  18. data/lib/packwerk/configuration.rb +61 -40
  19. data/lib/packwerk/const_node_inspector.rb +2 -0
  20. data/lib/packwerk/constant_context.rb +8 -0
  21. data/lib/packwerk/constant_discovery.rb +5 -6
  22. data/lib/packwerk/constant_name_inspector.rb +2 -0
  23. data/lib/packwerk/disable_sorbet.rb +41 -0
  24. data/lib/packwerk/extension_loader.rb +24 -0
  25. data/lib/packwerk/file_processor.rb +3 -1
  26. data/lib/packwerk/files_for_processing.rb +25 -12
  27. data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
  28. data/lib/packwerk/formatters/progress_formatter.rb +31 -12
  29. data/lib/packwerk/generators/configuration_file.rb +7 -2
  30. data/lib/packwerk/generators/root_package.rb +5 -1
  31. data/lib/packwerk/generators/templates/package.yml +0 -10
  32. data/lib/packwerk/graph.rb +10 -2
  33. data/lib/packwerk/node.rb +1 -1
  34. data/lib/packwerk/node_helpers.rb +14 -7
  35. data/lib/packwerk/node_processor.rb +2 -0
  36. data/lib/packwerk/node_processor_factory.rb +6 -4
  37. data/lib/packwerk/node_visitor.rb +10 -1
  38. data/lib/packwerk/offense_collection.rb +26 -18
  39. data/lib/packwerk/offenses_formatter.rb +59 -2
  40. data/lib/packwerk/package.rb +7 -35
  41. data/lib/packwerk/package_set.rb +1 -1
  42. data/lib/packwerk/package_todo.rb +15 -9
  43. data/lib/packwerk/parse_run.rb +27 -34
  44. data/lib/packwerk/parsed_constant_definitions.rb +28 -5
  45. data/lib/packwerk/parsers/erb.rb +23 -4
  46. data/lib/packwerk/parsers/factory.rb +11 -2
  47. data/lib/packwerk/parsers/parser_interface.rb +1 -1
  48. data/lib/packwerk/parsers/ruby.rb +13 -3
  49. data/lib/packwerk/parsers.rb +6 -2
  50. data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
  51. data/lib/packwerk/reference.rb +7 -1
  52. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
  53. data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
  54. data/lib/packwerk/reference_extractor.rb +24 -12
  55. data/lib/packwerk/reference_offense.rb +2 -2
  56. data/lib/packwerk/run_context.rb +7 -10
  57. data/lib/packwerk/spring_command.rb +11 -2
  58. data/lib/packwerk/unresolved_reference.rb +9 -1
  59. data/lib/packwerk/validator/result.rb +18 -0
  60. data/lib/packwerk/validator.rb +90 -0
  61. data/lib/packwerk/validators/dependency_validator.rb +154 -0
  62. data/lib/packwerk/version.rb +1 -1
  63. data/lib/packwerk.rb +64 -26
  64. data/packwerk.gemspec +4 -2
  65. data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
  66. data/sorbet/rbi/shims/minitest/test.rb +8 -0
  67. data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
  68. data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
  69. data/sorbet/rbi/shims/parser.rbi +13 -0
  70. metadata +34 -15
  71. data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
  72. data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
  73. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
  74. data/lib/packwerk/result.rb +0 -9
  75. data/lib/packwerk/sanity_checker.rb +0 -8
  76. data/lib/packwerk/violation_type.rb +0 -11
  77. data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
  78. data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +0 -8
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "benchmark"
@@ -14,37 +14,56 @@ module Packwerk
14
14
  @style = style
15
15
  end
16
16
 
17
- def started(target_files)
18
- files_size = target_files.size
19
- files_string = "file".pluralize(files_size)
20
- @out.puts("📦 Packwerk is inspecting #{files_size} #{files_string}")
21
- end
22
-
17
+ sig { params(block: T.proc.void).void }
23
18
  def started_validation(&block)
24
- @out.puts("📦 Packwerk is running validation...")
19
+ start_validation
25
20
 
26
21
  execution_time = Benchmark.realtime(&block)
27
22
  finished(execution_time)
23
+ end
28
24
 
29
- @out.puts("✅ Packages are valid. Use `packwerk check` to run static checks.")
25
+ sig { params(target_files: FilesForProcessing::RelativeFileSet, block: T.proc.void).void }
26
+ def started_inspection(target_files, &block)
27
+ start_inspection(target_files)
28
+
29
+ execution_time = Benchmark.realtime(&block)
30
+ finished(execution_time)
30
31
  end
31
32
 
33
+ sig { void }
32
34
  def mark_as_inspected
33
35
  @out.print(".")
34
36
  end
35
37
 
38
+ sig { void }
36
39
  def mark_as_failed
37
40
  @out.print("#{@style.error}E#{@style.reset}")
38
41
  end
39
42
 
43
+ sig { void }
44
+ def interrupted
45
+ @out.puts
46
+ @out.puts("Manually interrupted. Violations caught so far are listed below:")
47
+ end
48
+
49
+ private
50
+
51
+ sig { params(execution_time: Float).void }
40
52
  def finished(execution_time)
41
53
  @out.puts
42
54
  @out.puts("📦 Finished in #{execution_time.round(2)} seconds")
43
55
  end
44
56
 
45
- def interrupted
46
- @out.puts
47
- @out.puts("Manually interrupted. Violations caught so far are listed below:")
57
+ sig { void }
58
+ def start_validation
59
+ @out.puts("📦 Packwerk is running validation...")
60
+ end
61
+
62
+ sig { params(target_files: FilesForProcessing::RelativeFileSet).void }
63
+ def start_inspection(target_files)
64
+ files_size = target_files.size
65
+ files_string = "file".pluralize(files_size)
66
+ @out.puts("📦 Packwerk is inspecting #{files_size} #{files_string}")
48
67
  end
49
68
  end
50
69
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "erb"
@@ -11,6 +11,9 @@ module Packwerk
11
11
  CONFIGURATION_TEMPLATE_FILE_PATH = "templates/packwerk.yml.erb"
12
12
 
13
13
  class << self
14
+ extend T::Sig
15
+
16
+ sig { params(root: String, out: T.any(IO, StringIO)).returns(T::Boolean) }
14
17
  def generate(root:, out:)
15
18
  new(root: root, out: out).generate
16
19
  end
@@ -25,7 +28,7 @@ module Packwerk
25
28
  sig { returns(T::Boolean) }
26
29
  def generate
27
30
  @out.puts("📦 Generating Packwerk configuration file...")
28
- default_config_path = File.join(@root, ::Packwerk::Configuration::DEFAULT_CONFIG_PATH)
31
+ default_config_path = File.join(@root, Configuration::DEFAULT_CONFIG_PATH)
29
32
 
30
33
  if File.exist?(default_config_path)
31
34
  @out.puts("⚠️ Packwerk configuration file already exists.")
@@ -40,10 +43,12 @@ module Packwerk
40
43
 
41
44
  private
42
45
 
46
+ sig { returns(String) }
43
47
  def render
44
48
  ERB.new(template, trim_mode: "-").result(binding)
45
49
  end
46
50
 
51
+ sig { returns(String) }
47
52
  def template
48
53
  template_file_path = File.join(__dir__, CONFIGURATION_TEMPLATE_FILE_PATH)
49
54
  File.read(template_file_path)
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
@@ -7,11 +7,15 @@ module Packwerk
7
7
  extend T::Sig
8
8
 
9
9
  class << self
10
+ extend T::Sig
11
+
12
+ sig { params(root: String, out: T.any(IO, StringIO)).returns(T::Boolean) }
10
13
  def generate(root:, out:)
11
14
  new(root: root, out: out).generate
12
15
  end
13
16
  end
14
17
 
18
+ sig { params(root: String, out: T.any(IO, StringIO)).void }
15
19
  def initialize(root:, out: $stdout)
16
20
  @root = root
17
21
  @out = out
@@ -5,16 +5,6 @@
5
5
  # Turn on dependency checks for this package
6
6
  enforce_dependencies: true
7
7
 
8
- # Turn on privacy checks for this package
9
- # enforcing privacy is often not useful for the root package, because it would require defining a public interface
10
- # for something that should only be a thin wrapper in the first place.
11
- # We recommend enabling this for any new packages you create to aid with encapsulation.
12
- enforce_privacy: false
13
-
14
- # By default the public path will be app/public/, however this may not suit all applications' architecture so
15
- # this allows you to modify what your package's public path is.
16
- # public_path: app/public/
17
-
18
8
  # A list of this package's dependencies
19
9
  # Note that packages in this list require their own `package.yml` file
20
10
  # dependencies:
@@ -4,8 +4,14 @@
4
4
  module Packwerk
5
5
  # A general implementation of a graph data structure with the ability to check for - and list - cycles.
6
6
  class Graph
7
- # @param [Array<Array>] edges The edges of the graph; An edge being represented as an Array of two nodes.
8
- def initialize(*edges)
7
+ extend T::Sig
8
+ sig do
9
+ params(
10
+ # The edges of the graph; An edge being represented as an Array of two nodes.
11
+ edges: T::Array[T::Array[T.any(String, Integer, NilClass)]]
12
+ ).void
13
+ end
14
+ def initialize(edges)
9
15
  @edges = edges.uniq
10
16
  @cycles = Set.new
11
17
  process
@@ -73,4 +79,6 @@ module Packwerk
73
79
  @cycles << cycle
74
80
  end
75
81
  end
82
+
83
+ private_constant :Graph
76
84
  end
data/lib/packwerk/node.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
- class Node
5
+ module Node
6
6
  Location = Struct.new(:line, :column)
7
7
  end
8
8
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "parser"
@@ -59,11 +59,16 @@ module Packwerk
59
59
  end
60
60
  end
61
61
 
62
- sig { params(node: AST::Node).returns(T.untyped) }
63
- def each_child(node)
64
- if block_given?
62
+ sig do
63
+ params(
64
+ node: AST::Node,
65
+ block: T.nilable(T.proc.params(arg0: Parser::AST::Node).void),
66
+ ).returns(T::Enumerable[AST::Node])
67
+ end
68
+ def each_child(node, &block)
69
+ if block
65
70
  node.children.each do |child|
66
- yield child if child.is_a?(Parser::AST::Node)
71
+ yield(child) if child.is_a?(Parser::AST::Node)
67
72
  end
68
73
  else
69
74
  enum_for(:each_child, node)
@@ -181,9 +186,9 @@ module Packwerk
181
186
  end
182
187
  end
183
188
 
184
- sig { params(node: Parser::AST::Node).returns(T.nilable(Node::Location)) }
189
+ sig { params(node: AST::Node).returns(T.nilable(Node::Location)) }
185
190
  def name_location(node)
186
- location = node.location
191
+ location = T.cast(node, Parser::AST::Node).location
187
192
 
188
193
  if location.respond_to?(:name)
189
194
  name = location.name
@@ -332,4 +337,6 @@ module Packwerk
332
337
  end
333
338
  end
334
339
  end
340
+
341
+ private_constant :NodeHelpers
335
342
  end
@@ -29,4 +29,6 @@ module Packwerk
29
29
  @reference_extractor.reference_from_node(node, ancestors: ancestors, relative_file: @relative_file)
30
30
  end
31
31
  end
32
+
33
+ private_constant :NodeProcessor
32
34
  end
@@ -6,12 +6,12 @@ module Packwerk
6
6
  extend T::Sig
7
7
 
8
8
  const :root_path, String
9
- const :context_provider, Packwerk::ConstantDiscovery
9
+ const :context_provider, ConstantDiscovery
10
10
  const :constant_name_inspectors, T::Array[ConstantNameInspector]
11
11
 
12
12
  sig { params(relative_file: String, node: AST::Node).returns(NodeProcessor) }
13
13
  def for(relative_file:, node:)
14
- ::Packwerk::NodeProcessor.new(
14
+ NodeProcessor.new(
15
15
  reference_extractor: reference_extractor(node: node),
16
16
  relative_file: relative_file,
17
17
  )
@@ -19,13 +19,15 @@ module Packwerk
19
19
 
20
20
  private
21
21
 
22
- sig { params(node: AST::Node).returns(::Packwerk::ReferenceExtractor) }
22
+ sig { params(node: AST::Node).returns(ReferenceExtractor) }
23
23
  def reference_extractor(node:)
24
- ::Packwerk::ReferenceExtractor.new(
24
+ ReferenceExtractor.new(
25
25
  constant_name_inspectors: constant_name_inspectors,
26
26
  root_node: node,
27
27
  root_path: root_path,
28
28
  )
29
29
  end
30
30
  end
31
+
32
+ private_constant :NodeProcessorFactory
31
33
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
@@ -11,6 +11,13 @@ module Packwerk
11
11
  @node_processor = node_processor
12
12
  end
13
13
 
14
+ sig do
15
+ params(
16
+ node: Parser::AST::Node,
17
+ ancestors: T::Array[Parser::AST::Node],
18
+ result: T::Array[UnresolvedReference],
19
+ ).void
20
+ end
14
21
  def visit(node, ancestors:, result:)
15
22
  reference = @node_processor.call(node, ancestors)
16
23
  result << reference if reference
@@ -21,4 +28,6 @@ module Packwerk
21
28
  end
22
29
  end
23
30
  end
31
+
32
+ private_constant :NodeVisitor
24
33
  end
@@ -18,6 +18,7 @@ module Packwerk
18
18
  @root_path = root_path
19
19
  @package_todo = T.let(package_todo, T::Hash[Packwerk::Package, Packwerk::PackageTodo])
20
20
  @new_violations = T.let([], T::Array[Packwerk::ReferenceOffense])
21
+ @strict_mode_violations = T.let([], T::Array[Packwerk::ReferenceOffense])
21
22
  @errors = T.let([], T::Array[Packwerk::Offense])
22
23
  end
23
24
 
@@ -27,6 +28,9 @@ module Packwerk
27
28
  sig { returns(T::Array[Packwerk::Offense]) }
28
29
  attr_reader :errors
29
30
 
31
+ sig { returns(T::Array[Packwerk::ReferenceOffense]) }
32
+ attr_reader :strict_mode_violations
33
+
30
34
  sig do
31
35
  params(offense: Packwerk::Offense)
32
36
  .returns(T::Boolean)
@@ -35,7 +39,7 @@ module Packwerk
35
39
  return false unless offense.is_a?(ReferenceOffense)
36
40
 
37
41
  reference = offense.reference
38
- package_todo_for(reference.source_package).listed?(reference, violation_type: offense.violation_type)
42
+ package_todo_for(reference.package).listed?(reference, violation_type: offense.violation_type)
39
43
  end
40
44
 
41
45
  sig do
@@ -46,9 +50,11 @@ module Packwerk
46
50
  @errors << offense
47
51
  return
48
52
  end
49
- package_todo = package_todo_for(offense.reference.source_package)
50
- unless package_todo.add_entries(offense.reference, offense.violation_type)
53
+
54
+ if !already_listed?(offense)
51
55
  new_violations << offense
56
+ elsif strict_mode_violation?(offense)
57
+ strict_mode_violations << offense
52
58
  end
53
59
  end
54
60
 
@@ -70,12 +76,19 @@ module Packwerk
70
76
  errors + new_violations
71
77
  end
72
78
 
73
- sig { void }
74
- def dump_package_todo_files
75
- @package_todo.each_value(&:dump)
79
+ private
80
+
81
+ sig { params(offense: ReferenceOffense).returns(T::Boolean) }
82
+ def already_listed?(offense)
83
+ package_todo_for(offense.reference.package).add_entries(offense.reference,
84
+ offense.violation_type)
76
85
  end
77
86
 
78
- private
87
+ sig { params(offense: ReferenceOffense).returns(T::Boolean) }
88
+ def strict_mode_violation?(offense)
89
+ checker = Checker.find(offense.violation_type)
90
+ checker.strict_mode_violation?(offense)
91
+ end
79
92
 
80
93
  sig { params(package_set: Packwerk::PackageSet).void }
81
94
  def cleanup_extra_package_todo_files(package_set)
@@ -89,6 +102,11 @@ module Packwerk
89
102
  end
90
103
  end
91
104
 
105
+ sig { void }
106
+ def dump_package_todo_files
107
+ @package_todo.each_value(&:dump)
108
+ end
109
+
92
110
  sig { params(package: Packwerk::Package).returns(Packwerk::PackageTodo) }
93
111
  def package_todo_for(package)
94
112
  @package_todo[package] ||= Packwerk::PackageTodo.new(
@@ -99,17 +117,7 @@ module Packwerk
99
117
 
100
118
  sig { params(package: Packwerk::Package).returns(String) }
101
119
  def package_todo_file_for(package)
102
- if Pathname.new(@root_path).join(package.name, "deprecated_references.yml").exist?
103
- warning = <<~WARNING.squish
104
- DEPRECATION WARNING: `deprecated_references.yml` files have been renamed to `package_todo.yml`.
105
- Run `packwerk update-todo` to rename files automatically.
106
- WARNING
107
-
108
- warn(warning)
109
- File.join(@root_path, package.name, "deprecated_references.yml")
110
- else
111
- File.join(@root_path, package.name, "package_todo.yml")
112
- end
120
+ File.join(@root_path, package.name, "package_todo.yml")
113
121
  end
114
122
  end
115
123
  end
@@ -6,14 +6,71 @@ module Packwerk
6
6
  extend T::Sig
7
7
  extend T::Helpers
8
8
 
9
- interface!
9
+ abstract!
10
+
11
+ class DuplicateFormatterError < StandardError
12
+ extend T::Sig
13
+
14
+ sig { params(identifier: String).void }
15
+ def initialize(identifier)
16
+ super("Cannot have multiple identifiers with the same key (`#{identifier}`)")
17
+ end
18
+ end
19
+
20
+ class << self
21
+ extend T::Sig
22
+
23
+ sig { params(base: Class).void }
24
+ def included(base)
25
+ @offenses_formatters ||= T.let(@offenses_formatters, T.nilable(T::Array[Class]))
26
+ @offenses_formatters ||= []
27
+ @offenses_formatters << base
28
+ end
29
+
30
+ sig { returns(T::Array[OffensesFormatter]) }
31
+ def all
32
+ T.unsafe(@offenses_formatters).map(&:new)
33
+ end
34
+
35
+ sig { params(identifier: String).returns(OffensesFormatter) }
36
+ def find(identifier)
37
+ formatter_by_identifier(identifier)
38
+ end
39
+
40
+ private
41
+
42
+ sig { params(name: String).returns(OffensesFormatter) }
43
+ def formatter_by_identifier(name)
44
+ @formatter_by_identifier ||= T.let(nil, T.nilable(T::Hash[String, T.nilable(OffensesFormatter)]))
45
+ @formatter_by_identifier ||= begin
46
+ index = T.let({}, T::Hash[String, T.nilable(OffensesFormatter)])
47
+ OffensesFormatter.all.each do |formatter|
48
+ identifier = formatter.identifier
49
+ raise DuplicateFormatterError, identifier if index.key?(identifier)
50
+
51
+ index[identifier] = formatter
52
+ end
53
+ T.let(index, T.nilable(T::Hash[String, T.nilable(OffensesFormatter)]))
54
+ end
55
+
56
+ T.must(T.must(@formatter_by_identifier)[name])
57
+ end
58
+ end
10
59
 
11
60
  sig { abstract.params(offenses: T::Array[T.nilable(Offense)]).returns(String) }
12
61
  def show_offenses(offenses)
13
62
  end
14
63
 
15
- sig { abstract.params(offense_collection: Packwerk::OffenseCollection, for_files: T::Set[String]).returns(String) }
64
+ sig { abstract.params(offense_collection: OffenseCollection, for_files: T::Set[String]).returns(String) }
16
65
  def show_stale_violations(offense_collection, for_files)
17
66
  end
67
+
68
+ sig { abstract.returns(String) }
69
+ def identifier
70
+ end
71
+
72
+ sig { abstract.params(strict_mode_violations: T::Array[ReferenceOffense]).returns(String) }
73
+ def show_strict_mode_violations(strict_mode_violations)
74
+ end
18
75
  end
19
76
  end
@@ -17,22 +17,20 @@ module Packwerk
17
17
  sig { returns(T::Array[String]) }
18
18
  attr_reader :dependencies
19
19
 
20
- sig { params(name: String, config: T.nilable(T.any(T::Hash[T.untyped, T.untyped], FalseClass))).void }
21
- def initialize(name:, config:)
20
+ sig { returns(T::Hash[T.untyped, T.untyped]) }
21
+ attr_reader :config
22
+
23
+ sig { params(name: String, config: T.nilable(T::Hash[String, T.untyped])).void }
24
+ def initialize(name:, config: nil)
22
25
  @name = name
23
- @config = T.let(config || {}, T::Hash[T.untyped, T.untyped])
26
+ @config = T.let(config || {}, T::Hash[String, T.untyped])
24
27
  @dependencies = T.let(Array(@config["dependencies"]).freeze, T::Array[String])
25
28
  @public_path = T.let(nil, T.nilable(String))
26
29
  end
27
30
 
28
- sig { returns(T.nilable(T.any(T::Boolean, T::Array[String]))) }
29
- def enforce_privacy
30
- @config["enforce_privacy"]
31
- end
32
-
33
31
  sig { returns(T::Boolean) }
34
32
  def enforce_dependencies?
35
- @config["enforce_dependencies"] == true
33
+ [true, "strict"].include?(@config["enforce_dependencies"])
36
34
  end
37
35
 
38
36
  sig { params(package: Package).returns(T::Boolean) }
@@ -47,32 +45,6 @@ module Packwerk
47
45
  path.start_with?(@name)
48
46
  end
49
47
 
50
- sig { returns(String) }
51
- def public_path
52
- @public_path ||= begin
53
- unprefixed_public_path = user_defined_public_path || "app/public/"
54
-
55
- if root?
56
- unprefixed_public_path
57
- else
58
- File.join(@name, unprefixed_public_path)
59
- end
60
- end
61
- end
62
-
63
- sig { params(path: String).returns(T::Boolean) }
64
- def public_path?(path)
65
- path.start_with?(public_path)
66
- end
67
-
68
- sig { returns(T.nilable(String)) }
69
- def user_defined_public_path
70
- return unless @config["public_path"]
71
- return @config["public_path"] if @config["public_path"].end_with?("/")
72
-
73
- @config["public_path"] + "/"
74
- end
75
-
76
48
  sig { params(other: T.untyped).returns(T.nilable(Integer)) }
77
49
  def <=>(other)
78
50
  return nil unless other.is_a?(self.class)
@@ -26,7 +26,7 @@ module Packwerk
26
26
 
27
27
  packages = package_paths.map do |path|
28
28
  root_relative = path.dirname.relative_path_from(root_path)
29
- Package.new(name: root_relative.to_s, config: YAML.load_file(path))
29
+ Package.new(name: root_relative.to_s, config: YAML.load_file(path, fallback: nil))
30
30
  end
31
31
 
32
32
  create_root_package_if_none_in(packages)
@@ -20,7 +20,7 @@ module Packwerk
20
20
  end
21
21
 
22
22
  sig do
23
- params(reference: Packwerk::Reference, violation_type: ViolationType)
23
+ params(reference: Packwerk::Reference, violation_type: String)
24
24
  .returns(T::Boolean)
25
25
  end
26
26
  def listed?(reference, violation_type:)
@@ -30,16 +30,18 @@ module Packwerk
30
30
  violated_constant_in_file = violated_constants_found.fetch("files", []).include?(reference.relative_path)
31
31
  return false unless violated_constant_in_file
32
32
 
33
- violated_constants_found.fetch("violations", []).include?(violation_type.serialize)
33
+ violated_constants_found.fetch("violations", []).include?(violation_type)
34
34
  end
35
35
 
36
- sig { params(reference: Packwerk::Reference, violation_type: Packwerk::ViolationType).returns(T::Boolean) }
36
+ sig do
37
+ params(reference: Packwerk::Reference, violation_type: String).returns(T::Boolean)
38
+ end
37
39
  def add_entries(reference, violation_type)
38
40
  package_violations = @new_entries.fetch(reference.constant.package.name, {})
39
41
  entries_for_constant = package_violations[reference.constant.name] ||= {}
40
42
 
41
43
  entries_for_constant["violations"] ||= []
42
- entries_for_constant["violations"] << violation_type.serialize
44
+ entries_for_constant["violations"] << violation_type
43
45
 
44
46
  entries_for_constant["files"] ||= []
45
47
  entries_for_constant["files"] << reference.relative_path.to_s
@@ -64,11 +66,17 @@ module Packwerk
64
66
  }
65
67
  end
66
68
 
67
- return true if package_violations_for_files.empty?
69
+ # We `next false` because if we cannot find existing violations for `for_files` within
70
+ # the `package_todo.yml` file, then there are no violations that
71
+ # can be considered stale.
72
+ next false if package_violations_for_files.empty?
68
73
 
69
74
  package_violations_for_files.any? do |constant_name, entries_for_constant|
70
75
  new_entries_violation_types = @new_entries.dig(package, constant_name, "violations")
71
- return true if new_entries_violation_types.nil?
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?
72
80
 
73
81
  if entries_for_constant["violations"].all? { |type| new_entries_violation_types.include?(type) }
74
82
  stale_violations =
@@ -97,9 +105,7 @@ module Packwerk
97
105
  #
98
106
  # bin/packwerk update-todo #{@package.name}
99
107
  MESSAGE
100
- package_todo_filepath = File.join(File.dirname(@filepath), "package_todo.yml")
101
-
102
- File.open(package_todo_filepath, "w") do |f|
108
+ File.open(@filepath, "w") do |f|
103
109
  f.write(message)
104
110
  f.write(@new_entries.to_yaml)
105
111
  end