packwerk 2.3.0 → 3.0.1

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 (80) 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/RESOLVING_VIOLATIONS.md +3 -8
  8. data/TROUBLESHOOT.md +2 -25
  9. data/UPGRADING.md +12 -0
  10. data/USAGE.md +136 -54
  11. data/dev.yml +1 -1
  12. data/exe/packwerk +4 -0
  13. data/gemfiles/Gemfile-rails-6-1 +1 -1
  14. data/lib/packwerk/application_validator.rb +54 -285
  15. data/lib/packwerk/association_inspector.rb +2 -0
  16. data/lib/packwerk/cache.rb +6 -5
  17. data/lib/packwerk/checker.rb +54 -0
  18. data/lib/packwerk/cli/result.rb +11 -0
  19. data/lib/packwerk/cli.rb +55 -40
  20. data/lib/packwerk/configuration.rb +61 -40
  21. data/lib/packwerk/const_node_inspector.rb +2 -0
  22. data/lib/packwerk/constant_context.rb +8 -0
  23. data/lib/packwerk/constant_discovery.rb +5 -6
  24. data/lib/packwerk/constant_name_inspector.rb +2 -0
  25. data/lib/packwerk/disable_sorbet.rb +41 -0
  26. data/lib/packwerk/extension_loader.rb +24 -0
  27. data/lib/packwerk/file_processor.rb +3 -1
  28. data/lib/packwerk/files_for_processing.rb +25 -12
  29. data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
  30. data/lib/packwerk/formatters/progress_formatter.rb +31 -12
  31. data/lib/packwerk/generators/configuration_file.rb +7 -2
  32. data/lib/packwerk/generators/root_package.rb +5 -1
  33. data/lib/packwerk/generators/templates/package.yml +0 -10
  34. data/lib/packwerk/graph.rb +10 -2
  35. data/lib/packwerk/node.rb +1 -1
  36. data/lib/packwerk/node_helpers.rb +14 -7
  37. data/lib/packwerk/node_processor.rb +2 -0
  38. data/lib/packwerk/node_processor_factory.rb +6 -4
  39. data/lib/packwerk/node_visitor.rb +10 -1
  40. data/lib/packwerk/offense_collection.rb +26 -18
  41. data/lib/packwerk/offenses_formatter.rb +59 -2
  42. data/lib/packwerk/package.rb +7 -35
  43. data/lib/packwerk/package_set.rb +1 -1
  44. data/lib/packwerk/package_todo.rb +19 -20
  45. data/lib/packwerk/parse_run.rb +27 -34
  46. data/lib/packwerk/parsed_constant_definitions.rb +28 -5
  47. data/lib/packwerk/parsers/erb.rb +23 -4
  48. data/lib/packwerk/parsers/factory.rb +11 -2
  49. data/lib/packwerk/parsers/parser_interface.rb +1 -1
  50. data/lib/packwerk/parsers/ruby.rb +13 -3
  51. data/lib/packwerk/parsers.rb +6 -2
  52. data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
  53. data/lib/packwerk/reference.rb +7 -1
  54. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
  55. data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
  56. data/lib/packwerk/reference_extractor.rb +24 -12
  57. data/lib/packwerk/reference_offense.rb +2 -2
  58. data/lib/packwerk/run_context.rb +7 -10
  59. data/lib/packwerk/spring_command.rb +9 -2
  60. data/lib/packwerk/unresolved_reference.rb +9 -1
  61. data/lib/packwerk/validator/result.rb +18 -0
  62. data/lib/packwerk/validator.rb +90 -0
  63. data/lib/packwerk/validators/dependency_validator.rb +154 -0
  64. data/lib/packwerk/version.rb +1 -1
  65. data/lib/packwerk.rb +64 -26
  66. data/packwerk.gemspec +4 -2
  67. data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
  68. data/sorbet/rbi/shims/minitest/test.rb +8 -0
  69. data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
  70. data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
  71. data/sorbet/rbi/shims/parser.rbi +13 -0
  72. metadata +34 -15
  73. data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
  74. data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
  75. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
  76. data/lib/packwerk/result.rb +0 -9
  77. data/lib/packwerk/sanity_checker.rb +0 -8
  78. data/lib/packwerk/violation_type.rb +0 -11
  79. data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
  80. data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +0 -8
@@ -1,7 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "benchmark"
5
4
  require "parallel"
