power_assert 3.0.1 → 3.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 341a40efaf7fab3b700fe92e3e8c44d76101c8e91e6fbfe000f6914f1dcededf
4
- data.tar.gz: b9397b951b5834eea9a083a0352c312f13fdc5c58604fa90b4790d6f178ce5d5
3
+ metadata.gz: 85c370c0b3194fff746c4ee4af9a2f2e6ec67c4cc676bfb97a8a138df4a4a273
4
+ data.tar.gz: 21f6b36d50e8e0580889ff1ccacf88920075c90543f604b47a18e29360782d6b
5
5
  SHA512:
6
- metadata.gz: 34af70b5fa7ab2a92fbc64d6cabb8230748fb63aa3488d014c623583298c4ad1f59e15ab4774d6e6e2d624c733aab06a80ce0ebd26e51416ce4b1f3711736240
7
- data.tar.gz: 8490f46327ebc2ffc6118a12b7d3ce92ffbd37f4da10ca6add809e2c1d501feff94137cee1e55cb5e0da776619e92e6e825717cf745454c6f84fa2fc681134c2
6
+ metadata.gz: 35c8a4cb2fd2539ff16faa1c03bffa965786f52dbaad41c86a410d89bfd22bfccb6c32f833030d94904454e03251ace97de0dbf4a41f5977cf8f9e749fff7875
7
+ data.tar.gz: f5a06634da7fc0bd3399a8ab9f1db0277421b65d66d460eaf7dd00f5281f5254d69fab8c0106c3bf4e614eb6f8b9ab8d9c6e70d613b08e271222a3c2db0d0c0f
@@ -26,7 +26,7 @@ jobs:
26
26
  TEST_SYMLINK: ${{ matrix.TEST_SYMLINK }}
27
27
  continue-on-error: ${{ matrix.ruby-version == 'head' }}
28
28
  steps:
29
- - uses: actions/checkout@v5
29
+ - uses: actions/checkout@v7
30
30
  - name: Set up Ruby
31
31
  uses: ruby/setup-ruby@v1
32
32
  with:
@@ -1,7 +1,7 @@
1
1
  module PowerAssert
2
2
  class << self
3
3
  def configuration
4
- @configuration ||= Configuration[false, true, false, :p]
4
+ @configuration ||= Configuration[false, true, false, :p, :ripper]
5
5
  end
6
6
 
7
7
  def configure
@@ -9,7 +9,7 @@ module PowerAssert
9
9
  end
10
10
  end
11
11
 
12
- class Configuration < Struct.new(:lazy_inspection, :_redefinition, :colorize_message, :inspector)
12
+ class Configuration < Struct.new(:lazy_inspection, :_redefinition, :colorize_message, :inspector, :parser)
13
13
  def colorize_message=(bool)
14
14
  if bool
15
15
  require 'irb/color'
@@ -41,6 +41,18 @@ module PowerAssert
41
41
  end
42
42
  super
43
43
  end
44
+
45
+ def parser=(parser)
46
+ case parser
47
+ when :prism
48
+ require 'prism'
49
+ when :ripper
50
+ require 'ripper'
51
+ else
52
+ raise ArgumentError, "unknown parser: #{parser}"
53
+ end
54
+ super
55
+ end
44
56
  end
45
57
  private_constant :Configuration
46
58
  end
@@ -30,7 +30,8 @@ module PowerAssert
30
30
  line ||= File.open(path) {|fp| fp.each_line.drop(lineno - 1).first }
31
31
  end
32
32
  if line
33
- @parser = Parser.new(line, path, lineno, @assertion_proc.binding, assertion_method.to_s, @assertion_proc)
33
+ parser_class = PowerAssert.configuration.parser == :prism ? Parser::PrismParser : Parser::RipperParser
34
+ @parser = parser_class.new(line, path, lineno, @assertion_proc.binding, assertion_method.to_s, @assertion_proc)
34
35
  end
35
36
  end
36
37
  end
@@ -1,9 +1,10 @@
1
- require 'ripper'
2
-
3
1
  module PowerAssert
4
2
  class Parser
5
3
  Ident = Struct.new(:type, :name, :column)
6
4
 
5
+ class Branch < Array
6
+ end
7
+
7
8
  attr_reader :line, :path, :lineno, :binding
8
9
 
9
10
  def initialize(line, path, lineno, binding, assertion_method_name = nil, assertion_proc = nil)
@@ -18,7 +19,7 @@ module PowerAssert
18
19
  end
19
20
 
20
21
  def idents
21
- @idents ||= extract_idents(Ripper.sexp(@line_for_parsing))
22
+ @idents ||= []
22
23
  end
