packwerk 2.2.0 → 2.2.2

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 (188) 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 +202 -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_load_paths.rb +1 -1
  19. data/lib/packwerk/application_validator.rb +7 -6
  20. data/lib/packwerk/association_inspector.rb +17 -15
  21. data/lib/packwerk/cache.rb +36 -29
  22. data/lib/packwerk/cli.rb +24 -20
  23. data/lib/packwerk/const_node_inspector.rb +8 -7
  24. data/lib/packwerk/constant_name_inspector.rb +2 -2
  25. data/lib/packwerk/deprecated_references.rb +40 -20
  26. data/lib/packwerk/file_processor.rb +14 -14
  27. data/lib/packwerk/files_for_processing.rb +27 -31
  28. data/lib/packwerk/formatters/offenses_formatter.rb +3 -3
  29. data/lib/packwerk/formatters/progress_formatter.rb +2 -2
  30. data/lib/packwerk/node.rb +1 -294
  31. data/lib/packwerk/node_helpers.rb +335 -0
  32. data/lib/packwerk/node_processor.rb +6 -5
  33. data/lib/packwerk/node_processor_factory.rb +3 -3
  34. data/lib/packwerk/node_visitor.rb +1 -1
  35. data/lib/packwerk/offense_collection.rb +27 -8
  36. data/lib/packwerk/offenses_formatter.rb +2 -2
  37. data/lib/packwerk/package.rb +3 -0
  38. data/lib/packwerk/package_set.rb +2 -0
  39. data/lib/packwerk/parse_run.rb +29 -20
  40. data/lib/packwerk/parsed_constant_definitions.rb +23 -20
  41. data/lib/packwerk/parsers/erb.rb +3 -3
  42. data/lib/packwerk/reference_checking/checkers/checker.rb +16 -3
  43. data/lib/packwerk/reference_checking/checkers/dependency_checker.rb +16 -0
  44. data/lib/packwerk/reference_checking/checkers/privacy_checker.rb +18 -0
  45. data/lib/packwerk/reference_checking/reference_checker.rb +3 -1
  46. data/lib/packwerk/reference_extractor.rb +51 -48
  47. data/lib/packwerk/reference_offense.rb +3 -27
  48. data/lib/packwerk/run_context.rb +9 -8
  49. data/lib/packwerk/spring_command.rb +1 -1
  50. data/lib/packwerk/version.rb +1 -1
  51. data/lib/packwerk.rb +1 -0
  52. data/packwerk.gemspec +5 -12
  53. data/sorbet/rbi/gems/actioncable@7.0.3.1.rbi +2754 -0
  54. data/sorbet/rbi/gems/actionmailbox@7.0.3.1.rbi +1496 -0
  55. data/sorbet/rbi/gems/actionmailer@7.0.3.1.rbi +2362 -0
  56. data/sorbet/rbi/gems/actionpack@7.0.3.1.rbi +19397 -0
  57. data/sorbet/rbi/gems/actiontext@7.0.3.1.rbi +1569 -0
  58. data/sorbet/rbi/gems/actionview@7.0.3.1.rbi +14907 -0
  59. data/sorbet/rbi/gems/activejob@7.0.3.1.rbi +2553 -0
  60. data/sorbet/rbi/gems/activemodel@7.0.3.1.rbi +5999 -0
  61. data/sorbet/rbi/gems/activerecord@7.0.3.1.rbi +37832 -0
  62. data/sorbet/rbi/gems/activestorage@7.0.3.1.rbi +2321 -0
  63. data/sorbet/rbi/gems/activesupport@7.0.3.1.rbi +18818 -0
  64. data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +11722 -0
  65. data/sorbet/rbi/gems/constant_resolver@0.2.0.rbi +90 -0
  66. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1079 -0
  67. data/sorbet/rbi/gems/digest@3.1.0.rbi +189 -0
  68. data/sorbet/rbi/gems/erubi@1.11.0.rbi +140 -0
  69. data/sorbet/rbi/gems/globalid@1.0.0.rbi +572 -0
  70. data/sorbet/rbi/gems/i18n@1.12.0.rbi +2296 -0
  71. data/sorbet/rbi/gems/json@2.6.2.rbi +1548 -0
  72. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +8 -0
  73. data/sorbet/rbi/gems/loofah@2.18.0.rbi +877 -0
  74. data/sorbet/rbi/gems/m@1.6.0.rbi +257 -0
  75. data/sorbet/rbi/gems/marcel@1.0.2.rbi +220 -0
  76. data/sorbet/rbi/gems/mini_mime@1.1.2.rbi +170 -0
  77. data/sorbet/rbi/gems/mini_portile2@2.8.0.rbi +8 -0
  78. data/sorbet/rbi/gems/minitest-focus@1.3.1.rbi +104 -0
  79. data/sorbet/rbi/gems/minitest@5.16.2.rbi +2136 -0
  80. data/sorbet/rbi/gems/mocha@1.14.0.rbi +4177 -0
  81. data/sorbet/rbi/gems/net-imap@0.2.3.rbi +2147 -0
  82. data/sorbet/rbi/gems/net-pop@0.1.1.rbi +926 -0
  83. data/sorbet/rbi/gems/net-protocol@0.1.3.rbi +11 -0
  84. data/sorbet/rbi/gems/net-smtp@0.3.1.rbi +1108 -0
  85. data/sorbet/rbi/gems/netrc@0.11.0.rbi +153 -0
  86. data/sorbet/rbi/gems/nio4r@2.5.8.rbi +292 -0
  87. data/sorbet/rbi/gems/nokogiri@1.13.8.rbi +6478 -0
  88. data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
  89. data/sorbet/rbi/gems/parser@3.1.2.1.rbi +9029 -0
  90. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +8 -0
  91. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  92. data/sorbet/rbi/gems/racc@1.6.0.rbi +152 -0
  93. data/sorbet/rbi/gems/rack-test@2.0.2.rbi +953 -0
  94. data/sorbet/rbi/gems/rack@2.2.4.rbi +5636 -0
  95. data/sorbet/rbi/gems/rails-html-sanitizer@1.4.3.rbi +688 -0
  96. data/sorbet/rbi/gems/rails@7.0.3.1.rbi +8 -0
  97. data/sorbet/rbi/gems/railties@7.0.3.1.rbi +3507 -0
  98. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +392 -0
  99. data/sorbet/rbi/gems/rake@13.0.6.rbi +2924 -0
  100. data/sorbet/rbi/gems/rbi@0.0.15.rbi +3007 -0
  101. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +3383 -0
  102. data/sorbet/rbi/gems/rexml@3.2.5.rbi +4714 -0
  103. data/sorbet/rbi/gems/rubocop-ast@1.21.0.rbi +6961 -0
  104. data/sorbet/rbi/gems/rubocop-performance@1.14.3.rbi +2986 -0
  105. data/sorbet/rbi/gems/{rubocop-shopify@2.0.1.rbi → rubocop-shopify@2.9.0.rbi} +4 -4
  106. data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +992 -0
  107. data/sorbet/rbi/gems/rubocop@1.34.1.rbi +51820 -0
  108. data/sorbet/rbi/gems/ruby-lsp@0.2.1.rbi +11 -0
  109. data/sorbet/rbi/gems/smart_properties@1.17.0.rbi +474 -0
  110. data/sorbet/rbi/gems/spoom@1.1.11.rbi +2181 -0
  111. data/sorbet/rbi/gems/spring@4.0.0.rbi +411 -0
  112. data/sorbet/rbi/gems/strscan@3.0.4.rbi +8 -0
  113. data/sorbet/rbi/gems/syntax_tree@3.3.0.rbi +8 -0
  114. data/sorbet/rbi/gems/tapioca@0.9.2.rbi +3181 -0
  115. data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
  116. data/sorbet/rbi/gems/timeout@0.3.0.rbi +142 -0
  117. data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +5896 -0
  118. data/sorbet/rbi/gems/unicode-display_width@2.2.0.rbi +48 -0
  119. data/sorbet/rbi/gems/unparser@0.6.5.rbi +4529 -0
  120. data/sorbet/rbi/gems/webrick@1.7.0.rbi +2582 -0
  121. data/sorbet/rbi/gems/websocket-driver@0.7.5.rbi +993 -0
  122. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +388 -0
  123. data/sorbet/rbi/gems/yard@0.9.28.rbi +18242 -0
  124. data/sorbet/rbi/gems/zeitwerk@2.6.0.rbi +867 -0
  125. data/sorbet/rbi/shims/psych.rbi +5 -0
  126. data/sorbet/tapioca/require.rb +2 -3
  127. metadata +91 -146
  128. data/.github/probots.yml +0 -2
  129. data/library.yml +0 -6
  130. data/service.yml +0 -1
  131. data/sorbet/rbi/gems/actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -860
  132. data/sorbet/rbi/gems/actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -568
  133. data/sorbet/rbi/gems/actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -587
  134. data/sorbet/rbi/gems/actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -5314
  135. data/sorbet/rbi/gems/actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -699
  136. data/sorbet/rbi/gems/actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -2515
  137. data/sorbet/rbi/gems/activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -624
  138. data/sorbet/rbi/gems/activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -1248
  139. data/sorbet/rbi/gems/activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8363
  140. data/sorbet/rbi/gems/activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -876
  141. data/sorbet/rbi/gems/activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -3987
  142. data/sorbet/rbi/gems/colorize@0.8.1.rbi +0 -40
  143. data/sorbet/rbi/gems/commander@4.5.2.rbi +0 -8
  144. data/sorbet/rbi/gems/concurrent-ruby@1.1.8.rbi +0 -1969
  145. data/sorbet/rbi/gems/constant_resolver@0.1.5.rbi +0 -26
  146. data/sorbet/rbi/gems/erubi@1.10.0.rbi +0 -41
  147. data/sorbet/rbi/gems/globalid@0.4.2.rbi +0 -178
  148. data/sorbet/rbi/gems/highline@2.0.3.rbi +0 -8
  149. data/sorbet/rbi/gems/i18n@1.8.10.rbi +0 -600
  150. data/sorbet/rbi/gems/loofah@2.9.0.rbi +0 -274
  151. data/sorbet/rbi/gems/m@1.5.1.rbi +0 -108
  152. data/sorbet/rbi/gems/marcel@1.0.0.rbi +0 -70
  153. data/sorbet/rbi/gems/mini_mime@1.0.3.rbi +0 -71
  154. data/sorbet/rbi/gems/minitest-focus@1.2.1.rbi +0 -8
  155. data/sorbet/rbi/gems/minitest@5.14.4.rbi +0 -544
  156. data/sorbet/rbi/gems/mocha@1.12.0.rbi +0 -953
  157. data/sorbet/rbi/gems/nio4r@2.5.7.rbi +0 -90
  158. data/sorbet/rbi/gems/nokogiri@1.11.2.rbi +0 -1647
  159. data/sorbet/rbi/gems/parallel@1.20.1.rbi +0 -117
  160. data/sorbet/rbi/gems/parlour@6.0.0.rbi +0 -1272
  161. data/sorbet/rbi/gems/parser@3.0.0.0.rbi +0 -1745
  162. data/sorbet/rbi/gems/pry@0.14.0.rbi +0 -8
  163. data/sorbet/rbi/gems/psych@3.3.2.rbi +0 -24
  164. data/sorbet/rbi/gems/racc@1.5.2.rbi +0 -57
  165. data/sorbet/rbi/gems/rack-test@1.1.0.rbi +0 -335
  166. data/sorbet/rbi/gems/rack@2.2.3.rbi +0 -1718
  167. data/sorbet/rbi/gems/rails-html-sanitizer@1.3.0.rbi +0 -213
  168. data/sorbet/rbi/gems/rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -8
  169. data/sorbet/rbi/gems/railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi +0 -880
  170. data/sorbet/rbi/gems/rainbow@3.0.0.rbi +0 -155
  171. data/sorbet/rbi/gems/rake@13.0.3.rbi +0 -837
  172. data/sorbet/rbi/gems/regexp_parser@2.1.1.rbi +0 -8
  173. data/sorbet/rbi/gems/rexml@3.2.4.rbi +0 -8
  174. data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +0 -8
  175. data/sorbet/rbi/gems/rubocop-performance@1.10.2.rbi +0 -8
  176. data/sorbet/rbi/gems/rubocop-sorbet@0.6.1.rbi +0 -8
  177. data/sorbet/rbi/gems/rubocop@1.12.0.rbi +0 -8
  178. data/sorbet/rbi/gems/smart_properties@1.15.0.rbi +0 -168
  179. data/sorbet/rbi/gems/spoom@1.1.0.rbi +0 -1061
  180. data/sorbet/rbi/gems/spring@2.1.1.rbi +0 -160
  181. data/sorbet/rbi/gems/sprockets-rails@3.2.2.rbi +0 -451
  182. data/sorbet/rbi/gems/sprockets@4.0.2.rbi +0 -1133
  183. data/sorbet/rbi/gems/tapioca@0.4.19.rbi +0 -603
  184. data/sorbet/rbi/gems/thor@1.1.0.rbi +0 -893
  185. data/sorbet/rbi/gems/tzinfo@2.0.4.rbi +0 -566
  186. data/sorbet/rbi/gems/unicode-display_width@2.0.0.rbi +0 -8
  187. data/sorbet/rbi/gems/websocket-driver@0.7.3.rbi +0 -438
  188. 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,16 +50,17 @@ module Packwerk