6
5
 
7
6
  module Packwerk
@@ -16,41 +15,37 @@ module Packwerk
16
15
  params(
17
16
  relative_file_set: FilesForProcessing::RelativeFileSet,
18
17
  configuration: Configuration,
18
+ file_set_specified: T::Boolean,
19
+ offenses_formatter: T.nilable(OffensesFormatter),
19
20
  progress_formatter: Formatters::ProgressFormatter,
20
- offenses_formatter: OffensesFormatter,
21
21
  ).void
22
22
  end
23
23
  def initialize(
24
24
  relative_file_set:,
25
25
  configuration:,
26
- progress_formatter: Formatters::ProgressFormatter.new(StringIO.new),
27
- offenses_formatter: Formatters::OffensesFormatter.new
26
+ file_set_specified: false,
27
+ offenses_formatter: nil,
28
+ progress_formatter: Formatters::ProgressFormatter.new(StringIO.new)
28
29
  )
30
+
29
31
  @configuration = configuration
30
32
  @progress_formatter = progress_formatter
31
- @offenses_formatter = offenses_formatter
33
+ @offenses_formatter = T.let(offenses_formatter || configuration.offenses_formatter, Packwerk::OffensesFormatter)
32
34
  @relative_file_set = relative_file_set
35
+ @file_set_specified = file_set_specified
33
36
  end
34
37
 
35
- sig { returns(Result) }
36
- def detect_stale_violations
37
- warn(<<~WARNING.squish)
38
- DEPRECATION WARNING: `detect-stale-violation` is deprecated,
39
- the output of `check` includes stale references.
40
- WARNING
41
-
42
- run_context = Packwerk::RunContext.from_configuration(@configuration)
43
- offense_collection = find_offenses(run_context)
44
-
45
- result_status = !offense_collection.stale_violations?(@relative_file_set)
46
- message = @offenses_formatter.show_stale_violations(offense_collection, @relative_file_set)
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
47
44
 
48
- Result.new(message: message, status: result_status)
49
- end
45
+ return Cli::Result.new(message: message, status: false)
46
+ end
50
47
 
51
- sig { returns(Result) }
52
- def update_todo
53
- run_context = Packwerk::RunContext.from_configuration(@configuration)
48
+ run_context = RunContext.from_configuration(@configuration)
54
49
  offense_collection = find_offenses(run_context)
55
50
  offense_collection.persist_package_todo_files(run_context.package_set)
56
51
 
@@ -59,34 +54,32 @@ module Packwerk
59
54
  ✅ `package_todo.yml` has been updated.
60
55
  EOS
61
56
 
62
- Result.new(message: message, status: offense_collection.errors.empty?)
57
+ Cli::Result.new(message: message, status: offense_collection.errors.empty?)
63
58
  end
64
59
 
65
- sig { returns(Result) }
60
+ sig { returns(Cli::Result) }
66
61
  def check
67
- run_context = Packwerk::RunContext.from_configuration(@configuration)
62
+ run_context = RunContext.from_configuration(@configuration)
68
63
  offense_collection = find_offenses(run_context, show_errors: true)
69
64
 
70
65
  messages = [
71
66
  @offenses_formatter.show_offenses(offense_collection.outstanding_offenses),
72
67
  @offenses_formatter.show_stale_violations(offense_collection, @relative_file_set),
68
+ @offenses_formatter.show_strict_mode_violations(offense_collection.strict_mode_violations),
73
69
  ]
74
70
 
75
71
  result_status = offense_collection.outstanding_offenses.empty? &&
76
- !offense_collection.stale_violations?(@relative_file_set)
72
+ !offense_collection.stale_violations?(@relative_file_set) && offense_collection.strict_mode_violations.empty?
77
73
 
78
- Result.new(message: messages.join("\n") + "\n", status: result_status)
74
+ Cli::Result.new(message: messages.select(&:present?).join("\n") + "\n", status: result_status)
79
75
  end
80
76
 
81
77
  private
82
78
 
83
- sig { params(run_context: Packwerk::RunContext, show_errors: T::Boolean).returns(OffenseCollection) }
79
+ sig { params(run_context: RunContext, show_errors: T::Boolean).returns(OffenseCollection) }
84
80
  def find_offenses(run_context, show_errors: false)
85
81
  offense_collection = OffenseCollection.new(@configuration.root_path)