23
24
 
24
25
  def call_paths
@@ -52,134 +53,197 @@ module PowerAssert
52
53
  str
53
54
  end
54
55
 
55
- class Branch < Array
56
- end
56
+ class RipperParser < Parser
57
+ def idents
58
+ @idents ||=
59
+ begin
60
+ require 'ripper'
61
+ extract_idents(Ripper.sexp(@line_for_parsing))
62
+ end
63
+ end
57
64
 
58
- AND_OR_OPS = %i(and or && ||)
59
-
60
- #
61
- # Returns idents as graph structure.
62
- #
63
- # +--c--b--+
64
- # extract_idents(Ripper.sexp('a&.b(c).d')) #=> a--+ +--d
65
- # +--------+
66
- #
67
- def extract_idents(sexp)
68
- case sexp
69
- in [:arg_paren | :assoc_splat | :fcall | :hash | :method_add_block | :string_literal | :return, s, *]
70
- extract_idents(s)
71
- in [:assign | :massign, _, s]
72
- extract_idents(s)
73
- in [:opassign, _, [_, op_name, [_, op_column]], s]
74
- extract_idents(s) + [Ident[:method, op_name.sub(/=\z/, ''), op_column]]
75
- in [:dyna_symbol, [Symbol, *] => s]
76
- # s can be [:string_content, [..]] while parsing an expression like { "a": 1 }
77
- extract_idents(s)
78
- in [:dyna_symbol, ss]
79
- ss.flat_map {|s| extract_idents(s) }
80
- in [:assoclist_from_args | :bare_assoc_hash | :paren | :string_embexpr | :regexp_literal | :xstring_literal, ss, *]
81
- ss.flat_map {|s| extract_idents(s) }
82
- in [:command, s0, s1]
83
- [s1, s0].flat_map {|s| extract_idents(s) }
84
- in [:assoc_new | :dot2 | :dot3 | :string_content, *ss]
85
- ss.flat_map {|s| extract_idents(s) }
86
- in [:unary, mid, s]
87
- handle_columnless_ident([], mid, extract_idents(s))
88
- in [:binary, s0, op, s1] if AND_OR_OPS.include?(op)
89
- extract_idents(s0) + [Branch[extract_idents(s1), []]]
90
- in [:binary, s0, op, s1]
91
- handle_columnless_ident(extract_idents(s0), op, extract_idents(s1))
92
- in [:call, recv, [op_sym, op_name, _], method]
93
- with_safe_op = ((op_sym == :@op and op_name == '&.') or op_sym == :"&.")
94
- if method == :call
95
- handle_columnless_ident(extract_idents(recv), :call, [], with_safe_op)
65
+ private
66
+
67
+ AND_OR_OPS = %i(and or && ||)
68
+
69
+ #
70
+ # Returns idents as graph structure.
71
+ #
72
+ # +--c--b--+
73
+ # extract_idents(Ripper.sexp('a&.b(c).d')) #=> a--+ +--d
74
+ # +--------+
75
+ #
76
+ def extract_idents(sexp)
77
+ case sexp
78
+ in [:arg_paren | :assoc_splat | :fcall | :hash | :method_add_block | :string_literal | :return, s, *]
79
+ extract_idents(s)
80
+ in [:assign | :massign, _, s]
81
+ extract_idents(s)
82
+ in [:opassign, _, [_, op_name, [_, op_column]], s]
83
+ extract_idents(s) + [Ident[:method, op_name.sub(/=\z/, ''), op_column]]
84
+ in [:dyna_symbol, [Symbol, *] => s]
85
+ # s can be [:string_content, [..]] while parsing an expression like { "a": 1 }
86
+ extract_idents(s)
87
+ in [:dyna_symbol, ss]
88
+ ss.flat_map {|s| extract_idents(s) }
89
+ in [:assoclist_from_args | :bare_assoc_hash | :paren | :string_embexpr | :regexp_literal | :xstring_literal, ss, *]
90
+ ss.flat_map {|s| extract_idents(s) }
91
+ in [:command, s0, s1]
92
+ [s1, s0].flat_map {|s| extract_idents(s) }
93
+ in [:assoc_new | :dot2 | :dot3 | :string_content, *ss]
94
+ ss.flat_map {|s| extract_idents(s) }
95
+ in [:unary, mid, s]
96
+ handle_columnless_ident([], mid, extract_idents(s))
97
+ in [:binary, s0, op, s1] if AND_OR_OPS.include?(op)
98
+ extract_idents(s0) + [Branch[extract_idents(s1), []]]
99
+ in [:binary, s0, op, s1]
100
+ handle_columnless_ident(extract_idents(s0), op, extract_idents(s1))
101
+ in [:call, recv, [op_sym, op_name, _], method]
102
+ with_safe_op = ((op_sym == :@op and op_name == '&.') or op_sym == :"&.")
103
+ if method == :call
104
+ handle_columnless_ident(extract_idents(recv), :call, [], with_safe_op)
105
+ else
106
+ extract_idents(recv) + (with_safe_op ? [Branch[extract_idents(method), []]] : extract_idents(method))
107
+ end
108
+ in [:array, ss]
109
+ ss ? ss.flat_map {|s| extract_idents(s) } : []
110
+ in [:command_call, s0, _, s1, s2]
111
+ [s0, s2, s1].flat_map {|s| extract_idents(s) }
112
+ in [:aref, s0, s1]
113
+ handle_columnless_ident(extract_idents(s0), :[], extract_idents(s1))
114
+ in [:method_add_arg, s0, s1]
115
+ case extract_idents(s0)
116
+ in []
117
+ # idents(s0) may be empty(e.g. ->{}.())
118
+ extract_idents(s1)
119
+ in [*is0, Branch[is1, []]]
120
+ # Safe navigation operator is used. See :call clause also.
121
+ is0 + [Branch[extract_idents(s1) + is1, []]]
122
+ in [*is, i]
123
+ is + extract_idents(s1) + [i]
124
+ end
125
+ in [:args_add_block, [:args_add_star, ss0, *ss1], _]
126
+ (ss0 + ss1).flat_map {|s| extract_idents(s) }
127
+ in [:args_add_block, ss, _]
128
+ ss.flat_map {|s| extract_idents(s) }
129
+ in [:vcall, [:@ident, name, [_, column]]]
130
+ [Ident[@proc_local_variables.include?(name) ? :ref : :method, name, column]]
131
+ in [:vcall, _]
132
+ []
133
+ in [:program, [[:method_add_block, [:method_add_arg, [:fcall, [:@ident | :@const, ^@assertion_method_name, _]], _], [:brace_block | :do_block, _, ss]]]]
134
+ ss.flat_map {|s| extract_idents(s) }
135
+ in [:program, [s, *]]
136
+ extract_idents(s)
137
+ in [:ifop, s0, s1, s2]
138
+ [*extract_idents(s0), Branch[extract_idents(s1), extract_idents(s2)]]
139
+ in [:if | :unless, s0, ss0, [_, ss1]]
140
+ [*extract_idents(s0), Branch[ss0.flat_map {|s| extract_idents(s) }, ss1.flat_map {|s| extract_idents(s) }]]
141
+ in [:if | :unless, s0, ss0, _]
142
+ [*extract_idents(s0), Branch[ss0.flat_map {|s| extract_idents(s) }, []]]
143
+ in [:if_mod | :unless_mod, s0, s1]
144
+ [*extract_idents(s0), Branch[extract_idents(s1), []]]
145
+ in [:var_ref | :var_field, [:@kw, 'self', [_, column]]]
146
+ [Ident[:ref, 'self', column]]
147
+ in [:var_ref | :var_field, [:@ident | :@const | :@cvar | :@ivar | :@gvar, ref_name, [_, column]]]
148
+ [Ident[:ref, ref_name, column]]
149
+ in [:var_ref | :var_field, _]
150
+ []
151
+ in [:@ident | :@const | :@op, method_name, [_, column]]
152
+ [Ident[:method, method_name, column]]
96
153
  else
