solargraph-rspec 0.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +41 -0
- data/CHANGELOG.md +22 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +115 -0
- data/Rakefile +12 -0
- data/lib/solargraph/rspec/config.rb +41 -0
- data/lib/solargraph/rspec/convention.rb +223 -0
- data/lib/solargraph/rspec/correctors/base.rb +46 -0
- data/lib/solargraph/rspec/correctors/context_block_methods_corrector.rb +38 -0
- data/lib/solargraph/rspec/correctors/context_block_namespace_corrector.rb +65 -0
- data/lib/solargraph/rspec/correctors/described_class_corrector.rb +39 -0
- data/lib/solargraph/rspec/correctors/dsl_methods_corrector.rb +68 -0
- data/lib/solargraph/rspec/correctors/example_and_hook_blocks_binding_corrector.rb +74 -0
- data/lib/solargraph/rspec/correctors/implicit_subject_method_corrector.rb +48 -0
- data/lib/solargraph/rspec/correctors/let_methods_corrector.rb +44 -0
- data/lib/solargraph/rspec/correctors/subject_method_corrector.rb +24 -0
- data/lib/solargraph/rspec/correctors/walker_base.rb +27 -0
- data/lib/solargraph/rspec/spec_walker.rb +247 -0
- data/lib/solargraph/rspec/util.rb +77 -0
- data/lib/solargraph/rspec/version.rb +7 -0
- data/lib/solargraph/rspec/walker.rb +120 -0
- data/lib/solargraph-rspec.rb +19 -0
- data/lib/solargraph_rspec.rb +3 -0
- data/sig/solargraph/rspec.rbs +6 -0
- data/solargraph-rspec.gemspec +37 -0
- metadata +107 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'walker_base'
|
4
|
+
|
5
|
+
module Solargraph
|
6
|
+
module Rspec
|
7
|
+
module Correctors
|
8
|
+
# A corrector of RSpec parsed pins by Solargraph
|
9
|
+
class ContextBlockNamespaceCorrector < WalkerBase
|
10
|
+
# @param source_map [Solargraph::SourceMap]
|
11
|
+
def correct(source_map)
|
12
|
+
rspec_walker.on_each_context_block do |namespace_name, ast|
|
13
|
+
original_block_pin = source_map.locate_block_pin(ast.location.begin.line, ast.location.begin.column)
|
14
|
+
original_block_pin_index = source_map.pins.index(original_block_pin)
|
15
|
+
location = Util.build_location(ast, source_map.filename)
|
16
|
+
|
17
|
+
# Define a dynamic module for the example group block
|
18
|
+
# Example:
|
19
|
+
# RSpec.describe Foo::Bar do # => module RSpec::ExampleGroups::FooBar
|
20
|
+
# context 'some context' do # => module RSpec::ExampleGroups::FooBar::SomeContext
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
namespace_pin = Solargraph::Pin::Namespace.new(
|
24
|
+
name: namespace_name,
|
25
|
+
location: location
|
26
|
+
)
|
27
|
+
|
28
|
+
fixed_namespace_block_pin = Solargraph::Pin::Block.new(
|
29
|
+
closure: namespace_pin,
|
30
|
+
location: original_block_pin.location,
|
31
|
+
receiver: original_block_pin.receiver,
|
32
|
+
scope: original_block_pin.scope
|
33
|
+
)
|
34
|
+
|
35
|
+
source_map.pins[original_block_pin_index] = fixed_namespace_block_pin
|
36
|
+
|
37
|
+
# Include DSL methods in the example group block
|
38
|
+
# TOOD: This does not work on solagraph! Class methods are not included from parent class.
|
39
|
+
namespace_extend_pin = Util.build_module_extend(
|
40
|
+
namespace_pin,
|
41
|
+
root_example_group_namespace_pin.name,
|
42
|
+
location
|
43
|
+
)
|
44
|
+
|
45
|
+
# Include parent example groups to share let definitions
|
46
|
+
parent_namespace_name = namespace_name.split('::')[0..-2].join('::')
|
47
|
+
namespace_include_pin = Util.build_module_include(
|
48
|
+
namespace_pin,
|
49
|
+
parent_namespace_name,
|
50
|
+
location
|
51
|
+
)
|
52
|
+
|
53
|
+
namespace_pins << namespace_pin
|
54
|
+
if block_given?
|
55
|
+
yield [
|
56
|
+
namespace_include_pin,
|
57
|
+
namespace_extend_pin
|
58
|
+
]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'walker_base'
|
4
|
+
|
5
|
+
module Solargraph
|
6
|
+
module Rspec
|
7
|
+
module Correctors
|
8
|
+
class DescribedClassCorrector < WalkerBase
|
9
|
+
# @param source_map [Solargraph::SourceMap]
|
10
|
+
# @return [void]
|
11
|
+
def correct(_source_map)
|
12
|
+
rspec_walker.on_described_class do |ast, described_class_name|
|
13
|
+
namespace_pin = closest_namespace_pin(namespace_pins, ast.loc.line)
|
14
|
+
next unless namespace_pin
|
15
|
+
|
16
|
+
described_class_pin = rspec_described_class_method(namespace_pin, ast, described_class_name)
|
17
|
+
yield [described_class_pin].compact if block_given?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# @param namespace [Pin::Namespace]
|
24
|
+
# @param ast [Parser::AST::Node]
|
25
|
+
# @param described_class_name [String]
|
26
|
+
# @return [Pin::Method, nil]
|
27
|
+
def rspec_described_class_method(namespace, ast, described_class_name)
|
28
|
+
Util.build_public_method(
|
29
|
+
namespace,
|
30
|
+
'described_class',
|
31
|
+
types: ["Class<#{described_class_name}>"],
|
32
|
+
location: Util.build_location(ast, namespace.filename),
|
33
|
+
scope: :instance
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'walker_base'
|
4
|
+
|
5
|
+
module Solargraph
|
6
|
+
module Rspec
|
7
|
+
module Correctors
|
8
|
+
class DslMethodsCorrector < WalkerBase
|
9
|
+
# @param namespace_pins [Array<Solargraph::Pin::Base>]
|
10
|
+
# @param rspec_walker [Solargraph::Rspec::SpecWalker]
|
11
|
+
# @param config [Solargraph::Rspec::Config]
|
12
|
+
def initialize(namespace_pins:, rspec_walker:, config:)
|
13
|
+
super(namespace_pins: namespace_pins, rspec_walker: rspec_walker)
|
14
|
+
@config = config
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param source_map [Solargraph::SourceMap]
|
18
|
+
# @return [void]
|
19
|
+
def correct(_source_map)
|
20
|
+
rspec_walker.after_walk do
|
21
|
+
if block_given?
|
22
|
+
yield namespace_pins.flat_map { |namespace_pin| add_context_dsl_methods(namespace_pin) }
|
23
|
+
yield namespace_pins.flat_map { |namespace_pin| add_methods_with_example_binding(namespace_pin) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# @return [Solargraph::Rspec::Config]
|
31
|
+
attr_reader :config
|
32
|
+
|
33
|
+
# RSpec executes example and hook blocks (eg. it, before, after)in the context of the example group.
|
34
|
+
# @yieldsef changes the binding of the block to correct class.
|
35
|
+
# @return [Array<Solargraph::Pin::Method>]
|
36
|
+
def add_methods_with_example_binding(namespace_pin)
|
37
|
+
rspec_context_block_methods.map do |method|
|
38
|
+
Util.build_public_method(
|
39
|
+
namespace_pin,
|
40
|
+
method.to_s,
|
41
|
+
comments: ["@yieldself [#{namespace_pin.path}]"], # Fixes the binding of the block to the correct class
|
42
|
+
scope: :class
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# TODO: DSL methods should be defined once in the root example group and extended to all example groups.
|
48
|
+
# Fix this once Solargraph supports extending class methods.
|
49
|
+
# @param namespace_pin [Solargraph::Pin::Base]
|
50
|
+
# @return [Array<Solargraph::Pin::Base>]
|
51
|
+
def add_context_dsl_methods(namespace_pin)
|
52
|
+
Rspec::CONTEXT_METHODS.map do |method|
|
53
|
+
Util.build_public_method(
|
54
|
+
namespace_pin,
|
55
|
+
method.to_s,
|
56
|
+
scope: :class
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Array<String>]
|
62
|
+
def rspec_context_block_methods
|
63
|
+
config.let_methods + Rspec::HOOK_METHODS + Rspec::EXAMPLE_METHODS
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'walker_base'
|
4
|
+
|
5
|
+
module Solargraph
|
6
|
+
module Rspec
|
7
|
+
module Correctors
|
8
|
+
# Sets the correct namespace binding for example group blocks (it, example, etc.) and
|
9
|
+
# hook blocks (before, after, around)
|
10
|
+
class ExampleAndHookBlocksBindingCorrector < WalkerBase
|
11
|
+
# @param source_map [Solargraph::SourceMap]
|
12
|
+
# @return [void]
|
13
|
+
def correct(source_map)
|
14
|
+
rspec_walker.on_example_block do |block_ast|
|
15
|
+
bind_closest_namespace(block_ast, source_map)
|
16
|
+
|
17
|
+
yield [] if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
rspec_walker.on_hook_block do |block_ast|
|
21
|
+
bind_closest_namespace(block_ast, source_map)
|
22
|
+
|
23
|
+
yield [] if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
rspec_walker.on_let_method do |let_method_ast|
|
27
|
+
bind_closest_namespace(let_method_ast, source_map)
|
28
|
+
|
29
|
+
yield [] if block_given?
|
30
|
+
end
|
31
|
+
|
32
|
+
rspec_walker.on_blocks_in_examples do |block_ast|
|
33
|
+
bind_closest_namespace(block_ast, source_map)
|
34
|
+
|
35
|
+
yield [] if block_given?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# @param block_ast [Parser::AST::Node]
|
42
|
+
# @param source_map [Solargraph::SourceMap]
|
43
|
+
# @return [void]
|
44
|
+
def bind_closest_namespace(block_ast, source_map)
|
45
|
+
namespace_pin = closest_namespace_pin(namespace_pins, block_ast.loc.line)
|
46
|
+
return unless namespace_pin
|
47
|
+
|
48
|
+
original_block_pin = source_map.locate_block_pin(block_ast.location.begin.line,
|
49
|
+
block_ast.location.begin.column)
|
50
|
+
original_block_pin_index = source_map.pins.index(original_block_pin)
|
51
|
+
fixed_namespace_block_pin = Solargraph::Pin::Block.new(
|
52
|
+
closure: example_run_method(namespace_pin),
|
53
|
+
location: original_block_pin.location,
|
54
|
+
receiver: original_block_pin.receiver,
|
55
|
+
scope: original_block_pin.scope
|
56
|
+
)
|
57
|
+
|
58
|
+
source_map.pins[original_block_pin_index] = fixed_namespace_block_pin
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param namespace_pin [Solargraph::Pin::Namespace]
|
62
|
+
# @return [Solargraph::Pin::Method]
|
63
|
+
def example_run_method(namespace_pin)
|
64
|
+
Util.build_public_method(
|
65
|
+
namespace_pin,
|
66
|
+
'run',
|
67
|
+
# https://github.com/rspec/rspec-core/blob/main/lib/rspec/core/example.rb#L246
|
68
|
+
location: Solargraph::Location.new('lib/rspec/core/example.rb', Solargraph::Range.from_to(246, 1, 297, 1))
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'let_methods_corrector'
|
4
|
+
|
5
|
+
module Solargraph
|
6
|
+
module Rspec
|
7
|
+
module Correctors
|
8
|
+
# Defines let-like methods in the example group block
|
9
|
+
class ImplicitSubjectMethodCorrector < Base
|
10
|
+
# @return [Pin::Method]
|
11
|
+
attr_reader :described_class_pin
|
12
|
+
|
13
|
+
# @param namespace_pins [Array<Pin::Namespace>]
|
14
|
+
# @param described_class_pin [Pin::Method]
|
15
|
+
def initialize(namespace_pins:, described_class_pin:)
|
16
|
+
super(namespace_pins: namespace_pins)
|
17
|
+
|
18
|
+
@described_class_pin = described_class_pin
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param source_map [Solargraph::SourceMap]
|
22
|
+
# @return [void]
|
23
|
+
def correct(_source_map)
|
24
|
+
namespace_pin = closest_namespace_pin(namespace_pins, described_class_pin.location.range.start.line)
|
25
|
+
|
26
|
+
yield [implicit_subject_pin(described_class_pin, namespace_pin)] if block_given? && namespace_pin
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @param described_class_pin [Pin::Method]
|
32
|
+
# @param namespace_pin [Pin::Namespace]
|
33
|
+
# @return [Pin::Method]
|
34
|
+
def implicit_subject_pin(described_class_pin, namespace_pin)
|
35
|
+
described_class = described_class_pin.return_type.first.subtypes.first.name
|
36
|
+
|
37
|
+
Util.build_public_method(
|
38
|
+
namespace_pin,
|
39
|
+
'subject',
|
40
|
+
types: [described_class],
|
41
|
+
location: described_class_pin.location,
|
42
|
+
scope: :instance
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'walker_base'
|
4
|
+
|
5
|
+
module Solargraph
|
6
|
+
module Rspec
|
7
|
+
module Correctors
|
8
|
+
# Defines let-like methods in the example group block
|
9
|
+
class LetMethodsCorrector < WalkerBase
|
10
|
+
# @param source_map [Solargraph::SourceMap]
|
11
|
+
# @return [void]
|
12
|
+
def correct(_source_map)
|
13
|
+
rspec_walker.on_let_method do |ast|
|
14
|
+
namespace_pin = closest_namespace_pin(namespace_pins, ast.loc.line)
|
15
|
+
next unless namespace_pin
|
16
|
+
|
17
|
+
pin = rspec_let_method(namespace_pin, ast)
|
18
|
+
yield [pin] if block_given?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @param namespace [Pin::Namespace]
|
25
|
+
# @param ast [Parser::AST::Node]
|
26
|
+
# @param types [Array<String>, nil]
|
27
|
+
# @return [Pin::Method, nil]
|
28
|
+
def rspec_let_method(namespace, ast, types: nil)
|
29
|
+
return unless ast.children
|
30
|
+
return unless ast.children[2]&.children
|
31
|
+
|
32
|
+
method_name = ast.children[2].children[0]&.to_s or return
|
33
|
+
Util.build_public_method(
|
34
|
+
namespace,
|
35
|
+
method_name,
|
36
|
+
types: types,
|
37
|
+
location: Util.build_location(ast, namespace.filename),
|
38
|
+
scope: :instance
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'let_methods_corrector'
|
4
|
+
|
5
|
+
module Solargraph
|
6
|
+
module Rspec
|
7
|
+
module Correctors
|
8
|
+
# Defines let-like methods in the example group block
|
9
|
+
class SubjectMethodCorrector < LetMethodsCorrector
|
10
|
+
# @param source_map [Solargraph::SourceMap]
|
11
|
+
# @return [void]
|
12
|
+
def correct(_source_map)
|
13
|
+
rspec_walker.on_subject do |ast|
|
14
|
+
namespace_pin = closest_namespace_pin(namespace_pins, ast.loc.line)
|
15
|
+
next unless namespace_pin
|
16
|
+
|
17
|
+
subject_pin = rspec_let_method(namespace_pin, ast)
|
18
|
+
yield [subject_pin].compact if block_given?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
# A corrector that walks through RSpec AST nodes and corrects them
|
6
|
+
module Solargraph
|
7
|
+
module Rspec
|
8
|
+
module Correctors
|
9
|
+
# A corrector of RSpec parsed pins by Solargraph
|
10
|
+
# @abstract
|
11
|
+
class WalkerBase < Base
|
12
|
+
# @return [Array<Solargraph::Pin::Namespace>]
|
13
|
+
attr_reader :namespace_pins
|
14
|
+
|
15
|
+
# @return [Solargraph::Rspec::SpecWalker]
|
16
|
+
attr_reader :rspec_walker
|
17
|
+
|
18
|
+
# @param namespace_pins [Array<Solargraph::Pin::Base>]
|
19
|
+
# @param rspec_walker [Solargraph::Rspec::SpecWalker]
|
20
|
+
def initialize(namespace_pins:, rspec_walker:)
|
21
|
+
super(namespace_pins: namespace_pins)
|
22
|
+
@rspec_walker = rspec_walker
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'walker'
|
4
|
+
|
5
|
+
module Solargraph
|
6
|
+
module Rspec
|
7
|
+
class SpecWalker
|
8
|
+
# @param source_map [SourceMap]
|
9
|
+
# @param config [Config]
|
10
|
+
def initialize(source_map:, config:)
|
11
|
+
@source_map = source_map
|
12
|
+
@config = config
|
13
|
+
@walker = Rspec::Walker.from_source(source_map.source)
|
14
|
+
@handlers = {
|
15
|
+
on_described_class: [],
|
16
|
+
on_let_method: [],
|
17
|
+
on_subject: [],
|
18
|
+
on_each_context_block: [],
|
19
|
+
on_example_block: [],
|
20
|
+
on_hook_block: [],
|
21
|
+
on_blocks_in_examples: [],
|
22
|
+
after_walk: []
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Walker]
|
27
|
+
attr_reader :walker
|
28
|
+
|
29
|
+
# @return [Config]
|
30
|
+
attr_reader :config
|
31
|
+
|
32
|
+
# @param block [Proc]
|
33
|
+
# @return [void]
|
34
|
+
def on_described_class(&block)
|
35
|
+
@handlers[:on_described_class] << block
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param block [Proc]
|
39
|
+
# @return [void]
|
40
|
+
def on_let_method(&block)
|
41
|
+
@handlers[:on_let_method] << block
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param block [Proc]
|
45
|
+
# @return [void]
|
46
|
+
def on_subject(&block)
|
47
|
+
@handlers[:on_subject] << block
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param block [Proc]
|
51
|
+
# @return [void]
|
52
|
+
def on_each_context_block(&block)
|
53
|
+
@handlers[:on_each_context_block] << block
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# @param block [Proc]
|
58
|
+
# @return [void]
|
59
|
+
def on_example_block(&block)
|
60
|
+
@handlers[:on_example_block] << block
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param block [Proc]
|
64
|
+
# @return [void]
|
65
|
+
def on_hook_block(&block)
|
66
|
+
@handlers[:on_hook_block] << block
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param block [Proc]
|
70
|
+
# @return [void]
|
71
|
+
def on_blocks_in_examples(&block)
|
72
|
+
@handlers[:on_blocks_in_examples] << block
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param block [Proc]
|
76
|
+
# @return [void]
|
77
|
+
def after_walk(&block)
|
78
|
+
@handlers[:after_walk] << block
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [void]
|
82
|
+
def walk!
|
83
|
+
each_context_block(@walker.ast, Rspec::ROOT_NAMESPACE) do |namespace_name, ast|
|
84
|
+
@handlers[:on_each_context_block].each do |handler|
|
85
|
+
handler.call(namespace_name, ast)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
rspec_const = ::Parser::AST::Node.new(:const, [nil, :RSpec])
|
90
|
+
walker.on :send, [rspec_const, :describe, :any] do |ast|
|
91
|
+
@handlers[:on_described_class].each do |handler|
|
92
|
+
class_ast = ast.children[2]
|
93
|
+
next unless class_ast
|
94
|
+
|
95
|
+
class_name = full_constant_name(class_ast)
|
96
|
+
handler.call(class_ast, class_name)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
config.let_methods.each do |let_method|
|
101
|
+
walker.on :send, [nil, let_method] do |ast|
|
102
|
+
@handlers[:on_let_method].each do |handler|
|
103
|
+
handler.call(ast)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
walker.on :send, [nil, :subject] do |ast|
|
109
|
+
@handlers[:on_subject].each do |handler|
|
110
|
+
handler.call(ast)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
walker.on :block do |block_ast|
|
115
|
+
next if block_ast.children.first.type != :send
|
116
|
+
|
117
|
+
method_ast = block_ast.children.first
|
118
|
+
method_name = method_ast.children[1]
|
119
|
+
next unless Rspec::EXAMPLE_METHODS.include?(method_name.to_s)
|
120
|
+
|
121
|
+
@handlers[:on_example_block].each do |handler|
|
122
|
+
handler.call(block_ast)
|
123
|
+
end
|
124
|
+
|
125
|
+
# @param blocks_in_examples [Parser::AST::Node]
|
126
|
+
each_block(block_ast.children[2]) do |blocks_in_examples|
|
127
|
+
@handlers[:on_blocks_in_examples].each do |handler|
|
128
|
+
handler.call(blocks_in_examples)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
walker.on :block do |block_ast|
|
134
|
+
next if block_ast.children.first.type != :send
|
135
|
+
|
136
|
+
method_ast = block_ast.children.first
|
137
|
+
method_name = method_ast.children[1]
|
138
|
+
next unless Rspec::HOOK_METHODS.include?(method_name.to_s)
|
139
|
+
|
140
|
+
@handlers[:on_hook_block].each do |handler|
|
141
|
+
handler.call(block_ast)
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param blocks_in_examples [Parser::AST::Node]
|
145
|
+
each_block(block_ast.children[2]) do |blocks_in_examples|
|
146
|
+
@handlers[:on_blocks_in_examples].each do |handler|
|
147
|
+
handler.call(blocks_in_examples)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
walker.walk
|
153
|
+
|
154
|
+
@handlers[:after_walk].each(&:call)
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
# @param ast [Parser::AST::Node]
|
160
|
+
# @param parent_result [Object]
|
161
|
+
def each_block(ast, parent_result = nil, &block)
|
162
|
+
return unless ast.is_a?(::Parser::AST::Node)
|
163
|
+
|
164
|
+
is_a_block = ast.type == :block && ast.children[0].type == :send
|
165
|
+
|
166
|
+
if is_a_block
|
167
|
+
result = block&.call(ast, parent_result)
|
168
|
+
parent_result = result if result
|
169
|
+
end
|
170
|
+
|
171
|
+
ast.children.each { |child| each_block(child, parent_result, &block) }
|
172
|
+
end
|
173
|
+
|
174
|
+
# Find all describe/context blocks in the AST.
|
175
|
+
# @param ast [Parser::AST::Node]
|
176
|
+
# @yield [String, Parser::AST::Node]
|
177
|
+
def each_context_block(ast, root_namespace = Rspec::ROOT_NAMESPACE, &block)
|
178
|
+
each_block(ast, root_namespace) do |block_ast, parent_namespace|
|
179
|
+
is_a_context = %i[describe context].include?(block_ast.children[0].children[1])
|
180
|
+
|
181
|
+
next unless is_a_context
|
182
|
+
|
183
|
+
description_node = block_ast.children[0].children[2]
|
184
|
+
block_name = rspec_describe_class_name(description_node)
|
185
|
+
next unless block_name
|
186
|
+
|
187
|
+
parent_namespace = namespace_name = "#{parent_namespace}::#{block_name}"
|
188
|
+
block&.call(namespace_name, block_ast)
|
189
|
+
next parent_namespace
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# @param ast [Parser::AST::Node]
|
194
|
+
# @return [String, nil]
|
195
|
+
def rspec_describe_class_name(ast)
|
196
|
+
if ast.type == :str
|
197
|
+
string_to_const_name(ast)
|
198
|
+
elsif ast.type == :const
|
199
|
+
full_constant_name(ast).gsub('::', '')
|
200
|
+
else
|
201
|
+
Solargraph.logger.warn "[RSpec] Unexpected AST type #{ast.type}"
|
202
|
+
nil
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# @param ast [Parser::AST::Node]
|
207
|
+
# @return [String]
|
208
|
+
def full_constant_name(ast)
|
209
|
+
raise 'Node is not a constant' unless ast.type == :const
|
210
|
+
|
211
|
+
name = ast.children[1].to_s
|
212
|
+
if ast.children[0].nil?
|
213
|
+
name
|
214
|
+
else
|
215
|
+
"#{full_constant_name(ast.children[0])}::#{name}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# @see https://github.com/rspec/rspec-core/blob/1eeadce5aa7137ead054783c31ff35cbfe9d07cc/lib/rspec/core/example_group.rb#L862
|
220
|
+
# @param ast [Parser::AST::Node]
|
221
|
+
# @return [String]
|
222
|
+
def string_to_const_name(string_ast)
|
223
|
+
return unless string_ast.type == :str
|
224
|
+
|
225
|
+
name = string_ast.children[0]
|
226
|
+
return 'Anonymous'.dup if name.empty?
|
227
|
+
|
228
|
+
# Convert to CamelCase.
|
229
|
+
name = +" #{name}"
|
230
|
+
name.gsub!(/[^0-9a-zA-Z]+([0-9a-zA-Z])/) do
|
231
|
+
match = ::Regexp.last_match[1]
|
232
|
+
match.upcase!
|
233
|
+
match
|
234
|
+
end
|
235
|
+
|
236
|
+
name.lstrip! # Remove leading whitespace
|
237
|
+
name.gsub!(/\W/, '') # JRuby, RBX and others don't like non-ascii in const names
|
238
|
+
|
239
|
+
# Ruby requires first const letter to be A-Z. Use `Nested`
|
240
|
+
# as necessary to enforce that.
|
241
|
+
name.gsub!(/\A([^A-Z]|\z)/, 'Nested\1')
|
242
|
+
|
243
|
+
name
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|