86
- @progress_formatter.started(@relative_file_set)
87
-
88
82
  all_offenses = T.let([], T::Array[Offense])
89
-
90
83
  process_file = T.let(->(relative_file) do
91
84
  run_context.process_file(relative_file: relative_file).tap do |offenses|
92
85
  failed = show_errors && offenses.any? { |offense| !offense_collection.listed?(offense) }
@@ -94,7 +87,7 @@ module Packwerk
94
87
  end
95
88
  end, ProcessFileProc)
96
89
 
97
- execution_time = Benchmark.realtime do
90
+ @progress_formatter.started_inspection(@relative_file_set) do
98
91
  all_offenses = if @configuration.parallel?
99
92
  Parallel.flat_map(@relative_file_set, &process_file)
100
93
  else
@@ -102,8 +95,6 @@ module Packwerk
102
95
  end
103
96
  end
104
97
 
105
- @progress_formatter.finished(execution_time)
106
-
107
98
  all_offenses.each { |offense| offense_collection.add_offense(offense) }
108
99
  offense_collection
109
100
  end
@@ -132,4 +123,6 @@ module Packwerk
132
123
  end
133
124
  end
134
125
  end
126
+
127
+ private_constant :ParseRun
135
128
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "ast/node"
@@ -6,27 +6,40 @@ require "ast/node"
6
6
  module Packwerk
7
7
  # A collection of constant definitions parsed from an Abstract Syntax Tree (AST).
8
8
  class ParsedConstantDefinitions
9
+ extend T::Sig
10
+
9
11
  class << self
12
+ extend T::Sig
13
+
10
14
  # What fully qualified constants can this constant refer to in this context?
15
+ sig { params(constant_name: String, namespace_path: T::Array[T.nilable(String)]).returns(T::Array[String]) }
11
16
  def reference_qualifications(constant_name, namespace_path:)
12
17
  return [constant_name] if constant_name.start_with?("::")
13
18
 
14
19
  resolved_constant_name = "::#{constant_name}"
15
20
 
16
21
  possible_namespaces = namespace_path.each_with_object([""]) do |current, acc|
17
- acc << "#{acc.last}::#{current}" if acc.last && current
22
+ acc << "#{acc.last}::#{current}" if current
18
23
  end
19
24
 
20
25
  possible_namespaces.map { |namespace| namespace + resolved_constant_name }
21
26
  end
22
27
  end
23
28
 
29
+ sig { params(root_node: T.nilable(AST::Node)).void }
24
30
  def initialize(root_node:)
25
- @local_definitions = {}
31
+ @local_definitions = T.let({}, T::Hash[String, T.nilable(Node::Location)])
26
32
 
27
33
  collect_local_definitions_from_root(root_node) if root_node
28
34
  end
29
35
 
36
+ sig do
37
+ params(
38
+ constant_name: String,
39
+ location: T.nilable(Node::Location),
40
+ namespace_path: T::Array[String],
41
+ ).returns(T::Boolean)
42
+ end
30
43
  def local_reference?(constant_name, location: nil, namespace_path: [])
31
44
  qualifications = self.class.reference_qualifications(constant_name, namespace_path: namespace_path)
32
45
 
@@ -38,13 +51,14 @@ module Packwerk
38
51
 
39
52
  private
40
53
 
54
+ sig { params(node: AST::Node, current_namespace_path: T::Array[T.nilable(String)]).void }
41
55
  def collect_local_definitions_from_root(node, current_namespace_path = [])
42
56
  if NodeHelpers.constant_assignment?(node)
43
57
  add_definition(NodeHelpers.constant_name(node), current_namespace_path, NodeHelpers.name_location(node))
44
58
  elsif NodeHelpers.module_name_from_definition(node)
45
59
  # handle compact constant nesting (e.g. "module Sales::Order")
46
- tempnode = node
47
- while (tempnode = NodeHelpers.each_child(tempnode).find { |n| NodeHelpers.constant?(n) })
60
+ tempnode = T.let(node, T.nilable(AST::Node))
61
+ while (tempnode = NodeHelpers.each_child(T.must(tempnode)).find { |node| NodeHelpers.constant?(node) })
48
62
  add_definition(NodeHelpers.constant_name(tempnode), current_namespace_path,
49
63
  NodeHelpers.name_location(tempnode))
50
64
  end
