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

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.
@@ -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|