packwerk 2.3.0 → 3.0.1

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