@@ -55,10 +69,19 @@ module Packwerk
55
69
  NodeHelpers.each_child(node) { |child| collect_local_definitions_from_root(child, current_namespace_path) }
56
70
  end
57
71
 
72
+ sig do
73
+ params(
74
+ constant_name: String,
75
+ current_namespace_path: T::Array[T.nilable(String)],
76
+ location: T.nilable(Node::Location),
77
+ ).void
78
+ end
58
79
  def add_definition(constant_name, current_namespace_path, location)
59
80
  resolved_constant = [""].concat(current_namespace_path).push(constant_name).join("::")
60
81
 
61
82
  @local_definitions[resolved_constant] = location
62
83
  end
63
84
  end
85
+
86
+ private_constant :ParsedConstantDefinitions
64
87
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "ast/node"
@@ -9,19 +9,24 @@ require "parser/source/buffer"
9
9
  module Packwerk
10
10
  module Parsers
11
11
  class Erb
12
+ extend T::Sig
13
+
12
14
  include ParserInterface
13
15
 
16
+ sig { params(parser_class: T.untyped, ruby_parser: Ruby).void }
14
17
  def initialize(parser_class: BetterHtml::Parser, ruby_parser: Ruby.new)
15
- @parser_class = parser_class
18
+ @parser_class = T.let(parser_class, T.class_of(BetterHtml::Parser))
16
19
  @ruby_parser = ruby_parser
17
20
  end
18
21
 
22
+ sig { override.params(io: T.any(IO, StringIO), file_path: String).returns(T.untyped) }
19
23
  def call(io:, file_path: "<unknown>")
20
24
  buffer = Parser::Source::Buffer.new(file_path)
21
25
  buffer.source = io.read
22
26
  parse_buffer(buffer, file_path: file_path)
23
27
  end
24
28
 
29
+ sig { params(buffer: Parser::Source::Buffer, file_path: String).returns(T.nilable(AST::Node)) }
25
30
  def parse_buffer(buffer, file_path:)
26
31
  parser = @parser_class.new(buffer, template_language: :html)
27
32
  to_ruby_ast(parser.ast, file_path)
@@ -35,12 +40,18 @@ module Packwerk
35
40
 
36
41
  private
37
42
 
43
+ sig do
44
+ params(
45
+ erb_ast: T.all(::AST::Node, Object),
46
+ file_path: String
47
+ ).returns(T.nilable(::AST::Node))
48
+ end
38
49
  def to_ruby_ast(erb_ast, file_path)
39
50
  # Note that we're not using the source location (line/column) at the moment, but if we did
40
51
  # care about that, we'd need to tweak this to insert empty lines and spaces so that things
41
52
  # line up with the ERB file
42
- code_pieces = code_nodes(erb_ast).map do |node|
43
- node.children.first
53
+ code_pieces = T.must(code_nodes(erb_ast)).map do |node|
54
+ T.cast(node, ::AST::Node).children.first
44
55
  end
45
56
 
46
57
  @ruby_parser.call(
@@ -49,6 +60,14 @@ module Packwerk
49
60
  )
50
61
  end
51
62
 
63
+ sig do
64
+ params(
65
+ node: T.any(::AST::Node, String, NilClass),
66
+ block: T.nilable(T.proc.params(arg0: ::AST::Node).void),
67
+ ).returns(
68
+ T.any(T::Enumerator[::AST::Node], T::Array[String], NilClass)
69
+ )
70
+ end
52
71
  def code_nodes(node, &block)
53
72
  return enum_for(:code_nodes, node) unless block
54
73
  return unless node.is_a?(::AST::Node)
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "singleton"
@@ -20,20 +20,29 @@ module Packwerk
20
20
  ERB_REGEX = /\.erb\Z/
21
21
  private_constant :ERB_REGEX
22
22
 
23
+ sig { void }
24
+ def initialize
25
+ @ruby_parser = T.let(nil, T.nilable(ParserInterface))
26
+ @erb_parser = T.let(nil, T.nilable(ParserInterface))
27
+ @erb_parser_class = T.let(nil, T.nilable(Class))
28
+ end
29
+
23
30
  sig { params(path: String).returns(T.nilable(ParserInterface)) }
24
31
  def for_path(path)
25
32
  case path
26
33
  when RUBY_REGEX
27
34
  @ruby_parser ||= Ruby.new
28
35
  when ERB_REGEX
