ruby-lsp 0.16.4 → 0.16.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94d4dfcda701bdf2ea1dd04e2a271572a139fe3d716e1bd535e2c05bcc9b3d23
4
- data.tar.gz: b79dfc816bf027e733fa5f44db3e2d1039ae860f9dee88fe996ab1a27089d467
3
+ metadata.gz: bcd4426f17d6d429be733a9adcdf799e23c29689ad5fb69f9b9f7b3cd334bf9b
4
+ data.tar.gz: 071d5c37e1acf83c07a5b48e85a3bc4a18fb63a20b54ff191158799cf4320803
5
5
  SHA512:
6
- metadata.gz: 9de1f73feba2284c75294542a0e9ae1196f092004fe83ef815b2e08a1b404daceebc0b3f6f8f59149f936c8f7414821942556bee522776a909c071765dcc3443
7
- data.tar.gz: b7617a6d508a94e5a06b8d39acb4d2bcc28d02832b19ab8567ed58b6b297aa3fc450dd0a116257fe91fc9545683550456c0d712fc32dc3f3ff716e2cdca2985e
6
+ metadata.gz: 9edc44ec74f5f5d9b5f15fb7d22501126eb8f6bec3d9a69b3061946b369ecb0e453e45e9a0dc9df1482117dc738f7bd66005e2dcd0cd46e3aaad6f6658283766
7
+ data.tar.gz: c4f9ddb4da9373544b2456632a0fb76bdbca5e5ddf5e874b0e6ab4f6aae13ad8b17ef9482873b37ae33bc7ec797faebde76bf5a6e9d86e6a4ddf798b4eb9f0df
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.16.4
1
+ 0.16.5
@@ -11,7 +11,7 @@ module RubyIndexer
11
11
  sig { returns(String) }
12
12
  attr_reader :file_path
13
13
 
14
- sig { returns(Prism::Location) }
14
+ sig { returns(RubyIndexer::Location) }
15
15
  attr_reader :location
16
16
 
17
17
  sig { returns(T::Array[String]) }
@@ -20,13 +20,33 @@ module RubyIndexer
20
20
  sig { returns(Symbol) }
21
21
  attr_accessor :visibility
22
22
 
23
- sig { params(name: String, file_path: String, location: Prism::Location, comments: T::Array[String]).void }
23
+ sig do
24
+ params(
25
+ name: String,
26
+ file_path: String,
27
+ location: T.any(Prism::Location, RubyIndexer::Location),
28
+ comments: T::Array[String],
29
+ ).void
30
+ end
24
31
  def initialize(name, file_path, location, comments)
25
32
  @name = name
26
33
  @file_path = file_path
27
- @location = location
28
34
  @comments = comments
29
35
  @visibility = T.let(:public, Symbol)
36
+
37
+ @location = T.let(
38
+ if location.is_a?(Prism::Location)
39
+ Location.new(
40
+ location.start_line,
41
+ location.end_line,
42
+ location.start_column,
43
+ location.end_column,
44
+ )
45
+ else
46
+ location
47
+ end,
48
+ RubyIndexer::Location,
49
+ )
30
50
  end
31
51
 
32
52
  sig { returns(String) }
@@ -50,7 +70,7 @@ module RubyIndexer
50
70
  params(
51
71
  name: String,
52
72
  file_path: String,
53
- location: Prism::Location,
73
+ location: T.any(Prism::Location, RubyIndexer::Location),
54
74
  comments: T::Array[String],
55
75
  ).void
56
76
  end
@@ -81,7 +101,7 @@ module RubyIndexer
81
101
  params(
82
102
  name: String,
83
103
  file_path: String,
84
- location: Prism::Location,
104
+ location: T.any(Prism::Location, RubyIndexer::Location),
85
105
  comments: T::Array[String],
86
106
  parent_class: T.nilable(String),
87
107
  ).void
@@ -181,7 +201,7 @@ module RubyIndexer
181
201
  params(
182
202
  name: String,
183
203
  file_path: String,
184
- location: Prism::Location,
204
+ location: T.any(Prism::Location, RubyIndexer::Location),
185
205
  comments: T::Array[String],
186
206
  owner: T.nilable(Entry::Namespace),
187
207
  ).void
