packwerk 1.0.1 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
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