Skip to content

Commit 9b72a87

Browse files
authored
Merge pull request #67 from Shopify/andyw8/add-code-lens-extension
Add code lens extension (for Rails)
2 parents 421d773 + 28e0786 commit 9b72a87

File tree

8 files changed

+562
-245
lines changed

8 files changed

+562
-245
lines changed

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ PATH
33
specs:
44
ruby-lsp-rails (0.1.0)
55
rails (>= 6.0)
6-
ruby-lsp (>= 0.5.1, < 0.7.0)
6+
ruby-lsp (>= 0.6.2, < 0.7.0)
77
sorbet-runtime (>= 0.5.9897)
88

99
GEM
@@ -195,7 +195,7 @@ GEM
195195
rubocop (~> 1.51)
196196
rubocop-sorbet (0.7.0)
197197
rubocop (>= 0.90.0)
198-
ruby-lsp (0.6.1)
198+
ruby-lsp (0.6.2)
199199
language_server-protocol (~> 3.17.0)
200200
sorbet-runtime
201201
syntax_tree (>= 6.1.1, < 7)

Rakefile

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ RDoc::Task.new do |rdoc|
2727
rdoc.options.push("--copy-files", "LICENSE.txt")
2828
end
2929

30-
RubyLsp::CheckDocs.new(
31-
FileList["#{__dir__}/lib/ruby_lsp/ruby_lsp_rails/**/*.rb"],
32-
FileList["#{__dir__}/**/*.gif"],
33-
)
30+
RubyLsp::CheckDocs.new(FileList["#{__dir__}/lib/ruby_lsp/**/*.rb"], FileList["#{__dir__}/misc/**/*.gif"])
3431

3532
task default: :test
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module RubyLsp
5+
module Rails
6+
# ![CodeLens demo](../../code_lens.gif)
7+
#
8+
# This feature adds several CodeLens features for Rails applications using Active Support test cases:
9+
# - Run tests in the VS Terminal
10+
# - Run tests in the VS Code Test Explorer
11+
# - Debug tests
12+
#
13+
# The
14+
# [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
15+
# request informs the editor of runnable commands such as tests
16+
#
17+
# # Example:
18+
#
19+
# For the following code, Code Lenses will be added above the class definition above each test method.
20+
#
21+
# ```ruby
22+
# Run
23+
# class HelloTest < ActiveSupport::TestCase # <- Will show code lenses above for running or debugging the whole test
24+
# test "outputs hello" do # <- Will show code lenses above for running or debugging this test
25+
# # ...
26+
# end
27+
#
28+
# test "outputs goodbye" do # <- Will show code lenses above for running or debugging this test
29+
# # ...
30+
# end
31+
# end
32+
# ````
33+
#
34+
# The code lenses will be displayed above the class and above each test method.
35+
class CodeLens < ::RubyLsp::Listener
36+
extend T::Sig
37+
extend T::Generic
38+
39+
ResponseType = type_member { { fixed: T::Array[::RubyLsp::Interface::CodeLens] } }
40+
BASE_COMMAND = "bin/rails test"
41+
42+
::RubyLsp::Requests::CodeLens.add_listener(self)
43+
44+
sig { override.returns(ResponseType) }
45+
attr_reader :response
46+
47+
sig { params(uri: String, emitter: EventEmitter, message_queue: Thread::Queue).void }
48+
def initialize(uri, emitter, message_queue)
49+
@response = T.let([], ResponseType)
50+
@path = T.let(URI(uri).path, T.nilable(String))
51+
emitter.register(self, :on_command, :on_class, :on_def)
52+
53+
super(emitter, message_queue)
54+
end
55+
56+
sig { params(node: SyntaxTree::Command).void }
57+
def on_command(node)
58+
message_value = node.message.value
59+
return unless message_value == "test" && node.arguments.parts.any?
60+
61+
first_argument = node.arguments.parts.first
62+
return unless first_argument.is_a?(SyntaxTree::StringLiteral)
63+
64+
test_name = first_argument.parts.first.value
65+
return unless test_name
66+
67+
line_number = node.location.start_line
68+
command = "#{BASE_COMMAND} #{@path}:#{line_number}"
69+
add_test_code_lens(node, name: test_name, command: command, kind: :example)
70+
end
71+
72+
# Although uncommon, Rails tests can be written with the classic "def test_name" syntax.
73+
sig { params(node: SyntaxTree::DefNode).void }
74+
def on_def(node)
75+
method_name = node.name.value
76+
if method_name.start_with?("test_")
77+
line_number = node.location.start_line
78+
command = "#{BASE_COMMAND} #{@path}:#{line_number}"
79+
add_test_code_lens(node, name: method_name, command: command, kind: :example)
80+
end
81+
end
82+
83+
sig { params(node: SyntaxTree::ClassDeclaration).void }
84+
def on_class(node)
85+
class_name = node.constant.constant.value
86+
if class_name.end_with?("Test")
87+
command = "#{BASE_COMMAND} #{@path}"
88+
add_test_code_lens(node, name: class_name, command: command, kind: :group)
89+
end
90+
end
91+
92+
private
93+
94+
sig { params(node: SyntaxTree::Node, name: String, command: String, kind: Symbol).void }
95+
def add_test_code_lens(node, name:, command:, kind:)
96+
arguments = [
97+
@path,
98+
name,
99+
command,
100+
{
101+
start_line: node.location.start_line - 1,
102+
start_column: node.location.start_column,
103+
end_line: node.location.end_line - 1,
104+
end_column: node.location.end_column,
105+
},
106+
]
107+
108+
@response << create_code_lens(
109+
node,
110+
title: "Run",
111+
command_name: "rubyLsp.runTest",
112+
arguments: arguments,
113+
data: { type: "test", kind: kind },
114+
)
115+
116+
@response << create_code_lens(
117+
node,
118+
title: "Run In Terminal",
119+
command_name: "rubyLsp.runTestInTerminal",
120+
arguments: arguments,
121+
data: { type: "test_in_terminal", kind: kind },
122+
)
123+
124+
@response << create_code_lens(
125+
node,
126+
title: "Debug",
127+
command_name: "rubyLsp.debugTest",
128+
arguments: arguments,
129+
data: { type: "debug", kind: kind },
130+
)
131+
end
132+
end
133+
end
134+
end

lib/ruby_lsp/ruby_lsp_rails/extension.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
require_relative "rails_client"
77
require_relative "hover"
8+
require_relative "code_lens"
89

910
module RubyLsp
1011
module Rails

misc/code_lens.gif

641 KB
Loading

ruby-lsp-rails.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ Gem::Specification.new do |spec|
2222
end
2323

2424
spec.add_dependency("rails", ">= 6.0")
25-
spec.add_dependency("ruby-lsp", ">= 0.5.1", "< 0.7.0")
25+
spec.add_dependency("ruby-lsp", ">= 0.6.2", "< 0.7.0")
2626
spec.add_dependency("sorbet-runtime", ">= 0.5.9897")
2727
end

0 commit comments

Comments
 (0)