@@ -219,7 +239,7 @@ module RubyIndexer
219
239
  params(
220
240
  name: String,
221
241
  file_path: String,
222
- location: Prism::Location,
242
+ location: T.any(Prism::Location, RubyIndexer::Location),
223
243
  comments: T::Array[String],
224
244
  parameters_node: T.nilable(Prism::ParametersNode),
225
245
  owner: T.nilable(Entry::Namespace),
@@ -349,7 +369,7 @@ module RubyIndexer
349
369
  nesting: T::Array[String],
350
370
  name: String,
351
371
  file_path: String,
352
- location: Prism::Location,
372
+ location: T.any(Prism::Location, RubyIndexer::Location),
353
373
  comments: T::Array[String],
354
374
  ).void
355
375
  end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyIndexer
5
+ class Location
6
+ extend T::Sig
7
+
8
+ sig { returns(Integer) }
9
+ attr_reader :start_line, :end_line, :start_column, :end_column
10
+
11
+ sig do
12
+ params(
13
+ start_line: Integer,
14
+ end_line: Integer,
15
+ start_column: Integer,
16
+ end_column: Integer,
17
+ ).void
18
+ end
19
+ def initialize(start_line, end_line, start_column, end_column)
20
+ @start_line = start_line
21
+ @end_line = end_line
22
+ @start_column = start_column
23
+ @end_column = end_column
24
+ end
25
+ end
26
+ end
@@ -10,6 +10,7 @@ require "ruby_indexer/lib/ruby_indexer/index"
10
10
  require "ruby_indexer/lib/ruby_indexer/entry"
11
11
  require "ruby_indexer/lib/ruby_indexer/configuration"
12
12
  require "ruby_indexer/lib/ruby_indexer/prefix_tree"
13
+ require "ruby_indexer/lib/ruby_indexer/location"
13
14
 
14
15
  module RubyIndexer
15
16
  @configuration = T.let(Configuration.new, Configuration)
@@ -62,7 +62,7 @@ module RubyLsp
62
62
  # The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
63
63
  # else is pushed into the incoming queue
64
64
  case method
65
- when "initialize", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
65
+ when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
66
66
  process_message(message)
67
67
  when "shutdown"
68
68
  $stderr.puts("Shutting down Ruby LSP...")
@@ -29,8 +29,9 @@ module RubyLsp
29
29
  @encoding = T.let(Encoding::UTF_8, Encoding)
30
30
 
31
31
  @formatter = T.let("auto", String)
32
- @test_library = T.let(detect_test_library, String)
33
- @typechecker = T.let(detect_typechecker, T::Boolean)
32
+ @linters = T.let([], T::Array[String])
33
+ @test_library = T.let("minitest", String)
34
+ @typechecker = T.let(true, T::Boolean)
34
35
  @index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
35
36
  @supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
36
37
  @supports_watching_files = T.let(false, T::Boolean)
@@ -46,14 +47,25 @@ module RubyLsp
46
47
  @supported_formatters[@formatter]
47
48
  end
48
49
 
50
+ sig { returns(T::Array[Requests::Support::Formatter]) }
51
+ def active_linters
52
+ @linters.filter_map { |name| @supported_formatters[name] }
53
+ end
54
+
49
55
  sig { params(options: T::Hash[Symbol, T.untyped]).void }
50
56
  def apply_options(options)
57
+ dependencies = gather_dependencies
51
58
  workspace_uri = options.dig(:workspaceFolders, 0, :uri)
52
59
  @workspace_uri = URI(workspace_uri) if workspace_uri
53
60
 
54
61
  specified_formatter = options.dig(:initializationOptions, :formatter)
55
62
  @formatter = specified_formatter if specified_formatter
56
- @formatter = detect_formatter if @formatter == "auto"
63
+ @formatter = detect_formatter(dependencies) if @formatter == "auto"
64
+
65
+ specified_linters = options.dig(:initializationOptions, :linters)
66
+ @linters = specified_linters || detect_linters(dependencies)
67
+ @test_library = detect_test_library(dependencies)
68
+ @typechecker = detect_typechecker(dependencies)
57
69
 
58
70
  encodings = options.dig(:capabilities, :general, :positionEncodings)
59
71
  @encoding = if !encodings || encodings.empty?
@@ -89,28 +101,32 @@ module RubyLsp
89
101
  end
90
102
  end
91
103
 
92
- sig { params(gem_pattern: Regexp).returns(T::Boolean) }
93
- def direct_dependency?(gem_pattern)
94
- dependencies.any?(gem_pattern)
95
- end
96
-
97
104
  private
98
105
 
99
- sig { returns(String) }
100
- def detect_formatter
106
+ sig { params(dependencies: T::Array[String]).returns(String) }
107
+ def detect_formatter(dependencies)
101
108
  # NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
