packwerk 2.2.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +29 -20
  3. data/.github/workflows/cla.yml +22 -0
  4. data/.rubocop.yml +48 -19
  5. data/Gemfile +7 -2
  6. data/Gemfile.lock +201 -175
  7. data/README.md +1 -1
  8. data/RESOLVING_VIOLATIONS.md +81 -0
  9. data/Rakefile +1 -1
  10. data/USAGE.md +14 -5
  11. data/bin/m +1 -1
  12. data/bin/rake +1 -1
  13. data/bin/rubocop +1 -1
  14. data/bin/srb +1 -1
  15. data/bin/tapioca +1 -1
  16. data/gemfiles/Gemfile-rails-6-0 +1 -1
  17. data/gemfiles/Gemfile-rails-6-1 +22 -0
  18. data/lib/packwerk/application_validator.rb +7 -6
  19. data/lib/packwerk/association_inspector.rb +17 -15
  20. data/lib/packwerk/cache.rb +36 -29
  21. data/lib/packwerk/cli.rb +5 -6
  22. data/lib/packwerk/const_node_inspector.rb +8 -7
  23. data/lib/packwerk/constant_name_inspector.rb +2 -2
  24. data/lib/packwerk/deprecated_references.rb +34 -19
  25. data/lib/packwerk/file_processor.rb +14 -14
  26. data/lib/packwerk/files_for_processing.rb +27 -31
  27. data/lib/packwerk/formatters/offenses_formatter.rb +3 -3
  28. data/lib/packwerk/formatters/progress_formatter.rb +2 -2
  29. data/lib/packwerk/node.rb +1 -294
  30. data/lib/packwerk/node_helpers.rb +335 -0
  31. data/lib/packwerk/node_processor.rb +6 -5
  32. data/lib/packwerk/node_processor_factory.rb +3 -3
  33. data/lib/packwerk/node_visitor.rb +1 -1
  34. data/lib/packwerk/offense_collection.rb +6 -3
  35. data/lib/packwerk/offenses_formatter.rb +2 -2
  36. data/lib/packwerk/package.rb +3 -0
  37. data/lib/packwerk/package_set.rb +2 -0
  38. data/lib/packwerk/parse_run.rb +15 -13
  39. data/lib/packwerk/parsed_constant_definitions.rb +23 -20
  40. data/lib/packwerk/parsers/erb.rb +3 -3
  41. data/lib/packwerk/reference_checking/checkers/checker.rb +16 -3
  42. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +16 -0
  43. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +18 -0
  44. data/lib/packwerk/reference_checking/reference_checker.rb +3 -1
  45. data/lib/packwerk/reference_extractor.rb +51 -48
  46. data/lib/packwerk/reference_offense.rb +3 -27
  47. data/lib/packwerk/run_context.rb +3 -3
  48. data/lib/packwerk/spring_command.rb +1 -1
  49. data/lib/packwerk/version.rb +1 -1
  50. data/lib/packwerk.rb +1 -0
  51. data/packwerk.gemspec +4 -12
  52. data/sorbet/rbi/gems/actioncable@7.0.3.1.rbi +2754 -0
  53. data/sorbet/rbi/gems/actionmailbox@7.0.3.1.rbi +1496 -0
  54. data/sorbet/rbi/gems/actionmailer@7.0.3.1.rbi +2362 -0
  55. data/sorbet/rbi/gems/actionpack@7.0.3.1.rbi +19397 -0
  56. data/sorbet/rbi/gems/actiontext@7.0.3.1.rbi +1569 -0
  57. data/sorbet/rbi/gems/actionview@7.0.3.1.rbi +14907 -0
  58. data/sorbet/rbi/gems/activejob@7.0.3.1.rbi +2553 -0
  59. data/sorbet/rbi/gems/activemodel@7.0.3.1.rbi +5999 -0
  60. data/sorbet/rbi/gems/activerecord@7.0.3.1.rbi +37832 -0
  61. data/sorbet/rbi/gems/activestorage@7.0.3.1.rbi +2321 -0
  62. data/sorbet/rbi/gems/activesupport@7.0.3.1.rbi +18818 -0
  63. data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +11722 -0
  64. data/sorbet/rbi/gems/constant_resolver@0.2.0.rbi +90 -0
  65. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1079 -0
  66. data/sorbet/rbi/gems/digest@3.1.0.rbi +189 -0
  67. data/sorbet/rbi/gems/erubi@1.11.0.rbi +140 -0
  68. data/sorbet/rbi/gems/globalid@1.0.0.rbi +572 -0
  69. data/sorbet/rbi/gems/i18n@1.12.0.rbi +2296 -0
  70. data/sorbet/rbi/gems/json@2.6.2.rbi +1548 -0
  71. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +8 -0
  72. data/sorbet/rbi/gems/loofah@2.18.0.rbi +877 -0
  73. data/sorbet/rbi/gems/m@1.6.0.rbi +257 -0
  74. data/sorbet/rbi/gems/marcel@1.0.2.rbi +220 -0
  75. data/sorbet/rbi/gems/mini_mime@1.1.2.rbi +170 -0
  76. data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +8 -0
  77. data/sorbet/rbi/gems/minitest-focus@1.3.1.rbi +104 -0
  78. data/sorbet/rbi/gems/minitest@5.16.2.rbi +2136 -0
  79. data/sorbet/rbi/gems/mocha@1.14.0.rbi +4177 -0
  80. data/sorbet/rbi/gems/net-imap@0.2.3.rbi +2147 -0
  81. data/sorbet/rbi/gems/net-pop@0.1.1.rbi +926 -0
  82. data/sorbet/rbi/gems/net-protocol@0.1.3.rbi +11 -0
  83. data/sorbet/rbi/gems/net-smtp@0.3.1.rbi +1108 -0
  84. data/sorbet/rbi/gems/netrc@0.11.0.rbi +153 -0
  85. data/sorbet/rbi/gems/nio4r@2.5.8.rbi +292 -0
  86. data/sorbet/rbi/gems/nokogiri@1.13.8.rbi +6478 -0
  87. data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
  88. data/sorbet/rbi/gems/parser@3.1.2.1.rbi +9029 -0
  89. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +8 -0
  90. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  91. data/sorbet/rbi/gems/racc@1.6.0.rbi +152 -0
  92. data/sorbet/rbi/gems/rack-test@2.0.2.rbi +953 -0
  93. data/sorbet/rbi/gems/rack@2.2.4.rbi +5636 -0
  94. data/sorbet/rbi/gems/rails-html-sanitizer@1.4.3.rbi +688 -0
  95. data/sorbet/rbi/gems/rails@7.0.3.1.rbi +8 -0
  96. data/sorbet/rbi/gems/railties@7.0.3.1.rbi +3507 -0
  97. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +392 -0
  98. data/sorbet/rbi/gems/rake@13.0.6.rbi +2924 -0
  99. data/sorbet/rbi/gems/rbi@0.0.15.rbi +3007 -0
  100. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +3383 -0
  101. data/sorbet/rbi/gems/rexml@3.2.5.rbi +4714 -0
  102. data/sorbet/rbi/gems/rubocop-ast@1.21.0.rbi +6961 -0
  103. data/sorbet/rbi/gems/rubocop-performance@1.14.3.rbi +2986 -0
  104. data/sorbet/rbi/gems/{rubocop-shopify@2.0.1.rbi → rubocop-shopify@2.9.0.rbi} +4 -4
  105. data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +992 -0
  106. data/sorbet/rbi/gems/rubocop@1.34.1.rbi +51820 -0
  107. data/sorbet/rbi/gems/ruby-lsp@0.2.1.rbi +11 -0
  108. data/sorbet/rbi/gems/smart_properties@1.17.0.rbi +474 -0
  109. data/sorbet/rbi/gems/spoom@1.1.11.rbi +2181 -0
  110. data/sorbet/rbi/gems/spring@4.0.0.rbi +411 -0
  111. data/sorbet/rbi/gems/strscan@3.0.4.rbi +8 -0
  112. data/sorbet/rbi/gems/syntax_tree@3.3.0.rbi +8 -0
  113. data/sorbet/rbi/gems/tapioca@0.9.2.rbi +3181 -0
  114. data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
  115. data/sorbet/rbi/gems/timeout@0.3.0.rbi +142 -0
  116. data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +5896 -0
  117. data/sorbet/rbi/gems/unicode-display_width@2.2.0.rbi +48 -0
  118. data/sorbet/rbi/gems/unparser@0.6.5.rbi +4529 -0
  119. data/sorbet/rbi/gems/webrick@1.7.0.rbi +2582 -0
  120. data/sorbet/rbi/gems/websocket-driver@0.7.5.rbi +993 -0
  121. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +388 -0
  122. data/sorbet/rbi/gems/yard@0.9.28.rbi +18242 -0
  123. data/sorbet/rbi/gems/zeitwerk@2.6.0.rbi +867 -0
  124. data/sorbet/rbi/shims/psych.rbi +5 -0
  125. data/sorbet/tapioca/require.rb +2 -3
  126. metadata +88 -157
  127. data/.github/probots.yml +0 -2
  128. data/library.yml +0 -6
  129. data/service.yml +0 -1
  130. data/sorbet/rbi/gems/actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -860
  131. data/sorbet/rbi/gems/actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -568
  132. data/sorbet/rbi/gems/actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -587
  133. data/sorbet/rbi/gems/actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -5314
  134. data/sorbet/rbi/gems/actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -699
  135. data/sorbet/rbi/gems/actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -2515
  136. data/sorbet/rbi/gems/activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -624
  137. data/sorbet/rbi/gems/activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -1248
  138. data/sorbet/rbi/gems/activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8363
  139. data/sorbet/rbi/gems/activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -876
  140. data/sorbet/rbi/gems/activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -3987
  141. data/sorbet/rbi/gems/colorize@0.8.1.rbi +0 -40
  142. data/sorbet/rbi/gems/commander@4.5.2.rbi +0 -8
  143. data/sorbet/rbi/gems/concurrent-ruby@1.1.8.rbi +0 -1969
  144. data/sorbet/rbi/gems/constant_resolver@0.1.5.rbi +0 -26
  145. data/sorbet/rbi/gems/erubi@1.10.0.rbi +0 -41
  146. data/sorbet/rbi/gems/globalid@0.4.2.rbi +0 -178
  147. data/sorbet/rbi/gems/highline@2.0.3.rbi +0 -8
  148. data/sorbet/rbi/gems/i18n@1.8.10.rbi +0 -600
  149. data/sorbet/rbi/gems/loofah@2.9.0.rbi +0 -274
  150. data/sorbet/rbi/gems/m@1.5.1.rbi +0 -108
  151. data/sorbet/rbi/gems/marcel@1.0.0.rbi +0 -70
  152. data/sorbet/rbi/gems/mini_mime@1.0.3.rbi +0 -71
  153. data/sorbet/rbi/gems/minitest-focus@1.2.1.rbi +0 -8
  154. data/sorbet/rbi/gems/minitest@5.14.4.rbi +0 -544
  155. data/sorbet/rbi/gems/mocha@1.12.0.rbi +0 -953
  156. data/sorbet/rbi/gems/nio4r@2.5.7.rbi +0 -90
  157. data/sorbet/rbi/gems/nokogiri@1.11.2.rbi +0 -1647
  158. data/sorbet/rbi/gems/parallel@1.20.1.rbi +0 -117
  159. data/sorbet/rbi/gems/parlour@6.0.0.rbi +0 -1272
  160. data/sorbet/rbi/gems/parser@3.0.0.0.rbi +0 -1745
  161. data/sorbet/rbi/gems/pry@0.14.0.rbi +0 -8
  162. data/sorbet/rbi/gems/psych@3.3.2.rbi +0 -24
  163. data/sorbet/rbi/gems/racc@1.5.2.rbi +0 -57
  164. data/sorbet/rbi/gems/rack-test@1.1.0.rbi +0 -335
  165. data/sorbet/rbi/gems/rack@2.2.3.rbi +0 -1718
  166. data/sorbet/rbi/gems/rails-html-sanitizer@1.3.0.rbi +0 -213
  167. data/sorbet/rbi/gems/rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8
  168. data/sorbet/rbi/gems/railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -880
  169. data/sorbet/rbi/gems/rainbow@3.0.0.rbi +0 -155
  170. data/sorbet/rbi/gems/rake@13.0.3.rbi +0 -837
  171. data/sorbet/rbi/gems/regexp_parser@2.1.1.rbi +0 -8
  172. data/sorbet/rbi/gems/rexml@3.2.4.rbi +0 -8
  173. data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +0 -8
  174. data/sorbet/rbi/gems/rubocop-performance@1.10.2.rbi +0 -8
  175. data/sorbet/rbi/gems/rubocop-sorbet@0.6.1.rbi +0 -8
  176. data/sorbet/rbi/gems/rubocop@1.12.0.rbi +0 -8
  177. data/sorbet/rbi/gems/smart_properties@1.15.0.rbi +0 -168
  178. data/sorbet/rbi/gems/spoom@1.1.0.rbi +0 -1061
  179. data/sorbet/rbi/gems/spring@2.1.1.rbi +0 -160
  180. data/sorbet/rbi/gems/sprockets-rails@3.2.2.rbi +0 -451
  181. data/sorbet/rbi/gems/sprockets@4.0.2.rbi +0 -1133
  182. data/sorbet/rbi/gems/tapioca@0.4.19.rbi +0 -603
  183. data/sorbet/rbi/gems/thor@1.1.0.rbi +0 -893
  184. data/sorbet/rbi/gems/tzinfo@2.0.4.rbi +0 -566
  185. data/sorbet/rbi/gems/unicode-display_width@2.0.0.rbi +0 -8
  186. data/sorbet/rbi/gems/websocket-driver@0.7.3.rbi +0 -438
  187. 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
