steep 1.4.0.dev.3 → 1.4.0.dev.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,211 +3,323 @@ module Steep
3
3
  module LSPFormatter
4
4
  include Services
5
5
 
6
- class CommentBuilder
7
- def initialize
8
- @array = []
9
- end
10
-
11
- def self.build
12
- builder = CommentBuilder.new
13
- yield builder
14
- builder.to_s
15
- end
6
+ LSP = LanguageServer::Protocol
16
7
 
17
- def to_s
18
- unless @array.empty?
19
- @array.join("\n\n----\n\n")
20
- else
21
- ""
22
- end
23
- end
8
+ module_function
24
9
 
25
- def <<(string)
26
- if string
27
- s = string.rstrip.gsub(/^[ \t]*<!--(?~-->)-->\n/, "").gsub(/\A([ \t]*\n)+/, "")
28
- unless @array.include?(s)
29
- @array << s
30
- end
31
- end
10
+ def markup_content(string = nil, &block)
11
+ if block
12
+ string = yield()
32
13
  end
33
14
 
34
- def push
35
- s = ""
36
- yield s
37
- self << s
15
+ if string
16
+ LSP::Interface::MarkupContent.new(kind: LSP::Constant::MarkupKind::MARKDOWN, value: string)
38
17
  end
39
18
  end
40
19
 
41
- module_function
42
-
43
20
  def format_hover_content(content)
44
21
  case content
45
22
  when HoverProvider::Ruby::VariableContent
46
- "`#{content.name}`: `#{content.type.to_s}`"
23
+ local_variable(content.name, content.type)
47
24
 
48
25
  when HoverProvider::Ruby::MethodCallContent
49
- CommentBuilder.build do |builder|
50
- call = content.method_call
51
- builder.push do |s|
52
- case call
53
- when TypeInference::MethodCall::Special
54
- mt = call.actual_method_type.with(
55
- type: call.actual_method_type.type.with(return_type: call.return_type)
56
- )
57
- s << <<-EOM
58
- **💡 Custom typing rule applies**
59
-
60
- ```rbs
61
- #{mt.to_s}
62
- ```
26
+ io = StringIO.new
27
+ call = content.method_call
63
28
 