29
- @erb_parser ||= erb_parser_class.new
36
+ @erb_parser ||= T.unsafe(erb_parser_class).new
30
37
  end
31
38
  end
32
39
 
40
+ sig { returns(Class) }
33
41
  def erb_parser_class
34
42
  @erb_parser_class ||= Erb
35
43
  end
36
44
 
45
+ sig { params(klass: T.nilable(Class)).void }
37
46
  def erb_parser_class=(klass)
38
47
  @erb_parser_class = klass
39
48
  @erb_parser = nil
@@ -11,7 +11,7 @@ module Packwerk
11
11
 
12
12
  interface!
13
13
 
14
- sig { abstract.params(io: File, file_path: String).returns(T.untyped) }
14
+ sig { abstract.params(io: T.any(IO, StringIO), file_path: String).returns(T.untyped) }
15
15
  def call(io:, file_path:)
16
16
  end
17
17
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "parser"
@@ -7,9 +7,14 @@ require "parser/current"
7
7
  module Packwerk
8
8
  module Parsers
9
9
  class Ruby
10
+ extend T::Sig
11
+
10
12
  include ParserInterface
11
13
 
12
14
  class RaiseExceptionsParser < Parser::CurrentRuby
15
+ extend T::Sig
16
+
17
+ sig { params(builder: T.untyped).void }
13
18
  def initialize(builder)
14
19
  super(builder)
15
20
  super.diagnostics.all_errors_are_fatal = true
@@ -17,16 +22,21 @@ module Packwerk
17
22
  end
18
23
 
19
24
  class TolerateInvalidUtf8Builder < Parser::Builders::Default
25
+ extend T::Sig
26
+
27
+ sig { params(token: T.untyped).returns(T.untyped) }
20
28
  def string_value(token)
21
29
  value(token)
22
30
  end
23
31
  end
24
32
 
33
+ sig { params(parser_class: T.untyped).void }
25
34
  def initialize(parser_class: RaiseExceptionsParser)
26
- @builder = TolerateInvalidUtf8Builder.new
27
- @parser_class = parser_class
35
+ @builder = T.let(TolerateInvalidUtf8Builder.new, Object)
36
+ @parser_class = T.let(parser_class, T.class_of(RaiseExceptionsParser))
28
37
  end
29
38
 
39
+ sig { override.params(io: T.any(IO, StringIO), file_path: String).returns(T.nilable(Parser::AST::Node)) }
30
40
  def call(io:, file_path: "<unknown>")
31
41
  buffer = Parser::Source::Buffer.new(file_path)
32
42
  buffer.source = io.read
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
@@ -11,8 +11,12 @@ module Packwerk
11
11
  class ParseResult < Offense; end
12
12
 
13
13
  class ParseError < StandardError
14
- attr_reader :result
14
+ extend T::Sig
15
15
 
16
+ sig { returns(ParseResult) }
17
+ attr_reader(:result)
18
+
19
+ sig { params(result: ParseResult).void }
16
20
  def initialize(result)
17
21
  super(result.message)
18
22
  @result = result
@@ -2,15 +2,17 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "bundler"
5
+ gem "railties", ">= 6.0"
6
+ require "rails/railtie"
5
7
 
6
8
  module Packwerk
7
9
  # Extracts the load paths from the analyzed application so that we can map constant names to paths.
8
- module ApplicationLoadPaths
10
+ module RailsLoadPaths
9
11
  class << self
10
12
  extend T::Sig
11
13
 
12
14
  sig { params(root: String, environment: String).returns(T::Hash[String, Module]) }
13
- def extract_relevant_paths(root, environment)
15
+ def for(root, environment:)
14
16
  require_application(root, environment)
15
17
  all_paths = extract_application_autoload_paths
16
18
  relevant_paths = filter_relevant_paths(all_paths)
@@ -18,6 +20,8 @@ module Packwerk
18
20
  relative_path_strings(relevant_paths)
19
21
  end
20
22
 
23
+ private
24
+
21
25
  sig { returns(T::Hash[String, Module]) }
22
26
  def extract_application_autoload_paths
23
27
  Rails.autoloaders.inject({}) do |h, loader|
@@ -44,8 +48,6 @@ module Packwerk
44
48
  load_paths.transform_keys { |path| Pathname.new(path).relative_path_from(rails_root).to_s }
45
49
  end
46
50
 
47
- private
48
-
49
51
  sig { params(root: String, environment: String).void }