102
- if direct_dependency?(/^rubocop/)
109
+ if dependencies.any?(/^rubocop/)
103
110
  "rubocop"
104
- elsif direct_dependency?(/^syntax_tree$/)
111
+ elsif dependencies.any?(/^syntax_tree$/)
105
112
  "syntax_tree"
106
113
  else
107
114
  "none"
108
115
  end
109
116
  end
110
117
 
111
- sig { returns(String) }
112
- def detect_test_library
113
- if direct_dependency?(/^rspec/)
118
+ # Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
119
+ # single linter. To have multiple linters running, the user must configure them manually
120
+ sig { params(dependencies: T::Array[String]).returns(T::Array[String]) }
121
+ def detect_linters(dependencies)
122
+ linters = []
123
+ linters << "rubocop" if dependencies.any?(/^rubocop/)
124
+ linters
125
+ end
126
+
127
+ sig { params(dependencies: T::Array[String]).returns(String) }
128
+ def detect_test_library(dependencies)
129
+ if dependencies.any?(/^rspec/)
114
130
  "rspec"
115
131
  # A Rails app may have a dependency on minitest, but we would instead want to use the Rails test runner provided
116
132
  # by ruby-lsp-rails. A Rails app doesn't need to depend on the rails gem itself, individual components like
@@ -119,23 +135,23 @@ module RubyLsp
119
135
  elsif File.exist?(File.join(workspace_path, "bin/rails"))
120
136
  "rails"
121
137
  # NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
122
- elsif direct_dependency?(/^minitest$/)
138
+ elsif dependencies.any?(/^minitest$/)
123
139
  "minitest"
124
- elsif direct_dependency?(/^test-unit/)
140
+ elsif dependencies.any?(/^test-unit/)
125
141
  "test-unit"
126
142
  else
127
143
  "unknown"
128
144
  end
129
145
  end
130
146
 
131
- sig { returns(T::Boolean) }
132
- def detect_typechecker
147
+ sig { params(dependencies: T::Array[String]).returns(T::Boolean) }
148
+ def detect_typechecker(dependencies)
133
149
  return false if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
134
150
 
135
151
  # We can't read the env from within `Bundle.with_original_env` so we need to set it here.
136
152
  ruby_lsp_env_is_test = (ENV["RUBY_LSP_ENV"] == "test")
137
153
  Bundler.with_original_env do
138
- sorbet_static_detected = Bundler.locked_gems.specs.any? { |spec| spec.name == "sorbet-static" }
154
+ sorbet_static_detected = dependencies.any?(/^sorbet-static/)
139
155
  # Don't show message while running tests, since it's noisy
140
156
  if sorbet_static_detected && !ruby_lsp_env_is_test
141
157
  $stderr.puts("Ruby LSP detected this is a Sorbet project so will defer to Sorbet LSP for some functionality")
@@ -147,16 +163,11 @@ module RubyLsp
147
163
  end
148
164
 
149
165
  sig { returns(T::Array[String]) }
150
- def dependencies
151
- @dependencies ||= T.let(
152
- begin
153
- Bundler.with_original_env { Bundler.default_gemfile }
154
- Bundler.locked_gems.dependencies.keys + gemspec_dependencies
155
- rescue Bundler::GemfileNotFound
156
- []
157
- end,
158
- T.nilable(T::Array[String]),
159
- )
166
+ def gather_dependencies
167
+ Bundler.with_original_env { Bundler.default_gemfile }
168
+ Bundler.locked_gems.dependencies.keys + gemspec_dependencies
169
+ rescue Bundler::GemfileNotFound
170
+ []
160
171
  end
161
172
 
162
173
  sig { returns(T::Array[String]) }
@@ -7,6 +7,8 @@ module RubyLsp
7
7
  extend T::Sig
8
8
  include Requests::Support::Common
9
9
 
10
+ MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER = 10
11
+
10
12
  sig do