64
- EOM
65
- when TypeInference::MethodCall::Typed
66
- mt = call.actual_method_type.with(
29
+ case call
30
+ when TypeInference::MethodCall::Typed
31
+ method_types = call.method_decls.map(&:method_type)
32
+ if call.is_a?(TypeInference::MethodCall::Special)
33
+ method_types = [
34
+ call.actual_method_type.with(
67
35
  type: call.actual_method_type.type.with(return_type: call.return_type)
68
36
  )
69
- s << "```rbs\n#{mt.to_s}\n```\n\n"
70
- when TypeInference::MethodCall::Error
71
- s << "```rbs\n( ??? ) -> #{call.return_type.to_s}\n```\n\n"
72
- end
37
+ ]
73
38
 
74
- s << to_list(call.method_decls) do |decl|
75
- "`#{decl.method_name}`"
76
- end
39
+ header = <<~MD
40
+ **💡 Custom typing rule applies**
41
+
42
+ ----
43
+ MD
77
44
  end
45
+ when TypeInference::MethodCall::Error
46
+ method_types = call.method_decls.map {|decl| decl.method_type }
78
47
 
79
- call.method_decls.each do |decl|
80
- if comment = decl.method_def.comment
81
- builder << <<EOM
82
- **#{decl.method_name.to_s}**
48
+ header = <<~MD
49
+ **🚨 No compatible method type found**
83
50
 
84
- ```rbs
85
- #{decl.method_type}
86
- ```
51
+ ----
52
+ MD
53
+ end
87
54
 
88
- #{comment.string.gsub(/\A([ \t]*\n)+/, "")}
89
- EOM
90
- end
91
- end
55
+ method_names = call.method_decls.map {|decl| decl.method_name.relative }
56
+ docs = call.method_decls.map {|decl| [decl.method_name, decl.method_def.comment] }.to_h
57
+
58
+ if header
59
+ io.puts header
92
60
  end
93
61
 
62
+ io.puts(
63
+ format_method_item_doc(method_types, method_names, docs)
64
+ )
65
+
66
+ io.string
67
+
94
68
  when HoverProvider::Ruby::DefinitionContent
95
- CommentBuilder.build do |builder|
96
- builder << <<EOM
97
- ```
98
- #{content.method_name}: #{content.method_type}
99
- ```
100
- EOM
101
- if comments = content.definition&.comments
102
- comments.each do |comment|
103
- builder << comment.string
104
- end
105
- end
69
+ io = StringIO.new
106
70
 
107
- if content.definition.method_types.size > 1
108
- builder << to_list(content.definition.method_types) {|type| "`#{type.to_s}`" }
71
+ method_name =
72
+ if content.method_name.is_a?(SingletonMethodName)
73
+ "self.#{content.method_name.method_name}"
74
+ else
75
+ content.method_name.method_name
109
76
  end
77
+
78
+ prefix_size = "def ".size + method_name.size
79
+ method_types = content.definition.method_types
80
+
81
+ io.puts <<~MD
82
+ ```rbs
83
+ def #{method_name}: #{method_types.join("\n" + " "*prefix_size + "| ") }
84
+ ```
85
+
86
+ ----
87
+ MD
88
+
89
+ if content.definition.method_types.size > 1
90
+ io.puts "**Internal method type**"
91
+ io.puts <<~MD
92
+ ```rbs
93
+ #{content.method_type}
94
+ ```
95
+
96
+ ----
97
+ MD
110
98
  end
99
+
100
+ io.puts format_comments(
101
+ content.definition.comments.map {|comment|
102
+ [content.method_name.relative.to_s, comment] #: [String, RBS::AST::Comment?]
103
+ }
104
+ )
105
+
106
+ io.string
111
107
  when HoverProvider::Ruby::ConstantContent
112
- CommentBuilder.build do |builder|
108
+ io = StringIO.new
109
+
110
+ decl_summary =
113
111
  case
114
112
  when decl = content.class_decl
115
- builder << <<EOM
116
- ```rbs
117
- #{declaration_summary(decl.primary.decl)}
118
- ```
119
- EOM
113
+ declaration_summary(decl.primary.decl)
120
114
  when decl = content.constant_decl
121
- builder << <<EOM
122
- ```rbs
123
- #{content.full_name}: #{content.type}
124
- ```
125
- EOM
115
+ declaration_summary(decl.decl)
126
116
  when decl = content.class_alias
127
- builder << <<EOM
128
- ```rbs
129
- #{decl.is_a?(::RBS::Environment::ClassAliasEntry) ? "class" : "module"} #{decl.decl.new_name} = #{decl.decl.old_name}
130
- ```
131
- EOM
117
+ declaration_summary(decl.decl)
132
118
  end
133
119
 
134
- content.comments.each do |comment|
135
- builder << comment.string
136
- end
120
+ io.puts <<~MD
121
+ ```rbs
122
+ #{decl_summary}
123
+ ```
124
+ MD
125
+
126
+ comments = content.comments.map {|comment|
127
+ [content.full_name.relative!.to_s, comment] #: [String, RBS::AST::Comment?]
128
+ }
129
+
130
+ unless comments.all?(&:nil?)
131
+ io.puts "----"
132
+ io.puts format_comments(comments)
137
133
  end
134
+
135
+ io.string
138
136
  when HoverProvider::Ruby::TypeContent
139
- "`#{content.type}`"
140
- when HoverProvider::RBS::TypeAliasContent
141
- CommentBuilder.build do |builder|
142
- builder << <<EOM
143
- ```rbs
144
- #{declaration_summary(content.decl)}
145
- ```
146
- EOM
147
- if comment = content.decl.comment
148
- builder << comment.string
149
- end
150
- end
137
+ <<~MD
138
+ ```rbs
139
+ #{content.type}
140
+ ```
141
+ MD
142
+
151
143
  when HoverProvider::Ruby::TypeAssertionContent
152
- CommentBuilder.build do |builder|
153
- builder << <<-EOM
154
- `#{content.asserted_type.to_s}`
144
+ <<~MD
145
+ ```rbs
146
+ #{content.asserted_type}
147
+ ```
155
148
 
156
- ↑ Converted from `#{content.original_type.to_s}`
157
- EOM
149
+ ↑ Converted from `#{content.original_type.to_s}`
150
+ MD
151
+
152
+ when HoverProvider::RBS::TypeAliasContent, HoverProvider::RBS::InterfaceContent
153
+ io = StringIO.new()
154
+
155
+ io.puts <<~MD
156
+ ```rbs
157
+ #{declaration_summary(content.decl)}
158
+ ```
159
+ MD
160
+
161
+ if comment = content.decl.comment
162
+ io.puts
163
+ io.puts "----"
164
+
165
+ io.puts format_comment(comment, header: content.decl.name.relative!.to_s)
158
166
  end
167
+
168
+ io.string
169
+
159
170
  when HoverProvider::RBS::ClassContent
160
- CommentBuilder.build do |builder|
161
- builder << <<EOM
162
- ```rbs
163
- #{declaration_summary(content.decl)}
164
- ```
165
- EOM
166
- if comment = content.decl.comment
167
- builder << comment.string
168
- end
169
- end
170
- when HoverProvider::RBS::InterfaceContent
171
- CommentBuilder.build do |builder|
172
- builder << <<EOM
173
- ```rbs
174
- #{declaration_summary(content.decl)}
175
- ```
176
- EOM
177
- if comment = content.decl.comment
178
- builder << comment.string
179
- end
171
+ io = StringIO.new
172
+
173
+ io << <<~MD
174
+ ```rbs
175
+ #{declaration_summary(content.decl)}
176
+ ```
177
+ MD
178
+
179
+ if content.decl.comment
180
+ io.puts "----"
181
+
182
+ class_name =
183
+ case content.decl
184
+ when RBS::AST::Declarations::ModuleAlias, RBS::AST::Declarations::ClassAlias
185
+ content.decl.new_name
186
+ when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module
187
+ content.decl.name
188
+ else
189
+ raise
190
+ end
191
+
192
+ io << format_comments([[class_name.relative!.to_s, content.decl.comment]])
180
193
  end
194
+
195
+ io.string
181
196
  else
182
197
  raise content.class.to_s
183
198
  end
184
199
  end
185
200
 
186
- def to_list(collection, &block)
187
- buffer = ""
201
+ def format_completion_docs(item)
202
+ case item
203
+ when Services::CompletionProvider::LocalVariableItem
204
+ local_variable(item.identifier, item.type)
205
+ when Services::CompletionProvider::ConstantItem
206
+ io = StringIO.new
207
+
208
+ io.puts <<~MD
209
+ ```rbs
210
+ #{declaration_summary(item.decl)}
211
+ ```
212
+ MD
213
+
214
+ unless item.comments.all?(&:nil?)
215
+ io.puts "----"
216
+ io.puts format_comments(
217
+ item.comments.map {|comment|
218
+ [item.full_name.relative!.to_s, comment] #: [String, RBS::AST::Comment?]
219
+ }
220
+ )
221
+ end
222
+
223
+ io.string
224
+ when Services::CompletionProvider::InstanceVariableItem
225
+ instance_variable(item.identifier, item.type)
226
+ when Services::CompletionProvider::SimpleMethodNameItem
227
+ format_method_item_doc(item.method_types, [], { item.method_name => item.method_member.comment })
228
+ when Services::CompletionProvider::ComplexMethodNameItem
229
+ method_names = item.method_names.map(&:relative).uniq
230
+ comments = item.method_definitions.transform_values {|member| member.comment }
231
+ format_method_item_doc(item.method_types, method_names, comments)
232
+ when Services::CompletionProvider::GeneratedMethodNameItem
233
+ format_method_item_doc(item.method_types, [], {}, "🤖 Generated method for receiver type")
234
+ end
235
+ end
236
+
237
+ def format_rbs_completion_docs(type_name, decl, comments)
238
+ io = StringIO.new
239
+
240
+ io.puts <<~MD
241
+ ```rbs
242
+ #{declaration_summary(decl)}
243
+ ```
244
+ MD
245
+
246
+ unless comments.empty?
247
+ io.puts
248
+ io.puts "----"
188
249
 
189
- strings =
190
- if block
191
- collection.map(&block)
250
+ io.puts format_comments(
251
+ comments.map {|comment|
252
+ [type_name.relative!.to_s, comment] #: [String, RBS::AST::Comment?]
253
+ }
254
+ )
255
+ end
256
+
257
+ io.string
258
+ end
259
+
260
+ def format_comments(comments)
261
+ io = StringIO.new
262
+
263
+ with_docs = [] #: Array[[String, RBS::AST::Comment]]
264
+ without_docs = [] #: Array[String]
265
+
266
+ comments.each do |title, comment|
267
+ if comment
268
+ with_docs << [title, comment]
192
269
  else
193
- collection.map(&:to_s)
270
+ without_docs << title
194
271
  end
272
+ end
195
273
 
196
- strings.each do |s|
197
- buffer << "- #{s}\n"
274
+ unless with_docs.empty?
275
+ with_docs.each do |title, comment|
276
+ io.puts format_comment(comment, header: title)
277
+ io.puts
278
+ end
279
+
280
+ unless without_docs.empty?
281
+ io.puts
282
+ io.puts "----"
283
+ if without_docs.size == 1
284
+ io.puts "🔍 One more definition without docs"
285
+ else
286
+ io.puts "🔍 #{without_docs.size} more definitions without docs"
287
+ end
288
+ end
198
289
  end
199
290
 
200
- buffer
291
+ io.string
201
292
  end
202
293
 
203
- def name_and_args(name, args)
204
- if args.empty?
205
- "#{name}"
294
+ def format_comment(comment, header: nil, &block)
295
+ return unless comment
296
+
297
+ io = StringIO.new
298
+ if header
299
+ io.puts "### 📚 #{header}"
300
+ io.puts
301
+ end
302
+ io.puts comment.string.rstrip.gsub(/^[ \t]*<!--(?~-->)-->\n/, "").gsub(/\A([ \t]*\n)+/, "")
303
+
304
+ if block
305
+ yield io.string
206
306
  else
207
- "#{name}[#{args.map(&:to_s).join(", ")}]"
307
+ io.string
208
308
  end
209
309
  end
210
310
 
311
+ def local_variable(name, type)
312
+ <<~MD
313
+ **Local variable** `#{name}: #{type}`
314
+ MD
315
+ end
316
+
317
+ def instance_variable(name, type)
318
+ <<~MD
319
+ **Instance variable** `#{name}: #{type}`
320
+ MD
321
+ end
322
+
211
323
  def name_and_params(name, params)
212
324
  if params.empty?
213
325
  "#{name}"
@@ -238,28 +350,70 @@ EOM
238
350
  end
239
351
  end
240
352
 
353
+ def name_and_args(name, args)
354
+ if args.empty?
355
+ "#{name}"
356
+ else
357
+ "#{name}[#{args.map(&:to_s).join(", ")}]"
358
+ end
359
+ end
360
+
241
361
  def declaration_summary(decl)
362
+ # Note that all names in the declarations is absolute
242
363
  case decl
243
364
  when RBS::AST::Declarations::Class
244
365
  super_class = if super_class = decl.super_class
245
366
  " < #{name_and_args(super_class.name, super_class.args)}"
246
367
  end
247
- "class #{name_and_params(decl.name, decl.type_params)}#{super_class}"
368
+ "class #{name_and_params(decl.name.relative!, decl.type_params)}#{super_class}"
248
369
  when RBS::AST::Declarations::Module
249
370
  self_type = unless decl.self_types.empty?
250
371
  " : #{decl.self_types.map {|s| name_and_args(s.name, s.args) }.join(", ")}"
251
372
  end
252
- "module #{name_and_params(decl.name, decl.type_params)}#{self_type}"
373
+ "module #{name_and_params(decl.name.relative!, decl.type_params)}#{self_type}"
253
374
  when RBS::AST::Declarations::TypeAlias
254
- "type #{decl.name} = #{decl.type}"
375
+ "type #{name_and_params(decl.name.relative!, decl.type_params)} = #{decl.type}"
255
376
  when RBS::AST::Declarations::Interface
256
- "interface #{name_and_params(decl.name, decl.type_params)}"
377
+ "interface #{name_and_params(decl.name.relative!, decl.type_params)}"
257
378
  when RBS::AST::Declarations::ClassAlias
258
- "class #{decl.new_name} = #{decl.old_name}"
379
+ "class #{decl.new_name.relative!} = #{decl.old_name}"
259
380
  when RBS::AST::Declarations::ModuleAlias
260
- "module #{decl.new_name} = #{decl.old_name}"
381
+ "module #{decl.new_name.relative!} = #{decl.old_name}"
382
+ when RBS::AST::Declarations::Global
383
+ "#{decl.name}: #{decl.type}"
384
+ when RBS::AST::Declarations::Constant
385
+ "#{decl.name.relative!}: #{decl.type}"
261
386
  end
262
387
  end
388
+
389
+ def format_method_item_doc(method_types, method_names, comments, footer = "")
390
+ io = StringIO.new
391
+
392
+ io.puts "**Method type**:"
393
+ io.puts "```rbs"
394
+ if method_types.size == 1
395
+ io.puts method_types[0].to_s
396
+ else
397
+ io.puts " #{method_types.join("\n| ")}"
398
+ end
399
+ io.puts "```"
400
+
401
+ if method_names.size > 1
402
+ io.puts "**Possible methods**: #{method_names.map {|type| "`#{type.to_s}`" }.join(", ")}"
403
+ io.puts
404
+ end
405
+
406
+ unless comments.each_value.all?(&:nil?)
407
+ io.puts "----"
408
+ io.puts format_comments(comments.transform_keys {|name| name.relative.to_s }.entries)
409
+ end
410
+
411
+ unless footer.empty?
412
+ io.puts footer.rstrip
413
+ end
414
+
415
+ io.string
416
+ end
263
417
  end
264
418
  end
265
419
  end
@@ -548,6 +548,9 @@ module Steep
548
548
  trigger_characters: [".", "@", ":"],
549
549
  work_done_progress: true
550
550
  ),
551
+ signature_help_provider: {
552
+ triggerCharacters: ["("]
553
+ },
551
554
  workspace_symbol_provider: true,
552
555
  definition_provider: true,
553
556
  declaration_provider: false,
@@ -596,7 +599,7 @@ module Steep
596
599
  controller.update_priority(close: path)
597
600
  end
598
601
 
599
- when "textDocument/hover", "textDocument/completion"
602
+ when "textDocument/hover", "textDocument/completion", "textDocument/signatureHelp"
600
603
  if interaction_worker
601
604
  if path = pathname(message[:params][:textDocument][:uri])
602
605
  result_controller << send_request(method: message[:method], params: message[:params], worker: interaction_worker) do |handler|