solargraph-rspec 0.1.1 → 0.2.1
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/CHANGELOG.md +26 -1
- data/README.md +5 -2
- data/codecov.yml +13 -0
- data/lib/solargraph/rspec/convention.rb +20 -70
- data/lib/solargraph/rspec/correctors/base.rb +26 -3
- data/lib/solargraph/rspec/correctors/context_block_methods_corrector.rb +1 -3
- data/lib/solargraph/rspec/correctors/context_block_namespace_corrector.rb +11 -14
- data/lib/solargraph/rspec/correctors/described_class_corrector.rb +12 -11
- data/lib/solargraph/rspec/correctors/dsl_methods_corrector.rb +49 -20
- data/lib/solargraph/rspec/correctors/example_and_hook_blocks_binding_corrector.rb +20 -30
- data/lib/solargraph/rspec/correctors/let_methods_corrector.rb +12 -14
- data/lib/solargraph/rspec/correctors/subject_method_corrector.rb +44 -4
- data/lib/solargraph/rspec/pin_factory.rb +100 -0
- data/lib/solargraph/rspec/spec_walker/fake_let_method.rb +35 -0
- data/lib/solargraph/rspec/spec_walker/full_constant_name.rb +29 -0
- data/lib/solargraph/rspec/spec_walker/node_types.rb +82 -0
- data/lib/solargraph/rspec/spec_walker/rspec_context_namespace.rb +56 -0
- data/lib/solargraph/rspec/spec_walker.rb +63 -101
- data/lib/solargraph/rspec/version.rb +1 -1
- data/lib/solargraph/rspec/walker.rb +11 -35
- metadata +8 -5
- data/lib/solargraph/rspec/correctors/implicit_subject_method_corrector.rb +0 -48
- data/lib/solargraph/rspec/correctors/walker_base.rb +0 -27
- data/lib/solargraph/rspec/util.rb +0 -77
@@ -10,13 +10,53 @@ module Solargraph
|
|
10
10
|
# @param source_map [Solargraph::SourceMap]
|
11
11
|
# @return [void]
|
12
12
|
def correct(_source_map)
|
13
|
-
rspec_walker.on_subject do |
|
14
|
-
namespace_pin = closest_namespace_pin(namespace_pins,
|
13
|
+
rspec_walker.on_subject do |subject_name, location_range, fake_method_ast|
|
14
|
+
namespace_pin = closest_namespace_pin(namespace_pins, location_range.start.line)
|
15
15
|
next unless namespace_pin
|
16
16
|
|
17
|
-
subject_pin =
|
18
|
-
|
17
|
+
subject_pin = rspec_subject_method(namespace_pin, subject_name, location_range, fake_method_ast)
|
18
|
+
add_pin(subject_pin)
|
19
19
|
end
|
20
|
+
|
21
|
+
rspec_walker.after_walk do
|
22
|
+
next unless described_class_pin
|
23
|
+
|
24
|
+
namespace_pin = closest_namespace_pin(namespace_pins, described_class_pin.location.range.start.line)
|
25
|
+
|
26
|
+
add_pin(implicit_subject_pin(described_class_pin, namespace_pin)) if namespace_pin
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# @return [Pin::Method, nil]
|
33
|
+
def described_class_pin
|
34
|
+
@described_class_pin ||= added_pins.find { |pin| pin.is_a?(Pin::Method) && pin.name == 'described_class' }
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param namespace_pin [Pin::Namespace]
|
38
|
+
# @param subject_name [String, nil]
|
39
|
+
# @param location_range [Solargraph::Range]
|
40
|
+
# @param fake_method_ast [Parser::AST::Node]
|
41
|
+
# @return [Pin::Method]
|
42
|
+
def rspec_subject_method(namespace_pin, subject_name, location_range, fake_method_ast)
|
43
|
+
method_name = subject_name || 'subject'
|
44
|
+
rspec_let_method(namespace_pin, method_name, location_range, fake_method_ast)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param described_class_pin [Pin::Method]
|
48
|
+
# @param namespace_pin [Pin::Namespace]
|
49
|
+
# @return [Pin::Method]
|
50
|
+
def implicit_subject_pin(described_class_pin, namespace_pin)
|
51
|
+
described_class = described_class_pin.return_type.first.subtypes.first.name
|
52
|
+
|
53
|
+
PinFactory.build_public_method(
|
54
|
+
namespace_pin,
|
55
|
+
'subject',
|
56
|
+
types: ["::#{described_class}"],
|
57
|
+
location: described_class_pin.location,
|
58
|
+
scope: :instance
|
59
|
+
)
|
20
60
|
end
|
21
61
|
end
|
22
62
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Credits: This file is a copy of the file from the solargraph-rspec gem
|
4
|
+
|
5
|
+
module Solargraph
|
6
|
+
module Rspec
|
7
|
+
# Factory class for building pins and references.
|
8
|
+
module PinFactory
|
9
|
+
# @param namespace [Solargraph::Pin::Namespace]
|
10
|
+
# @param name [String]
|
11
|
+
# @param types [Array<String>]
|
12
|
+
# @param location [Solargraph::Location]
|
13
|
+
# @param comments [Array<String>]
|
14
|
+
# @param attribute [Boolean]
|
15
|
+
# @param scope [:instance, :class]
|
16
|
+
# @return [Solargraph::Pin::Method]
|
17
|
+
def self.build_public_method(
|
18
|
+
namespace,
|
19
|
+
name,
|
20
|
+
types: nil,
|
21
|
+
location: nil,
|
22
|
+
comments: [],
|
23
|
+
attribute: false,
|
24
|
+
scope: :instance,
|
25
|
+
node: nil
|
26
|
+
)
|
27
|
+
opts = {
|
28
|
+
name: name,
|
29
|
+
location: location,
|
30
|
+
closure: namespace,
|
31
|
+
scope: scope,
|
32
|
+
attribute: attribute,
|
33
|
+
comments: [],
|
34
|
+
node: node
|
35
|
+
}
|
36
|
+
|
37
|
+
comments << "@return [#{types.join(",")}]" if types
|
38
|
+
|
39
|
+
opts[:comments] = comments.join("\n")
|
40
|
+
|
41
|
+
Solargraph::Pin::Method.new(**opts)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param namespace [Solargraph::Pin::Namespace]
|
45
|
+
# @param name [String]
|
46
|
+
# @param location [Solargraph::Location]
|
47
|
+
# @return [Solargraph::Pin::Reference::Include]
|
48
|
+
def self.build_module_include(namespace, module_name, location)
|
49
|
+
Solargraph::Pin::Reference::Include.new(
|
50
|
+
closure: namespace,
|
51
|
+
name: module_name,
|
52
|
+
location: location
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param namespace [Solargraph::Pin::Namespace]
|
57
|
+
# @param module_name [String]
|
58
|
+
# @param location [Solargraph::Location]
|
59
|
+
# @return [Solargraph::Pin::Reference::Extend]
|
60
|
+
def self.build_module_extend(namespace, module_name, location)
|
61
|
+
Solargraph::Pin::Reference::Extend.new(
|
62
|
+
closure: namespace,
|
63
|
+
name: module_name,
|
64
|
+
location: location
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param path [String]
|
69
|
+
# @return [Solargraph::Location]
|
70
|
+
def self.dummy_location(path)
|
71
|
+
Solargraph::Location.new(
|
72
|
+
File.expand_path(path),
|
73
|
+
Solargraph::Range.from_to(0, 0, 0, 0)
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node]
|
78
|
+
# @see [RubyVM::AbstractSyntaxTree::NodeWrapper] - for why we need -1 for lineno
|
79
|
+
# @return [Solargraph::Range]
|
80
|
+
def self.build_location_range(ast)
|
81
|
+
Solargraph::Range.from_to(
|
82
|
+
ast.first_lineno - 1,
|
83
|
+
ast.first_column,
|
84
|
+
ast.last_lineno - 1,
|
85
|
+
ast.last_column
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param location_range [Solargraph::Range]
|
90
|
+
# @param path [String]
|
91
|
+
# @return [Solargraph::Location]
|
92
|
+
def self.build_location(location_range, path)
|
93
|
+
Solargraph::Location.new(
|
94
|
+
File.expand_path(path),
|
95
|
+
location_range
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solargraph
|
4
|
+
module Rspec
|
5
|
+
class SpecWalker
|
6
|
+
class FakeLetMethod
|
7
|
+
MATCH_BODY = Regexp.union(
|
8
|
+
/do(.*)end/m,
|
9
|
+
/{(.*)}/m
|
10
|
+
)
|
11
|
+
|
12
|
+
# @param block_ast [RubyVM::AbstractSyntaxTree::Node]
|
13
|
+
# @return [RubyVM::AbstractSyntaxTree::Node]
|
14
|
+
def self.transform_block(block_ast, code, method_name = nil)
|
15
|
+
method_name ||= NodeTypes.let_method_name(block_ast)
|
16
|
+
block_body = block_ast.children[1]
|
17
|
+
matches = code.lines[block_body.first_lineno - 1..block_body.last_lineno - 1].join.match(MATCH_BODY)
|
18
|
+
method_body = (matches[1] || matches[2]).strip
|
19
|
+
|
20
|
+
ast = RubyVM::AbstractSyntaxTree.parse <<~RUBY
|
21
|
+
def #{method_name}
|
22
|
+
#{method_body}
|
23
|
+
end
|
24
|
+
RUBY
|
25
|
+
|
26
|
+
ast.children[2]
|
27
|
+
rescue SyntaxError
|
28
|
+
raise "Failed to build fake let method: #{block_ast.inspect}, message: #{e.message}"
|
29
|
+
ensure
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solargraph
|
4
|
+
module Rspec
|
5
|
+
class SpecWalker
|
6
|
+
class FullConstantName
|
7
|
+
class << self
|
8
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node]
|
9
|
+
# @return [String]
|
10
|
+
def from_ast(ast)
|
11
|
+
raise 'Node is not a constant' unless NodeTypes.a_constant?(ast)
|
12
|
+
|
13
|
+
if ast.type == :CONST
|
14
|
+
ast.children[0].to_s
|
15
|
+
elsif ast.type == :COLON2
|
16
|
+
name = ast.children[1].to_s
|
17
|
+
"#{from_ast(ast.children[0])}::#{name}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def from_context_block_ast(block_ast)
|
22
|
+
ast = NodeTypes.context_description_node(block_ast)
|
23
|
+
from_ast(ast)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solargraph
|
4
|
+
module Rspec
|
5
|
+
class SpecWalker
|
6
|
+
class NodeTypes
|
7
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node]
|
8
|
+
# @return [Boolean]
|
9
|
+
def self.a_block?(ast)
|
10
|
+
return false unless ast.is_a?(RubyVM::AbstractSyntaxTree::Node)
|
11
|
+
|
12
|
+
%i[ITER LAMBDA].include?(ast.type)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node]
|
16
|
+
# @return [Boolean]
|
17
|
+
def self.a_context_block?(block_ast)
|
18
|
+
Solargraph::Rspec::CONTEXT_METHODS.include?(method_with_block_name(block_ast))
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node]
|
22
|
+
# @return [Boolean]
|
23
|
+
def self.a_subject_block?(block_ast)
|
24
|
+
Solargraph::Rspec::SUBJECT_METHODS.include?(method_with_block_name(block_ast))
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node]
|
28
|
+
# @return [Boolean]
|
29
|
+
def self.a_example_block?(block_ast)
|
30
|
+
Solargraph::Rspec::EXAMPLE_METHODS.include?(method_with_block_name(block_ast))
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node]
|
34
|
+
# @param config [Config]
|
35
|
+
# @return [Boolean]
|
36
|
+
def self.a_let_block?(block_ast, config)
|
37
|
+
config.let_methods.map(&:to_s).include?(method_with_block_name(block_ast))
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node]
|
41
|
+
# @return [Boolean]
|
42
|
+
def self.a_hook_block?(block_ast)
|
43
|
+
Solargraph::Rspec::HOOK_METHODS.include?(method_with_block_name(block_ast))
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.a_constant?(ast)
|
47
|
+
%i[CONST COLON2].include?(ast.type)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param block_ast [RubyVM::AbstractSyntaxTree::Node]
|
51
|
+
# @return [String, nil]
|
52
|
+
def self.method_with_block_name(block_ast)
|
53
|
+
return nil unless a_block?(block_ast)
|
54
|
+
|
55
|
+
method_call = %i[CALL FCALL].include?(block_ast.children[0].type)
|
56
|
+
return nil unless method_call
|
57
|
+
|
58
|
+
block_ast.children[0].children.select { |child| child.is_a?(Symbol) }.first&.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param block_ast [RubyVM::AbstractSyntaxTree::Node]
|
62
|
+
# @return [RubyVM::AbstractSyntaxTree::Node]
|
63
|
+
def self.context_description_node(block_ast)
|
64
|
+
return nil unless a_context_block?(block_ast)
|
65
|
+
|
66
|
+
case block_ast.children[0].type
|
67
|
+
when :CALL # RSpec.describe "something" do end
|
68
|
+
block_ast.children[0].children[2].children[0]
|
69
|
+
when :FCALL # describe "something" do end
|
70
|
+
block_ast.children[0].children[1].children[0]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param block_ast [RubyVM::AbstractSyntaxTree::Node]
|
75
|
+
# @return [String]
|
76
|
+
def self.let_method_name(block_ast)
|
77
|
+
block_ast.children[0].children[1]&.children&.[](0)&.children&.[](0)&.to_s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solargraph
|
4
|
+
module Rspec
|
5
|
+
class SpecWalker
|
6
|
+
class RspecContextNamespace
|
7
|
+
class << self
|
8
|
+
# @param block_ast [RubyVM::AbstractSyntaxTree::Node]
|
9
|
+
# @return [String, nil]
|
10
|
+
def from_block_ast(block_ast)
|
11
|
+
return unless block_ast.is_a?(RubyVM::AbstractSyntaxTree::Node)
|
12
|
+
|
13
|
+
ast = NodeTypes.context_description_node(block_ast)
|
14
|
+
if ast.type == :STR
|
15
|
+
string_to_const_name(ast)
|
16
|
+
elsif NodeTypes.a_constant?(ast)
|
17
|
+
FullConstantName.from_ast(ast).gsub('::', '')
|
18
|
+
else
|
19
|
+
Solargraph.logger.warn "[RSpec] Unexpected AST type #{ast.type}"
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# @see https://github.com/rspec/rspec-core/blob/1eeadce5aa7137ead054783c31ff35cbfe9d07cc/lib/rspec/core/example_group.rb#L862
|
27
|
+
# @param ast [Parser::AST::Node]
|
28
|
+
# @return [String]
|
29
|
+
def string_to_const_name(string_ast)
|
30
|
+
return unless string_ast.type == :STR
|
31
|
+
|
32
|
+
name = string_ast.children[0]
|
33
|
+
return 'Anonymous'.dup if name.empty?
|
34
|
+
|
35
|
+
# Convert to CamelCase.
|
36
|
+
name = +" #{name}"
|
37
|
+
name.gsub!(/[^0-9a-zA-Z]+([0-9a-zA-Z])/) do
|
38
|
+
match = ::Regexp.last_match[1]
|
39
|
+
match.upcase!
|
40
|
+
match
|
41
|
+
end
|
42
|
+
|
43
|
+
name.lstrip! # Remove leading whitespace
|
44
|
+
name.gsub!(/\W/, '') # JRuby, RBX and others don't like non-ascii in const names
|
45
|
+
|
46
|
+
# Ruby requires first const letter to be A-Z. Use `Nested`
|
47
|
+
# as necessary to enforce that.
|
48
|
+
name.gsub!(/\A([^A-Z]|\z)/, 'Nested\1')
|
49
|
+
|
50
|
+
name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'walker'
|
4
|
+
require_relative 'spec_walker/node_types'
|
5
|
+
require_relative 'spec_walker/full_constant_name'
|
6
|
+
require_relative 'spec_walker/rspec_context_namespace'
|
7
|
+
require_relative 'spec_walker/fake_let_method'
|
4
8
|
|
5
9
|
module Solargraph
|
6
10
|
module Rspec
|
@@ -10,7 +14,7 @@ module Solargraph
|
|
10
14
|
def initialize(source_map:, config:)
|
11
15
|
@source_map = source_map
|
12
16
|
@config = config
|
13
|
-
@walker = Rspec::Walker.
|
17
|
+
@walker = Rspec::Walker.new(source_map.source.node)
|
14
18
|
@handlers = {
|
15
19
|
on_described_class: [],
|
16
20
|
on_let_method: [],
|
@@ -30,24 +34,34 @@ module Solargraph
|
|
30
34
|
attr_reader :config
|
31
35
|
|
32
36
|
# @param block [Proc]
|
37
|
+
# @yieldparam class_name [String]
|
38
|
+
# @yieldparam location_range [Solargraph::Range]
|
33
39
|
# @return [void]
|
34
40
|
def on_described_class(&block)
|
35
41
|
@handlers[:on_described_class] << block
|
36
42
|
end
|
37
43
|
|
38
44
|
# @param block [Proc]
|
45
|
+
# @yieldparam method_name [String]
|
46
|
+
# @yieldparam location_range [Solargraph::Range]
|
47
|
+
# @yieldparam fake_method_ast [RubyVM::AbstractSyntaxTree::Node]
|
39
48
|
# @return [void]
|
40
49
|
def on_let_method(&block)
|
41
50
|
@handlers[:on_let_method] << block
|
42
51
|
end
|
43
52
|
|
44
53
|
# @param block [Proc]
|
54
|
+
# @yieldparam method_name [String]
|
55
|
+
# @yieldparam location_range [Solargraph::Range]
|
56
|
+
# @yieldparam fake_method_ast [RubyVM::AbstractSyntaxTree::Node]
|
45
57
|
# @return [void]
|
46
58
|
def on_subject(&block)
|
47
59
|
@handlers[:on_subject] << block
|
48
60
|
end
|
49
61
|
|
50
62
|
# @param block [Proc]
|
63
|
+
# @yieldparam namespace_name [String]
|
64
|
+
# @yieldparam location_range [Solargraph::Range]
|
51
65
|
# @return [void]
|
52
66
|
def on_each_context_block(&block)
|
53
67
|
@handlers[:on_each_context_block] << block
|
@@ -55,18 +69,21 @@ module Solargraph
|
|
55
69
|
|
56
70
|
#
|
57
71
|
# @param block [Proc]
|
72
|
+
# @yieldparam location_range [Solargraph::Range]
|
58
73
|
# @return [void]
|
59
74
|
def on_example_block(&block)
|
60
75
|
@handlers[:on_example_block] << block
|
61
76
|
end
|
62
77
|
|
63
78
|
# @param block [Proc]
|
79
|
+
# @yieldparam location_range [Solargraph::Range]
|
64
80
|
# @return [void]
|
65
81
|
def on_hook_block(&block)
|
66
82
|
@handlers[:on_hook_block] << block
|
67
83
|
end
|
68
84
|
|
69
85
|
# @param block [Proc]
|
86
|
+
# @yieldparam location_range [Solargraph::Range]
|
70
87
|
# @return [void]
|
71
88
|
def on_blocks_in_examples(&block)
|
72
89
|
@handlers[:on_blocks_in_examples] << block
|
@@ -80,77 +97,72 @@ module Solargraph
|
|
80
97
|
|
81
98
|
# @return [void]
|
82
99
|
def walk!
|
83
|
-
each_context_block(@walker.ast, Rspec::ROOT_NAMESPACE) do |namespace_name,
|
100
|
+
each_context_block(@walker.ast, Rspec::ROOT_NAMESPACE) do |namespace_name, block_ast|
|
101
|
+
desc_node = NodeTypes.context_description_node(block_ast)
|
102
|
+
|
84
103
|
@handlers[:on_each_context_block].each do |handler|
|
85
|
-
handler.call(namespace_name,
|
104
|
+
handler.call(namespace_name, PinFactory.build_location_range(block_ast))
|
86
105
|
end
|
87
|
-
end
|
88
106
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
class_name = full_constant_name(class_ast)
|
96
|
-
handler.call(class_ast, class_name)
|
107
|
+
if NodeTypes.a_constant?(desc_node) # rubocop:disable Style/Next
|
108
|
+
@handlers[:on_described_class].each do |handler|
|
109
|
+
class_name_ast = NodeTypes.context_description_node(block_ast)
|
110
|
+
class_name = FullConstantName.from_ast(class_name_ast)
|
111
|
+
handler.call(class_name, PinFactory.build_location_range(class_name_ast))
|
112
|
+
end
|
97
113
|
end
|
98
114
|
end
|
99
115
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
116
|
+
walker.on :ITER do |block_ast|
|
117
|
+
next unless NodeTypes.a_let_block?(block_ast, config)
|
118
|
+
|
119
|
+
method_name = NodeTypes.let_method_name(block_ast)
|
120
|
+
next unless method_name
|
121
|
+
|
122
|
+
fake_method_ast = FakeLetMethod.transform_block(block_ast, @source_map.source.code)
|
123
|
+
|
124
|
+
@handlers[:on_let_method].each do |handler|
|
125
|
+
handler.call(method_name, PinFactory.build_location_range(block_ast.children[0]), fake_method_ast)
|
105
126
|
end
|
106
127
|
end
|
107
128
|
|
108
|
-
walker.on :
|
109
|
-
next
|
129
|
+
walker.on :ITER do |block_ast|
|
130
|
+
next unless NodeTypes.a_subject_block?(block_ast)
|
110
131
|
|
111
|
-
|
112
|
-
|
113
|
-
next unless Rspec::SUBJECT_METHODS.include?(method_name.to_s)
|
132
|
+
method_name = NodeTypes.let_method_name(block_ast)
|
133
|
+
fake_method_ast = FakeLetMethod.transform_block(block_ast, @source_map.source.code, method_name || 'subject')
|
114
134
|
|
115
135
|
@handlers[:on_subject].each do |handler|
|
116
|
-
handler.call(
|
136
|
+
handler.call(method_name, PinFactory.build_location_range(block_ast.children[0]), fake_method_ast)
|
117
137
|
end
|
118
138
|
end
|
119
139
|
|
120
|
-
walker.on :
|
121
|
-
next
|
122
|
-
|
123
|
-
method_ast = block_ast.children.first
|
124
|
-
method_name = method_ast.children[1]
|
125
|
-
next unless Rspec::EXAMPLE_METHODS.include?(method_name.to_s)
|
140
|
+
walker.on :ITER do |block_ast|
|
141
|
+
next unless NodeTypes.a_example_block?(block_ast)
|
126
142
|
|
127
143
|
@handlers[:on_example_block].each do |handler|
|
128
|
-
handler.call(block_ast)
|
144
|
+
handler.call(PinFactory.build_location_range(block_ast))
|
129
145
|
end
|
130
146
|
|
131
|
-
# @param blocks_in_examples [
|
132
|
-
each_block(block_ast.children[
|
147
|
+
# @param blocks_in_examples [RubyVM::AbstractSyntaxTree::Node]
|
148
|
+
each_block(block_ast.children[1]) do |blocks_in_examples|
|
133
149
|
@handlers[:on_blocks_in_examples].each do |handler|
|
134
|
-
handler.call(blocks_in_examples)
|
150
|
+
handler.call(PinFactory.build_location_range(blocks_in_examples))
|
135
151
|
end
|
136
152
|
end
|
137
153
|
end
|
138
154
|
|
139
|
-
walker.on :
|
140
|
-
next
|
141
|
-
|
142
|
-
method_ast = block_ast.children.first
|
143
|
-
method_name = method_ast.children[1]
|
144
|
-
next unless Rspec::HOOK_METHODS.include?(method_name.to_s)
|
155
|
+
walker.on :ITER do |block_ast|
|
156
|
+
next unless NodeTypes.a_hook_block?(block_ast)
|
145
157
|
|
146
158
|
@handlers[:on_hook_block].each do |handler|
|
147
|
-
handler.call(block_ast)
|
159
|
+
handler.call(PinFactory.build_location_range(block_ast))
|
148
160
|
end
|
149
161
|
|
150
|
-
# @param blocks_in_examples [
|
151
|
-
each_block(block_ast.children[
|
162
|
+
# @param blocks_in_examples [RubyVM::AbstractSyntaxTree::Node]
|
163
|
+
each_block(block_ast.children[1]) do |blocks_in_examples|
|
152
164
|
@handlers[:on_blocks_in_examples].each do |handler|
|
153
|
-
handler.call(blocks_in_examples)
|
165
|
+
handler.call(PinFactory.build_location_range(blocks_in_examples))
|
154
166
|
end
|
155
167
|
end
|
156
168
|
end
|
@@ -165,9 +177,9 @@ module Solargraph
|
|
165
177
|
# @param ast [Parser::AST::Node]
|
166
178
|
# @param parent_result [Object]
|
167
179
|
def each_block(ast, parent_result = nil, &block)
|
168
|
-
return unless ast.is_a?(::
|
180
|
+
return unless ast.is_a?(RubyVM::AbstractSyntaxTree::Node)
|
169
181
|
|
170
|
-
is_a_block =
|
182
|
+
is_a_block = NodeTypes.a_block?(ast)
|
171
183
|
|
172
184
|
if is_a_block
|
173
185
|
result = block&.call(ast, parent_result)
|
@@ -182,72 +194,22 @@ module Solargraph
|
|
182
194
|
# @yield [String, Parser::AST::Node]
|
183
195
|
def each_context_block(ast, root_namespace = Rspec::ROOT_NAMESPACE, &block)
|
184
196
|
each_block(ast, root_namespace) do |block_ast, parent_namespace|
|
185
|
-
is_a_context =
|
197
|
+
is_a_context = NodeTypes.a_context_block?(block_ast)
|
186
198
|
|
187
199
|
next unless is_a_context
|
188
200
|
|
189
|
-
|
190
|
-
block_name = rspec_describe_class_name(description_node)
|
201
|
+
block_name = RspecContextNamespace.from_block_ast(block_ast)
|
191
202
|
next unless block_name
|
192
203
|
|
204
|
+
# @HACK: When we describe `SomeClass` without a namespace, Solargraph confuses described_class with the
|
205
|
+
# `RSpec::ExampleGroups::SomeClass` constant. To avoid this, we append the root namespace with "Test"
|
206
|
+
block_name = "Test#{block_name}" if parent_namespace == Rspec::ROOT_NAMESPACE
|
207
|
+
|
193
208
|
parent_namespace = namespace_name = "#{parent_namespace}::#{block_name}"
|
194
209
|
block&.call(namespace_name, block_ast)
|
195
210
|
next parent_namespace
|
196
211
|
end
|
197
212
|
end
|
198
|
-
|
199
|
-
# @param ast [Parser::AST::Node]
|
200
|
-
# @return [String, nil]
|
201
|
-
def rspec_describe_class_name(ast)
|
202
|
-
if ast.type == :str
|
203
|
-
string_to_const_name(ast)
|
204
|
-
elsif ast.type == :const
|
205
|
-
full_constant_name(ast).gsub('::', '')
|
206
|
-
else
|
207
|
-
Solargraph.logger.warn "[RSpec] Unexpected AST type #{ast.type}"
|
208
|
-
nil
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
# @param ast [Parser::AST::Node]
|
213
|
-
# @return [String]
|
214
|
-
def full_constant_name(ast)
|
215
|
-
raise 'Node is not a constant' unless ast.type == :const
|
216
|
-
|
217
|
-
name = ast.children[1].to_s
|
218
|
-
if ast.children[0].nil?
|
219
|
-
name
|
220
|
-
else
|
221
|
-
"#{full_constant_name(ast.children[0])}::#{name}"
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
# @see https://github.com/rspec/rspec-core/blob/1eeadce5aa7137ead054783c31ff35cbfe9d07cc/lib/rspec/core/example_group.rb#L862
|
226
|
-
# @param ast [Parser::AST::Node]
|
227
|
-
# @return [String]
|
228
|
-
def string_to_const_name(string_ast)
|
229
|
-
return unless string_ast.type == :str
|
230
|
-
|
231
|
-
name = string_ast.children[0]
|
232
|
-
return 'Anonymous'.dup if name.empty?
|
233
|
-
|
234
|
-
# Convert to CamelCase.
|
235
|
-
name = +" #{name}"
|
236
|
-
name.gsub!(/[^0-9a-zA-Z]+([0-9a-zA-Z])/) do
|
237
|
-
match = ::Regexp.last_match[1]
|
238
|
-
match.upcase!
|
239
|
-
match
|
240
|
-
end
|
241
|
-
|
242
|
-
name.lstrip! # Remove leading whitespace
|
243
|
-
name.gsub!(/\W/, '') # JRuby, RBX and others don't like non-ascii in const names
|
244
|
-
|
245
|
-
# Ruby requires first const letter to be A-Z. Use `Nested`
|
246
|
-
# as necessary to enforce that.
|
247
|
-
name.gsub!(/\A([^A-Z]|\z)/, 'Nested\1')
|
248
|
-
|
249
|
-
name
|
250
|
-
end
|
251
213
|
end
|
252
214
|
end
|
253
215
|
end
|