49
50
  end
50
51
  end
51
52
 
52
- sig { returns(T::Boolean) }
53
- def stale_violations?
54
- @deprecated_references.values.any?(&: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
- sig { void }
58
- def dump_deprecated_references_files
59
- @deprecated_references.each do |_, deprecated_references_file|
60
- deprecated_references_file.dump
61
- end
60
+ sig { params(package_set: Packwerk::PackageSet).void }
61
+ def persist_deprecated_references_files(package_set)
62
+ dump_deprecated_references_files
63
+ cleanup_extra_deprecated_references_files(package_set)
62
64
  end
63
65
 
64
66
  sig { returns(T::Array[Packwerk::Offense]) }
@@ -68,6 +70,23 @@ module Packwerk
68
70
 
69
71
  private
70
72
 
73
+ sig { params(package_set: Packwerk::PackageSet).void }
74
+ def cleanup_extra_deprecated_references_files(package_set)
75
+ packages_without_todos = (package_set.packages.values - @deprecated_references.keys)
76
+
77
+ packages_without_todos.each do |package|
78
+ Packwerk::DeprecatedReferences.new(
79
+ package,
80
+ deprecated_references_file_for(package),
81
+ ).delete_if_exists
82
+ end
83
+ end
84
+
85
+ sig { void }
86
+ def dump_deprecated_references_files
87
+ @deprecated_references.each_value(&:dump)
88
+ end
89
+
71
90
  sig { params(package: Packwerk::Package).returns(Packwerk::DeprecatedReferences) }
72
91
  def deprecated_references_for(package)
73
92
  @deprecated_references[package] ||= Packwerk::DeprecatedReferences.new(
@@ -12,8 +12,8 @@ module Packwerk
12
12
  def show_offenses(offenses)
13
13
  end
14
14
 
15
- sig { abstract.params(offense_collection: Packwerk::OffenseCollection).returns(String) }
16
- def show_stale_violations(offense_collection)
15
+ sig { abstract.params(offense_collection: Packwerk::OffenseCollection, for_files: T::Set[String]).returns(String) }
16
+ def show_stale_violations(offense_collection, for_files)
17
17
  end
18
18
  end
19
19
  end
@@ -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,23 +29,30 @@ 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
- offense_collection = find_offenses
37
+ warn(<<~WARNING.squish)
38
+ DEPRECATION WARNING: `detect-stale-violation` is deprecated,
39
+ the output of `check` includes stale references.
40
+ WARNING
38
41
 
39
- result_status = !offense_collection.stale_violations?
40
- message = @offenses_formatter.show_stale_violations(offense_collection)
42
+ run_context = Packwerk::RunContext.from_configuration(@configuration)
43
+ offense_collection = find_offenses(run_context)
44
+
45
+ result_status = !offense_collection.stale_violations?(@relative_file_set)
46
+ message = @offenses_formatter.show_stale_violations(offense_collection, @relative_file_set)
41
47
 
42
48
  Result.new(message: message, status: result_status)
43
49
  end
44
50
 
45
51
  sig { returns(Result) }
46
52
  def update_deprecations
47
- offense_collection = find_offenses
48
- offense_collection.dump_deprecated_references_files
53
+ run_context = Packwerk::RunContext.from_configuration(@configuration)
54
+ offense_collection = find_offenses(run_context)
55
+ offense_collection.persist_deprecated_references_files(run_context.package_set)
49
56
 
50
57
  message = <<~EOS
51
58
  #{@offenses_formatter.show_offenses(offense_collection.errors)}
@@ -57,29 +64,31 @@ module Packwerk
57
64
 
58
65
  sig { returns(Result) }
59
66
  def check
60
- offense_collection = find_offenses(show_errors: true)
67
+ run_context = Packwerk::RunContext.from_configuration(@configuration)
68
+ offense_collection = find_offenses(run_context, show_errors: true)
61
69
 
62
70
  messages = [
63
71
  @offenses_formatter.show_offenses(offense_collection.outstanding_offenses),
64
- @offenses_formatter.show_stale_violations(offense_collection),
72
+ @offenses_formatter.show_stale_violations(offense_collection, @relative_file_set),
65
73
  ]
66
- result_status = offense_collection.outstanding_offenses.empty? && !offense_collection.stale_violations?
74
+
75
+ result_status = offense_collection.outstanding_offenses.empty? &&
76
+ !offense_collection.stale_violations?(@relative_file_set)
67
77
 
68
78
  Result.new(message: messages.join("\n") + "\n", status: result_status)
69
79
  end
70
80
 
71
81
  private
72
82
 
73
- sig { params(show_errors: T::Boolean).returns(OffenseCollection) }
74
- def find_offenses(show_errors: false)
83
+ sig { params(run_context: Packwerk::RunContext, show_errors: T::Boolean).returns(OffenseCollection) }
84
+ def find_offenses(run_context, show_errors: false)
75
85
  offense_collection = OffenseCollection.new(@configuration.root_path)
76
- @progress_formatter.started(@absolute_file_set)
86
+ @progress_formatter.started(@relative_file_set)
77
87
 
78
- run_context = Packwerk::RunContext.from_configuration(@configuration)
79
88
  all_offenses = T.let([], T::Array[Offense])
80
89
 
81
- process_file = T.let(-> (absolute_file) do
82
- run_context.process_file(absolute_file: absolute_file).tap do |offenses|
90
+ process_file = T.let(->(relative_file) do
91
+ run_context.process_file(relative_file: relative_file).tap do |offenses|
83
92
  failed = show_errors && offenses.any? { |offense| !offense_collection.listed?(offense) }
84
93
  update_progress(failed: failed)
85
94
  end
@@ -87,7 +96,7 @@ module Packwerk
87
96
 
88
97
  execution_time = Benchmark.realtime do
89
98
  all_offenses = if @configuration.parallel?
90
- Parallel.flat_map(@absolute_file_set, &process_file)
99
+ Parallel.flat_map(@relative_file_set, &process_file)
91
100
  else
92
101
  serial_find_offenses(&process_file)
93
102
  end
@@ -103,8 +112,8 @@ module Packwerk
103
112
  def serial_find_offenses(&block)
104
113
  all_offenses = T.let([], T::Array[Offense])
105
114
  begin
106
- @absolute_file_set.each do |absolute_file|
107
- offenses = block.call(absolute_file)
115
+ @relative_file_set.each do |relative_file|
116
+ offenses = yield(relative_file)
108
117
  all_offenses.concat(offenses)
109
118
  end
110
119
  rescue Interrupt
@@ -6,6 +6,21 @@ require "ast/node"
6
6
  module Packwerk
7
7
  # A collection of constant definitions parsed from an Abstract Syntax Tree (AST).
8
8
  class ParsedConstantDefinitions
9
+ class << self
10
+ # What fully qualified constants can this constant refer to in this context?
11
+ def reference_qualifications(constant_name, namespace_path:)
12
+ return [constant_name] if constant_name.start_with?("::")
13
+
14
+ resolved_constant_name = "::#{constant_name}"
15
+
16
+ possible_namespaces = namespace_path.each_with_object([""]) do |current, acc|
17
+ acc << "#{acc.last}::#{current}" if acc.last && current
18
+ end
19
+
20
+ possible_namespaces.map { |namespace| namespace + resolved_constant_name }
21
+ end
22
+ end
23
+
9
24
  def initialize(root_node:)
10
25
  @local_definitions = {}
11
26
 
@@ -21,35 +36,23 @@ module Packwerk
21
36
  end
22
37
  end
23
38
 
24
- # What fully qualified constants can this constant refer to in this context?
25
- def self.reference_qualifications(constant_name, namespace_path:)
26
- return [constant_name] if constant_name.start_with?("::")
27
-
28
- resolved_constant_name = "::#{constant_name}"
29
-
30
- possible_namespaces = namespace_path.each_with_object([""]) do |current, acc|
31
- acc << "#{acc.last}::#{current}" if acc.last && current
32
- end
33
-
34
- possible_namespaces.map { |namespace| namespace + resolved_constant_name }
35
- end
36
-
37
39
  private
38
40
 
39
41
  def collect_local_definitions_from_root(node, current_namespace_path = [])
40
- if 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)