packwerk 2.2.0 → 2.2.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 +29 -20
- data/.github/workflows/cla.yml +22 -0
- data/.rubocop.yml +48 -19
- data/Gemfile +7 -2
- data/Gemfile.lock +201 -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_validator.rb +7 -6
- data/lib/packwerk/association_inspector.rb +17 -15
- data/lib/packwerk/cache.rb +36 -29
- data/lib/packwerk/cli.rb +5 -6
- data/lib/packwerk/const_node_inspector.rb +8 -7
- data/lib/packwerk/constant_name_inspector.rb +2 -2
- data/lib/packwerk/deprecated_references.rb +34 -19
- 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 +6 -3
- 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 +15 -13
- 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 +3 -3
- data/lib/packwerk/spring_command.rb +1 -1
- data/lib/packwerk/version.rb +1 -1
- data/lib/packwerk.rb +1 -0
- data/packwerk.gemspec +4 -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 +88 -157
- 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,9 +50,11 @@ 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
60
|
sig { void }
|
|
@@ -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,15 +29,15 @@ 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
|
offense_collection = find_offenses
|
|
38
38
|
|
|
39
|
-
result_status = !offense_collection.stale_violations?
|
|
40
|
-
message = @offenses_formatter.show_stale_violations(offense_collection)
|
|
39
|
+
result_status = !offense_collection.stale_violations?(@relative_file_set)
|
|
40
|
+
message = @offenses_formatter.show_stale_violations(offense_collection, @relative_file_set)
|
|
41
41
|
|
|
42
42
|
Result.new(message: message, status: result_status)
|
|
43
43
|
end
|
|
@@ -61,9 +61,11 @@ module Packwerk
|
|
|
61
61
|
|
|
62
62
|
messages = [
|
|
63
63
|
@offenses_formatter.show_offenses(offense_collection.outstanding_offenses),
|
|
64
|
-
@offenses_formatter.show_stale_violations(offense_collection),
|
|
64
|
+
@offenses_formatter.show_stale_violations(offense_collection, @relative_file_set),
|
|
65
65
|
]
|
|
66
|
-
|
|
66
|
+
|
|
67
|
+
result_status = offense_collection.outstanding_offenses.empty? &&
|
|
68
|
+
!offense_collection.stale_violations?(@relative_file_set)
|
|
67
69
|
|
|
68
70
|
Result.new(message: messages.join("\n") + "\n", status: result_status)
|
|
69
71
|
end
|
|
@@ -73,13 +75,13 @@ module Packwerk
|
|
|
73
75
|
sig { params(show_errors: T::Boolean).returns(OffenseCollection) }
|
|
74
76
|
def find_offenses(show_errors: false)
|
|
75
77
|
offense_collection = OffenseCollection.new(@configuration.root_path)
|
|
76
|
-
@progress_formatter.started(@
|
|
78
|
+
@progress_formatter.started(@relative_file_set)
|
|
77
79
|
|
|
78
80
|
run_context = Packwerk::RunContext.from_configuration(@configuration)
|
|
79
81
|
all_offenses = T.let([], T::Array[Offense])
|
|
80
82
|
|
|
81
|
-
process_file = T.let(->
|
|
82
|
-
run_context.process_file(
|
|
83
|
+
process_file = T.let(->(relative_file) do
|
|
84
|
+
run_context.process_file(relative_file: relative_file).tap do |offenses|
|
|
83
85
|
failed = show_errors && offenses.any? { |offense| !offense_collection.listed?(offense) }
|
|
84
86
|
update_progress(failed: failed)
|
|
85
87
|
end
|
|
@@ -87,7 +89,7 @@ module Packwerk
|
|
|
87
89
|
|
|
88
90
|
execution_time = Benchmark.realtime do
|
|
89
91
|
all_offenses = if @configuration.parallel?
|
|
90
|
-
Parallel.flat_map(@
|
|
92
|
+
Parallel.flat_map(@relative_file_set, &process_file)
|
|
91
93
|
else
|
|
92
94
|
serial_find_offenses(&process_file)
|
|
93
95
|
end
|
|
@@ -103,8 +105,8 @@ module Packwerk
|
|
|
103
105
|
def serial_find_offenses(&block)
|
|
104
106
|
all_offenses = T.let([], T::Array[Offense])
|
|
105
107
|
begin
|
|
106
|
-
@
|
|
107
|
-
offenses =
|
|
108
|
+
@relative_file_set.each do |relative_file|
|
|
109
|
+
offenses = yield(relative_file)
|
|
108
110
|
all_offenses.concat(offenses)
|
|
109
111
|
end
|
|
110
112
|
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)
|
data/lib/packwerk/parsers/erb.rb
CHANGED
|
@@ -49,8 +49,8 @@ module Packwerk
|
|
|
49
49
|
)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
def code_nodes(node)
|
|
53
|
-
return enum_for(:code_nodes, node) unless
|
|
52
|
+
def code_nodes(node, &block)
|
|
53
|
+
return enum_for(:code_nodes, node) unless block
|
|
54
54
|
return unless node.is_a?(::AST::Node)
|
|
55
55
|
|
|
56
56
|
yield node if node.type == :code
|
|
@@ -62,7 +62,7 @@ module Packwerk
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
node.children.each do |child|
|
|
65
|
-
code_nodes(child)
|
|
65
|
+
code_nodes(child, &block)
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
end
|
|
@@ -8,13 +8,26 @@ module Packwerk
|
|
|
8
8
|
extend T::Sig
|
|
9
9
|
extend T::Helpers
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
abstract!
|
|
12
12
|
|
|
13
|
-
sig { returns(ViolationType)
|
|
13
|
+
sig { abstract.returns(ViolationType) }
|
|
14
14
|
def violation_type; end
|
|
15
15
|
|
|
16
|
-
sig { params(reference: Reference).returns(T::Boolean)
|
|
16
|
+
sig { abstract.params(reference: Reference).returns(T::Boolean) }
|
|
17
17
|
def invalid_reference?(reference); end
|
|
18
|
+
|
|
19
|
+
sig { abstract.params(reference: Reference).returns(String) }
|
|
20
|
+
def message(reference); end
|
|
21
|
+
|
|
22
|
+
sig { params(reference: Reference).returns(String) }
|
|
23
|
+
def standard_help_message(reference)
|
|
24
|
+
standard_message = <<~EOS
|
|
25
|
+
Inference details: this is a reference to #{reference.constant.name} which seems to be defined in #{reference.constant.location}.
|
|
26
|
+
To receive help interpreting or resolving this error message, see: https://github.com/Shopify/packwerk/blob/main/TROUBLESHOOT.md#Troubleshooting-violations
|
|
27
|
+
EOS
|
|
28
|
+
|
|
29
|
+
standard_message.chomp
|
|
30
|
+
end
|
|
18
31
|
end
|
|
19
32
|
end
|
|
20
33
|
end
|