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