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.
- 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
|