11
13
  params(
12
14
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
@@ -64,12 +66,17 @@ module RubyLsp
64
66
 
65
67
  sig { params(node: Prism::CallNode).void }
66
68
  def handle_method_definition(node)
67
- return unless self_receiver?(node)
68
-
69
69
  message = node.message
70
70
  return unless message
71
71
 
72
- methods = @index.resolve_method(message, @nesting.join("::"))
72
+ methods = if self_receiver?(node)
73
+ @index.resolve_method(message, @nesting.join("::"))
74
+ else
75
+ # If the method doesn't have a receiver, then we provide a few candidates to jump to
76
+ # But we don't want to provide too many candidates, as it can be overwhelming
77
+ @index[message]&.take(MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER)
78
+ end
79
+
73
80
  return unless methods
74
81
 
75
82
  methods.each do |target_method|
@@ -34,7 +34,7 @@ module RubyLsp
34
34
  sig { params(global_state: GlobalState, document: Document).void }
35
35
  def initialize(global_state, document)
36
36
  super()
37
- @active_formatter = T.let(global_state.active_formatter, T.nilable(Support::Formatter))
37
+ @active_linters = T.let(global_state.active_linters, T::Array[Support::Formatter])
38
38
  @document = document
39
39
  @uri = T.let(document.uri, URI::Generic)
40
40
  end
@@ -45,10 +45,13 @@ module RubyLsp
45
45
  diagnostics.concat(syntax_error_diagnostics, syntax_warning_diagnostics)
46
46
 
47
47
  # Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid
48
- return diagnostics if @document.syntax_error? || !@active_formatter
48
+ return diagnostics if @document.syntax_error? || @active_linters.empty?
49
+
50
+ @active_linters.each do |linter|
51
+ linter_diagnostics = linter.run_diagnostic(@uri, @document)
52
+ diagnostics.concat(linter_diagnostics) if linter_diagnostics
53
+ end
49
54
 
50
- formatter_diagnostics = @active_formatter.run_diagnostic(@uri, @document)
51
- diagnostics.concat(formatter_diagnostics) if formatter_diagnostics
52
55
  diagnostics
53
56
  end
54
57
 
@@ -40,10 +40,13 @@ module RubyLsp
40
40
  end
41
41
  end
42
42
 
43
- sig { params(dispatcher: Prism::Dispatcher, range: T.nilable(T::Range[Integer])).void }
44
- def initialize(dispatcher, range: nil)
43
+ sig { params(global_state: GlobalState, dispatcher: Prism::Dispatcher, range: T.nilable(T::Range[Integer])).void }
44
+ def initialize(global_state, dispatcher, range: nil)
45
45
  super()
46
- @response_builder = T.let(ResponseBuilders::SemanticHighlighting.new, ResponseBuilders::SemanticHighlighting)
46
+ @response_builder = T.let(
47
+ ResponseBuilders::SemanticHighlighting.new(global_state.encoding),
48
+ ResponseBuilders::SemanticHighlighting,
49
+ )
47
50
  Listeners::SemanticHighlighting.new(dispatcher, @response_builder, range: range)
48
51
 
49
52
  Addon.addons.each do |addon|
@@ -55,19 +55,21 @@ module RubyLsp
55
55
 
56
56
  ResponseType = type_member { { fixed: Interface::SemanticTokens } }
57
57
 
58
- sig { void }
59
- def initialize
60
- super
58
+ sig { params(encoding: Encoding).void }
59
+ def initialize(encoding)
60
+ super()
61
+ @encoding = encoding
61
62
  @stack = T.let([], T::Array[SemanticToken])
62
63
  end
63
64
 
64
65
  sig { params(location: Prism::Location, type: Symbol, modifiers: T::Array[Symbol]).void }
65
66
  def add_token(location, type, modifiers = [])
66
- length = location.end_offset - location.start_offset
67
+ length = location.end_code_units_offset(@encoding) - location.start_code_units_offset(@encoding)
67
68
  modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
68
69
  @stack.push(
69
70
  SemanticToken.new(
70
- location: location,
71
+ start_line: location.start_line,
72
+ start_code_unit_column: location.start_code_units_column(@encoding),
71
73
  length: length,
72
74
  type: T.must(TOKEN_TYPES[type]),
73
75
  modifier: modifiers_indices,
@@ -75,6 +77,15 @@ module RubyLsp
75
77
  )
76
78
  end
77
79
 
80
+ sig { params(location: Prism::Location).returns(T::Boolean) }
81
+ def last_token_matches?(location)
82
+ token = @stack.last
83
+ return false unless token
84
+
85
+ token.start_line == location.start_line &&
86
+ token.start_code_unit_column == location.start_code_units_column(@encoding)
87
+ end
88
+
78
89
  sig { returns(T.nilable(SemanticToken)) }
79
90
  def last
80
91
  @stack.last
@@ -88,8 +99,11 @@ module RubyLsp
88
99
  class SemanticToken
89
100
  extend T::Sig
90
101
 
91
- sig { returns(Prism::Location) }
92
- attr_reader :location
102
+ sig { returns(Integer) }
103
+ attr_reader :start_line
104
+
105
+ sig { returns(Integer) }
106
+ attr_reader :start_code_unit_column
93
107
 
94
108
  sig { returns(Integer) }
95
109
  attr_reader :length
@@ -100,9 +114,18 @@ module RubyLsp
100
114
  sig { returns(T::Array[Integer]) }
101
115
  attr_reader :modifier
102
116
 
103
- sig { params(location: Prism::Location, length: Integer, type: Integer, modifier: T::Array[Integer]).void }
104
- def initialize(location:, length:, type:, modifier:)
105
- @location = location
117
+ sig do
118
+ params(
119
+ start_line: Integer,
120
+ start_code_unit_column: Integer,
121
+ length: Integer,
122
+ type: Integer,
123
+ modifier: T::Array[Integer],
124
+ ).void
125
+ end
126
+ def initialize(start_line:, start_code_unit_column:, length:, type:, modifier:)
127
+ @start_line = start_line
128
+ @start_code_unit_column = start_code_unit_column
106
129
  @length = length
107
130
  @type = type
108
131
  @modifier = modifier
@@ -146,7 +169,7 @@ module RubyLsp
146
169
  # Enumerable#sort_by is not deterministic when the compared values are equal.
147
170
  # When that happens, we need to use the index as a tie breaker to ensure
148
171
  # that the order of the tokens is always the same.
149
- [token.location.start_line, token.location.start_column, index]
172
+ [token.start_line, token.start_code_unit_column, index]
150
173
  end
151
174
 
152
175
  delta = sorted_tokens.flat_map do |token|
@@ -167,8 +190,8 @@ module RubyLsp
167
190
  # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
168
191
  sig { params(token: SemanticToken).returns(T::Array[Integer]) }
169
192
  def compute_delta(token)
170
- row = token.location.start_line - 1
171
- column = token.location.start_column
193
+ row = token.start_line - 1
194
+ column = token.start_code_unit_column
172
195
 
173
196
  begin
174
197
  delta_line = row - @current_row
@@ -334,7 +334,7 @@ module RubyLsp
334
334
  document_link = Requests::DocumentLink.new(uri, document.comments, dispatcher)
335
335
  code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
336
336
 
337
- semantic_highlighting = Requests::SemanticHighlighting.new(dispatcher)
337
+ semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher)
338
338
  dispatcher.dispatch(document.tree)
339
339
 
340
340
  # Store all responses retrieve in this round of visits in the cache and then return the response for the request
@@ -366,7 +366,7 @@ module RubyLsp
366
366
  end_line = range.dig(:end, :line)
367
367
 
368
368
  dispatcher = Prism::Dispatcher.new
369
- request = Requests::SemanticHighlighting.new(dispatcher, range: start_line..end_line)
369
+ request = Requests::SemanticHighlighting.new(@global_state, dispatcher, range: start_line..end_line)
370
370
  dispatcher.visit(document.tree)
371
371
 
372
372
  response = request.perform
@@ -75,7 +75,7 @@ module RubyLsp
75
75
  class Request < Message
76
76
  extend T::Sig
77
77
 
78
- sig { params(id: Integer, method: String, params: Object).void }
78
+ sig { params(id: T.any(Integer, String), method: String, params: Object).void }
79
79
  def initialize(id:, method:, params:)
80
80
  @id = id
81
81
  super(method: method, params: params)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.4
4
+ version: 0.16.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-11 00:00:00.000000000 Z
11
+ date: 2024-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -30,20 +30,20 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.22.0
33
+ version: 0.23.0
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: '0.25'
36
+ version: '0.28'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 0.22.0
43
+ version: 0.23.0
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.25'
46
+ version: '0.28'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sorbet-runtime
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -83,6 +83,7 @@ files:
83
83
  - lib/ruby_indexer/lib/ruby_indexer/entry.rb
84
84
  - lib/ruby_indexer/lib/ruby_indexer/index.rb
85
85
  - lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb
86
+ - lib/ruby_indexer/lib/ruby_indexer/location.rb
86
87
  - lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb
87
88
  - lib/ruby_indexer/ruby_indexer.rb
88
89
  - lib/ruby_indexer/test/classes_and_modules_test.rb
@@ -177,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
178
  - !ruby/object:Gem::Version
178
179
  version: '0'
179
180
  requirements: []
180
- rubygems_version: 3.5.7
181
+ rubygems_version: 3.5.9
181
182
  signing_key:
182
183
  specification_version: 4
183
184
  summary: An opinionated language server for Ruby