97
- extract_idents(recv) + (with_safe_op ? [Branch[extract_idents(method), []]] : extract_idents(method))
154
+ []
98
155
  end
99
- in [:array, ss]
100
- ss ? ss.flat_map {|s| extract_idents(s) } : []
101
- in [:command_call, s0, _, s1, s2]
102
- [s0, s2, s1].flat_map {|s| extract_idents(s) }
103
- in [:aref, s0, s1]
104
- handle_columnless_ident(extract_idents(s0), :[], extract_idents(s1))
105
- in [:method_add_arg, s0, s1]
106
- case extract_idents(s0)
107
- in []
108
- # idents(s0) may be empty(e.g. ->{}.())
109
- extract_idents(s1)
110
- in [*is0, Branch[is1, []]]
111
- # Safe navigation operator is used. See :call clause also.
112
- is0 + [Branch[extract_idents(s1) + is1, []]]
113
- in [*is, i]
114
- is + extract_idents(s1) + [i]
156
+ end
157
+
158
+ def str_indices(str, re, offset, limit)
159
+ idx = str.index(re, offset)
160
+ if idx and idx <= limit
161
+ [idx, *str_indices(str, re, idx + 1, limit)]
162
+ else
163
+ []
115
164
  end
116
- in [:args_add_block, [:args_add_star, ss0, *ss1], _]
117
- (ss0 + ss1).flat_map {|s| extract_idents(s) }
118
- in [:args_add_block, ss, _]
119
- ss.flat_map {|s| extract_idents(s) }
120
- in [:vcall, [:@ident, name, [_, column]]]
121
- [Ident[@proc_local_variables.include?(name) ? :ref : :method, name, column]]
122
- in [:vcall, _]
123
- []
124
- in [:program, [[:method_add_block, [:method_add_arg, [:fcall, [:@ident | :@const, ^@assertion_method_name, _]], _], [:brace_block | :do_block, _, ss]]]]
125
- ss.flat_map {|s| extract_idents(s) }
126
- in [:program, [s, *]]
127
- extract_idents(s)
128
- in [:ifop, s0, s1, s2]
129
- [*extract_idents(s0), Branch[extract_idents(s1), extract_idents(s2)]]
130
- in [:if | :unless, s0, ss0, [_, ss1]]
131
- [*extract_idents(s0), Branch[ss0.flat_map {|s| extract_idents(s) }, ss1.flat_map {|s| extract_idents(s) }]]
132
- in [:if | :unless, s0, ss0, _]
133
- [*extract_idents(s0), Branch[ss0.flat_map {|s| extract_idents(s) }, []]]
134
- in [:if_mod | :unless_mod, s0, s1]
135
- [*extract_idents(s0), Branch[extract_idents(s1), []]]
136
- in [:var_ref | :var_field, [:@kw, 'self', [_, column]]]
137
- [Ident[:ref, 'self', column]]
138
- in [:var_ref | :var_field, [:@ident | :@const | :@cvar | :@ivar | :@gvar, ref_name, [_, column]]]
139
- [Ident[:ref, ref_name, column]]
140
- in [:var_ref | :var_field, _]
141
- []
142
- in [:@ident | :@const | :@op, method_name, [_, column]]
143
- [Ident[:method, method_name, column]]
144
- else
145
- []
146
165
  end