- absolute_file: String,
12
+ relative_file: String,
13
13
  ).void
14
14
  end
15
- def initialize(reference_extractor:, absolute_file:)
15
+ def initialize(reference_extractor:, relative_file:)
16
16
  @reference_extractor = reference_extractor
17
- @absolute_file = absolute_file
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 Node.method_call?(node) || Node.constant?(node)
28
- @reference_extractor.reference_from_node(node, ancestors: ancestors, absolute_file: @absolute_file)
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(absolute_file: String, node: AST::Node).returns(NodeProcessor) }
13
- def for(absolute_file:, node:)
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
- absolute_file: absolute_file,
16
+ relative_file: relative_file,
17
17
  )
18
18
  end
19
19
 
@@ -16,7 +16,7 @@ module Packwerk
16
16
  result << reference if reference
17
17
 
18
18
  child_ancestors = [node] + ancestors
19
- Node.each_child(node) do |child|
19
+ NodeHelpers.each_child(node) do |child|
20
20
  visit(child, ancestors: child_ancestors, result: result)
21
21
  end
22
22
  end
@@ -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?(&:stale_violations?)
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
@@ -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
 
@@ -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
 
@@ -14,14 +14,14 @@ module Packwerk
14
14
 
15
15
  sig do
16
16
  params(
17
- absolute_file_set: FilesForProcessing::AbsoluteFileSet,
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
- absolute_file_set:,
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
- @absolute_file_set = absolute_file_set
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
- result_status = offense_collection.outstanding_offenses.empty? && !offense_collection.stale_violations?
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(@absolute_file_set)
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(-> (absolute_file) do
82
- run_context.process_file(absolute_file: absolute_file).tap do |offenses|
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(@absolute_file_set, &process_file)
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
- @absolute_file_set.each do |absolute_file|
107
- offenses = block.call(absolute_file)
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 Node.constant_assignment?(node)
41
- add_definition(Node.constant_name(node), current_namespace_path, Node.name_location(node))
42
- elsif Node.module_name_from_definition(node)
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 = Node.each_child(tempnode).find { |n| Node.constant?(n) })
46
- add_definition(Node.constant_name(tempnode), current_namespace_path, Node.name_location(tempnode))
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 += Node.class_or_module_name(node).split("::")
52
+ current_namespace_path += NodeHelpers.class_or_module_name(node).split("::")
50
53
  end
51
54
 
52
- Node.each_child(node) { |child| collect_local_definitions_from_root(child, current_namespace_path) }
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)
@@ -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 block_given?
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) { |n| yield n }
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
- interface!
11
+ abstract!
12
12
 
13
- sig { returns(ViolationType).abstract }
13
+ sig { abstract.returns(ViolationType) }
14
14
  def violation_type; end
15
15
 
16
- sig { params(reference: Reference).returns(T::Boolean).abstract }
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