50
52
  def require_application(root, environment)
51
53
  environment_file = "#{root}/config/environment"
@@ -3,5 +3,11 @@
3
3
 
4
4
  module Packwerk
5
5
  # A reference from a file in one package to a constant that may be defined in a different package.
6
- Reference = Struct.new(:source_package, :relative_path, :constant, :source_location)
6
+ Reference = Struct.new(
7
+ :package,
8
+ :relative_path,
9
+ :constant,
10
+ :source_location,
11
+ keyword_init: true,
12
+ )
7
13
  end
@@ -9,9 +9,11 @@ module Packwerk
9
9
  extend T::Sig
10
10
  include Checker
11
11
 
12
- sig { override.returns(ViolationType) }
12
+ VIOLATION_TYPE = T.let("dependency", String)
13
+
14
+ sig { override.returns(String) }
13
15
  def violation_type
14
- ViolationType::Dependency
16
+ VIOLATION_TYPE
15
17
  end
16
18
 
17
19
  sig do
@@ -20,9 +22,8 @@ module Packwerk
20
22
  .returns(T::Boolean)
21
23
  end
22
24
  def invalid_reference?(reference)
23
- return false unless reference.source_package
24
- return false unless reference.source_package.enforce_dependencies?
25
- return false if reference.source_package.dependency?(reference.constant.package)
25
+ return false unless reference.package.enforce_dependencies?
26
+ return false if reference.package.dependency?(reference.constant.package)
26
27
 
27
28
  true
28
29
  end
@@ -33,14 +34,36 @@ module Packwerk
33
34
  .returns(String)
34
35
  end
35
36
  def message(reference)
37
+ const_name = reference.constant.name
38
+ const_package = reference.constant.package
39
+ ref_package = reference.package
40
+
36
41
  <<~EOS
37
- Dependency violation: #{reference.constant.name} belongs to '#{reference.constant.package}', but '#{reference.source_package}' does not specify a dependency on '#{reference.constant.package}'.
42
+ Dependency violation: #{const_name} belongs to '#{const_package}', but '#{ref_package}' does not specify a dependency on '#{const_package}'.
38
43
  Are we missing an abstraction?
39
44
  Is the code making the reference, and the referenced constant, in the right packages?
40
45
 
41
46
  #{standard_help_message(reference)}
42
47
  EOS
43
48
  end
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
53
+ referencing_package.config["enforce_dependencies"] == "strict"
54
+ end
55
+
56
+ private
57
+
58
+ sig { params(reference: Reference).returns(String) }
59
+ def standard_help_message(reference)
60
+ standard_message = <<~EOS
61
+ Inference details: this is a reference to #{reference.constant.name} which seems to be defined in #{reference.constant.location}.
62
+ To receive help interpreting or resolving this error message, see: https://github.com/Shopify/packwerk/blob/main/TROUBLESHOOT.md#Troubleshooting-violations
63
+ EOS
64
+
65
+ standard_message.chomp
66
+ end
44
67
  end
45
68
  end
46
69
  end
@@ -6,7 +6,7 @@ module Packwerk
6
6
  class ReferenceChecker
7
7
  extend T::Sig
8
8
 
9
- sig { params(checkers: T::Array[Checkers::Checker]).void }
9
+ sig { params(checkers: T::Array[Checker]).void }
10
10
  def initialize(checkers)
11
11
  @checkers = checkers
12
12
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
@@ -38,10 +38,10 @@ module Packwerk
38
38
  next if source_package == package_for_constant
39
39
 
40
40
  fully_qualified_references << Reference.new(
41
- source_package,
42
- unresolved_reference.relative_path,
43
- constant,
44
- unresolved_reference.source_location
41
+ package: source_package,
42
+ relative_path: unresolved_reference.relative_path,
43
+ constant: constant,
44
+ source_location: unresolved_reference.source_location,
45
45
  )
46
46
  end
47
47
 
@@ -51,8 +51,8 @@ module Packwerk
51
51
 
52
52
  sig do
53
53
  params(
54
- constant_name_inspectors: T::Array[Packwerk::ConstantNameInspector],
55
- root_node: ::AST::Node,
54
+ constant_name_inspectors: T::Array[ConstantNameInspector],
55
+ root_node: AST::Node,
56
56
  root_path: String,
57
57
  ).void
58
58
  end