147
- end
148
166
 
149
- def str_indices(str, re, offset, limit)
150
- idx = str.index(re, offset)
151
- if idx and idx <= limit
152
- [idx, *str_indices(str, re, idx + 1, limit)]
153
- else
154
- []
167
+ MID2SRCTXT = {
168
+ :[] => '[',
169
+ :+@ => '+',
170
+ :-@ => '-',
171
+ :call => '('
172
+ }
173
+
174
+ def handle_columnless_ident(left_idents, mid, right_idents, with_safe_op = false)
175
+ left_max = left_idents.flatten.max_by(&:column)
176
+ right_min = right_idents.flatten.min_by(&:column)
177
+ bg = left_max ? left_max.column + left_max.name.length : 0
178
+ ed = right_min ? right_min.column - 1 : @line_for_parsing.length - 1
179
+ mname = mid.to_s
180
+ srctxt = MID2SRCTXT[mid] || mname
181
+ re = /
182
+ #{'\b' if /\A\w/ =~ srctxt}
183
+ #{Regexp.escape(srctxt)}
184
+ #{'\b' if /\w\z/ =~ srctxt}
185
+ /x
186
+ indices = str_indices(@line_for_parsing, re, bg, ed)
187
+ if indices.length == 1 or !(right_idents.empty? and left_idents.empty?)
188
+ ident = Ident[:method, mname, right_idents.empty? ? indices.first : indices.last]
189
+ left_idents + right_idents + (with_safe_op ? [Branch[[ident], []]] : [ident])
190
+ else
191
+ left_idents + right_idents
192
+ end
155
193
  end
156
194
  end
157
195
 
