packwerk 3.2.3 → 3.3.0
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/exe/packwerk +0 -4
- data/lib/packwerk/application_validator.rb +11 -10
- data/lib/packwerk/association_inspector.rb +21 -32
- data/lib/packwerk/cache.rb +43 -36
- data/lib/packwerk/checker.rb +24 -26
- data/lib/packwerk/cli.rb +13 -20
- data/lib/packwerk/commands/base_command.rb +20 -26
- data/lib/packwerk/commands/check_command.rb +8 -8
- data/lib/packwerk/commands/help_command.rb +3 -4
- data/lib/packwerk/commands/init_command.rb +2 -3
- data/lib/packwerk/commands/lazy_loaded_entry.rb +5 -7
- data/lib/packwerk/commands/update_todo_command.rb +3 -3
- data/lib/packwerk/commands/uses_parse_run.rb +20 -26
- data/lib/packwerk/commands/validate_command.rb +6 -7
- data/lib/packwerk/commands/version_command.rb +2 -3
- data/lib/packwerk/commands.rb +5 -8
- data/lib/packwerk/configuration.rb +29 -43
- data/lib/packwerk/const_node_inspector.rb +5 -9
- data/lib/packwerk/constant_context.rb +0 -2
- data/lib/packwerk/constant_discovery.rb +3 -16
- data/lib/packwerk/constant_name_inspector.rb +4 -11
- data/lib/packwerk/extension_loader.rb +1 -2
- data/lib/packwerk/file_processor.rb +20 -25
- data/lib/packwerk/files_for_processing.rb +13 -29
- data/lib/packwerk/formatters/default_offenses_formatter.rb +14 -12
- data/lib/packwerk/formatters/progress_formatter.rb +10 -12
- data/lib/packwerk/generators/configuration_file.rb +5 -9
- data/lib/packwerk/generators/root_package.rb +3 -7
- data/lib/packwerk/graph.rb +1 -7
- data/lib/packwerk/node_helpers.rb +32 -38
- data/lib/packwerk/node_processor.rb +2 -14
- data/lib/packwerk/node_processor_factory.rb +13 -7
- data/lib/packwerk/node_visitor.rb +2 -10
- data/lib/packwerk/offense.rb +6 -12
- data/lib/packwerk/offense_collection.rb +22 -35
- data/lib/packwerk/offenses_formatter.rb +27 -34
- data/lib/packwerk/output_style.rb +10 -11
- data/lib/packwerk/output_styles/coloured.rb +6 -4
- data/lib/packwerk/output_styles/plain.rb +6 -4
- data/lib/packwerk/package.rb +15 -16
- data/lib/packwerk/package_set.rb +15 -25
- data/lib/packwerk/package_todo.rb +21 -30
- data/lib/packwerk/parse_run.rb +10 -37
- data/lib/packwerk/parsed_constant_definitions.rb +10 -24
- data/lib/packwerk/parsers/erb.rb +11 -22
- data/lib/packwerk/parsers/factory.rb +9 -9
- data/lib/packwerk/parsers/parser_interface.rb +5 -10
- data/lib/packwerk/parsers/ruby.rb +9 -14
- data/lib/packwerk/parsers.rb +2 -4
- data/lib/packwerk/rails_load_paths.rb +6 -11
- data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +10 -15
- data/lib/packwerk/reference_checking/reference_checker.rb +2 -8
- data/lib/packwerk/reference_extractor.rb +22 -55
- data/lib/packwerk/reference_offense.rb +5 -15
- data/lib/packwerk/run_context.rb +28 -39
- data/lib/packwerk/spring_command.rb +4 -7
- data/lib/packwerk/validator/result.rb +12 -5
- data/lib/packwerk/validator.rb +23 -33
- data/lib/packwerk/validators/dependency_validator.rb +9 -10
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +0 -1
- data/sorbet/config +2 -0
- data/sorbet/rbi/gems/prism@1.9.0.rbi +43359 -0
- data/sorbet/rbi/shims/packwerk/reference.rbi +5 -12
- data/sorbet/rbi/shims/packwerk/unresolved_reference.rbi +5 -12
- data/sorbet/rbi/shims/parser.rbi +1 -1
- metadata +18 -19
- data/lib/packwerk/disable_sorbet.rb +0 -41
- data/sorbet/rbi/gems/prism@0.27.0.rbi +0 -36983
|
@@ -5,28 +5,21 @@ require "yaml"
|
|
|
5
5
|
|
|
6
6
|
module Packwerk
|
|
7
7
|
class PackageTodo
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Entry = T.type_alias { T::Hash[ConstantName, T::Hash[ConstantName, T::Array[FilePath]]] }
|
|
14
|
-
Entries = T.type_alias do
|
|
15
|
-
T::Hash[PackageName, Entry]
|
|
16
|
-
end
|
|
8
|
+
#: type package_name = String
|
|
9
|
+
#: type constant_name = String
|
|
10
|
+
#: type file_path = String
|
|
11
|
+
#: type entry = Hash[constant_name, Hash[constant_name, Array[file_path]]]
|
|
12
|
+
#: type entries = Hash[package_name, entry]
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
#: (Packwerk::Package package, String path) -> void
|
|
19
15
|
def initialize(package, path)
|
|
20
16
|
@package = package
|
|
21
17
|
@path = path
|
|
22
|
-
@new_entries =
|
|
23
|
-
@old_entries =
|
|
18
|
+
@new_entries = {} #: entries
|
|
19
|
+
@old_entries = nil #: entries?
|
|
24
20
|
end
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
params(reference: Packwerk::Reference, violation_type: String)
|
|
28
|
-
.returns(T::Boolean)
|
|
29
|
-
end
|
|
22
|
+
#: (Packwerk::Reference reference, violation_type: String) -> bool
|
|
30
23
|
def listed?(reference, violation_type:)
|
|
31
24
|
violated_constants_found = old_entries.dig(reference.constant.package.name, reference.constant.name)
|
|
32
25
|
return false unless violated_constants_found
|
|
@@ -37,9 +30,7 @@ module Packwerk
|
|
|
37
30
|
violated_constants_found.fetch("violations", []).include?(violation_type)
|
|
38
31
|
end
|
|
39
32
|
|
|
40
|
-
|
|
41
|
-
params(reference: Packwerk::Reference, violation_type: String).returns(T::Boolean)
|
|
42
|
-
end
|
|
33
|
+
#: (Packwerk::Reference reference, String violation_type) -> bool
|
|
43
34
|
def add_entries(reference, violation_type)
|
|
44
35
|
package_violations = new_entries.fetch(reference.constant.package.name, {})
|
|
45
36
|
entries_for_constant = package_violations[reference.constant.name] ||= {}
|
|
@@ -54,7 +45,7 @@ module Packwerk
|
|
|
54
45
|
listed?(reference, violation_type: violation_type)
|
|
55
46
|
end
|
|
56
47
|
|
|
57
|
-
|
|
48
|
+
#: (Set[String] for_files) -> bool
|
|
58
49
|
def stale_violations?(for_files)
|
|
59
50
|
prepare_entries_for_dump
|
|
60
51
|
|
|
@@ -71,7 +62,7 @@ module Packwerk
|
|
|
71
62
|
end
|
|
72
63
|
end
|
|
73
64
|
|
|
74
|
-
|
|
65
|
+
#: -> void
|
|
75
66
|
def dump
|
|
76
67
|
if new_entries.empty?
|
|
77
68
|
delete_if_exists
|
|
@@ -93,24 +84,24 @@ module Packwerk
|
|
|
93
84
|
end
|
|
94
85
|
end
|
|
95
86
|
|
|
96
|
-
|
|
87
|
+
#: -> void
|
|
97
88
|
def delete_if_exists
|
|
98
89
|
File.delete(@path) if File.exist?(@path)
|
|
99
90
|
end
|
|
100
91
|
|
|
101
92
|
private
|
|
102
93
|
|
|
103
|
-
|
|
94
|
+
#: entries
|
|
104
95
|
attr_reader(:new_entries)
|
|
105
96
|
|
|
106
|
-
|
|
97
|
+
#: (String package) -> Array[String]
|
|
107
98
|
def deleted_files_for(package)
|
|
108
99
|
old_files = old_entries.fetch(package, {}).values.flat_map { |violation| violation.fetch("files") }
|
|
109
100
|
new_files = new_entries.fetch(package, {}).values.flat_map { |violation| violation.fetch("files") }
|
|
110
101
|
old_files - new_files
|
|
111
102
|
end
|
|
112
103
|
|
|
113
|
-
|
|
104
|
+
#: (String package, violations: entry) -> bool
|
|
114
105
|
def stale_violation_for_package?(package, violations:)
|
|
115
106
|
violations.any? do |constant_name, entries_for_constant|
|
|
116
107
|
new_entries_violation_types = new_entries.dig(package, constant_name, "violations")
|
|
@@ -129,10 +120,10 @@ module Packwerk
|
|
|
129
120
|
end
|
|
130
121
|
end
|
|
131
122
|
|
|
132
|
-
|
|
123
|
+
#: (entry package_violations, files: Set[String]) -> entry
|
|
133
124
|
def package_violations_for(package_violations, files:)
|
|
134
125
|
{}.tap do |package_violations_for_files|
|
|
135
|
-
package_violations_for_files =
|
|
126
|
+
package_violations_for_files = package_violations_for_files #: as entry
|
|
136
127
|
|
|
137
128
|
package_violations.each do |constant_name, entries_for_constant|
|
|
138
129
|
entries_for_files = files & entries_for_constant.fetch("files")
|
|
@@ -146,7 +137,7 @@ module Packwerk
|
|
|
146
137
|
end
|
|
147
138
|
end
|
|
148
139
|
|
|
149
|
-
|
|
140
|
+
#: -> entries
|
|
150
141
|
def prepare_entries_for_dump
|
|
151
142
|
new_entries.each do |package_name, package_violations|
|
|
152
143
|
package_violations.each do |_, entries_for_constant|
|
|
@@ -159,12 +150,12 @@ module Packwerk
|
|
|
159
150
|
@new_entries = new_entries.sort.to_h
|
|
160
151
|
end
|
|
161
152
|
|
|
162
|
-
|
|
153
|
+
#: -> entries
|
|
163
154
|
def old_entries
|
|
164
155
|
@old_entries ||= load_yaml_file(@path)
|
|
165
156
|
end
|
|
166
157
|
|
|
167
|
-
|
|
158
|
+
#: (String path) -> entries
|
|
168
159
|
def load_yaml_file(path)
|
|
169
160
|
File.exist?(path) && YAML.load_file(path) || {}
|
|
170
161
|
rescue Psych::Exception
|
data/lib/packwerk/parse_run.rb
CHANGED
|
@@ -5,32 +5,15 @@ require "parallel"
|
|
|
5
5
|
|
|
6
6
|
module Packwerk
|
|
7
7
|
class ParseRun
|
|
8
|
-
|
|
8
|
+
#: type process_file_proc = ^(String path) -> Array[Offense]
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
T.proc.params(path: String).returns(T::Array[Offense])
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
sig do
|
|
15
|
-
params(
|
|
16
|
-
relative_file_set: FilesForProcessing::RelativeFileSet,
|
|
17
|
-
parallel: T::Boolean,
|
|
18
|
-
).void
|
|
19
|
-
end
|
|
10
|
+
#: (relative_file_set: FilesForProcessing::relative_file_set, parallel: bool) -> void
|
|
20
11
|
def initialize(relative_file_set:, parallel:)
|
|
21
12
|
@relative_file_set = relative_file_set
|
|
22
13
|
@parallel = parallel
|
|
23
14
|
end
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
params(
|
|
27
|
-
run_context: RunContext,
|
|
28
|
-
on_interrupt: T.nilable(T.proc.void),
|
|
29
|
-
block: T.nilable(T.proc.params(
|
|
30
|
-
offenses: T::Array[Packwerk::Offense],
|
|
31
|
-
).void)
|
|
32
|
-
).returns(T::Array[Offense])
|
|
33
|
-
end
|
|
16
|
+
#: (RunContext run_context, ?on_interrupt: (^-> void)?) ?{ (Array[Packwerk::Offense] offenses) -> void } -> Array[Offense]
|
|
34
17
|
def find_offenses(run_context, on_interrupt: nil, &block)
|
|
35
18
|
process_file_proc = process_file_proc(run_context, &block)
|
|
36
19
|
|
|
@@ -45,32 +28,22 @@ module Packwerk
|
|
|
45
28
|
|
|
46
29
|
private
|
|
47
30
|
|
|
48
|
-
|
|
49
|
-
params(
|
|
50
|
-
run_context: RunContext,
|
|
51
|
-
block: T.nilable(T.proc.params(offenses: T::Array[Offense]).void)
|
|
52
|
-
).returns(ProcessFileProc)
|
|
53
|
-
end
|
|
31
|
+
#: (RunContext run_context) ?{ (Array[Offense] offenses) -> void } -> process_file_proc
|
|
54
32
|
def process_file_proc(run_context, &block)
|
|
55
33
|
if block
|
|
56
|
-
|
|
34
|
+
proc do |relative_file|
|
|
57
35
|
run_context.process_file(relative_file: relative_file).tap(&block)
|
|
58
|
-
end
|
|
36
|
+
end #: process_file_proc
|
|
59
37
|
else
|
|
60
|
-
|
|
38
|
+
proc do |relative_file|
|
|
61
39
|
run_context.process_file(relative_file: relative_file)
|
|
62
|
-
end
|
|
40
|
+
end #: process_file_proc
|
|
63
41
|
end
|
|
64
42
|
end
|
|
65
43
|
|
|
66
|
-
|
|
67
|
-
params(
|
|
68
|
-
on_interrupt: T.nilable(T.proc.void),
|
|
69
|
-
block: ProcessFileProc
|
|
70
|
-
).returns(T::Array[Offense])
|
|
71
|
-
end
|
|
44
|
+
#: (?on_interrupt: (^-> void)?) { (?) -> untyped } -> Array[Offense]
|
|
72
45
|
def serial_find_offenses(on_interrupt: nil, &block)
|
|
73
|
-
all_offenses =
|
|
46
|
+
all_offenses = [] #: Array[Offense]
|
|
74
47
|
begin
|
|
75
48
|
@relative_file_set.each do |relative_file|
|
|
76
49
|
offenses = yield(relative_file)
|
|
@@ -6,13 +6,9 @@ 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
|
-
|
|
11
9
|
class << self
|
|
12
|
-
extend T::Sig
|
|
13
|
-
|
|
14
10
|
# What fully qualified constants can this constant refer to in this context?
|
|
15
|
-
|
|
11
|
+
#: (String constant_name, namespace_path: Array[String?]) -> Array[String]
|
|
16
12
|
def reference_qualifications(constant_name, namespace_path:)
|
|
17
13
|
return [constant_name] if constant_name.start_with?("::")
|
|
18
14
|
|
|
@@ -26,20 +22,14 @@ module Packwerk
|
|
|
26
22
|
end
|
|
27
23
|
end
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
#: (root_node: AST::Node?) -> void
|
|
30
26
|
def initialize(root_node:)
|
|
31
|
-
@local_definitions =
|
|
27
|
+
@local_definitions = {} #: Hash[String, Node::Location?]
|
|
32
28
|
|
|
33
29
|
collect_local_definitions_from_root(root_node) if root_node
|
|
34
30
|
end
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
params(
|
|
38
|
-
constant_name: String,
|
|
39
|
-
location: T.nilable(Node::Location),
|
|
40
|
-
namespace_path: T::Array[String],
|
|
41
|
-
).returns(T::Boolean)
|
|
42
|
-
end
|
|
32
|
+
#: (String constant_name, ?location: Node::Location?, ?namespace_path: Array[String]) -> bool
|
|
43
33
|
def local_reference?(constant_name, location: nil, namespace_path: [])
|
|
44
34
|
qualifications = self.class.reference_qualifications(constant_name, namespace_path: namespace_path)
|
|
45
35
|
|
|
@@ -51,14 +41,16 @@ module Packwerk
|
|
|
51
41
|
|
|
52
42
|
private
|
|
53
43
|
|
|
54
|
-
|
|
44
|
+
#: (AST::Node node, ?Array[String?] current_namespace_path) -> void
|
|
55
45
|
def collect_local_definitions_from_root(node, current_namespace_path = [])
|
|
56
46
|
if NodeHelpers.constant_assignment?(node)
|
|
57
47
|
add_definition(NodeHelpers.constant_name(node), current_namespace_path, NodeHelpers.name_location(node))
|
|
58
48
|
elsif NodeHelpers.module_name_from_definition(node)
|
|
59
49
|
# handle compact constant nesting (e.g. "module Sales::Order")
|
|
60
|
-
tempnode =
|
|
61
|
-
while (tempnode = NodeHelpers.each_child(
|
|
50
|
+
tempnode = node #: AST::Node?
|
|
51
|
+
while (tempnode = NodeHelpers.each_child(
|
|
52
|
+
tempnode #: as !nil
|
|
53
|
+
).find { |node| NodeHelpers.constant?(node) })
|
|
62
54
|
add_definition(NodeHelpers.constant_name(tempnode), current_namespace_path,
|
|
63
55
|
NodeHelpers.name_location(tempnode))
|
|
64
56
|
end
|
|
@@ -69,13 +61,7 @@ module Packwerk
|
|
|
69
61
|
NodeHelpers.each_child(node) { |child| collect_local_definitions_from_root(child, current_namespace_path) }
|
|
70
62
|
end
|
|
71
63
|
|
|
72
|
-
|
|
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
|
|
64
|
+
#: (String constant_name, Array[String?] current_namespace_path, Node::Location? location) -> void
|
|
79
65
|
def add_definition(constant_name, current_namespace_path, location)
|
|
80
66
|
resolved_constant = [""].concat(current_namespace_path).push(constant_name).join("::")
|
|
81
67
|
|
data/lib/packwerk/parsers/erb.rb
CHANGED
|
@@ -9,24 +9,23 @@ require "parser/source/buffer"
|
|
|
9
9
|
module Packwerk
|
|
10
10
|
module Parsers
|
|
11
11
|
class Erb
|
|
12
|
-
extend T::Sig
|
|
13
|
-
|
|
14
12
|
include ParserInterface
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
#: (?parser_class: untyped, ?ruby_parser: Ruby) -> void
|
|
17
15
|
def initialize(parser_class: BetterHtml::Parser, ruby_parser: Ruby.new)
|
|
18
|
-
@parser_class =
|
|
16
|
+
@parser_class = parser_class #: singleton(BetterHtml::Parser)
|
|
19
17
|
@ruby_parser = ruby_parser
|
|
20
18
|
end
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
# @override
|
|
21
|
+
#: (io: (IO | StringIO), ?file_path: String) -> untyped
|
|
23
22
|
def call(io:, file_path: "<unknown>")
|
|
24
23
|
buffer = Parser::Source::Buffer.new(file_path)
|
|
25
24
|
buffer.source = io.read
|
|
26
25
|
parse_buffer(buffer, file_path: file_path)
|
|
27
26
|
end
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
#: (Parser::Source::Buffer buffer, file_path: String) -> AST::Node?
|
|
30
29
|
def parse_buffer(buffer, file_path:)
|
|
31
30
|
parser = @parser_class.new(buffer, template_language: :html)
|
|
32
31
|
to_ruby_ast(parser.ast, file_path)
|
|
@@ -40,18 +39,15 @@ module Packwerk
|
|
|
40
39
|
|
|
41
40
|
private
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
params(
|
|
45
|
-
erb_ast: T.all(::AST::Node, Object),
|
|
46
|
-
file_path: String
|
|
47
|
-
).returns(T.nilable(::AST::Node))
|
|
48
|
-
end
|
|
42
|
+
#: ((::AST::Node & Object) erb_ast, String file_path) -> ::AST::Node?
|
|
49
43
|
def to_ruby_ast(erb_ast, file_path)
|
|
50
44
|
# Note that we're not using the source location (line/column) at the moment, but if we did
|
|
51
45
|
# care about that, we'd need to tweak this to insert empty lines and spaces so that things
|
|
52
46
|
# line up with the ERB file
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
nodes = code_nodes(erb_ast) #: as !nil
|
|
48
|
+
code_pieces = nodes.map do |node|
|
|
49
|
+
node #: as ::AST::Node
|
|
50
|
+
.children.first
|
|
55
51
|
end
|
|
56
52
|
|
|
57
53
|
@ruby_parser.call(
|
|
@@ -60,14 +56,7 @@ module Packwerk
|
|
|
60
56
|
)
|
|
61
57
|
end
|
|
62
58
|
|
|
63
|
-
|
|
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
|
|
59
|
+
#: ((::AST::Node | String)? node) ?{ (::AST::Node arg0) -> void } -> (Enumerator[::AST::Node] | Array[String])?
|
|
71
60
|
def code_nodes(node, &block)
|
|
72
61
|
return enum_for(:code_nodes, node) unless block
|
|
73
62
|
return unless node.is_a?(::AST::Node)
|
|
@@ -6,7 +6,6 @@ require "singleton"
|
|
|
6
6
|
module Packwerk
|
|
7
7
|
module Parsers
|
|
8
8
|
class Factory
|
|
9
|
-
extend T::Sig
|
|
10
9
|
include Singleton
|
|
11
10
|
|
|
12
11
|
RUBY_REGEX = %r{
|
|
@@ -20,29 +19,30 @@ module Packwerk
|
|
|
20
19
|
ERB_REGEX = /\.erb\Z/
|
|
21
20
|
private_constant :ERB_REGEX
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
#: -> void
|
|
24
23
|
def initialize
|
|
25
|
-
@ruby_parser =
|
|
26
|
-
@erb_parser =
|
|
27
|
-
@erb_parser_class =
|
|
24
|
+
@ruby_parser = nil #: ParserInterface?
|
|
25
|
+
@erb_parser = nil #: ParserInterface?
|
|
26
|
+
@erb_parser_class = nil #: Class[top]?
|
|
28
27
|
end
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
#: (String path) -> ParserInterface?
|
|
31
30
|
def for_path(path)
|
|
32
31
|
case path
|
|
33
32
|
when RUBY_REGEX
|
|
34
33
|
@ruby_parser ||= Ruby.new
|
|
35
34
|
when ERB_REGEX
|
|
36
|
-
|
|
35
|
+
erb_parser_class_ = erb_parser_class #: as untyped
|
|
36
|
+
@erb_parser ||= erb_parser_class_.new
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
#: -> Class[top]
|
|
41
41
|
def erb_parser_class
|
|
42
42
|
@erb_parser_class ||= Erb
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
#: (Class[top]? klass) -> void
|
|
46
46
|
def erb_parser_class=(klass)
|
|
47
47
|
@erb_parser_class = klass
|
|
48
48
|
@erb_parser = nil
|
|
@@ -3,17 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
module Packwerk
|
|
5
5
|
module Parsers
|
|
6
|
+
# @requires_ancestor: Kernel
|
|
7
|
+
# @interface
|
|
6
8
|
module ParserInterface
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
requires_ancestor { Kernel }
|
|
11
|
-
|
|
12
|
-
interface!
|
|
13
|
-
|
|
14
|
-
sig { abstract.params(io: T.any(IO, StringIO), file_path: String).returns(T.untyped) }
|
|
15
|
-
def call(io:, file_path:)
|
|
16
|
-
end
|
|
9
|
+
# @abstract
|
|
10
|
+
#: (io: (IO | StringIO), file_path: String) -> untyped
|
|
11
|
+
def call(io:, file_path:) = raise NotImplementedError, "Abstract method called"
|
|
17
12
|
end
|
|
18
13
|
end
|
|
19
14
|
end
|
|
@@ -7,14 +7,10 @@ require "prism"
|
|
|
7
7
|
module Packwerk
|
|
8
8
|
module Parsers
|
|
9
9
|
class Ruby
|
|
10
|
-
extend T::Sig
|
|
11
|
-
|
|
12
10
|
include ParserInterface
|
|
13
11
|
|
|
14
12
|
class RaiseExceptionsParser < Prism::Translation::Parser
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
sig { params(builder: T.untyped).void }
|
|
13
|
+
#: (untyped builder) -> void
|
|
18
14
|
def initialize(builder)
|
|
19
15
|
super(builder)
|
|
20
16
|
super.diagnostics.all_errors_are_fatal = true
|
|
@@ -22,28 +18,27 @@ module Packwerk
|
|
|
22
18
|
|
|
23
19
|
private
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
#: (Prism::ParseError error) -> bool
|
|
26
22
|
def valid_error?(error)
|
|
27
23
|
error.type != :invalid_yield
|
|
28
24
|
end
|
|
29
25
|
end
|
|
30
26
|
|
|
31
|
-
class TolerateInvalidUtf8Builder < Parser::
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
sig { params(token: T.untyped).returns(T.untyped) }
|
|
27
|
+
class TolerateInvalidUtf8Builder < Prism::Translation::Parser::Builder
|
|
28
|
+
#: (untyped token) -> untyped
|
|
35
29
|
def string_value(token)
|
|
36
30
|
value(token)
|
|
37
31
|
end
|
|
38
32
|
end
|
|
39
33
|
|
|
40
|
-
|
|
34
|
+
#: (?parser_class: untyped) -> void
|
|
41
35
|
def initialize(parser_class: RaiseExceptionsParser)
|
|
42
|
-
@builder =
|
|
43
|
-
@parser_class =
|
|
36
|
+
@builder = TolerateInvalidUtf8Builder.new #: Object
|
|
37
|
+
@parser_class = parser_class #: singleton(RaiseExceptionsParser)
|
|
44
38
|
end
|
|
45
39
|
|
|
46
|
-
|
|
40
|
+
# @override
|
|
41
|
+
#: (io: (IO | StringIO), ?file_path: String) -> Parser::AST::Node?
|
|
47
42
|
def call(io:, file_path: "<unknown>")
|
|
48
43
|
buffer = Parser::Source::Buffer.new(file_path)
|
|
49
44
|
buffer.source = io.read
|
data/lib/packwerk/parsers.rb
CHANGED
|
@@ -11,12 +11,10 @@ module Packwerk
|
|
|
11
11
|
class ParseResult < Offense; end
|
|
12
12
|
|
|
13
13
|
class ParseError < StandardError
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
sig { returns(ParseResult) }
|
|
14
|
+
#: ParseResult
|
|
17
15
|
attr_reader(:result)
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
#: (ParseResult result) -> void
|
|
20
18
|
def initialize(result)
|
|
21
19
|
super(result.message)
|
|
22
20
|
@result = result
|
|
@@ -9,9 +9,7 @@ module Packwerk
|
|
|
9
9
|
# Extracts the load paths from the analyzed application so that we can map constant names to paths.
|
|
10
10
|
module RailsLoadPaths
|
|
11
11
|
class << self
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
sig { params(root: String, environment: String).returns(T::Hash[String, Module]) }
|
|
12
|
+
#: (String root, environment: String) -> Hash[String, Module[top]]
|
|
15
13
|
def for(root, environment:)
|
|
16
14
|
require_application(root, environment)
|
|
17
15
|
all_paths = extract_application_autoload_paths
|
|
@@ -22,17 +20,14 @@ module Packwerk
|
|
|
22
20
|
|
|
23
21
|
private
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
#: -> Hash[String, Module[top]]
|
|
26
24
|
def extract_application_autoload_paths
|
|
27
25
|
Rails.autoloaders.inject({}) do |h, loader|
|
|
28
26
|
h.merge(loader.dirs(namespaces: true))
|
|
29
27
|
end
|
|
30
28
|
end
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
params(all_paths: T::Hash[String, Module], bundle_path: Pathname, rails_root: Pathname)
|
|
34
|
-
.returns(T::Hash[Pathname, Module])
|
|
35
|
-
end
|
|
30
|
+
#: (Hash[String, Module[top]] all_paths, ?bundle_path: Pathname, ?rails_root: Pathname) -> Hash[Pathname, Module[top]]
|
|
36
31
|
def filter_relevant_paths(all_paths, bundle_path: Bundler.bundle_path, rails_root: Rails.root)
|
|
37
32
|
bundle_path_match = bundle_path.join("**")
|
|
38
33
|
rails_root_match = rails_root.join("**")
|
|
@@ -43,12 +38,12 @@ module Packwerk
|
|
|
43
38
|
.reject { |path| path.fnmatch(bundle_path_match.to_s) } # reject paths from vendored gems
|
|
44
39
|
end
|
|
45
40
|
|
|
46
|
-
|
|
41
|
+
#: (Hash[Pathname, Module[top]] load_paths, ?rails_root: Pathname) -> Hash[String, Module[top]]
|
|
47
42
|
def relative_path_strings(load_paths, rails_root: Rails.root)
|
|
48
43
|
load_paths.transform_keys { |path| Pathname.new(path).relative_path_from(rails_root).to_s }
|
|
49
44
|
end
|
|
50
45
|
|
|
51
|
-
|
|
46
|
+
#: (String root, String environment) -> void
|
|
52
47
|
def require_application(root, environment)
|
|
53
48
|
environment_file = "#{root}/config/environment"
|
|
54
49
|
|
|
@@ -61,7 +56,7 @@ module Packwerk
|
|
|
61
56
|
end
|
|
62
57
|
end
|
|
63
58
|
|
|
64
|
-
|
|
59
|
+
#: (Hash[untyped, Module[top]] paths) -> void
|
|
65
60
|
def assert_load_paths_present(paths)
|
|
66
61
|
if paths.empty?
|
|
67
62
|
raise <<~EOS
|
|
@@ -6,21 +6,18 @@ module Packwerk
|
|
|
6
6
|
module Checkers
|
|
7
7
|
# Checks whether a given reference conforms to the configured graph of dependencies.
|
|
8
8
|
class DependencyChecker
|
|
9
|
-
extend T::Sig
|
|
10
9
|
include Checker
|
|
11
10
|
|
|
12
|
-
VIOLATION_TYPE =
|
|
11
|
+
VIOLATION_TYPE = "dependency" #: String
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
# @override
|
|
14
|
+
#: -> String
|
|
15
15
|
def violation_type
|
|
16
16
|
VIOLATION_TYPE
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
.params(reference: Packwerk::Reference)
|
|
22
|
-
.returns(T::Boolean)
|
|
23
|
-
end
|
|
19
|
+
# @override
|
|
20
|
+
#: (Packwerk::Reference reference) -> bool
|
|
24
21
|
def invalid_reference?(reference)
|
|
25
22
|
return false unless reference.package.enforce_dependencies?
|
|
26
23
|
return false if reference.package.dependency?(reference.constant.package)
|
|
@@ -28,11 +25,8 @@ module Packwerk
|
|
|
28
25
|
true
|
|
29
26
|
end
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.params(reference: Packwerk::Reference)
|
|
34
|
-
.returns(String)
|
|
35
|
-
end
|
|
28
|
+
# @override
|
|
29
|
+
#: (Packwerk::Reference reference) -> String
|
|
36
30
|
def message(reference)
|
|
37
31
|
const_name = reference.constant.name
|
|
38
32
|
const_package = reference.constant.package
|
|
@@ -46,7 +40,8 @@ module Packwerk
|
|
|
46
40
|
EOS
|
|
47
41
|
end
|
|
48
42
|
|
|
49
|
-
|
|
43
|
+
# @override
|
|
44
|
+
#: (ReferenceOffense offense) -> bool
|
|
50
45
|
def strict_mode_violation?(offense)
|
|
51
46
|
referencing_package = offense.reference.package
|
|
52
47
|
referencing_package.config["enforce_dependencies"] == "strict"
|
|
@@ -54,7 +49,7 @@ module Packwerk
|
|
|
54
49
|
|
|
55
50
|
private
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
#: (Reference reference) -> String
|
|
58
53
|
def standard_help_message(reference)
|
|
59
54
|
standard_message = <<~EOS
|
|
60
55
|
Inference details: this is a reference to #{reference.constant.name} which seems to be defined in #{reference.constant.location}.
|
|
@@ -4,18 +4,12 @@
|
|
|
4
4
|
module Packwerk
|
|
5
5
|
module ReferenceChecking
|
|
6
6
|
class ReferenceChecker
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
sig { params(checkers: T::Array[Checker]).void }
|
|
7
|
+
#: (Array[Checker] checkers) -> void
|
|
10
8
|
def initialize(checkers)
|
|
11
9
|
@checkers = checkers
|
|
12
10
|
end
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
params(
|
|
16
|
-
reference: Reference
|
|
17
|
-
).returns(T::Array[Packwerk::Offense])
|
|
18
|
-
end
|
|
12
|
+
#: (Reference reference) -> Array[Packwerk::Offense]
|
|
19
13
|
def call(reference)
|
|
20
14
|
@checkers.each_with_object([]) do |checker, violations|
|
|
21
15
|
next unless checker.invalid_reference?(reference)
|