@@ -63,7 +63,10 @@ module Packwerk
63
63
  )
64
64
  @constant_name_inspectors = constant_name_inspectors
65
65
  @root_path = root_path
66
- @local_constant_definitions = ParsedConstantDefinitions.new(root_node: root_node)
66
+ @local_constant_definitions = T.let(
67
+ ParsedConstantDefinitions.new(root_node: root_node),
68
+ ParsedConstantDefinitions,
69
+ )
67
70
  end
68
71
 
69
72
  sig do
@@ -110,13 +113,20 @@ module Packwerk
110
113
  location = NodeHelpers.location(node)
111
114
 
112
115
  UnresolvedReference.new(
113
- constant_name,
114
- namespace_path,
115
- relative_file,
116
- location
116
+ constant_name: constant_name,
117
+ namespace_path: namespace_path,
118
+ relative_path: relative_file,
119
+ source_location: location
117
120
  )
118
121
  end
119
122
 
123
+ sig do
124
+ params(
125
+ constant_name: String,
126
+ name_location: T.nilable(Node::Location),
127
+ namespace_path: T::Array[String],
128
+ ).returns(T::Boolean)
129
+ end
120
130
  def local_reference?(constant_name, name_location, namespace_path)
121
131
  @local_constant_definitions.local_reference?(
122
132
  constant_name,
@@ -125,4 +135,6 @@ module Packwerk
125
135
  )
126
136
  end
127
137
  end
138
+
139
+ private_constant :ReferenceExtractor
128
140
  end
@@ -10,13 +10,13 @@ module Packwerk
10
10
  sig { returns(Reference) }
11
11
  attr_reader :reference
12
12
 
13
- sig { returns(ViolationType) }
13
+ sig { returns(String) }
14
14
  attr_reader :violation_type
15
15
 
16
16
  sig do
17
17
  params(
18
18
  reference: Packwerk::Reference,
19
- violation_type: Packwerk::ViolationType,
19
+ violation_type: String,
20
20
  message: String,
21
21
  location: T.nilable(Node::Location)
22
22
  )
@@ -8,11 +8,6 @@ module Packwerk
8
8
  class RunContext
9
9
  extend T::Sig
10
10
 
11
- DEFAULT_CHECKERS = T.let([
12
- ::Packwerk::ReferenceChecking::Checkers::DependencyChecker.new,
13
- ::Packwerk::ReferenceChecking::Checkers::PrivacyChecker.new,
14
- ], T::Array[ReferenceChecking::Checkers::Checker])
15
-
16
11
  class << self
17
12
  extend T::Sig
18
13
 
@@ -44,7 +39,7 @@ module Packwerk
44
39
  config_path: T.nilable(String),
45
40
  package_paths: T.nilable(T.any(T::Array[String], String)),
46
41
  custom_associations: AssociationInspector::CustomAssociations,
47
- checkers: T::Array[ReferenceChecking::Checkers::Checker],
42
+ checkers: T::Array[Checker],
48
43
  cache_enabled: T::Boolean,
49
44
  ).void
50
45
  end
@@ -56,7 +51,7 @@ module Packwerk
56
51
  config_path: nil,
57
52
  package_paths: nil,
58
53
  custom_associations: [],
59
- checkers: DEFAULT_CHECKERS,
54
+ checkers: Checker.all,
60
55
  cache_enabled: false
61
56
  )
62
57
  @root_path = root_path
@@ -114,7 +109,7 @@ module Packwerk
114
109
 
115
110
  sig { returns(ConstantDiscovery) }
116
111
  def context_provider
117
- @context_provider ||= ::Packwerk::ConstantDiscovery.new(
112
+ @context_provider ||= ConstantDiscovery.new(
118
113
  constant_resolver: resolver,
119
114
  packages: package_set
120
115
  )
@@ -132,9 +127,11 @@ module Packwerk
132
127
  sig { returns(T::Array[ConstantNameInspector]) }
133
128
  def constant_name_inspectors
134
129
  [
135
- ::Packwerk::ConstNodeInspector.new,
136
- ::Packwerk::AssociationInspector.new(inflector: @inflector, custom_associations: @custom_associations),
130
+ ConstNodeInspector.new,
131
+ AssociationInspector.new(inflector: @inflector, custom_associations: @custom_associations),
137
132
  ]
138
133
  end
139
134
  end
135
+
136
+ private_constant :RunContext
140
137
  end