158
- MID2SRCTXT = {
159
- :[] => '[',
160
- :+@ => '+',
161
- :-@ => '-',
162
- :call => '('
163
- }
164
-
165
- def handle_columnless_ident(left_idents, mid, right_idents, with_safe_op = false)
166
- left_max = left_idents.flatten.max_by(&:column)
167
- right_min = right_idents.flatten.min_by(&:column)
168
- bg = left_max ? left_max.column + left_max.name.length : 0
169
- ed = right_min ? right_min.column - 1 : @line_for_parsing.length - 1
170
- mname = mid.to_s
171
- srctxt = MID2SRCTXT[mid] || mname
172
- re = /
173
- #{'\b' if /\A\w/ =~ srctxt}
174
- #{Regexp.escape(srctxt)}
175
- #{'\b' if /\w\z/ =~ srctxt}
176
- /x
177
- indices = str_indices(@line_for_parsing, re, bg, ed)
178
- if indices.length == 1 or !(right_idents.empty? and left_idents.empty?)
179
- ident = Ident[:method, mname, right_idents.empty? ? indices.first : indices.last]
180
- left_idents + right_idents + (with_safe_op ? [Branch[[ident], []]] : [ident])
181
- else
182
- left_idents + right_idents
196
+ class PrismParser < Parser
197
+ def idents
198
+ @idents ||=
199
+ begin
200
+ require 'prism'
201
+ result = Prism.parse(@line_for_parsing, scopes: [@proc_local_variables.map(&:to_sym)])
202
+ result.success? ? extract_idents(result.value) : []
203
+ end
204
+ end
205
+
206
+ private
207
+
208
+ def extract_idents(node)
209
+ case node&.type
210
+ when nil
211
+ []
212
+ when :program_node
213
+ if (statement = node.statements.body.first).is_a?(Prism::CallNode) && (statement.name == @assertion_method_name&.to_sym) && (block = statement.block).is_a?(Prism::BlockNode) && (body = block.body).is_a?(Prism::StatementsNode)
214
+ extract_idents(body)
215
+ else
216
+ extract_idents(statement)
217
+ end
218
+ when :call_node
219
+ message_ident =
220
+ if (node.name == :[] && !node.call_operator_loc)
221
+ Ident[:method, "[]", node.opening_loc.start_column]
222
+ elsif (message_loc = node.message_loc)
223
+ Ident[:method, node.name.name, message_loc.start_column]
224
+ else
225
+ Ident[:method, "call", node.opening_loc.start_column]
226
+ end
227
+
228
+ if node.safe_navigation?
229
+ extract_idents(node.receiver) + [Branch[extract_idents(node.arguments) + [message_ident], []]]
230
+ else
231
+ extract_idents(node.receiver) + extract_idents(node.arguments) + [message_ident]
232
+ end
233
+ when :call_operator_write_node, :class_variable_operator_write_node, :constant_operator_write_node, :constant_path_operator_write_node, :global_variable_operator_write_node, :index_operator_write_node, :instance_variable_operator_write_node, :local_variable_operator_write_node
234
+ binary_operator_loc = node.binary_operator_loc.chop
235
+ extract_idents(node.value) + [Ident[:method, binary_operator_loc.slice, binary_operator_loc.start_column]]
236
+ when :if_node
237
+ extract_idents(node.predicate) + [Branch[extract_idents(node.statements), extract_idents(node.subsequent)]]
238
+ when :unless_node
239
+ extract_idents(node.predicate) + [Branch[extract_idents(node.statements), extract_idents(node.else_clause)]]
240
+ when :and_node, :or_node
241
+ extract_idents(node.left) + [Branch[extract_idents(node.right), []]]
242
+ when :class_variable_read_node, :constant_read_node, :global_variable_read_node, :instance_variable_read_node, :local_variable_read_node, :self_node
243
+ [Ident[:ref, node.slice, node.start_column]]
244
+ else
245
+ node.compact_child_nodes.flat_map { |child| extract_idents(child) }
246
+ end
183
247
  end
184
248
  end
185
249
 
@@ -1,3 +1,3 @@
1
1
  module PowerAssert
2
- VERSION = "3.0.1"
2
+ VERSION = "3.1.0"
3
3
  end
data/lib/power_assert.rb CHANGED
@@ -37,13 +37,15 @@ module PowerAssert
37
37
  end
38
38
 
39
39
  def app_context?
40
- top_frame = caller_locations.drop_while {|i| i.path.start_with?(POWER_ASSERT_LIB_DIR) }.first
40
+ top_frame = caller_locations.drop_while {|i| i.path&.start_with?(POWER_ASSERT_LIB_DIR) }.first
41
41
  top_frame and ! internal_file?(top_frame.path)
42
42
  end
43
43
 
44
44
  private
45
45
 
46
46
  def internal_file?(file)
47
+ return false unless file
48
+
47
49
  INTERNAL_LIB_DIRS.find do |_, dir|
48
50
  file.start_with?(dir)
49
51
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: power_assert
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuki Tsujimoto
@@ -58,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
58
  - !ruby/object:Gem::Version
59
59
  version: '0'
60
60
  requirements: []
61
- rubygems_version: 3.6.9
61
+ rubygems_version: 4.0.16
62
62
  specification_version: 4
63
63
  summary: Power Assert for Ruby
64
64
  test_files: []