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