ruby-lsp-rspec 0.1.21 → 0.1.23
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/.rspec +1 -0
- data/.rubocop.yml +4 -1
- data/README.md +82 -1
- data/lib/ruby-lsp-rspec.rb +0 -1
- data/lib/ruby_lsp/ruby_lsp_rspec/addon.rb +130 -33
- data/lib/ruby_lsp/ruby_lsp_rspec/code_lens.rb +22 -36
- data/lib/ruby_lsp/ruby_lsp_rspec/definition.rb +2 -15
- data/lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb +6 -13
- data/lib/ruby_lsp/ruby_lsp_rspec/indexing_enhancement.rb +4 -5
- data/lib/ruby_lsp/ruby_lsp_rspec/rspec_formatter.rb +66 -0
- data/lib/ruby_lsp/ruby_lsp_rspec/spec_style_patch.rb +14 -0
- data/lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb +131 -0
- data/lib/ruby_lsp_rspec/version.rb +1 -1
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6204c70ec6917f93238449949f16c24892d0145cb10dd32c42819021ef617c83
|
4
|
+
data.tar.gz: 510b118788f0469ef5783453042fb5c4f56aef9da2f77f0b1567b77e73d0b959
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '04947d8bf9659b8d8aa213ea2d305f7d2049719a30aa753e30f289da98b6e41991118681405a3166aac93f5c8d23d4f2ce9f808dcf99d6a1f43aabb85c564980'
|
7
|
+
data.tar.gz: 2aab5409230f0783b598fe53511d712e4643f84ec506b63278803ccc93002ca37e981c219041f4fa8b33f768432fa5c2eab13e6941bb76261bddfe9a31b70cb1
|
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
@@ -2,9 +2,11 @@ inherit_gem:
|
|
2
2
|
rubocop-shopify: rubocop.yml
|
3
3
|
|
4
4
|
require:
|
5
|
-
- rubocop-sorbet
|
6
5
|
- rubocop-rake
|
7
6
|
|
7
|
+
plugins:
|
8
|
+
- rubocop-sorbet
|
9
|
+
|
8
10
|
AllCops:
|
9
11
|
NewCops: disable
|
10
12
|
SuggestExtensions: false
|
@@ -30,6 +32,7 @@ Sorbet/StrictSigil:
|
|
30
32
|
- "lib/**/*.rb"
|
31
33
|
Exclude:
|
32
34
|
- "**/*.rake"
|
35
|
+
- "lib/ruby_lsp/ruby_lsp_rspec/rspec_formatter.rb"
|
33
36
|
- "spec/**/*.rb"
|
34
37
|
|
35
38
|
Style/StderrPuts:
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ group :development do
|
|
15
15
|
end
|
16
16
|
```
|
17
17
|
|
18
|
-
> [!IMPORTANT]
|
18
|
+
> [!IMPORTANT]
|
19
19
|
> Make sure the relevant features are [enabled](https://github.com/Shopify/ruby-lsp/tree/main/vscode#enable-or-disable-features) under your VSCode's `rubyLsp.enabledFeatures` setting, such as `codeLens`.
|
20
20
|
|
21
21
|
After running `bundle install`, restart Ruby LSP and you should start seeing CodeLens in your RSpec test files.
|
@@ -56,6 +56,87 @@ In VS Code this feature can be triggered by one of the following methods:
|
|
56
56
|
|
57
57
|
<img src="misc/go-to-definition.gif" alt="Go to definition" width="75%">
|
58
58
|
|
59
|
+
### VS Code Configuration
|
60
|
+
|
61
|
+
`ruby-lsp-rspec` can be configured through VS Code's `settings.json` file.
|
62
|
+
|
63
|
+
All configuration options must be nested under the `Ruby LSP RSpec` addon within `rubyLsp.addonSettings`:
|
64
|
+
|
65
|
+
```json
|
66
|
+
{
|
67
|
+
// ...
|
68
|
+
"rubyLsp.addonSettings": {
|
69
|
+
"Ruby LSP RSpec": {
|
70
|
+
// Configuration options go here
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
```
|
75
|
+
|
76
|
+
#### `rspecCommand`
|
77
|
+
|
78
|
+
**Description:**
|
79
|
+
|
80
|
+
Customize the command used to run tests via CodeLens. If not set, the command will be inferred based on the presence of a binstub or Gemfile.
|
81
|
+
|
82
|
+
**Default Value**: `nil`
|
83
|
+
|
84
|
+
**Example:**
|
85
|
+
|
86
|
+
```json
|
87
|
+
{
|
88
|
+
// ...
|
89
|
+
"rubyLsp.addonSettings": {
|
90
|
+
"Ruby LSP RSpec": {
|
91
|
+
"rspecCommand": "rspec -f d"
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
```
|
96
|
+
|
97
|
+
#### `debug`
|
98
|
+
|
99
|
+
**Description:**
|
100
|
+
|
101
|
+
Enable debug logging. Currently, this only logs the RSpec command used by CodeLens to stderr, which can be viewed in VS Code's `OUTPUT` panel under `Ruby LSP`.
|
102
|
+
|
103
|
+
**Default Value**: `false`
|
104
|
+
|
105
|
+
**Example:**
|
106
|
+
|
107
|
+
```json
|
108
|
+
{
|
109
|
+
"rubyLsp.addonSettings": {
|
110
|
+
"Ruby LSP RSpec": {
|
111
|
+
"debug": true
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
```
|
116
|
+
|
117
|
+
### Container Development
|
118
|
+
|
119
|
+
When developing in containers, use the official [`Dev Containers`](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension. This ensures Ruby LSP and Ruby LSP RSpec run inside the container, allowing correct spec path resolution.
|
120
|
+
|
121
|
+
For detailed container setup instructions, see the [Ruby LSP documentation](https://github.com/Shopify/ruby-lsp/blob/main/vscode/README.md?tab=readme-ov-file#developing-on-containers).
|
122
|
+
|
123
|
+
Make sure to configure Ruby LSP to run inside the container by adding it to your `.devcontainer.json`:
|
124
|
+
|
125
|
+
```json
|
126
|
+
{
|
127
|
+
"name": "my-app",
|
128
|
+
// ...
|
129
|
+
"customizations": {
|
130
|
+
"vscode": {
|
131
|
+
"extensions": [
|
132
|
+
"Shopify.ruby-lsp",
|
133
|
+
// ...
|
134
|
+
]
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
```
|
139
|
+
|
59
140
|
## Development
|
60
141
|
|
61
142
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/ruby-lsp-rspec.rb
CHANGED
@@ -8,69 +8,166 @@ require_relative "code_lens"
|
|
8
8
|
require_relative "document_symbol"
|
9
9
|
require_relative "definition"
|
10
10
|
require_relative "indexing_enhancement"
|
11
|
+
require_relative "test_discovery"
|
12
|
+
require_relative "spec_style_patch"
|
11
13
|
|
12
14
|
module RubyLsp
|
13
15
|
module RSpec
|
14
16
|
class Addon < ::RubyLsp::Addon
|
15
|
-
|
17
|
+
FORMATTER_PATH = File.expand_path("rspec_formatter.rb", __dir__) #: String
|
18
|
+
FORMATTER_NAME = "RubyLsp::RSpec::RSpecFormatter" #: String
|
16
19
|
|
17
|
-
|
20
|
+
#: bool
|
21
|
+
attr_reader :debug
|
22
|
+
|
23
|
+
#: -> void
|
24
|
+
def initialize
|
25
|
+
super
|
26
|
+
@debug = false #: bool
|
27
|
+
@rspec_command = nil #: String?
|
28
|
+
end
|
29
|
+
|
30
|
+
# @override
|
31
|
+
#: (GlobalState, Thread::Queue) -> void
|
18
32
|
def activate(global_state, message_queue)
|
19
|
-
@index =
|
33
|
+
@index = global_state.index #: RubyIndexer::Index?
|
34
|
+
@global_state = global_state #: GlobalState?
|
35
|
+
|
36
|
+
settings = global_state.settings_for_addon(name)
|
37
|
+
@rspec_command = rspec_command(settings)
|
38
|
+
@workspace_path = global_state.workspace_path #: String?
|
39
|
+
@debug = settings&.dig(:debug) || false
|
20
40
|
end
|
21
41
|
|
22
|
-
|
42
|
+
# @override
|
43
|
+
#: -> void
|
23
44
|
def deactivate; end
|
24
45
|
|
25
|
-
|
46
|
+
# @override
|
47
|
+
#: -> String
|
48
|
+
def name
|
49
|
+
"ruby-lsp-rspec"
|
50
|
+
end
|
51
|
+
|
52
|
+
# @override
|
53
|
+
#: -> String
|
26
54
|
def version
|
27
55
|
VERSION
|
28
56
|
end
|
29
57
|
|
30
58
|
# Creates a new CodeLens listener. This method is invoked on every CodeLens request
|
31
|
-
|
32
|
-
|
33
|
-
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
|
34
|
-
uri: URI::Generic,
|
35
|
-
dispatcher: Prism::Dispatcher,
|
36
|
-
).void
|
37
|
-
end
|
59
|
+
# @override
|
60
|
+
#: (ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens], URI::Generic, Prism::Dispatcher) -> void
|
38
61
|
def create_code_lens_listener(response_builder, uri, dispatcher)
|
39
62
|
return unless uri.to_standardized_path&.end_with?("_test.rb") || uri.to_standardized_path&.end_with?("_spec.rb")
|
63
|
+
return if @global_state&.enabled_feature?(:fullTestDiscovery)
|
40
64
|
|
41
|
-
CodeLens.new(
|
65
|
+
CodeLens.new(
|
66
|
+
response_builder,
|
67
|
+
uri,
|
68
|
+
dispatcher,
|
69
|
+
@rspec_command, #: as !nil
|
70
|
+
debug: debug,
|
71
|
+
)
|
42
72
|
end
|
43
73
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
)
|
74
|
+
# Creates a new Discover Tests listener. This method is invoked on every DiscoverTests request
|
75
|
+
# @override
|
76
|
+
#: (ResponseBuilders::TestCollection, Prism::Dispatcher, URI::Generic) -> void
|
77
|
+
def create_discover_tests_listener(response_builder, dispatcher, uri)
|
78
|
+
return unless uri.to_standardized_path&.end_with?("_spec.rb")
|
79
|
+
|
80
|
+
TestDiscovery.new(
|
81
|
+
response_builder,
|
82
|
+
dispatcher,
|
83
|
+
uri,
|
84
|
+
@workspace_path, #: as !nil
|
85
|
+
)
|
49
86
|
end
|
87
|
+
|
88
|
+
# Resolves the minimal set of commands required to execute the requested tests
|
89
|
+
# @override
|
90
|
+
#: (Array[Hash[Symbol, untyped]]) -> Array[String]
|
91
|
+
def resolve_test_commands(items)
|
92
|
+
commands = []
|
93
|
+
queue = items.dup
|
94
|
+
|
95
|
+
full_files = []
|
96
|
+
|
97
|
+
until queue.empty?
|
98
|
+
item = queue.shift #: as !nil
|
99
|
+
tags = Set.new(item[:tags])
|
100
|
+
next unless tags.include?("framework:rspec")
|
101
|
+
|
102
|
+
children = item[:children]
|
103
|
+
uri = URI(item[:uri])
|
104
|
+
path = uri.full_path
|
105
|
+
next unless path
|
106
|
+
|
107
|
+
if tags.include?("test_dir")
|
108
|
+
if children.empty?
|
109
|
+
full_files.concat(Dir.glob(
|
110
|
+
"#{path}/**/*_spec.rb",
|
111
|
+
File::Constants::FNM_EXTGLOB | File::Constants::FNM_PATHNAME,
|
112
|
+
))
|
113
|
+
end
|
114
|
+
elsif tags.include?("test_file")
|
115
|
+
full_files << path if children.empty?
|
116
|
+
elsif tags.include?("test_group")
|
117
|
+
start_line = item.dig(:range, :start, :line)
|
118
|
+
commands << "#{@rspec_command} -r #{FORMATTER_PATH} -f #{FORMATTER_NAME} #{path}:#{start_line + 1}"
|
119
|
+
else
|
120
|
+
full_files << "#{path}:#{item.dig(:range, :start, :line) + 1}"
|
121
|
+
end
|
122
|
+
|
123
|
+
queue.concat(children)
|
124
|
+
end
|
125
|
+
|
126
|
+
unless full_files.empty?
|
127
|
+
commands << "#{@rspec_command} -r #{FORMATTER_PATH} -f #{FORMATTER_NAME} #{full_files.join(" ")}"
|
128
|
+
end
|
129
|
+
|
130
|
+
commands
|
131
|
+
end
|
132
|
+
|
133
|
+
# @override
|
134
|
+
#: (ResponseBuilders::DocumentSymbol, Prism::Dispatcher) -> void
|
50
135
|
def create_document_symbol_listener(response_builder, dispatcher)
|
51
136
|
DocumentSymbol.new(response_builder, dispatcher)
|
52
137
|
end
|
53
138
|
|
54
|
-
|
55
|
-
|
56
|
-
response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
|
57
|
-
Interface::Location,
|
58
|
-
Interface::LocationLink,
|
59
|
-
)],
|
60
|
-
uri: URI::Generic,
|
61
|
-
node_context: NodeContext,
|
62
|
-
dispatcher: Prism::Dispatcher,
|
63
|
-
).void
|
64
|
-
end
|
139
|
+
# @override
|
140
|
+
#: (ResponseBuilders::CollectionResponseBuilder[Interface::Location | Interface::LocationLink], URI::Generic, NodeContext, Prism::Dispatcher) -> void
|
65
141
|
def create_definition_listener(response_builder, uri, node_context, dispatcher)
|
66
142
|
return unless uri.to_standardized_path&.end_with?("_test.rb") || uri.to_standardized_path&.end_with?("_spec.rb")
|
67
143
|
|
68
|
-
Definition.new(
|
144
|
+
Definition.new(
|
145
|
+
response_builder,
|
146
|
+
uri,
|
147
|
+
node_context,
|
148
|
+
@index, #: as !nil
|
149
|
+
dispatcher,
|
150
|
+
)
|
69
151
|
end
|
70
152
|
|
71
|
-
|
72
|
-
|
73
|
-
|
153
|
+
private
|
154
|
+
|
155
|
+
#: (Hash[Symbol, untyped]?) -> String
|
156
|
+
def rspec_command(settings)
|
157
|
+
@rspec_command ||= settings&.dig(:rspecCommand) || begin
|
158
|
+
cmd = if File.exist?(File.join(Dir.pwd, "bin", "rspec"))
|
159
|
+
"bin/rspec"
|
160
|
+
else
|
161
|
+
"rspec"
|
162
|
+
end
|
163
|
+
|
164
|
+
begin
|
165
|
+
Bundler.with_original_env { Bundler.default_lockfile }
|
166
|
+
"bundle exec #{cmd}"
|
167
|
+
rescue Bundler::GemfileNotFound
|
168
|
+
cmd
|
169
|
+
end
|
170
|
+
end
|
74
171
|
end
|
75
172
|
end
|
76
173
|
end
|
@@ -4,45 +4,24 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
module RSpec
|
6
6
|
class CodeLens
|
7
|
-
extend T::Sig
|
8
|
-
|
9
7
|
include ::RubyLsp::Requests::Support::Common
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
|
14
|
-
uri: URI::Generic,
|
15
|
-
dispatcher: Prism::Dispatcher,
|
16
|
-
).void
|
17
|
-
end
|
18
|
-
def initialize(response_builder, uri, dispatcher)
|
9
|
+
#: (ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens], URI::Generic, Prism::Dispatcher, String, ?debug: bool) -> void
|
10
|
+
def initialize(response_builder, uri, dispatcher, rspec_command, debug: false)
|
19
11
|
@response_builder = response_builder
|
20
12
|
# Listener is only initialized if uri.to_standardized_path is valid
|
21
|
-
|
22
|
-
@
|
23
|
-
@
|
24
|
-
@
|
13
|
+
path = uri.to_standardized_path #: as !nil
|
14
|
+
@path = path #: String
|
15
|
+
@group_id = 1 #: Integer
|
16
|
+
@group_id_stack = [] #: Array[Integer]
|
17
|
+
@rspec_command = rspec_command
|
18
|
+
@anonymous_example_count = 0 #: Integer
|
25
19
|
dispatcher.register(self, :on_call_node_enter, :on_call_node_leave)
|
26
20
|
|
27
|
-
@
|
28
|
-
begin
|
29
|
-
cmd = if File.exist?(File.join(Dir.pwd, "bin", "rspec"))
|
30
|
-
"bin/rspec"
|
31
|
-
else
|
32
|
-
"rspec"
|
33
|
-
end
|
34
|
-
|
35
|
-
if File.exist?("Gemfile.lock")
|
36
|
-
"bundle exec #{cmd}"
|
37
|
-
else
|
38
|
-
cmd
|
39
|
-
end
|
40
|
-
end,
|
41
|
-
String,
|
42
|
-
)
|
21
|
+
@debug = debug
|
43
22
|
end
|
44
23
|
|
45
|
-
|
24
|
+
#: (Prism::CallNode) -> void
|
46
25
|
def on_call_node_enter(node)
|
47
26
|
case node.message
|
48
27
|
when "example", "it", "specify"
|
@@ -59,7 +38,7 @@ module RubyLsp
|
|
59
38
|
end
|
60
39
|
end
|
61
40
|
|
62
|
-
|
41
|
+
#: (Prism::CallNode) -> void
|
63
42
|
def on_call_node_leave(node)
|
64
43
|
case node.message
|
65
44
|
when "context", "describe"
|
@@ -71,12 +50,17 @@ module RubyLsp
|
|
71
50
|
|
72
51
|
private
|
73
52
|
|
74
|
-
|
53
|
+
#: (String) -> void
|
54
|
+
def log_message(message)
|
55
|
+
puts "[#{self.class}]: #{message}"
|
56
|
+
end
|
57
|
+
|
58
|
+
#: (Prism::CallNode) -> bool
|
75
59
|
def valid_group?(node)
|
76
60
|
!(node.block.nil? || (node.receiver && node.receiver&.slice != "RSpec"))
|
77
61
|
end
|
78
62
|
|
79
|
-
|
63
|
+
#: (Prism::CallNode) -> String
|
80
64
|
def generate_name(node)
|
81
65
|
arguments = node.arguments&.arguments
|
82
66
|
|
@@ -99,10 +83,12 @@ module RubyLsp
|
|
99
83
|
end
|
100
84
|
end
|
101
85
|
|
102
|
-
|
86
|
+
#: (Prism::Node, name: String, kind: Symbol) -> void
|
103
87
|
def add_test_code_lens(node, name:, kind:)
|
104
88
|
line_number = node.location.start_line
|
105
|
-
command = "#{@
|
89
|
+
command = "#{@rspec_command} #{@path}:#{line_number}"
|
90
|
+
|
91
|
+
log_message("Full command: `#{command}`") if @debug
|
106
92
|
|
107
93
|
grouping_data = { group_id: @group_id_stack.last, kind: kind }
|
108
94
|
grouping_data[:id] = @group_id if kind == :group
|
@@ -4,22 +4,9 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
module RSpec
|
6
6
|
class Definition
|
7
|
-
extend T::Sig
|
8
|
-
|
9
7
|
include ::RubyLsp::Requests::Support::Common
|
10
8
|
|
11
|
-
|
12
|
-
params(
|
13
|
-
response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
|
14
|
-
Interface::Location,
|
15
|
-
Interface::LocationLink,
|
16
|
-
)],
|
17
|
-
uri: URI::Generic,
|
18
|
-
node_context: NodeContext,
|
19
|
-
index: RubyIndexer::Index,
|
20
|
-
dispatcher: Prism::Dispatcher,
|
21
|
-
).void
|
22
|
-
end
|
9
|
+
#: (ResponseBuilders::CollectionResponseBuilder[Interface::LocationLink | Interface::Location], URI::Generic, NodeContext, RubyIndexer::Index, Prism::Dispatcher) -> void
|
23
10
|
def initialize(response_builder, uri, node_context, index, dispatcher)
|
24
11
|
@response_builder = response_builder
|
25
12
|
@uri = uri
|
@@ -28,7 +15,7 @@ module RubyLsp
|
|
28
15
|
dispatcher.register(self, :on_call_node_enter)
|
29
16
|
end
|
30
17
|
|
31
|
-
|
18
|
+
#: (Prism::CallNode) -> void
|
32
19
|
def on_call_node_enter(node)
|
33
20
|
message = node.message
|
34
21
|
return unless message
|
@@ -4,23 +4,16 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
module RSpec
|
6
6
|
class DocumentSymbol
|
7
|
-
extend T::Sig
|
8
|
-
|
9
7
|
include ::RubyLsp::Requests::Support::Common
|
10
8
|
|
11
|
-
|
12
|
-
params(
|
13
|
-
response_builder: ResponseBuilders::DocumentSymbol,
|
14
|
-
dispatcher: Prism::Dispatcher,
|
15
|
-
).void
|
16
|
-
end
|
9
|
+
#: (ResponseBuilders::DocumentSymbol, Prism::Dispatcher) -> void
|
17
10
|
def initialize(response_builder, dispatcher)
|
18
11
|
@response_builder = response_builder
|
19
12
|
|
20
13
|
dispatcher.register(self, :on_call_node_enter, :on_call_node_leave)
|
21
14
|
end
|
22
15
|
|
23
|
-
|
16
|
+
#: (Prism::CallNode) -> void
|
24
17
|
def on_call_node_enter(node)
|
25
18
|
case node.message
|
26
19
|
when "example", "it", "specify"
|
@@ -30,7 +23,7 @@ module RubyLsp
|
|
30
23
|
|
31
24
|
@response_builder.last.children << RubyLsp::Interface::DocumentSymbol.new(
|
32
25
|
name: name,
|
33
|
-
kind:
|
26
|
+
kind: RubyLsp::Constant::SymbolKind::METHOD,
|
34
27
|
selection_range: range_from_node(node),
|
35
28
|
range: range_from_node(node),
|
36
29
|
)
|
@@ -43,7 +36,7 @@ module RubyLsp
|
|
43
36
|
|
44
37
|
symbol = RubyLsp::Interface::DocumentSymbol.new(
|
45
38
|
name: name,
|
46
|
-
kind:
|
39
|
+
kind: RubyLsp::Constant::SymbolKind::MODULE,
|
47
40
|
selection_range: range_from_node(node),
|
48
41
|
range: range_from_node(node),
|
49
42
|
children: [],
|
@@ -54,7 +47,7 @@ module RubyLsp
|
|
54
47
|
end
|
55
48
|
end
|
56
49
|
|
57
|
-
|
50
|
+
#: (Prism::CallNode) -> void
|
58
51
|
def on_call_node_leave(node)
|
59
52
|
case node.message
|
60
53
|
when "context", "describe", "shared_examples", "shared_context", "shared_examples_for"
|
@@ -64,7 +57,7 @@ module RubyLsp
|
|
64
57
|
end
|
65
58
|
end
|
66
59
|
|
67
|
-
|
60
|
+
#: (Prism::CallNode) -> String?
|
68
61
|
def generate_name(node)
|
69
62
|
arguments = node.arguments&.arguments
|
70
63
|
|
@@ -4,9 +4,8 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
module RSpec
|
6
6
|
class IndexingEnhancement < RubyIndexer::Enhancement
|
7
|
-
|
8
|
-
|
9
|
-
sig { override.params(node: Prism::CallNode).void }
|
7
|
+
# @override
|
8
|
+
#: (Prism::CallNode) -> void
|
10
9
|
def on_call_node_enter(node)
|
11
10
|
return if node.receiver
|
12
11
|
|
@@ -22,7 +21,7 @@ module RubyLsp
|
|
22
21
|
|
23
22
|
return if arguments.arguments.count != 1
|
24
23
|
|
25
|
-
method_name_node =
|
24
|
+
method_name_node = arguments.arguments.first #: as !nil
|
26
25
|
|
27
26
|
method_name = case method_name_node
|
28
27
|
when Prism::StringNode
|
@@ -41,7 +40,7 @@ module RubyLsp
|
|
41
40
|
arguments = node.arguments
|
42
41
|
|
43
42
|
if arguments && arguments.arguments.count == 1
|
44
|
-
method_name_node =
|
43
|
+
method_name_node = arguments.arguments.first #: as !nil
|
45
44
|
end
|
46
45
|
|
47
46
|
method_name = if method_name_node
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "rspec/core/formatters"
|
5
|
+
require "ruby_lsp/test_reporters/lsp_reporter"
|
6
|
+
|
7
|
+
module RubyLsp
|
8
|
+
module RSpec
|
9
|
+
class RSpecFormatter
|
10
|
+
::RSpec::Core::Formatters.register(
|
11
|
+
self,
|
12
|
+
:example_passed,
|
13
|
+
:example_pending,
|
14
|
+
:example_failed,
|
15
|
+
:example_started,
|
16
|
+
:stop,
|
17
|
+
)
|
18
|
+
|
19
|
+
def initialize(output)
|
20
|
+
@output = output
|
21
|
+
end
|
22
|
+
|
23
|
+
def example_started(notification)
|
24
|
+
example = notification.example
|
25
|
+
uri = uri_for(example)
|
26
|
+
id = generate_id(example)
|
27
|
+
line = example.location.split(":").last
|
28
|
+
RubyLsp::LspReporter.instance.start_test(id: id, uri: uri, line: line)
|
29
|
+
end
|
30
|
+
|
31
|
+
def example_passed(notification)
|
32
|
+
example = notification.example
|
33
|
+
uri = uri_for(example)
|
34
|
+
id = generate_id(example)
|
35
|
+
RubyLsp::LspReporter.instance.record_pass(id: id, uri: uri)
|
36
|
+
end
|
37
|
+
|
38
|
+
def example_failed(notification)
|
39
|
+
example = notification.example
|
40
|
+
uri = uri_for(example)
|
41
|
+
id = generate_id(example)
|
42
|
+
RubyLsp::LspReporter.instance.record_fail(id: id, message: notification.exception.message, uri: uri)
|
43
|
+
end
|
44
|
+
|
45
|
+
def example_pending(notification)
|
46
|
+
example = notification.example
|
47
|
+
uri = uri_for(example)
|
48
|
+
id = generate_id(example)
|
49
|
+
RubyLsp::LspReporter.instance.record_skip(id: id, uri: uri)
|
50
|
+
end
|
51
|
+
|
52
|
+
def stop(notification)
|
53
|
+
RubyLsp::LspReporter.instance.shutdown
|
54
|
+
end
|
55
|
+
|
56
|
+
def uri_for(example)
|
57
|
+
absolute_path = File.expand_path(example.file_path)
|
58
|
+
URI::Generic.from_path(path: absolute_path)
|
59
|
+
end
|
60
|
+
|
61
|
+
def generate_id(example)
|
62
|
+
[example, *example.example_group.parent_groups].reverse.map(&:location).join("::")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Listeners
|
6
|
+
# Patching this listener so it doesn't generate test items for RSpec tests
|
7
|
+
class SpecStyle
|
8
|
+
#: (ResponseBuilders::TestCollection, GlobalState, Prism::Dispatcher, URI::Generic) -> void
|
9
|
+
def initialize(response_builder, global_state, dispatcher, uri)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module RSpec
|
6
|
+
class TestDiscovery
|
7
|
+
include ::RubyLsp::Requests::Support::Common
|
8
|
+
|
9
|
+
#: (ResponseBuilders::TestCollection, Prism::Dispatcher, URI::Generic, String) -> void
|
10
|
+
def initialize(response_builder, dispatcher, uri, workspace_path)
|
11
|
+
@response_builder = response_builder
|
12
|
+
@dispatcher = dispatcher
|
13
|
+
@uri = uri
|
14
|
+
|
15
|
+
path = uri.to_standardized_path #: as !nil
|
16
|
+
@path = path #: String
|
17
|
+
@workspace_path = workspace_path #: String
|
18
|
+
@group_stack = [] #: Array[::RubyLsp::Requests::Support::TestItem]
|
19
|
+
|
20
|
+
dispatcher.register(
|
21
|
+
self,
|
22
|
+
:on_call_node_enter,
|
23
|
+
:on_call_node_leave,
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
#: (Prism::CallNode) -> void
|
28
|
+
def on_call_node_enter(node)
|
29
|
+
return unless ["describe", "context", "it", "specify", "example"].include?(node.message)
|
30
|
+
|
31
|
+
case node.message
|
32
|
+
when "describe", "context"
|
33
|
+
handle_describe(node)
|
34
|
+
when "it", "specify", "example"
|
35
|
+
handle_example(node)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#: (Prism::CallNode) -> void
|
40
|
+
def on_call_node_leave(node)
|
41
|
+
case node.message
|
42
|
+
when "context", "describe"
|
43
|
+
return unless valid_group?(node)
|
44
|
+
|
45
|
+
@group_stack.pop
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
#: (Prism::CallNode) -> String?
|
52
|
+
def extract_description(node)
|
53
|
+
# Try to extract the description from a string literal argument
|
54
|
+
first_arg = node.arguments&.arguments&.first
|
55
|
+
return "example at #{relative_location(node)}" if first_arg.nil?
|
56
|
+
|
57
|
+
case first_arg
|
58
|
+
when Prism::StringNode
|
59
|
+
first_arg.content
|
60
|
+
when Prism::SymbolNode
|
61
|
+
first_arg.value
|
62
|
+
when Prism::ConstantReadNode
|
63
|
+
first_arg.name.to_s
|
64
|
+
when Prism::ConstantPathNode
|
65
|
+
first_arg.full_name
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
#: (Prism::CallNode) -> void
|
70
|
+
def handle_describe(node)
|
71
|
+
description = extract_description(node)
|
72
|
+
return if description.nil?
|
73
|
+
|
74
|
+
parent = find_parent_test_group
|
75
|
+
parent_id = parent ? "#{parent.id}::" : ""
|
76
|
+
|
77
|
+
test_item = ::RubyLsp::Requests::Support::TestItem.new(
|
78
|
+
"#{parent_id}#{relative_location(node)}",
|
79
|
+
description,
|
80
|
+
@uri,
|
81
|
+
range_from_node(node),
|
82
|
+
framework: :rspec,
|
83
|
+
)
|
84
|
+
|
85
|
+
if parent
|
86
|
+
parent.add(test_item)
|
87
|
+
else
|
88
|
+
@response_builder.add(test_item)
|
89
|
+
end
|
90
|
+
|
91
|
+
@response_builder.add_code_lens(test_item)
|
92
|
+
@group_stack.push(test_item)
|
93
|
+
end
|
94
|
+
|
95
|
+
#: (Prism::CallNode) -> void
|
96
|
+
def handle_example(node)
|
97
|
+
description = extract_description(node)
|
98
|
+
parent = find_parent_test_group
|
99
|
+
return unless parent
|
100
|
+
|
101
|
+
test_item = ::RubyLsp::Requests::Support::TestItem.new(
|
102
|
+
"#{parent.id}::#{relative_location(node)}",
|
103
|
+
description,
|
104
|
+
@uri,
|
105
|
+
range_from_node(node),
|
106
|
+
framework: :rspec,
|
107
|
+
)
|
108
|
+
|
109
|
+
parent.add(test_item)
|
110
|
+
@response_builder.add_code_lens(test_item)
|
111
|
+
end
|
112
|
+
|
113
|
+
#: -> ::RubyLsp::Requests::Support::TestItem??
|
114
|
+
def find_parent_test_group
|
115
|
+
@group_stack.last
|
116
|
+
end
|
117
|
+
|
118
|
+
#: (Prism::CallNode) -> bool
|
119
|
+
def valid_group?(node)
|
120
|
+
!(node.block.nil? || (node.receiver && node.receiver&.slice != "RSpec"))
|
121
|
+
end
|
122
|
+
|
123
|
+
#: (Prism::CallNode) -> String
|
124
|
+
def relative_location(node)
|
125
|
+
uri_path = @uri.to_standardized_path #: as !nil
|
126
|
+
relative_path = Pathname.new(uri_path).relative_path_from(Pathname.new(@workspace_path))
|
127
|
+
"./#{relative_path}:#{node.location.start_line}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp-rspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.23
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stan Lo
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date: 2025-
|
10
|
+
date: 2025-05-14 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: ruby-lsp
|
@@ -16,14 +15,14 @@ dependencies:
|
|
16
15
|
requirements:
|
17
16
|
- - "~>"
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.23.
|
18
|
+
version: 0.23.19
|
20
19
|
type: :runtime
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
23
|
- - "~>"
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.23.
|
25
|
+
version: 0.23.19
|
27
26
|
description: RSpec addon for ruby-lsp
|
28
27
|
email:
|
29
28
|
- stan001212@gmail.com
|
@@ -44,6 +43,9 @@ files:
|
|
44
43
|
- lib/ruby_lsp/ruby_lsp_rspec/definition.rb
|
45
44
|
- lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb
|
46
45
|
- lib/ruby_lsp/ruby_lsp_rspec/indexing_enhancement.rb
|
46
|
+
- lib/ruby_lsp/ruby_lsp_rspec/rspec_formatter.rb
|
47
|
+
- lib/ruby_lsp/ruby_lsp_rspec/spec_style_patch.rb
|
48
|
+
- lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb
|
47
49
|
- lib/ruby_lsp_rspec/version.rb
|
48
50
|
homepage: https://github.com/st0012/ruby-lsp-rspec
|
49
51
|
licenses:
|
@@ -52,7 +54,6 @@ metadata:
|
|
52
54
|
homepage_uri: https://github.com/st0012/ruby-lsp-rspec
|
53
55
|
source_code_uri: https://github.com/st0012/ruby-lsp-rspec
|
54
56
|
changelog_uri: https://github.com/st0012/ruby-lsp-rspec/releases
|
55
|
-
post_install_message:
|
56
57
|
rdoc_options: []
|
57
58
|
require_paths:
|
58
59
|
- lib
|
@@ -67,8 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
68
|
- !ruby/object:Gem::Version
|
68
69
|
version: '0'
|
69
70
|
requirements: []
|
70
|
-
rubygems_version: 3.
|
71
|
-
signing_key:
|
71
|
+
rubygems_version: 3.6.3
|
72
72
|
specification_version: 4
|
73
73
|
summary: RSpec addon for ruby-lsp
|
74
74
|
test_files: []
|