rspock 2.2.0 → 2.3.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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +36 -6
- data/lib/rspock/ast/comparison_to_assertion_transformation.rb +1 -1
- data/lib/rspock/ast/interaction_to_block_identity_assertion_transformation.rb +30 -0
- data/lib/rspock/ast/interaction_to_mocha_mock_transformation.rb +104 -0
- data/lib/rspock/ast/node.rb +97 -0
- data/lib/rspock/ast/parser/block.rb +88 -0
- data/lib/rspock/ast/parser/cleanup_block.rb +22 -0
- data/lib/rspock/ast/parser/expect_block.rb +26 -0
- data/lib/rspock/ast/parser/given_block.rb +22 -0
- data/lib/rspock/ast/parser/interaction_parser.rb +131 -0
- data/lib/rspock/ast/parser/test_method_parser.rb +103 -0
- data/lib/rspock/ast/parser/then_block.rb +29 -0
- data/lib/rspock/ast/parser/when_block.rb +22 -0
- data/lib/rspock/ast/parser/where_block.rb +94 -0
- data/lib/rspock/ast/test_method_transformation.rb +114 -115
- data/lib/rspock/ast/transformation.rb +16 -24
- data/lib/rspock/helpers/block_capture.rb +41 -0
- data/lib/rspock/version.rb +1 -1
- metadata +15 -12
- data/lib/rspock/ast/block.rb +0 -95
- data/lib/rspock/ast/cleanup_block.rb +0 -16
- data/lib/rspock/ast/end_block.rb +0 -17
- data/lib/rspock/ast/expect_block.rb +0 -21
- data/lib/rspock/ast/given_block.rb +0 -16
- data/lib/rspock/ast/interaction_transformation.rb +0 -165
- data/lib/rspock/ast/start_block.rb +0 -28
- data/lib/rspock/ast/then_block.rb +0 -34
- data/lib/rspock/ast/when_block.rb +0 -16
- data/lib/rspock/ast/where_block.rb +0 -86
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'rspock/ast/node'
|
|
3
|
+
|
|
4
|
+
module RSpock
|
|
5
|
+
module AST
|
|
6
|
+
module Parser
|
|
7
|
+
# Parses a Ruby test method AST node into a self-contained RSpock AST.
|
|
8
|
+
#
|
|
9
|
+
# Input: s(:block, s(:send, nil, :test, ...), s(:args), s(:begin, ...))
|
|
10
|
+
# Output: s(:rspock_test,
|
|
11
|
+
# s(:rspock_def, method_call_node, args_node),
|
|
12
|
+
# s(:rspock_body, s(:rspock_given, ...), s(:rspock_when, ...), ...),
|
|
13
|
+
# s(:rspock_where, ...)) # optional
|
|
14
|
+
class TestMethodParser
|
|
15
|
+
include RSpock::AST::NodeBuilder
|
|
16
|
+
|
|
17
|
+
def initialize(block_registry, strict: true)
|
|
18
|
+
@block_registry = block_registry
|
|
19
|
+
@strict = strict
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Parses a Ruby test method AST into an RSpock AST (TestNode).
|
|
23
|
+
# Returns nil when non-strict and no RSpock blocks are found.
|
|
24
|
+
def parse(node)
|
|
25
|
+
blocks = parse_blocks(node)
|
|
26
|
+
|
|
27
|
+
if blocks.empty?
|
|
28
|
+
return nil unless @strict
|
|
29
|
+
raise BlockError, "Test method @ #{node.loc&.expression || '?'} must start with one of: Given, When, Expect"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
validate_blocks(blocks, node)
|
|
33
|
+
build_rspock_ast(node, blocks)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def parse_blocks(node)
|
|
39
|
+
blocks = []
|
|
40
|
+
test_method_nodes(node).each do |n|
|
|
41
|
+
new_block = parse_block(n)
|
|
42
|
+
if new_block
|
|
43
|
+
validate_succession(blocks, new_block)
|
|
44
|
+
blocks << new_block
|
|
45
|
+
elsif blocks.empty?
|
|
46
|
+
raise BlockError, "Test method must start with one of: Given, When, Expect" if @strict
|
|
47
|
+
# non-strict: ignore pre-block statements in plain minitest tests
|
|
48
|
+
else
|
|
49
|
+
# regular statement — associate with the current block as a child
|
|
50
|
+
blocks.last << n
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
blocks
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def parse_block(node)
|
|
57
|
+
return unless @block_registry.key?(node.children[1])
|
|
58
|
+
|
|
59
|
+
@block_registry[node.children[1]].new(node)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def validate_succession(blocks, new_block)
|
|
63
|
+
return if blocks.empty?
|
|
64
|
+
|
|
65
|
+
current = blocks.last
|
|
66
|
+
unless current.valid_successor?(new_block)
|
|
67
|
+
raise BlockError, current.succession_error_msg
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def validate_blocks(blocks, node)
|
|
72
|
+
unless blocks.first.can_start?
|
|
73
|
+
raise BlockError, "Test method @ #{node.loc&.expression || '?'} must start with one of: Given, When, Expect"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
unless blocks.last.can_end?
|
|
77
|
+
raise BlockError, "Block #{blocks.last.type} @ #{blocks.last.range} must be followed by one of these Blocks: #{blocks.last.successors}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_method_nodes(node)
|
|
82
|
+
return [] if node.children[2].nil?
|
|
83
|
+
|
|
84
|
+
node.children[2]&.type == :begin ? node.children[2].children : [node.children[2]]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def build_rspock_ast(node, blocks)
|
|
88
|
+
def_node = s(:rspock_def, node.children[0], node.children[1])
|
|
89
|
+
where_block = blocks.find { |b| b.type == :Where }
|
|
90
|
+
body_blocks = blocks.reject { |b| b.type == :Where }
|
|
91
|
+
|
|
92
|
+
body_node = s(:rspock_body, *body_blocks.map(&:to_rspock_node))
|
|
93
|
+
|
|
94
|
+
if where_block
|
|
95
|
+
s(:rspock_test, def_node, body_node, where_block.to_rspock_node)
|
|
96
|
+
else
|
|
97
|
+
s(:rspock_test, def_node, body_node)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'rspock/ast/parser/block'
|
|
3
|
+
require 'rspock/ast/parser/interaction_parser'
|
|
4
|
+
|
|
5
|
+
module RSpock
|
|
6
|
+
module AST
|
|
7
|
+
module Parser
|
|
8
|
+
class ThenBlock < Block
|
|
9
|
+
def initialize(node)
|
|
10
|
+
super(:Then, node)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def can_end?
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def successors
|
|
18
|
+
@successors ||= [:Cleanup, :Where].freeze
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_rspock_node
|
|
22
|
+
parser = InteractionParser.new
|
|
23
|
+
spock_children = @children.map { |child| parser.parse(child) }
|
|
24
|
+
s(:rspock_then, *spock_children)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'rspock/ast/parser/block'
|
|
3
|
+
|
|
4
|
+
module RSpock
|
|
5
|
+
module AST
|
|
6
|
+
module Parser
|
|
7
|
+
class WhenBlock < Block
|
|
8
|
+
def initialize(node)
|
|
9
|
+
super(:When, node)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def can_start?
|
|
13
|
+
true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def successors
|
|
17
|
+
@successors ||= [:Then].freeze
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'rspock/ast/parser/block'
|
|
3
|
+
|
|
4
|
+
module RSpock
|
|
5
|
+
module AST
|
|
6
|
+
module Parser
|
|
7
|
+
class WhereBlock < Block
|
|
8
|
+
class MalformedError < StandardError; end
|
|
9
|
+
|
|
10
|
+
def initialize(node)
|
|
11
|
+
super(:Where, node)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def header
|
|
15
|
+
@header ||= parse_header
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def data
|
|
19
|
+
@data ||= parse_data
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_rspock_node
|
|
23
|
+
header_node = s(:rspock_where_header, *header.map { |col| s(:sym, col) })
|
|
24
|
+
data_nodes = data.map { |row| s(:array, *row) }
|
|
25
|
+
s(:rspock_where, header_node, *data_nodes)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def can_end?
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def parse_header
|
|
35
|
+
header = []
|
|
36
|
+
header_pipe_node?(children.first, header) || terminal_header_node?(children.first, header)
|
|
37
|
+
header
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def header_pipe_node?(node, header)
|
|
41
|
+
return false if node.nil?
|
|
42
|
+
|
|
43
|
+
node.type == :send &&
|
|
44
|
+
node.children.count == 3 &&
|
|
45
|
+
(header_pipe_node?(node.children[0], header) || terminal_header_node?(node.children[0], header)) &&
|
|
46
|
+
node.children[1] == :| &&
|
|
47
|
+
terminal_header_node?(node.children[2], header)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def terminal_header_node?(node, header)
|
|
51
|
+
return false if node.nil?
|
|
52
|
+
|
|
53
|
+
result = node.type == :send &&
|
|
54
|
+
node.children.count == 2 &&
|
|
55
|
+
node.children.first.nil? &&
|
|
56
|
+
node.children.last.is_a?(Symbol)
|
|
57
|
+
|
|
58
|
+
raise MalformedError, "Where Block is malformed at location: #{node.loc&.expression || "?"}" unless result
|
|
59
|
+
|
|
60
|
+
header << node.children.last if result
|
|
61
|
+
|
|
62
|
+
result
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def parse_data
|
|
66
|
+
_header_node, *row_nodes = children
|
|
67
|
+
row_nodes.map do |node|
|
|
68
|
+
data = []
|
|
69
|
+
data_pipe_node?(node, data) || terminal_data_node?(node, data)
|
|
70
|
+
data
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def data_pipe_node?(node, data)
|
|
75
|
+
return false if node.nil?
|
|
76
|
+
return false unless node.type == :send && node.children.count == 3 && node.children[1] == :|
|
|
77
|
+
|
|
78
|
+
unless data_pipe_node?(node.children[0], data)
|
|
79
|
+
terminal_data_node?(node.children[0], data)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
unless data_pipe_node?(node.children[2], data)
|
|
83
|
+
terminal_data_node?(node.children[2], data)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def terminal_data_node?(node, data)
|
|
88
|
+
data << node
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -1,165 +1,164 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'ast_transform/abstract_transformation'
|
|
3
|
+
require 'rspock/ast/node'
|
|
4
|
+
require 'rspock/ast/comparison_to_assertion_transformation'
|
|
3
5
|
require 'rspock/ast/header_nodes_transformation'
|
|
6
|
+
require 'rspock/ast/interaction_to_mocha_mock_transformation'
|
|
7
|
+
require 'rspock/ast/interaction_to_block_identity_assertion_transformation'
|
|
4
8
|
require 'rspock/ast/method_call_to_lvar_transformation'
|
|
5
9
|
require 'rspock/ast/test_method_def_transformation'
|
|
10
|
+
require 'rspock/ast/parser/test_method_parser'
|
|
6
11
|
|
|
7
12
|
module RSpock
|
|
8
13
|
module AST
|
|
9
14
|
class TestMethodTransformation < ASTTransform::AbstractTransformation
|
|
10
|
-
def initialize(
|
|
11
|
-
@
|
|
12
|
-
@
|
|
13
|
-
@end_block_class = end_block_class
|
|
14
|
-
@strict = strict
|
|
15
|
-
@blocks = []
|
|
15
|
+
def initialize(block_registry, strict: true)
|
|
16
|
+
@parser = Parser::TestMethodParser.new(block_registry, strict: strict)
|
|
17
|
+
@comparison_transformation = ComparisonToAssertionTransformation.new(:_test_index_, :_line_number_)
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def run(node)
|
|
19
|
-
parse(node)
|
|
20
|
-
|
|
21
|
+
rspock_ast = @parser.parse(node)
|
|
22
|
+
return node if rspock_ast.nil?
|
|
23
|
+
transform(rspock_ast)
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
private
|
|
24
27
|
|
|
25
|
-
def
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
def transform(rspock_ast)
|
|
29
|
+
hoisted_setups = []
|
|
30
|
+
|
|
31
|
+
method_call = rspock_ast.def_node.method_call
|
|
32
|
+
method_args = rspock_ast.def_node.args
|
|
33
|
+
where = rspock_ast.where_node
|
|
34
|
+
|
|
35
|
+
transformed_blocks = rspock_ast.body_node.children.map do |block_node|
|
|
36
|
+
case block_node.type
|
|
37
|
+
when :rspock_then
|
|
38
|
+
transform_then_block(block_node, hoisted_setups)
|
|
39
|
+
when :rspock_expect
|
|
40
|
+
transform_expect_block(block_node)
|
|
41
|
+
else
|
|
42
|
+
block_node
|
|
43
|
+
end
|
|
44
|
+
end
|
|
28
45
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
add_block(@end_block_class.new)
|
|
32
|
-
nil
|
|
46
|
+
transformed_body = rspock_ast.body_node.updated(nil, transformed_blocks)
|
|
47
|
+
build_ruby_ast(method_call, method_args, transformed_body, where, hoisted_setups)
|
|
33
48
|
end
|
|
34
49
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
def transform_then_block(then_node, hoisted_setups)
|
|
51
|
+
interaction_setups = []
|
|
52
|
+
then_children = []
|
|
53
|
+
|
|
54
|
+
then_node.children.each_with_index do |child, idx|
|
|
55
|
+
if child.type == :rspock_interaction
|
|
56
|
+
setup = InteractionToMochaMockTransformation.new(idx).run(child)
|
|
57
|
+
assertion = InteractionToBlockIdentityAssertionTransformation.new(idx).run(child)
|
|
58
|
+
|
|
59
|
+
interaction_setups << setup
|
|
60
|
+
then_children << assertion unless assertion.equal?(child)
|
|
61
|
+
else
|
|
62
|
+
then_children << @comparison_transformation.run(child)
|
|
63
|
+
end
|
|
44
64
|
end
|
|
45
|
-
end
|
|
46
65
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
unless interaction_setups.empty?
|
|
67
|
+
interaction_setups.each do |node|
|
|
68
|
+
if node.type == :begin
|
|
69
|
+
hoisted_setups.concat(node.children)
|
|
70
|
+
else
|
|
71
|
+
hoisted_setups << node
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
56
75
|
|
|
57
|
-
|
|
58
|
-
rows.map(&method(:build_where_block_data_row))
|
|
76
|
+
then_node.updated(nil, then_children)
|
|
59
77
|
end
|
|
60
78
|
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
s(:array, *children)
|
|
79
|
+
def transform_expect_block(expect_node)
|
|
80
|
+
new_children = expect_node.children.map { |child| @comparison_transformation.run(child) }
|
|
81
|
+
expect_node.updated(nil, new_children)
|
|
66
82
|
end
|
|
67
83
|
|
|
68
|
-
|
|
69
|
-
injected_args = header.map { |column| s(:arg, column) }
|
|
70
|
-
injected_args << s(:arg, :_line_number_)
|
|
84
|
+
# --- Build final Ruby AST ---
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
s(:
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
def build_ruby_ast(method_call, method_args, body_node, where, hoisted_setups)
|
|
87
|
+
if where
|
|
88
|
+
test_def = s(:block,
|
|
89
|
+
TestMethodDefTransformation.new.run(method_call),
|
|
90
|
+
method_args,
|
|
91
|
+
build_test_body(body_node, hoisted_setups)
|
|
92
|
+
)
|
|
93
|
+
test_def = HeaderNodesTransformation.new(where.header).run(test_def)
|
|
77
94
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
node.children[1],
|
|
83
|
-
build_test_body
|
|
95
|
+
s(:block,
|
|
96
|
+
build_where_iterator(where.data_rows),
|
|
97
|
+
build_where_args(where.header),
|
|
98
|
+
test_def
|
|
84
99
|
)
|
|
85
|
-
HeaderNodesTransformation.new(where_block.header).run(ast)
|
|
86
100
|
else
|
|
87
101
|
s(:block,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
build_test_body
|
|
102
|
+
method_call,
|
|
103
|
+
method_args,
|
|
104
|
+
build_test_body(body_node, hoisted_setups)
|
|
91
105
|
)
|
|
92
106
|
end
|
|
93
107
|
end
|
|
94
108
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
def build_test_body(body_node, hoisted_setups)
|
|
110
|
+
body_children = []
|
|
111
|
+
|
|
112
|
+
body_node.children.each do |block_node|
|
|
113
|
+
case block_node.type
|
|
114
|
+
when :rspock_given
|
|
115
|
+
body_children.concat(block_node.children)
|
|
116
|
+
when :rspock_when
|
|
117
|
+
body_children.concat(hoisted_setups)
|
|
118
|
+
body_children.concat(block_node.children)
|
|
119
|
+
when :rspock_then, :rspock_expect
|
|
120
|
+
body_children.concat(block_node.children)
|
|
121
|
+
when :rspock_cleanup
|
|
122
|
+
# handled below as ensure
|
|
123
|
+
end
|
|
106
124
|
end
|
|
107
125
|
|
|
108
|
-
|
|
109
|
-
end
|
|
126
|
+
ast = s(:begin, *body_children)
|
|
110
127
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def parse_node(node)
|
|
118
|
-
if @source_map.key?(node.children[1])
|
|
119
|
-
add_block(build_block(node))
|
|
120
|
-
else
|
|
121
|
-
current_scope << node
|
|
128
|
+
cleanup = body_node.children.find { |n| n.type == :rspock_cleanup }
|
|
129
|
+
if cleanup && !cleanup.children.empty?
|
|
130
|
+
ensure_node = s(:begin, *cleanup.children)
|
|
131
|
+
ast = s(:kwbegin, s(:ensure, ast, ensure_node))
|
|
122
132
|
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def build_block(node)
|
|
126
|
-
@source_map[node.children[1]].new(node)
|
|
127
|
-
end
|
|
128
133
|
|
|
129
|
-
|
|
130
|
-
@when_block ||= @blocks.detect { |block| block.type == :When }
|
|
134
|
+
MethodCallToLVarTransformation.new(:_test_index_, :_line_number_).run(ast)
|
|
131
135
|
end
|
|
132
136
|
|
|
133
|
-
|
|
134
|
-
@then_block ||= @blocks.detect { |block| block.type == :Then }
|
|
135
|
-
end
|
|
137
|
+
# --- Where block helpers ---
|
|
136
138
|
|
|
137
|
-
def
|
|
138
|
-
|
|
139
|
+
def build_where_iterator(data_rows)
|
|
140
|
+
s(:send,
|
|
141
|
+
s(:send,
|
|
142
|
+
s(:array, *data_rows.map { |row| build_where_data_row(row) }),
|
|
143
|
+
:each,
|
|
144
|
+
),
|
|
145
|
+
:with_index
|
|
146
|
+
)
|
|
139
147
|
end
|
|
140
148
|
|
|
141
|
-
def
|
|
142
|
-
|
|
149
|
+
def build_where_data_row(row)
|
|
150
|
+
children = row.dup
|
|
151
|
+
children << s(:int, row.first&.loc&.expression&.line)
|
|
152
|
+
s(:array, *children)
|
|
143
153
|
end
|
|
144
154
|
|
|
145
|
-
def
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
ast = s(:begin, *test_body_children)
|
|
153
|
-
|
|
154
|
-
if cleanup_block && !cleanup_block.children.empty?
|
|
155
|
-
ensure_node = s(:begin, *cleanup_block.children)
|
|
156
|
-
|
|
157
|
-
ast = s(:kwbegin,
|
|
158
|
-
s(:ensure, ast, ensure_node)
|
|
159
|
-
)
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
MethodCallToLVarTransformation.new(:_test_index_, :_line_number_).run(ast)
|
|
155
|
+
def build_where_args(header)
|
|
156
|
+
injected_args = header.map { |column| s(:arg, column) }
|
|
157
|
+
injected_args << s(:arg, :_line_number_)
|
|
158
|
+
s(:args,
|
|
159
|
+
s(:mlhs, *injected_args),
|
|
160
|
+
s(:arg, :_test_index_),
|
|
161
|
+
)
|
|
163
162
|
end
|
|
164
163
|
end
|
|
165
164
|
end
|
|
@@ -1,37 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'ast_transform/abstract_transformation'
|
|
3
|
-
require 'rspock/ast/
|
|
4
|
-
require 'rspock/ast/
|
|
5
|
-
require 'rspock/ast/
|
|
6
|
-
require 'rspock/ast/
|
|
7
|
-
require 'rspock/ast/
|
|
8
|
-
require 'rspock/ast/
|
|
9
|
-
require 'rspock/ast/where_block'
|
|
10
|
-
require 'rspock/ast/end_block'
|
|
3
|
+
require 'rspock/ast/parser/given_block'
|
|
4
|
+
require 'rspock/ast/parser/when_block'
|
|
5
|
+
require 'rspock/ast/parser/then_block'
|
|
6
|
+
require 'rspock/ast/parser/expect_block'
|
|
7
|
+
require 'rspock/ast/parser/cleanup_block'
|
|
8
|
+
require 'rspock/ast/parser/where_block'
|
|
11
9
|
require 'rspock/ast/test_method_transformation'
|
|
12
10
|
|
|
13
11
|
module RSpock
|
|
14
12
|
module AST
|
|
15
13
|
class Transformation < ASTTransform::AbstractTransformation
|
|
16
|
-
|
|
17
|
-
Given:
|
|
18
|
-
When:
|
|
19
|
-
Then:
|
|
20
|
-
Expect:
|
|
21
|
-
Cleanup:
|
|
22
|
-
Where:
|
|
14
|
+
DEFAULT_BLOCK_REGISTRY = {
|
|
15
|
+
Given: Parser::GivenBlock,
|
|
16
|
+
When: Parser::WhenBlock,
|
|
17
|
+
Then: Parser::ThenBlock,
|
|
18
|
+
Expect: Parser::ExpectBlock,
|
|
19
|
+
Cleanup: Parser::CleanupBlock,
|
|
20
|
+
Where: Parser::WhereBlock,
|
|
23
21
|
}.freeze
|
|
24
22
|
|
|
25
23
|
def initialize(
|
|
26
|
-
|
|
27
|
-
end_block_class: EndBlock,
|
|
28
|
-
source_map: DefaultSourceMap,
|
|
24
|
+
block_registry: DEFAULT_BLOCK_REGISTRY,
|
|
29
25
|
strict: true
|
|
30
26
|
)
|
|
31
27
|
super()
|
|
32
|
-
@
|
|
33
|
-
@source_map = source_map
|
|
34
|
-
@end_block_class = end_block_class
|
|
28
|
+
@block_registry = block_registry
|
|
35
29
|
@strict = strict
|
|
36
30
|
end
|
|
37
31
|
|
|
@@ -93,9 +87,7 @@ module RSpock
|
|
|
93
87
|
end
|
|
94
88
|
|
|
95
89
|
TestMethodTransformation.new(
|
|
96
|
-
@
|
|
97
|
-
@start_block_class,
|
|
98
|
-
@end_block_class,
|
|
90
|
+
@block_registry,
|
|
99
91
|
strict: @strict
|
|
100
92
|
).run(node)
|
|
101
93
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSpock
|
|
4
|
+
module Helpers
|
|
5
|
+
module BlockCapture
|
|
6
|
+
# Installs a block-capture wrapper on +obj+ for +method_name+.
|
|
7
|
+
# Must be called AFTER Mocha's expects/stubs so the wrapper sits
|
|
8
|
+
# in front of whatever Mocha installed.
|
|
9
|
+
#
|
|
10
|
+
# Returns a lambda that, when called, returns the captured block
|
|
11
|
+
# (or nil if no block was passed).
|
|
12
|
+
def self.capture(obj, method_name)
|
|
13
|
+
state = { captured: nil }
|
|
14
|
+
|
|
15
|
+
if obj.respond_to?(method_name, true)
|
|
16
|
+
# Real objects or objects where Mocha defined the method on
|
|
17
|
+
# the singleton class. Prepend a module so we intercept the
|
|
18
|
+
# call before Mocha's stub (prepend wins over define_singleton_method).
|
|
19
|
+
s = state
|
|
20
|
+
capture_mod = Module.new do
|
|
21
|
+
define_method(method_name) do |*args, **kwargs, &blk|
|
|
22
|
+
s[:captured] = blk
|
|
23
|
+
super(*args, **kwargs, &blk)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
obj.singleton_class.prepend(capture_mod)
|
|
27
|
+
else
|
|
28
|
+
# Mock objects where the method goes through method_missing.
|
|
29
|
+
original_mm = obj.method(:method_missing)
|
|
30
|
+
s = state
|
|
31
|
+
obj.define_singleton_method(:method_missing) do |name, *args, **kwargs, &blk|
|
|
32
|
+
s[:captured] = blk if name == method_name
|
|
33
|
+
original_mm.call(name, *args, **kwargs, &blk)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
-> { state[:captured] }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/rspock/version.rb
CHANGED