packwerk 2.2.0 → 2.2.2
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 +29 -20
- data/.github/workflows/cla.yml +22 -0
- data/.rubocop.yml +48 -19
- data/Gemfile +7 -2
- data/Gemfile.lock +202 -175
- data/README.md +1 -1
- data/RESOLVING_VIOLATIONS.md +81 -0
- data/Rakefile +1 -1
- data/USAGE.md +14 -5
- data/bin/m +1 -1
- data/bin/rake +1 -1
- data/bin/rubocop +1 -1
- data/bin/srb +1 -1
- data/bin/tapioca +1 -1
- data/gemfiles/Gemfile-rails-6-0 +1 -1
- data/gemfiles/Gemfile-rails-6-1 +22 -0
- data/lib/packwerk/application_load_paths.rb +1 -1
- data/lib/packwerk/application_validator.rb +7 -6
- data/lib/packwerk/association_inspector.rb +17 -15
- data/lib/packwerk/cache.rb +36 -29
- data/lib/packwerk/cli.rb +24 -20
- data/lib/packwerk/const_node_inspector.rb +8 -7
- data/lib/packwerk/constant_name_inspector.rb +2 -2
- data/lib/packwerk/deprecated_references.rb +40 -20
- data/lib/packwerk/file_processor.rb +14 -14
- data/lib/packwerk/files_for_processing.rb +27 -31
- data/lib/packwerk/formatters/offenses_formatter.rb +3 -3
- data/lib/packwerk/formatters/progress_formatter.rb +2 -2
- data/lib/packwerk/node.rb +1 -294
- data/lib/packwerk/node_helpers.rb +335 -0
- data/lib/packwerk/node_processor.rb +6 -5
- data/lib/packwerk/node_processor_factory.rb +3 -3
- data/lib/packwerk/node_visitor.rb +1 -1
- data/lib/packwerk/offense_collection.rb +27 -8
- data/lib/packwerk/offenses_formatter.rb +2 -2
- data/lib/packwerk/package.rb +3 -0
- data/lib/packwerk/package_set.rb +2 -0
- data/lib/packwerk/parse_run.rb +29 -20
- data/lib/packwerk/parsed_constant_definitions.rb +23 -20
- data/lib/packwerk/parsers/erb.rb +3 -3
- data/lib/packwerk/reference_checking/checkers/checker.rb +16 -3
- data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +16 -0
- data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +18 -0
- data/lib/packwerk/reference_checking/reference_checker.rb +3 -1
- data/lib/packwerk/reference_extractor.rb +51 -48
- data/lib/packwerk/reference_offense.rb +3 -27
- data/lib/packwerk/run_context.rb +9 -8
- data/lib/packwerk/spring_command.rb +1 -1
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +1 -0
- data/packwerk.gemspec +5 -12
- data/sorbet/rbi/gems/actioncable@7.0.3.1.rbi +2754 -0
- data/sorbet/rbi/gems/actionmailbox@7.0.3.1.rbi +1496 -0
- data/sorbet/rbi/gems/actionmailer@7.0.3.1.rbi +2362 -0
- data/sorbet/rbi/gems/actionpack@7.0.3.1.rbi +19397 -0
- data/sorbet/rbi/gems/actiontext@7.0.3.1.rbi +1569 -0
- data/sorbet/rbi/gems/actionview@7.0.3.1.rbi +14907 -0
- data/sorbet/rbi/gems/activejob@7.0.3.1.rbi +2553 -0
- data/sorbet/rbi/gems/activemodel@7.0.3.1.rbi +5999 -0
- data/sorbet/rbi/gems/activerecord@7.0.3.1.rbi +37832 -0
- data/sorbet/rbi/gems/activestorage@7.0.3.1.rbi +2321 -0
- data/sorbet/rbi/gems/activesupport@7.0.3.1.rbi +18818 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +11722 -0
- data/sorbet/rbi/gems/constant_resolver@0.2.0.rbi +90 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1079 -0
- data/sorbet/rbi/gems/digest@3.1.0.rbi +189 -0
- data/sorbet/rbi/gems/erubi@1.11.0.rbi +140 -0
- data/sorbet/rbi/gems/globalid@1.0.0.rbi +572 -0
- data/sorbet/rbi/gems/i18n@1.12.0.rbi +2296 -0
- data/sorbet/rbi/gems/json@2.6.2.rbi +1548 -0
- data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +8 -0
- data/sorbet/rbi/gems/loofah@2.18.0.rbi +877 -0
- data/sorbet/rbi/gems/m@1.6.0.rbi +257 -0
- data/sorbet/rbi/gems/marcel@1.0.2.rbi +220 -0
- data/sorbet/rbi/gems/mini_mime@1.1.2.rbi +170 -0
- data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +8 -0
- data/sorbet/rbi/gems/minitest-focus@1.3.1.rbi +104 -0
- data/sorbet/rbi/gems/minitest@5.16.2.rbi +2136 -0
- data/sorbet/rbi/gems/mocha@1.14.0.rbi +4177 -0
- data/sorbet/rbi/gems/net-imap@0.2.3.rbi +2147 -0
- data/sorbet/rbi/gems/net-pop@0.1.1.rbi +926 -0
- data/sorbet/rbi/gems/net-protocol@0.1.3.rbi +11 -0
- data/sorbet/rbi/gems/net-smtp@0.3.1.rbi +1108 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +153 -0
- data/sorbet/rbi/gems/nio4r@2.5.8.rbi +292 -0
- data/sorbet/rbi/gems/nokogiri@1.13.8.rbi +6478 -0
- data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
- data/sorbet/rbi/gems/parser@3.1.2.1.rbi +9029 -0
- data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +8 -0
- data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
- data/sorbet/rbi/gems/racc@1.6.0.rbi +152 -0
- data/sorbet/rbi/gems/rack-test@2.0.2.rbi +953 -0
- data/sorbet/rbi/gems/rack@2.2.4.rbi +5636 -0
- data/sorbet/rbi/gems/rails-html-sanitizer@1.4.3.rbi +688 -0
- data/sorbet/rbi/gems/rails@7.0.3.1.rbi +8 -0
- data/sorbet/rbi/gems/railties@7.0.3.1.rbi +3507 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +392 -0
- data/sorbet/rbi/gems/rake@13.0.6.rbi +2924 -0
- data/sorbet/rbi/gems/rbi@0.0.15.rbi +3007 -0
- data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +3383 -0
- data/sorbet/rbi/gems/rexml@3.2.5.rbi +4714 -0
- data/sorbet/rbi/gems/rubocop-ast@1.21.0.rbi +6961 -0
- data/sorbet/rbi/gems/rubocop-performance@1.14.3.rbi +2986 -0
- data/sorbet/rbi/gems/{rubocop-shopify@2.0.1.rbi → rubocop-shopify@2.9.0.rbi} +4 -4
- data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +992 -0
- data/sorbet/rbi/gems/rubocop@1.34.1.rbi +51820 -0
- data/sorbet/rbi/gems/ruby-lsp@0.2.1.rbi +11 -0
- data/sorbet/rbi/gems/smart_properties@1.17.0.rbi +474 -0
- data/sorbet/rbi/gems/spoom@1.1.11.rbi +2181 -0
- data/sorbet/rbi/gems/spring@4.0.0.rbi +411 -0
- data/sorbet/rbi/gems/strscan@3.0.4.rbi +8 -0
- data/sorbet/rbi/gems/syntax_tree@3.3.0.rbi +8 -0
- data/sorbet/rbi/gems/tapioca@0.9.2.rbi +3181 -0
- data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
- data/sorbet/rbi/gems/timeout@0.3.0.rbi +142 -0
- data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +5896 -0
- data/sorbet/rbi/gems/unicode-display_width@2.2.0.rbi +48 -0
- data/sorbet/rbi/gems/unparser@0.6.5.rbi +4529 -0
- data/sorbet/rbi/gems/webrick@1.7.0.rbi +2582 -0
- data/sorbet/rbi/gems/websocket-driver@0.7.5.rbi +993 -0
- data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +388 -0
- data/sorbet/rbi/gems/yard@0.9.28.rbi +18242 -0
- data/sorbet/rbi/gems/zeitwerk@2.6.0.rbi +867 -0
- data/sorbet/rbi/shims/psych.rbi +5 -0
- data/sorbet/tapioca/require.rb +2 -3
- metadata +91 -146
- data/.github/probots.yml +0 -2
- data/library.yml +0 -6
- data/service.yml +0 -1
- data/sorbet/rbi/gems/actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -860
- data/sorbet/rbi/gems/actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -568
- data/sorbet/rbi/gems/actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -587
- data/sorbet/rbi/gems/actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -5314
- data/sorbet/rbi/gems/actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -699
- data/sorbet/rbi/gems/actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -2515
- data/sorbet/rbi/gems/activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -624
- data/sorbet/rbi/gems/activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -1248
- data/sorbet/rbi/gems/activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8363
- data/sorbet/rbi/gems/activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -876
- data/sorbet/rbi/gems/activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -3987
- data/sorbet/rbi/gems/colorize@0.8.1.rbi +0 -40
- data/sorbet/rbi/gems/commander@4.5.2.rbi +0 -8
- data/sorbet/rbi/gems/concurrent-ruby@1.1.8.rbi +0 -1969
- data/sorbet/rbi/gems/constant_resolver@0.1.5.rbi +0 -26
- data/sorbet/rbi/gems/erubi@1.10.0.rbi +0 -41
- data/sorbet/rbi/gems/globalid@0.4.2.rbi +0 -178
- data/sorbet/rbi/gems/highline@2.0.3.rbi +0 -8
- data/sorbet/rbi/gems/i18n@1.8.10.rbi +0 -600
- data/sorbet/rbi/gems/loofah@2.9.0.rbi +0 -274
- data/sorbet/rbi/gems/m@1.5.1.rbi +0 -108
- data/sorbet/rbi/gems/marcel@1.0.0.rbi +0 -70
- data/sorbet/rbi/gems/mini_mime@1.0.3.rbi +0 -71
- data/sorbet/rbi/gems/minitest-focus@1.2.1.rbi +0 -8
- data/sorbet/rbi/gems/minitest@5.14.4.rbi +0 -544
- data/sorbet/rbi/gems/mocha@1.12.0.rbi +0 -953
- data/sorbet/rbi/gems/nio4r@2.5.7.rbi +0 -90
- data/sorbet/rbi/gems/nokogiri@1.11.2.rbi +0 -1647
- data/sorbet/rbi/gems/parallel@1.20.1.rbi +0 -117
- data/sorbet/rbi/gems/parlour@6.0.0.rbi +0 -1272
- data/sorbet/rbi/gems/parser@3.0.0.0.rbi +0 -1745
- data/sorbet/rbi/gems/pry@0.14.0.rbi +0 -8
- data/sorbet/rbi/gems/psych@3.3.2.rbi +0 -24
- data/sorbet/rbi/gems/racc@1.5.2.rbi +0 -57
- data/sorbet/rbi/gems/rack-test@1.1.0.rbi +0 -335
- data/sorbet/rbi/gems/rack@2.2.3.rbi +0 -1718
- data/sorbet/rbi/gems/rails-html-sanitizer@1.3.0.rbi +0 -213
- data/sorbet/rbi/gems/rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8
- data/sorbet/rbi/gems/railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -880
- data/sorbet/rbi/gems/rainbow@3.0.0.rbi +0 -155
- data/sorbet/rbi/gems/rake@13.0.3.rbi +0 -837
- data/sorbet/rbi/gems/regexp_parser@2.1.1.rbi +0 -8
- data/sorbet/rbi/gems/rexml@3.2.4.rbi +0 -8
- data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +0 -8
- data/sorbet/rbi/gems/rubocop-performance@1.10.2.rbi +0 -8
- data/sorbet/rbi/gems/rubocop-sorbet@0.6.1.rbi +0 -8
- data/sorbet/rbi/gems/rubocop@1.12.0.rbi +0 -8
- data/sorbet/rbi/gems/smart_properties@1.15.0.rbi +0 -168
- data/sorbet/rbi/gems/spoom@1.1.0.rbi +0 -1061
- data/sorbet/rbi/gems/spring@2.1.1.rbi +0 -160
- data/sorbet/rbi/gems/sprockets-rails@3.2.2.rbi +0 -451
- data/sorbet/rbi/gems/sprockets@4.0.2.rbi +0 -1133
- data/sorbet/rbi/gems/tapioca@0.4.19.rbi +0 -603
- data/sorbet/rbi/gems/thor@1.1.0.rbi +0 -893
- data/sorbet/rbi/gems/tzinfo@2.0.4.rbi +0 -566
- data/sorbet/rbi/gems/unicode-display_width@2.0.0.rbi +0 -8
- data/sorbet/rbi/gems/websocket-driver@0.7.3.rbi +0 -438
- data/sorbet/rbi/gems/zeitwerk@2.4.2.rbi +0 -177
@@ -0,0 +1,335 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "parser"
|
5
|
+
require "parser/ast/node"
|
6
|
+
|
7
|
+
module Packwerk
|
8
|
+
# Convenience methods for working with Parser::AST::Node nodes.
|
9
|
+
module NodeHelpers
|
10
|
+
class TypeError < ArgumentError; end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
sig { params(class_or_module_node: AST::Node).returns(String) }
|
16
|
+
def class_or_module_name(class_or_module_node)
|
17
|
+
case type_of(class_or_module_node)
|
18
|
+
when CLASS, MODULE
|
19
|
+
# (class (const nil :Foo) (const nil :Bar) (nil))
|
20
|
+
# "class Foo < Bar; end"
|
21
|
+
# (module (const nil :Foo) (nil))
|
22
|
+
# "module Foo; end"
|
23
|
+
identifier = class_or_module_node.children[0]
|
24
|
+
constant_name(identifier)
|
25
|
+
else
|
26
|
+
raise TypeError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
sig { params(constant_node: AST::Node).returns(String) }
|
31
|
+
def constant_name(constant_node)
|
32
|
+
case type_of(constant_node)
|
33
|
+
when CONSTANT_ROOT_NAMESPACE
|
34
|
+
""
|
35
|
+
when CONSTANT, CONSTANT_ASSIGNMENT, SELF
|
36
|
+
# (const nil :Foo)
|
37
|
+
# "Foo"
|
38
|
+
# (const (cbase) :Foo)
|
39
|
+
# "::Foo"
|
40
|
+
# (const (lvar :a) :Foo)
|
41
|
+
# "a::Foo"
|
42
|
+
# (casgn nil :Foo (int 1))
|
43
|
+
# "Foo = 1"
|
44
|
+
# (casgn (cbase) :Foo (int 1))
|
45
|
+
# "::Foo = 1"
|
46
|
+
# (casgn (lvar :a) :Foo (int 1))
|
47
|
+
# "a::Foo = 1"
|
48
|
+
# (casgn (self) :Foo (int 1))
|
49
|
+
# "self::Foo = 1"
|
50
|
+
namespace, name = constant_node.children
|
51
|
+
|
52
|
+
if namespace
|
53
|
+
[constant_name(namespace), name].join("::")
|
54
|
+
else
|
55
|
+
name.to_s
|
56
|
+
end
|
57
|
+
else
|
58
|
+
raise TypeError
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { params(node: AST::Node).returns(T.untyped) }
|
63
|
+
def each_child(node)
|
64
|
+
if block_given?
|
65
|
+
node.children.each do |child|
|
66
|
+
yield child if child.is_a?(Parser::AST::Node)
|
67
|
+
end
|
68
|
+
else
|
69
|
+
enum_for(:each_child, node)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
sig { params(starting_node: AST::Node, ancestors: T::Array[AST::Node]).returns(T::Array[String]) }
|
74
|
+
def enclosing_namespace_path(starting_node, ancestors:)
|
75
|
+
ancestors.select { |n| [CLASS, MODULE].include?(type_of(n)) }
|
76
|
+
.each_with_object([]) do |node, namespace|
|
77
|
+
# when evaluating `class Child < Parent`, the const node for `Parent` is a child of the class
|
78
|
+
# node, so it'll be an ancestor, but `Parent` is not evaluated in the namespace of `Child`, so
|
79
|
+
# we need to skip it here
|
80
|
+
next if type_of(node) == CLASS && parent_class(node) == starting_node
|
81
|
+
|
82
|
+
namespace.prepend(class_or_module_name(node))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
sig { params(string_or_symbol_node: AST::Node).returns(T.any(String, Symbol)) }
|
87
|
+
def literal_value(string_or_symbol_node)
|
88
|
+
case type_of(string_or_symbol_node)
|
89
|
+
when STRING, SYMBOL
|
90
|
+
# (str "foo")
|
91
|
+
# "'foo'"
|
92
|
+
# (sym :foo)
|
93
|
+
# ":foo"
|
94
|
+
string_or_symbol_node.children[0]
|
95
|
+
else
|
96
|
+
raise TypeError
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
sig { params(node: Parser::AST::Node).returns(Node::Location) }
|
101
|
+
def location(node)
|
102
|
+
location = node.location
|
103
|
+
Node::Location.new(location.line, location.column)
|
104
|
+
end
|
105
|
+
|
106
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
107
|
+
def constant?(node)
|
108
|
+
type_of(node) == CONSTANT
|
109
|
+
end
|
110
|
+
|
111
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
112
|
+
def constant_assignment?(node)
|
113
|
+
type_of(node) == CONSTANT_ASSIGNMENT
|
114
|
+
end
|
115
|
+
|
116
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
117
|
+
def class?(node)
|
118
|
+
type_of(node) == CLASS
|
119
|
+
end
|
120
|
+
|
121
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
122
|
+
def method_call?(node)
|
123
|
+
type_of(node) == METHOD_CALL
|
124
|
+
end
|
125
|
+
|
126
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
127
|
+
def hash?(node)
|
128
|
+
type_of(node) == HASH
|
129
|
+
end
|
130
|
+
|
131
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
132
|
+
def string?(node)
|
133
|
+
type_of(node) == STRING
|
134
|
+
end
|
135
|
+
|
136
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
137
|
+
def symbol?(node)
|
138
|
+
type_of(node) == SYMBOL
|
139
|
+
end
|
140
|
+
|
141
|
+
sig { params(method_call_node: AST::Node).returns(T::Array[AST::Node]) }
|
142
|
+
def method_arguments(method_call_node)
|
143
|
+
raise TypeError unless method_call?(method_call_node)
|
144
|
+
|
145
|
+
# (send (lvar :foo) :bar (int 1))
|
146
|
+
# "foo.bar(1)"
|
147
|
+
method_call_node.children.slice(2..-1)
|
148
|
+
end
|
149
|
+
|
150
|
+
sig { params(method_call_node: AST::Node).returns(Symbol) }
|
151
|
+
def method_name(method_call_node)
|
152
|
+
raise TypeError unless method_call?(method_call_node)
|
153
|
+
|
154
|
+
# (send (lvar :foo) :bar (int 1))
|
155
|
+
# "foo.bar(1)"
|
156
|
+
method_call_node.children[1]
|
157
|
+
end
|
158
|
+
|
159
|
+
sig { params(node: AST::Node).returns(T.nilable(String)) }
|
160
|
+
def module_name_from_definition(node)
|
161
|
+
case type_of(node)
|
162
|
+
when CLASS, MODULE
|
163
|
+
# "class My::Class; end"
|
164
|
+
# "module My::Module; end"
|
165
|
+
class_or_module_name(node)
|
166
|
+
when CONSTANT_ASSIGNMENT
|
167
|
+
# "My::Class = ..."
|
168
|
+
# "My::Module = ..."
|
169
|
+
rvalue = node.children.last
|
170
|
+
|
171
|
+
case type_of(rvalue)
|
172
|
+
when METHOD_CALL
|
173
|
+
# "Class.new"
|
174
|
+
# "Module.new"
|
175
|
+
constant_name(node) if module_creation?(rvalue)
|
176
|
+
when BLOCK
|
177
|
+
# "Class.new do end"
|
178
|
+
# "Module.new do end"
|
179
|
+
constant_name(node) if module_creation?(method_call_node(rvalue))
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
sig { params(node: Parser::AST::Node).returns(T.nilable(Node::Location)) }
|
185
|
+
def name_location(node)
|
186
|
+
location = node.location
|
187
|
+
|
188
|
+
if location.respond_to?(:name)
|
189
|
+
name = location.name
|
190
|
+
Node::Location.new(name.line, name.column)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
sig { params(class_node: AST::Node).returns(T.nilable(AST::Node)) }
|
195
|
+
def parent_class(class_node)
|
196
|
+
raise TypeError unless type_of(class_node) == CLASS
|
197
|
+
|
198
|
+
# (class (const nil :Foo) (const nil :Bar) (nil))
|
199
|
+
# "class Foo < Bar; end"
|
200
|
+
class_node.children[1]
|
201
|
+
end
|
202
|
+
|
203
|
+
sig { params(ancestors: T::Array[AST::Node]).returns(String) }
|
204
|
+
def parent_module_name(ancestors:)
|
205
|
+
definitions = ancestors
|
206
|
+
.select { |n| [CLASS, MODULE, CONSTANT_ASSIGNMENT, BLOCK].include?(type_of(n)) }
|
207
|
+
|
208
|
+
names = definitions.map do |definition|
|
209
|
+
name_part_from_definition(definition)
|
210
|
+
end.compact
|
211
|
+
|
212
|
+
names.empty? ? "Object" : names.reverse.join("::")
|
213
|
+
end
|
214
|
+
|
215
|
+
sig { params(hash_node: AST::Node, key: Symbol).returns(T.untyped) }
|
216
|
+
def value_from_hash(hash_node, key)
|
217
|
+
raise TypeError unless hash?(hash_node)
|
218
|
+
|
219
|
+
pair = hash_pairs(hash_node).detect { |pair_node| literal_value(hash_pair_key(pair_node)) == key }
|
220
|
+
hash_pair_value(pair) if pair
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
BLOCK = :block
|
226
|
+
CLASS = :class
|
227
|
+
CONSTANT = :const
|
228
|
+
CONSTANT_ASSIGNMENT = :casgn
|
229
|
+
CONSTANT_ROOT_NAMESPACE = :cbase
|
230
|
+
HASH = :hash
|
231
|
+
HASH_PAIR = :pair
|
232
|
+
METHOD_CALL = :send
|
233
|
+
MODULE = :module
|
234
|
+
SELF = :self
|
235
|
+
STRING = :str
|
236
|
+
SYMBOL = :sym
|
237
|
+
|
238
|
+
private_constant(
|
239
|
+
:BLOCK, :CLASS, :CONSTANT, :CONSTANT_ASSIGNMENT, :CONSTANT_ROOT_NAMESPACE, :HASH, :HASH_PAIR, :METHOD_CALL,
|
240
|
+
:MODULE, :SELF, :STRING, :SYMBOL,
|
241
|
+
)
|
242
|
+
|
243
|
+
sig { params(node: AST::Node).returns(Symbol) }
|
244
|
+
def type_of(node)
|
245
|
+
node.type
|
246
|
+
end
|
247
|
+
|
248
|
+
sig { params(hash_pair_node: AST::Node).returns(T.untyped) }
|
249
|
+
def hash_pair_key(hash_pair_node)
|
250
|
+
raise TypeError unless type_of(hash_pair_node) == HASH_PAIR
|
251
|
+
|
252
|
+
# (pair (int 1) (int 2))
|
253
|
+
# "1 => 2"
|
254
|
+
# (pair (sym :answer) (int 42))
|
255
|
+
# "answer: 42"
|
256
|
+
hash_pair_node.children[0]
|
257
|
+
end
|
258
|
+
|
259
|
+
sig { params(hash_pair_node: AST::Node).returns(T.untyped) }
|
260
|
+
def hash_pair_value(hash_pair_node)
|
261
|
+
raise TypeError unless type_of(hash_pair_node) == HASH_PAIR
|
262
|
+
|
263
|
+
# (pair (int 1) (int 2))
|
264
|
+
# "1 => 2"
|
265
|
+
# (pair (sym :answer) (int 42))
|
266
|
+
# "answer: 42"
|
267
|
+
hash_pair_node.children[1]
|
268
|
+
end
|
269
|
+
|
270
|
+
sig { params(hash_node: AST::Node).returns(T::Array[AST::Node]) }
|
271
|
+
def hash_pairs(hash_node)
|
272
|
+
raise TypeError unless hash?(hash_node)
|
273
|
+
|
274
|
+
# (hash (pair (int 1) (int 2)) (pair (int 3) (int 4)))
|
275
|
+
# "{1 => 2, 3 => 4}"
|
276
|
+
hash_node.children.select { |n| type_of(n) == HASH_PAIR }
|
277
|
+
end
|
278
|
+
|
279
|
+
sig { params(block_node: AST::Node).returns(AST::Node) }
|
280
|
+
def method_call_node(block_node)
|
281
|
+
raise TypeError unless type_of(block_node) == BLOCK
|
282
|
+
|
283
|
+
# (block (send (lvar :foo) :bar) (args) (int 42))
|
284
|
+
# "foo.bar do 42 end"
|
285
|
+
block_node.children[0]
|
286
|
+
end
|
287
|
+
|
288
|
+
sig { params(node: AST::Node).returns(T::Boolean) }
|
289
|
+
def module_creation?(node)
|
290
|
+
# "Class.new"
|
291
|
+
# "Module.new"
|
292
|
+
method_call?(node) &&
|
293
|
+
dynamic_class_creation?(receiver(node)) &&
|
294
|
+
method_name(node) == :new
|
295
|
+
end
|
296
|
+
|
297
|
+
sig { params(node: T.nilable(AST::Node)).returns(T::Boolean) }
|
298
|
+
def dynamic_class_creation?(node)
|
299
|
+
!!node &&
|
300
|
+
constant?(node) &&
|
301
|
+
["Class", "Module"].include?(constant_name(node))
|
302
|
+
end
|
303
|
+
|
304
|
+
sig { params(node: AST::Node).returns(T.nilable(String)) }
|
305
|
+
def name_from_block_definition(node)
|
306
|
+
if method_name(method_call_node(node)) == :class_eval
|
307
|
+
receiver = receiver(node)
|
308
|
+
constant_name(receiver) if receiver && constant?(receiver)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
sig { params(node: AST::Node).returns(T.nilable(String)) }
|
313
|
+
def name_part_from_definition(node)
|
314
|
+
case type_of(node)
|
315
|
+
when CLASS, MODULE, CONSTANT_ASSIGNMENT
|
316
|
+
module_name_from_definition(node)
|
317
|
+
when BLOCK
|
318
|
+
name_from_block_definition(node)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
sig { params(method_call_or_block_node: AST::Node).returns(T.nilable(AST::Node)) }
|
323
|
+
def receiver(method_call_or_block_node)
|
324
|
+
case type_of(method_call_or_block_node)
|
325
|
+
when METHOD_CALL
|
326
|
+
method_call_or_block_node.children[0]
|
327
|
+
when BLOCK
|
328
|
+
receiver(method_call_node(method_call_or_block_node))
|
329
|
+
else
|
330
|
+
raise TypeError
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
@@ -9,12 +9,12 @@ module Packwerk
|
|
9
9
|
sig do
|
10
10
|
params(
|
11
11
|
reference_extractor: ReferenceExtractor,
|
12
|
-
|
12
|
+
relative_file: String,
|
13
13
|
).void
|
14
14
|
end
|
15
|
-
def initialize(reference_extractor:,
|
15
|
+
def initialize(reference_extractor:, relative_file:)
|
16
16
|
@reference_extractor = reference_extractor
|
17
|
-
@
|
17
|
+
@relative_file = relative_file
|
18
18
|
end
|
19
19
|
|
20
20
|
sig do
|
@@ -24,8 +24,9 @@ module Packwerk
|
|
24
24
|
).returns(T.nilable(UnresolvedReference))
|
25
25
|
end
|
26
26
|
def call(node, ancestors)
|
27
|
-
return unless
|
28
|
-
|
27
|
+
return unless NodeHelpers.method_call?(node) || NodeHelpers.constant?(node)
|
28
|
+
|
29
|
+
@reference_extractor.reference_from_node(node, ancestors: ancestors, relative_file: @relative_file)
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
@@ -9,11 +9,11 @@ module Packwerk
|
|
9
9
|
const :context_provider, Packwerk::ConstantDiscovery
|
10
10
|
const :constant_name_inspectors, T::Array[ConstantNameInspector]
|
11
11
|
|
12
|
-
sig { params(
|
13
|
-
def for(
|
12
|
+
sig { params(relative_file: String, node: AST::Node).returns(NodeProcessor) }
|
13
|
+
def for(relative_file:, node:)
|
14
14
|
::Packwerk::NodeProcessor.new(
|
15
15
|
reference_extractor: reference_extractor(node: node),
|
16
|
-
|
16
|
+
relative_file: relative_file,
|
17
17
|
)
|
18
18
|
end
|
19
19
|
|
@@ -31,6 +31,7 @@ module Packwerk
|
|
31
31
|
end
|
32
32
|
def listed?(offense)
|
33
33
|
return false unless offense.is_a?(ReferenceOffense)
|
34
|
+
|
34
35
|
reference = offense.reference
|
35
36
|
deprecated_references_for(reference.source_package).listed?(reference, violation_type: offense.violation_type)
|
36
37
|
end
|
@@ -49,16 +50,17 @@ module Packwerk
|
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
52
|
-
sig { returns(T::Boolean) }
|
53
|
-
def stale_violations?
|
54
|
-
@deprecated_references.values.any?
|
53
|
+
sig { params(for_files: T::Set[String]).returns(T::Boolean) }
|
54
|
+
def stale_violations?(for_files)
|
55
|
+
@deprecated_references.values.any? do |deprecated_references|
|
56
|
+
deprecated_references.stale_violations?(for_files)
|
57
|
+
end
|
55
58
|
end
|
56
59
|
|
57
|
-
sig { void }
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
end
|
60
|
+
sig { params(package_set: Packwerk::PackageSet).void }
|
61
|
+
def persist_deprecated_references_files(package_set)
|
62
|
+
dump_deprecated_references_files
|
63
|
+
cleanup_extra_deprecated_references_files(package_set)
|
62
64
|
end
|
63
65
|
|
64
66
|
sig { returns(T::Array[Packwerk::Offense]) }
|
@@ -68,6 +70,23 @@ module Packwerk
|
|
68
70
|
|
69
71
|
private
|
70
72
|
|
73
|
+
sig { params(package_set: Packwerk::PackageSet).void }
|
74
|
+
def cleanup_extra_deprecated_references_files(package_set)
|
75
|
+
packages_without_todos = (package_set.packages.values - @deprecated_references.keys)
|
76
|
+
|
77
|
+
packages_without_todos.each do |package|
|
78
|
+
Packwerk::DeprecatedReferences.new(
|
79
|
+
package,
|
80
|
+
deprecated_references_file_for(package),
|
81
|
+
).delete_if_exists
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
sig { void }
|
86
|
+
def dump_deprecated_references_files
|
87
|
+
@deprecated_references.each_value(&:dump)
|
88
|
+
end
|
89
|
+
|
71
90
|
sig { params(package: Packwerk::Package).returns(Packwerk::DeprecatedReferences) }
|
72
91
|
def deprecated_references_for(package)
|
73
92
|
@deprecated_references[package] ||= Packwerk::DeprecatedReferences.new(
|
@@ -12,8 +12,8 @@ module Packwerk
|
|
12
12
|
def show_offenses(offenses)
|
13
13
|
end
|
14
14
|
|
15
|
-
sig { abstract.params(offense_collection: Packwerk::OffenseCollection).returns(String) }
|
16
|
-
def show_stale_violations(offense_collection)
|
15
|
+
sig { abstract.params(offense_collection: Packwerk::OffenseCollection, for_files: T::Set[String]).returns(String) }
|
16
|
+
def show_stale_violations(offense_collection, for_files)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/lib/packwerk/package.rb
CHANGED
@@ -13,6 +13,7 @@ module Packwerk
|
|
13
13
|
|
14
14
|
sig { returns(String) }
|
15
15
|
attr_reader :name
|
16
|
+
|
16
17
|
sig { returns(T::Array[String]) }
|
17
18
|
attr_reader :dependencies
|
18
19
|
|
@@ -42,6 +43,7 @@ module Packwerk
|
|
42
43
|
sig { params(path: String).returns(T::Boolean) }
|
43
44
|
def package_path?(path)
|
44
45
|
return true if root?
|
46
|
+
|
45
47
|
path.start_with?(@name)
|
46
48
|
end
|
47
49
|
|
@@ -74,6 +76,7 @@ module Packwerk
|
|
74
76
|
sig { params(other: T.untyped).returns(T.nilable(Integer)) }
|
75
77
|
def <=>(other)
|
76
78
|
return nil unless other.is_a?(self.class)
|
79
|
+
|
77
80
|
name <=> other.name
|
78
81
|
end
|
79
82
|
|
data/lib/packwerk/package_set.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "pathname"
|
5
|
+
require "bundler"
|
5
6
|
|
6
7
|
module Packwerk
|
7
8
|
PathSpec = T.type_alias { T.any(String, T::Array[String]) }
|
@@ -59,6 +60,7 @@ module Packwerk
|
|
59
60
|
sig { params(packages: T::Array[Package]).void }
|
60
61
|
def create_root_package_if_none_in(packages)
|
61
62
|
return if packages.any?(&:root?)
|
63
|
+
|
62
64
|
packages << Package.new(name: Package::ROOT_PACKAGE_NAME, config: nil)
|
63
65
|
end
|
64
66
|
|
data/lib/packwerk/parse_run.rb
CHANGED
@@ -14,14 +14,14 @@ module Packwerk
|
|
14
14
|
|
15
15
|
sig do
|
16
16
|
params(
|
17
|
-
|
17
|
+
relative_file_set: FilesForProcessing::RelativeFileSet,
|
18
18
|
configuration: Configuration,
|
19
19
|
progress_formatter: Formatters::ProgressFormatter,
|
20
20
|
offenses_formatter: OffensesFormatter,
|
21
21
|
).void
|
22
22
|
end
|
23
23
|
def initialize(
|
24
|
-
|
24
|
+
relative_file_set:,
|
25
25
|
configuration:,
|
26
26
|
progress_formatter: Formatters::ProgressFormatter.new(StringIO.new),
|
27
27
|
offenses_formatter: Formatters::OffensesFormatter.new
|
@@ -29,23 +29,30 @@ module Packwerk
|
|
29
29
|
@configuration = configuration
|
30
30
|
@progress_formatter = progress_formatter
|
31
31
|
@offenses_formatter = offenses_formatter
|
32
|
-
@
|
32
|
+
@relative_file_set = relative_file_set
|
33
33
|
end
|
34
34
|
|
35
35
|
sig { returns(Result) }
|
36
36
|
def detect_stale_violations
|
37
|
-
|
37
|
+
warn(<<~WARNING.squish)
|
38
|
+
DEPRECATION WARNING: `detect-stale-violation` is deprecated,
|
39
|
+
the output of `check` includes stale references.
|
40
|
+
WARNING
|
38
41
|
|
39
|
-
|
40
|
-
|
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)
|
41
47
|
|
42
48
|
Result.new(message: message, status: result_status)
|
43
49
|
end
|
44
50
|
|
45
51
|
sig { returns(Result) }
|
46
52
|
def update_deprecations
|
47
|
-
|
48
|
-
offense_collection
|
53
|
+
run_context = Packwerk::RunContext.from_configuration(@configuration)
|
54
|
+
offense_collection = find_offenses(run_context)
|
55
|
+
offense_collection.persist_deprecated_references_files(run_context.package_set)
|
49
56
|
|
50
57
|
message = <<~EOS
|
51
58
|
#{@offenses_formatter.show_offenses(offense_collection.errors)}
|
@@ -57,29 +64,31 @@ module Packwerk
|
|
57
64
|
|
58
65
|
sig { returns(Result) }
|
59
66
|
def check
|
60
|
-
|
67
|
+
run_context = Packwerk::RunContext.from_configuration(@configuration)
|
68
|
+
offense_collection = find_offenses(run_context, show_errors: true)
|
61
69
|
|
62
70
|
messages = [
|
63
71
|
@offenses_formatter.show_offenses(offense_collection.outstanding_offenses),
|
64
|
-
@offenses_formatter.show_stale_violations(offense_collection),
|
72
|
+
@offenses_formatter.show_stale_violations(offense_collection, @relative_file_set),
|
65
73
|
]
|
66
|
-
|
74
|
+
|
75
|
+
result_status = offense_collection.outstanding_offenses.empty? &&
|
76
|
+
!offense_collection.stale_violations?(@relative_file_set)
|
67
77
|
|
68
78
|
Result.new(message: messages.join("\n") + "\n", status: result_status)
|
69
79
|
end
|
70
80
|
|
71
81
|
private
|
72
82
|
|
73
|
-
sig { params(show_errors: T::Boolean).returns(OffenseCollection) }
|
74
|
-
def find_offenses(show_errors: false)
|
83
|
+
sig { params(run_context: Packwerk::RunContext, show_errors: T::Boolean).returns(OffenseCollection) }
|
84
|
+
def find_offenses(run_context, show_errors: false)
|
75
85
|
offense_collection = OffenseCollection.new(@configuration.root_path)
|
76
|
-
@progress_formatter.started(@
|
86
|
+
@progress_formatter.started(@relative_file_set)
|
77
87
|
|
78
|
-
run_context = Packwerk::RunContext.from_configuration(@configuration)
|
79
88
|
all_offenses = T.let([], T::Array[Offense])
|
80
89
|
|
81
|
-
process_file = T.let(->
|
82
|
-
run_context.process_file(
|
90
|
+
process_file = T.let(->(relative_file) do
|
91
|
+
run_context.process_file(relative_file: relative_file).tap do |offenses|
|
83
92
|
failed = show_errors && offenses.any? { |offense| !offense_collection.listed?(offense) }
|
84
93
|
update_progress(failed: failed)
|
85
94
|
end
|
@@ -87,7 +96,7 @@ module Packwerk
|
|
87
96
|
|
88
97
|
execution_time = Benchmark.realtime do
|
89
98
|
all_offenses = if @configuration.parallel?
|
90
|
-
Parallel.flat_map(@
|
99
|
+
Parallel.flat_map(@relative_file_set, &process_file)
|
91
100
|
else
|
92
101
|
serial_find_offenses(&process_file)
|
93
102
|
end
|
@@ -103,8 +112,8 @@ module Packwerk
|
|
103
112
|
def serial_find_offenses(&block)
|
104
113
|
all_offenses = T.let([], T::Array[Offense])
|
105
114
|
begin
|
106
|
-
@
|
107
|
-
offenses =
|
115
|
+
@relative_file_set.each do |relative_file|
|
116
|
+
offenses = yield(relative_file)
|
108
117
|
all_offenses.concat(offenses)
|
109
118
|
end
|
110
119
|
rescue Interrupt
|
@@ -6,6 +6,21 @@ 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
|
+
class << self
|
10
|
+
# What fully qualified constants can this constant refer to in this context?
|
11
|
+
def reference_qualifications(constant_name, namespace_path:)
|
12
|
+
return [constant_name] if constant_name.start_with?("::")
|
13
|
+
|
14
|
+
resolved_constant_name = "::#{constant_name}"
|
15
|
+
|
16
|
+
possible_namespaces = namespace_path.each_with_object([""]) do |current, acc|
|
17
|
+
acc << "#{acc.last}::#{current}" if acc.last && current
|
18
|
+
end
|
19
|
+
|
20
|
+
possible_namespaces.map { |namespace| namespace + resolved_constant_name }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
9
24
|
def initialize(root_node:)
|
10
25
|
@local_definitions = {}
|
11
26
|
|
@@ -21,35 +36,23 @@ module Packwerk
|
|
21
36
|
end
|
22
37
|
end
|
23
38
|
|
24
|
-
# What fully qualified constants can this constant refer to in this context?
|
25
|
-
def self.reference_qualifications(constant_name, namespace_path:)
|
26
|
-
return [constant_name] if constant_name.start_with?("::")
|
27
|
-
|
28
|
-
resolved_constant_name = "::#{constant_name}"
|
29
|
-
|
30
|
-
possible_namespaces = namespace_path.each_with_object([""]) do |current, acc|
|
31
|
-
acc << "#{acc.last}::#{current}" if acc.last && current
|
32
|
-
end
|
33
|
-
|
34
|
-
possible_namespaces.map { |namespace| namespace + resolved_constant_name }
|
35
|
-
end
|
36
|
-
|
37
39
|
private
|
38
40
|
|
39
41
|
def collect_local_definitions_from_root(node, current_namespace_path = [])
|
40
|
-
if
|
41
|
-
add_definition(
|
42
|
-
elsif
|
42
|
+
if NodeHelpers.constant_assignment?(node)
|
43
|
+
add_definition(NodeHelpers.constant_name(node), current_namespace_path, NodeHelpers.name_location(node))
|
44
|
+
elsif NodeHelpers.module_name_from_definition(node)
|
43
45
|
# handle compact constant nesting (e.g. "module Sales::Order")
|
44
46
|
tempnode = node
|
45
|
-
while (tempnode =
|
46
|
-
add_definition(
|
47
|
+
while (tempnode = NodeHelpers.each_child(tempnode).find { |n| NodeHelpers.constant?(n) })
|
48
|
+
add_definition(NodeHelpers.constant_name(tempnode), current_namespace_path,
|
49
|
+
NodeHelpers.name_location(tempnode))
|
47
50
|
end
|
48
51
|
|
49
|
-
current_namespace_path +=
|
52
|
+
current_namespace_path += NodeHelpers.class_or_module_name(node).split("::")
|
50
53
|
end
|
51
54
|
|
52
|
-
|
55
|
+
NodeHelpers.each_child(node) { |child| collect_local_definitions_from_root(child, current_namespace_path) }
|
53
56
|
end
|
54
57
|
|
55
58
|
def add_definition(constant_name, current_namespace_path, location)
|