packwerk 2.3.0 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -5
- data/.ruby-version +1 -1
- data/Gemfile +0 -1
- data/Gemfile.lock +5 -95
- data/README.md +2 -7
- data/RESOLVING_VIOLATIONS.md +3 -8
- data/TROUBLESHOOT.md +2 -25
- data/UPGRADING.md +12 -0
- data/USAGE.md +136 -54
- data/dev.yml +1 -1
- data/exe/packwerk +4 -0
- data/gemfiles/Gemfile-rails-6-1 +1 -1
- data/lib/packwerk/application_validator.rb +54 -285
- data/lib/packwerk/association_inspector.rb +2 -0
- data/lib/packwerk/cache.rb +6 -5
- data/lib/packwerk/checker.rb +54 -0
- data/lib/packwerk/cli/result.rb +11 -0
- data/lib/packwerk/cli.rb +55 -40
- data/lib/packwerk/configuration.rb +61 -40
- data/lib/packwerk/const_node_inspector.rb +2 -0
- data/lib/packwerk/constant_context.rb +8 -0
- data/lib/packwerk/constant_discovery.rb +5 -6
- data/lib/packwerk/constant_name_inspector.rb +2 -0
- data/lib/packwerk/disable_sorbet.rb +41 -0
- data/lib/packwerk/extension_loader.rb +24 -0
- data/lib/packwerk/file_processor.rb +3 -1
- data/lib/packwerk/files_for_processing.rb +25 -12
- data/lib/packwerk/formatters/default_offenses_formatter.rb +77 -0
- data/lib/packwerk/formatters/progress_formatter.rb +31 -12
- data/lib/packwerk/generators/configuration_file.rb +7 -2
- data/lib/packwerk/generators/root_package.rb +5 -1
- data/lib/packwerk/generators/templates/package.yml +0 -10
- data/lib/packwerk/graph.rb +10 -2
- data/lib/packwerk/node.rb +1 -1
- data/lib/packwerk/node_helpers.rb +14 -7
- data/lib/packwerk/node_processor.rb +2 -0
- data/lib/packwerk/node_processor_factory.rb +6 -4
- data/lib/packwerk/node_visitor.rb +10 -1
- data/lib/packwerk/offense_collection.rb +26 -18
- data/lib/packwerk/offenses_formatter.rb +59 -2
- data/lib/packwerk/package.rb +7 -35
- data/lib/packwerk/package_set.rb +1 -1
- data/lib/packwerk/package_todo.rb +19 -20
- data/lib/packwerk/parse_run.rb +27 -34
- data/lib/packwerk/parsed_constant_definitions.rb +28 -5
- data/lib/packwerk/parsers/erb.rb +23 -4
- data/lib/packwerk/parsers/factory.rb +11 -2
- data/lib/packwerk/parsers/parser_interface.rb +1 -1
- data/lib/packwerk/parsers/ruby.rb +13 -3
- data/lib/packwerk/parsers.rb +6 -2
- data/lib/packwerk/{application_load_paths.rb → rails_load_paths.rb} +6 -4
- data/lib/packwerk/reference.rb +7 -1
- data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +29 -6
- data/lib/packwerk/reference_checking/reference_checker.rb +1 -1
- data/lib/packwerk/reference_extractor.rb +24 -12
- data/lib/packwerk/reference_offense.rb +2 -2
- data/lib/packwerk/run_context.rb +7 -10
- data/lib/packwerk/spring_command.rb +9 -2
- data/lib/packwerk/unresolved_reference.rb +9 -1
- data/lib/packwerk/validator/result.rb +18 -0
- data/lib/packwerk/validator.rb +90 -0
- data/lib/packwerk/validators/dependency_validator.rb +154 -0
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +64 -26
- data/packwerk.gemspec +4 -2
- data/sorbet/rbi/gems/{zeitwerk@2.6.0.rbi → zeitwerk@2.6.4.rbi} +291 -228
- data/sorbet/rbi/shims/minitest/test.rb +8 -0
- data/sorbet/rbi/shims/packwerk/reference.rbi +33 -0
- data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +33 -0
- data/sorbet/rbi/shims/parser.rbi +13 -0
- metadata +34 -15
- data/lib/packwerk/formatters/offenses_formatter.rb +0 -52
- data/lib/packwerk/reference_checking/checkers/checker.rb +0 -34
- data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +0 -76
- data/lib/packwerk/result.rb +0 -9
- data/lib/packwerk/sanity_checker.rb +0 -8
- data/lib/packwerk/violation_type.rb +0 -11
- data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +0 -46
- data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +0 -8
data/lib/packwerk/parse_run.rb
CHANGED
@@ -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
|
-
|
27
|
-
offenses_formatter:
|
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
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
49
|
-
|
45
|
+
return Cli::Result.new(message: message, status: false)
|
46
|
+
end
|
50
47
|
|
51
|
-
|
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 =
|
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:
|
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
|
-
|
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:
|
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
|
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 { |
|
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
|
data/lib/packwerk/parsers/erb.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
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:
|
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
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
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
|
data/lib/packwerk/parsers.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
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
|
-
|
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
|
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
|
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"
|
data/lib/packwerk/reference.rb
CHANGED
@@ -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(
|
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
|
-
|
12
|
+
VIOLATION_TYPE = T.let("dependency", String)
|
13
|
+
|
14
|
+
sig { override.returns(String) }
|
13
15
|
def violation_type
|
14
|
-
|
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.
|
24
|
-
return false
|
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: #{
|
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
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
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[
|
55
|
-
root_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 =
|
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(
|
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:
|
19
|
+
violation_type: String,
|
20
20
|
message: String,
|
21
21
|
location: T.nilable(Node::Location)
|
22
22
|
)
|
data/lib/packwerk/run_context.rb
CHANGED
@@ -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[
|
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:
|
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 ||=
|
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
|
-
|
136
|
-
|
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
|