packwerk 1.0.1 → 1.1.3

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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +8 -7
  3. data/.github/workflows/ci.yml +14 -5
  4. data/.ruby-version +1 -1
  5. data/Gemfile +2 -1
  6. data/Gemfile.lock +130 -110
  7. data/README.md +8 -1
  8. data/USAGE.md +23 -2
  9. data/dev.yml +1 -1
  10. data/exe/packwerk +1 -1
  11. data/gemfiles/Gemfile-rails-6-0 +22 -0
  12. data/lib/packwerk.rb +4 -2
  13. data/lib/packwerk/application_load_paths.rb +68 -0
  14. data/lib/packwerk/application_validator.rb +94 -74
  15. data/lib/packwerk/association_inspector.rb +24 -9
  16. data/lib/packwerk/cache_deprecated_references.rb +55 -0
  17. data/lib/packwerk/checker.rb +3 -0
  18. data/lib/packwerk/checking_deprecated_references.rb +5 -2
  19. data/lib/packwerk/cli.rb +56 -55
  20. data/lib/packwerk/commands/detect_stale_violations_command.rb +60 -0
  21. data/lib/packwerk/commands/offense_progress_marker.rb +24 -0
  22. data/lib/packwerk/commands/result.rb +13 -0
  23. data/lib/packwerk/commands/update_deprecations_command.rb +81 -0
  24. data/lib/packwerk/configuration.rb +5 -37
  25. data/lib/packwerk/const_node_inspector.rb +28 -17
  26. data/lib/packwerk/dependency_checker.rb +13 -5
  27. data/lib/packwerk/deprecated_references.rb +23 -0
  28. data/lib/packwerk/detect_stale_deprecated_references.rb +14 -0
  29. data/lib/packwerk/file_processor.rb +4 -4
  30. data/lib/packwerk/formatters/offenses_formatter.rb +48 -0
  31. data/lib/packwerk/formatters/progress_formatter.rb +6 -2
  32. data/lib/packwerk/inflector.rb +17 -8
  33. data/lib/packwerk/node.rb +61 -38
  34. data/lib/packwerk/node_processor.rb +4 -4
  35. data/lib/packwerk/node_processor_factory.rb +39 -0
  36. data/lib/packwerk/node_visitor.rb +1 -1
  37. data/lib/packwerk/offense.rb +4 -6
  38. data/lib/packwerk/output_style.rb +20 -0
  39. data/lib/packwerk/output_styles/coloured.rb +29 -0
  40. data/lib/packwerk/output_styles/plain.rb +26 -0
  41. data/lib/packwerk/package_set.rb +9 -3
  42. data/lib/packwerk/parsed_constant_definitions.rb +4 -4
  43. data/lib/packwerk/parsers/erb.rb +4 -0
  44. data/lib/packwerk/parsers/factory.rb +10 -1
  45. data/lib/packwerk/privacy_checker.rb +23 -5
  46. data/lib/packwerk/run_context.rb +69 -46
  47. data/lib/packwerk/sanity_checker.rb +1 -1
  48. data/lib/packwerk/spring_command.rb +1 -1
  49. data/lib/packwerk/updating_deprecated_references.rb +2 -39
  50. data/lib/packwerk/version.rb +1 -1
  51. data/library.yml +1 -1
  52. data/packwerk.gemspec +1 -1
  53. data/service.yml +2 -2
  54. data/shipit.rubygems.yml +5 -1
  55. data/sorbet/rbi/gems/{actioncable@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actioncable@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +56 -36
  56. data/sorbet/rbi/gems/{actionmailbox@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailbox@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +25 -28
  57. data/sorbet/rbi/gems/{actionmailer@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionmailer@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +43 -24
  58. data/sorbet/rbi/gems/{actionpack@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionpack@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +382 -284
  59. data/sorbet/rbi/gems/{actiontext@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actiontext@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +76 -40
  60. data/sorbet/rbi/gems/{actionview@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → actionview@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +206 -195
  61. data/sorbet/rbi/gems/{activejob@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activejob@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +64 -75
  62. data/sorbet/rbi/gems/{activemodel@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activemodel@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +103 -56
  63. data/sorbet/rbi/gems/{activerecord@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activerecord@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1250 -898
  64. data/sorbet/rbi/gems/{activestorage@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activestorage@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +92 -120
  65. data/sorbet/rbi/gems/{activesupport@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → activesupport@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +292 -193
  66. data/sorbet/rbi/gems/{ast@2.4.1.rbi → ast@2.4.2.rbi} +2 -1
  67. data/sorbet/rbi/gems/{better_html@1.0.15.rbi → better_html@1.0.16.rbi} +2 -2
  68. data/sorbet/rbi/gems/{concurrent-ruby@1.1.6.rbi → concurrent-ruby@1.1.8.rbi} +12 -9
  69. data/sorbet/rbi/gems/{erubi@1.9.0.rbi → erubi@1.10.0.rbi} +3 -1
  70. data/sorbet/rbi/gems/{i18n@1.8.2.rbi → i18n@1.8.10.rbi} +19 -52
  71. data/sorbet/rbi/gems/{loofah@2.5.0.rbi → loofah@2.9.0.rbi} +3 -1
  72. data/sorbet/rbi/gems/marcel@1.0.0.rbi +70 -0
  73. data/sorbet/rbi/gems/{mini_mime@1.0.2.rbi → mini_mime@1.0.3.rbi} +6 -6
  74. data/sorbet/rbi/gems/{mini_portile2@2.4.0.rbi → minitest-focus@1.2.1.rbi} +2 -2
  75. data/sorbet/rbi/gems/{minitest@5.14.0.rbi → minitest@5.14.4.rbi} +31 -29
  76. data/sorbet/rbi/gems/{mocha@1.11.2.rbi → mocha@1.12.0.rbi} +25 -36
  77. data/sorbet/rbi/gems/{nio4r@2.5.2.rbi → nio4r@2.5.7.rbi} +21 -20
  78. data/sorbet/rbi/gems/{nokogiri@1.10.9.rbi → nokogiri@1.11.2.rbi} +193 -154
  79. data/sorbet/rbi/gems/{parallel@1.19.1.rbi → parallel@1.20.1.rbi} +1 -1
  80. data/sorbet/rbi/gems/parlour@6.0.0.rbi +1272 -0
  81. data/sorbet/rbi/gems/{parser@2.7.1.4.rbi → parser@3.0.0.0.rbi} +287 -174
  82. data/sorbet/rbi/gems/{pry@0.13.1.rbi → pry@0.14.0.rbi} +1 -1
  83. data/sorbet/rbi/gems/racc@1.5.2.rbi +57 -0
  84. data/sorbet/rbi/gems/{rack@2.2.2.rbi → rack@2.2.3.rbi} +23 -35
  85. data/sorbet/rbi/gems/{rails@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → rails@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +1 -1
  86. data/sorbet/rbi/gems/{railties@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi → railties@7.0.0.alpha-d612542336d9a61381311c95a27d801bb4094779.rbi} +132 -121
  87. data/sorbet/rbi/gems/{rake@13.0.1.rbi → rake@13.0.3.rbi} +16 -20
  88. data/sorbet/rbi/gems/regexp_parser@2.1.1.rbi +8 -0
  89. data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +8 -0
  90. data/sorbet/rbi/gems/{rubocop-performance@1.5.2.rbi → rubocop-performance@1.10.2.rbi} +1 -1
  91. data/sorbet/rbi/gems/{rubocop-shopify@1.0.2.rbi → rubocop-shopify@2.0.1.rbi} +1 -1
  92. data/sorbet/rbi/gems/{rubocop-sorbet@0.3.7.rbi → rubocop-sorbet@0.6.1.rbi} +1 -1
  93. data/sorbet/rbi/gems/{rubocop@0.82.0.rbi → rubocop@1.12.0.rbi} +1 -1
  94. data/sorbet/rbi/gems/{ruby-progressbar@1.10.1.rbi → ruby-progressbar@1.11.0.rbi} +1 -1
  95. data/sorbet/rbi/gems/spoom@1.1.0.rbi +1061 -0
  96. data/sorbet/rbi/gems/{spring@2.1.0.rbi → spring@2.1.1.rbi} +7 -7
  97. data/sorbet/rbi/gems/{sprockets-rails@3.2.1.rbi → sprockets-rails@3.2.2.rbi} +88 -68
  98. data/sorbet/rbi/gems/{sprockets@4.0.0.rbi → sprockets@4.0.2.rbi} +8 -7
  99. data/sorbet/rbi/gems/{tapioca@0.4.5.rbi → tapioca@0.4.19.rbi} +109 -24
  100. data/sorbet/rbi/gems/{thor@1.0.1.rbi → thor@1.1.0.rbi} +16 -15
  101. data/sorbet/rbi/gems/{tzinfo@2.0.2.rbi → tzinfo@2.0.4.rbi} +21 -2
  102. data/sorbet/rbi/gems/{unicode-display_width@1.7.0.rbi → unicode-display_width@2.0.0.rbi} +1 -1
  103. data/sorbet/rbi/gems/{websocket-driver@0.7.1.rbi → websocket-driver@0.7.3.rbi} +29 -29
  104. data/sorbet/rbi/gems/{websocket-extensions@0.1.4.rbi → websocket-extensions@0.1.5.rbi} +2 -2
  105. data/sorbet/rbi/gems/zeitwerk@2.4.2.rbi +177 -0
  106. metadata +66 -58
  107. data/lib/packwerk/output_styles.rb +0 -41
  108. data/sorbet/rbi/gems/jaro_winkler@1.5.4.rbi +0 -8
  109. data/sorbet/rbi/gems/marcel@0.3.3.rbi +0 -30
  110. data/sorbet/rbi/gems/mimemagic@0.3.5.rbi +0 -47
  111. data/sorbet/rbi/gems/parlour@4.0.1.rbi +0 -561
  112. data/sorbet/rbi/gems/spoom@1.0.4.rbi +0 -418
  113. data/sorbet/rbi/gems/zeitwerk@2.3.0.rbi +0 -8
  114. data/static/packwerk-check-demo.png +0 -0
  115. data/static/packwerk_check.gif +0 -0
  116. data/static/packwerk_check_violation.gif +0 -0
  117. data/static/packwerk_update.gif +0 -0
  118. data/static/packwerk_validate.gif +0 -0
data/lib/packwerk/node.rb CHANGED
@@ -5,24 +5,14 @@ require "parser/ast/node"
5
5
 
6
6
  module Packwerk
7
7
  module Node
8
- BLOCK = :block
9
- CLASS = :class
10
- CONSTANT = :const
11
- CONSTANT_ASSIGNMENT = :casgn
12
- CONSTANT_ROOT_NAMESPACE = :cbase
13
- HASH = :hash
14
- HASH_PAIR = :pair
15
- METHOD_CALL = :send
16
- MODULE = :module
17
- STRING = :str
18
- SYMBOL = :sym
19
-
20
8
  class TypeError < ArgumentError; end
21
9
  Location = Struct.new(:line, :column)
22
10
 
23
11
  class << self
12
+ extend T::Sig
13
+
24
14
  def class_or_module_name(class_or_module_node)
25
- case type(class_or_module_node)
15
+ case type_of(class_or_module_node)
26
16
  when CLASS, MODULE
27
17
  # (class (const nil :Foo) (const nil :Bar) (nil))
28
18
  # "class Foo < Bar; end"
@@ -36,10 +26,10 @@ module Packwerk
36
26
  end
37
27
 
38
28
  def constant_name(constant_node)
39
- case type(constant_node)
29
+ case type_of(constant_node)
40
30
  when CONSTANT_ROOT_NAMESPACE
41
31
  ""
42
- when CONSTANT, CONSTANT_ASSIGNMENT
32
+ when CONSTANT, CONSTANT_ASSIGNMENT, SELF
43
33
  # (const nil :Foo)
44
34
  # "Foo"
45
35
  # (const (cbase) :Foo)
@@ -52,6 +42,8 @@ module Packwerk
52
42
  # "::Foo = 1"
53
43
  # (casgn (lvar :a) :Foo (int 1))
54
44
  # "a::Foo = 1"
45
+ # (casgn (self) :Foo (int 1))
46
+ # "self::Foo = 1"
55
47
  namespace, name = constant_node.children
56
48
  if namespace
57
49
  [constant_name(namespace), name].join("::")
@@ -74,19 +66,19 @@ module Packwerk
74
66
  end
75
67
 
76
68
  def enclosing_namespace_path(starting_node, ancestors:)
77
- ancestors.select { |n| [CLASS, MODULE].include?(type(n)) }
69
+ ancestors.select { |n| [CLASS, MODULE].include?(type_of(n)) }
78
70
  .each_with_object([]) do |node, namespace|
79
71
  # when evaluating `class Child < Parent`, the const node for `Parent` is a child of the class
80
72
  # node, so it'll be an ancestor, but `Parent` is not evaluated in the namespace of `Child`, so
81
73
  # we need to skip it here
82
- next if type(node) == CLASS && parent_class(node) == starting_node
74
+ next if type_of(node) == CLASS && parent_class(node) == starting_node
83
75
 
84
76
  namespace.prepend(class_or_module_name(node))
85
77
  end
86
78
  end
87
79
 
88
80
  def literal_value(string_or_symbol_node)
89
- case type(string_or_symbol_node)
81
+ case type_of(string_or_symbol_node)
90
82
  when STRING, SYMBOL
91
83
  # (str "foo")
92
84
  # "'foo'"
@@ -103,20 +95,32 @@ module Packwerk
103
95
  Location.new(location.line, location.column)
104
96
  end
105
97
 
98
+ def constant?(node)
99
+ type_of(node) == CONSTANT
100
+ end
101
+
102
+ def constant_assignment?(node)
103
+ type_of(node) == CONSTANT_ASSIGNMENT
104
+ end
105
+
106
+ def class?(node)
107
+ type_of(node) == CLASS
108
+ end
109
+
106
110
  def method_call?(node)
107
- type(node) == METHOD_CALL
111
+ type_of(node) == METHOD_CALL
108
112
  end
109
113
 
110
114
  def hash?(node)
111
- type(node) == HASH
115
+ type_of(node) == HASH
112
116
  end
113
117
 
114
118
  def string?(node)
115
- type(node) == STRING
119
+ type_of(node) == STRING
116
120
  end
117
121
 
118
122
  def symbol?(node)
119
- type(node) == SYMBOL
123
+ type_of(node) == SYMBOL
120
124
  end
121
125
 
122
126
  def method_arguments(method_call_node)
@@ -136,7 +140,7 @@ module Packwerk
136
140
  end
137
141
 
138
142
  def module_name_from_definition(node)
139
- case type(node)
143
+ case type_of(node)
140
144
  when CLASS, MODULE
141
145
  # "class My::Class; end"
142
146
  # "module My::Module; end"
@@ -146,7 +150,7 @@ module Packwerk
146
150
  # "My::Module = ..."
147
151
  rvalue = node.children.last
148
152
 
149
- case type(rvalue)
153
+ case type_of(rvalue)
150
154
  when METHOD_CALL
151
155
  # "Class.new"
152
156
  # "Module.new"
@@ -169,16 +173,17 @@ module Packwerk
169
173
  end
170
174
 
171
175
  def parent_class(class_node)
172
- raise TypeError unless type(class_node) == CLASS
176
+ raise TypeError unless type_of(class_node) == CLASS
173
177
 
174
178
  # (class (const nil :Foo) (const nil :Bar) (nil))
175
179
  # "class Foo < Bar; end"
176
180
  class_node.children[1]
177
181
  end
178
182
 
183
+ sig { params(ancestors: T::Array[AST::Node]).returns(String) }
179
184
  def parent_module_name(ancestors:)
180
185
  definitions = ancestors
181
- .select { |n| [CLASS, MODULE, CONSTANT_ASSIGNMENT, BLOCK].include?(type(n)) }
186
+ .select { |n| [CLASS, MODULE, CONSTANT_ASSIGNMENT, BLOCK].include?(type_of(n)) }
182
187
 
183
188
  names = definitions.map do |definition|
184
189
  name_part_from_definition(definition)
@@ -187,10 +192,6 @@ module Packwerk
187
192
  names.empty? ? "Object" : names.reverse.join("::")
188
193
  end
189
194
 
190
- def type(node)
191
- node.type
192
- end
193
-
194
195
  def value_from_hash(hash_node, key)
195
196
  raise TypeError unless hash?(hash_node)
196
197
  pair = hash_pairs(hash_node).detect { |pair_node| literal_value(hash_pair_key(pair_node)) == key }
@@ -199,8 +200,30 @@ module Packwerk
199
200
 
200
201
  private
201
202
 
203
+ BLOCK = :block
204
+ CLASS = :class
205
+ CONSTANT = :const
206
+ CONSTANT_ASSIGNMENT = :casgn
207
+ CONSTANT_ROOT_NAMESPACE = :cbase
208
+ HASH = :hash
209
+ HASH_PAIR = :pair
210
+ METHOD_CALL = :send
211
+ MODULE = :module
212
+ SELF = :self
213
+ STRING = :str
214
+ SYMBOL = :sym
215
+
216
+ private_constant(
217
+ :BLOCK, :CLASS, :CONSTANT, :CONSTANT_ASSIGNMENT, :CONSTANT_ROOT_NAMESPACE, :HASH, :HASH_PAIR, :METHOD_CALL,
218
+ :MODULE, :SELF, :STRING, :SYMBOL,
219
+ )
220
+
221
+ def type_of(node)
222
+ node.type
223
+ end
224
+
202
225
  def hash_pair_key(hash_pair_node)
203
- raise TypeError unless type(hash_pair_node) == HASH_PAIR
226
+ raise TypeError unless type_of(hash_pair_node) == HASH_PAIR
204
227
 
205
228
  # (pair (int 1) (int 2))
206
229
  # "1 => 2"
@@ -210,7 +233,7 @@ module Packwerk
210
233
  end
211
234
 
212
235
  def hash_pair_value(hash_pair_node)
213
- raise TypeError unless type(hash_pair_node) == HASH_PAIR
236
+ raise TypeError unless type_of(hash_pair_node) == HASH_PAIR
214
237
 
215
238
  # (pair (int 1) (int 2))
216
239
  # "1 => 2"
@@ -224,11 +247,11 @@ module Packwerk
224
247
 
225
248
  # (hash (pair (int 1) (int 2)) (pair (int 3) (int 4)))
226
249
  # "{1 => 2, 3 => 4}"
227
- hash_node.children.select { |n| type(n) == HASH_PAIR }
250
+ hash_node.children.select { |n| type_of(n) == HASH_PAIR }
228
251
  end
229
252
 
230
253
  def method_call_node(block_node)
231
- raise TypeError unless type(block_node) == BLOCK
254
+ raise TypeError unless type_of(block_node) == BLOCK
232
255
 
233
256
  # (block (send (lvar :foo) :bar) (args) (int 42))
234
257
  # "foo.bar do 42 end"
@@ -239,7 +262,7 @@ module Packwerk
239
262
  # "Class.new"
240
263
  # "Module.new"
241
264
  method_call?(node) &&
242
- type(receiver(node)) == CONSTANT &&
265
+ constant?(receiver(node)) &&
243
266
  ["Class", "Module"].include?(constant_name(receiver(node))) &&
244
267
  method_name(node) == :new
245
268
  end
@@ -247,12 +270,12 @@ module Packwerk
247
270
  def name_from_block_definition(node)
248
271
  if method_name(method_call_node(node)) == :class_eval
249
272
  receiver = receiver(node)
250
- constant_name(receiver) if receiver && type(receiver) == CONSTANT
273
+ constant_name(receiver) if receiver && constant?(receiver)
251
274
  end
252
275
  end
253
276
 
254
277
  def name_part_from_definition(node)
255
- case type(node)
278
+ case type_of(node)
256
279
  when CLASS, MODULE, CONSTANT_ASSIGNMENT
257
280
  module_name_from_definition(node)
258
281
  when BLOCK
@@ -261,7 +284,7 @@ module Packwerk
261
284
  end
262
285
 
263
286
  def receiver(method_call_or_block_node)
264
- case type(method_call_or_block_node)
287
+ case type_of(method_call_or_block_node)
265
288
  when METHOD_CALL
266
289
  method_call_or_block_node.children[0]
267
290
  when BLOCK
@@ -25,9 +25,9 @@ module Packwerk
25
25
  @checkers = checkers
26
26
  end
27
27
 
28
- def call(node, ancestors:)
29
- case Node.type(node)
30
- when Node::METHOD_CALL, Node::CONSTANT
28
+ sig { params(node: Parser::AST::Node, ancestors: T::Array[Parser::AST::Node]).returns(T.nilable(Offense)) }
29
+ def call(node, ancestors)
30
+ if Node.method_call?(node) || Node.constant?(node)
31
31
  reference = @reference_extractor.reference_from_node(node, ancestors: ancestors, file_path: @filename)
32
32
  check_reference(reference, node) if reference
33
33
  end
@@ -42,7 +42,7 @@ module Packwerk
42
42
 
43
43
  Packwerk::Offense.new(
44
44
  location: Node.location(node),
45
- file: @filename,
45
+ file: reference.relative_path,
46
46
  message: <<~EOS
47
47
  #{message}
48
48
  Inference details: this is a reference to #{constant.name} which seems to be defined in #{constant.location}.
@@ -0,0 +1,39 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "packwerk/constant_name_inspector"
5
+ require "packwerk/checker"
6
+
7
+ module Packwerk
8
+ class NodeProcessorFactory < T::Struct
9
+ extend T::Sig
10
+
11
+ const :root_path, String
12
+ const :reference_lister, ReferenceLister
13
+ const :context_provider, Packwerk::ConstantDiscovery
14
+ const :constant_name_inspectors, T::Array[ConstantNameInspector]
15
+ const :checkers, T::Array[Checker]
16
+
17
+ sig { params(filename: String, node: AST::Node).returns(NodeProcessor) }
18
+ def for(filename:, node:)
19
+ ::Packwerk::NodeProcessor.new(
20
+ reference_extractor: reference_extractor(node: node),
21
+ reference_lister: reference_lister,
22
+ filename: filename,
23
+ checkers: checkers,
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ sig { params(node: AST::Node).returns(::Packwerk::ReferenceExtractor) }
30
+ def reference_extractor(node:)
31
+ ::Packwerk::ReferenceExtractor.new(
32
+ context_provider: context_provider,
33
+ constant_name_inspectors: constant_name_inspectors,
34
+ root_node: node,
35
+ root_path: root_path,
36
+ )
37
+ end
38
+ end
39
+ end
@@ -10,7 +10,7 @@ module Packwerk
10
10
  end
11
11
 
12
12
  def visit(node, ancestors:, result:)
13
- offense = @node_processor.call(node, ancestors: ancestors)
13
+ offense = @node_processor.call(node, ancestors)
14
14
  result << offense if offense
15
15
 
16
16
  child_ancestors = [node] + ancestors
@@ -4,7 +4,8 @@
4
4
  require "parser/source/map"
5
5
  require "sorbet-runtime"
6
6
 
7
- require "packwerk/output_styles"
7
+ require "packwerk/output_style"
8
+ require "packwerk/output_styles/plain"
8
9
 
9
10
  module Packwerk
10
11
  class Offense
@@ -23,11 +24,8 @@ module Packwerk
23
24
  @message = message
24
25
  end
25
26
 
26
- sig do
27
- params(style: T.any(T.class_of(OutputStyles::Plain), T.class_of(OutputStyles::Coloured)))
28
- .returns(String)
29
- end
30
- def to_s(style = OutputStyles::Plain)
27
+ sig { params(style: OutputStyle).returns(String) }
28
+ def to_s(style = OutputStyles::Plain.new)
31
29
  if location
32
30
  <<~EOS
33
31
  #{style.filename}#{file}#{style.reset}:#{location.line}:#{location.column}
@@ -0,0 +1,20 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module OutputStyle
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ interface!
10
+
11
+ sig { abstract.returns(String) }
12
+ def reset; end
13
+
14
+ sig { abstract.returns(String) }
15
+ def filename; end
16
+
17
+ sig { abstract.returns(String) }
18
+ def error; end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module OutputStyles
6
+ # See https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit for ANSI escape colour codes
7
+ class Coloured
8
+ extend T::Sig
9
+ include OutputStyle
10
+
11
+ sig { override.returns(String) }
12
+ def reset
13
+ "\033[m"
14
+ end
15
+
16
+ sig { override.returns(String) }
17
+ def filename
18
+ # 36 is foreground cyan
19
+ "\033[36m"
20
+ end
21
+
22
+ sig { override.returns(String) }
23
+ def error
24
+ # 31 is foreground red
25
+ "\033[31m"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module OutputStyles
6
+ class Plain
7
+ extend T::Sig
8
+ include OutputStyle
9
+
10
+ sig { override.returns(String) }
11
+ def reset
12
+ ""
13
+ end
14
+
15
+ sig { override.returns(String) }
16
+ def filename
17
+ ""
18
+ end
19
+
20
+ sig { override.returns(String) }
21
+ def error
22
+ ""
23
+ end
24
+ end
25
+ end
26
+ end
@@ -28,8 +28,13 @@ module Packwerk
28
28
  def package_paths(root_path, package_pathspec)
29
29
  bundle_path_match = Bundler.bundle_path.join("**").to_s
30
30
 
31
- Dir.glob(File.join(root_path, package_pathspec, PACKAGE_CONFIG_FILENAME))
32
- .map { |path| Pathname.new(path) }.reject { |path| path.realpath.fnmatch(bundle_path_match) }
31
+ glob_patterns = Array(package_pathspec).map do |pathspec|
32
+ File.join(root_path, pathspec, PACKAGE_CONFIG_FILENAME)
33
+ end
34
+
35
+ Dir.glob(glob_patterns)
36
+ .map { |path| Pathname.new(path).cleanpath }
37
+ .reject { |path| path.realpath.fnmatch(bundle_path_match) }
33
38
  end
34
39
 
35
40
  private
@@ -55,7 +60,8 @@ module Packwerk
55
60
  end
56
61
 
57
62
  def package_from_path(file_path)
58
- @packages.values.find { |package| package.package_path?(file_path) }
63
+ path_string = file_path.to_s
64
+ @packages.values.find { |package| package.package_path?(path_string) }
59
65
  end
60
66
  end
61
67
  end
@@ -28,8 +28,8 @@ module Packwerk
28
28
 
29
29
  fully_qualified_constant_name = "::#{constant_name}"
30
30
 
31
- possible_namespaces = namespace_path.reduce([""]) do |acc, current|
32
- acc << acc.last + "::" + current
31
+ possible_namespaces = namespace_path.each_with_object([""]) do |current, acc|
32
+ acc << "#{acc.last}::#{current}" if acc.last && current
33
33
  end
34
34
 
35
35
  possible_namespaces.map { |namespace| namespace + fully_qualified_constant_name }
@@ -38,12 +38,12 @@ module Packwerk
38
38
  private
39
39
 
40
40
  def collect_local_definitions_from_root(node, current_namespace_path = [])
41
- if Node.type(node) == Node::CONSTANT_ASSIGNMENT
41
+ if Node.constant_assignment?(node)
42
42
  add_definition(Node.constant_name(node), current_namespace_path, Node.name_location(node))
43
43
  elsif Node.module_name_from_definition(node)
44
44
  # handle compact constant nesting (e.g. "module Sales::Order")
45
45
  tempnode = node
46
- while (tempnode = Node.each_child(tempnode).find { |n| Node.type(n) == Node::CONSTANT })
46
+ while (tempnode = Node.each_child(tempnode).find { |n| Node.constant?(n) })
47
47
  add_definition(Node.constant_name(tempnode), current_namespace_path, Node.name_location(tempnode))
48
48
  end
49
49