steep 1.4.0 → 1.5.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.vscode/steep-shared.code-snippets +41 -0
  4. data/CHANGELOG.md +37 -0
  5. data/Gemfile +2 -5
  6. data/Gemfile.lock +20 -17
  7. data/Gemfile.steep +1 -1
  8. data/Gemfile.steep.lock +6 -6
  9. data/Rakefile +198 -0
  10. data/Steepfile +3 -1
  11. data/lib/steep/ast/builtin.rb +9 -7
  12. data/lib/steep/ast/node/type_application.rb +13 -5
  13. data/lib/steep/ast/node/type_assertion.rb +28 -9
  14. data/lib/steep/ast/types/factory.rb +39 -7
  15. data/lib/steep/cli.rb +2 -1
  16. data/lib/steep/diagnostic/deprecated/else_on_exhaustive_case.rb +20 -0
  17. data/lib/steep/diagnostic/lsp_formatter.rb +3 -3
  18. data/lib/steep/diagnostic/ruby.rb +73 -12
  19. data/lib/steep/drivers/annotations.rb +1 -0
  20. data/lib/steep/drivers/check.rb +18 -13
  21. data/lib/steep/drivers/checkfile.rb +1 -1
  22. data/lib/steep/drivers/diagnostic_printer.rb +6 -4
  23. data/lib/steep/drivers/init.rb +2 -1
  24. data/lib/steep/drivers/print_project.rb +3 -1
  25. data/lib/steep/drivers/stats.rb +1 -1
  26. data/lib/steep/drivers/utils/driver_helper.rb +10 -8
  27. data/lib/steep/drivers/utils/jobs_option.rb +6 -1
  28. data/lib/steep/drivers/validate.rb +9 -5
  29. data/lib/steep/drivers/watch.rb +8 -3
  30. data/lib/steep/expectations.rb +144 -75
  31. data/lib/steep/index/signature_symbol_provider.rb +22 -13
  32. data/lib/steep/node_helper.rb +172 -0
  33. data/lib/steep/server/base_worker.rb +2 -1
  34. data/lib/steep/server/change_buffer.rb +17 -15
  35. data/lib/steep/server/interaction_worker.rb +20 -0
  36. data/lib/steep/server/lsp_formatter.rb +20 -1
  37. data/lib/steep/server/master.rb +51 -36
  38. data/lib/steep/server/type_check_worker.rb +18 -2
  39. data/lib/steep/server/worker_process.rb +19 -2
  40. data/lib/steep/services/completion_provider.rb +189 -3
  41. data/lib/steep/services/file_loader.rb +1 -1
  42. data/lib/steep/services/goto_service.rb +123 -27
  43. data/lib/steep/services/signature_help_provider.rb +1 -6
  44. data/lib/steep/signature/validator.rb +6 -1
  45. data/lib/steep/source.rb +165 -108
  46. data/lib/steep/subtyping/check.rb +5 -3
  47. data/lib/steep/subtyping/variable_variance.rb +11 -0
  48. data/lib/steep/thread_waiter.rb +35 -0
  49. data/lib/steep/type_construction.rb +416 -171
  50. data/lib/steep/type_inference/block_params.rb +50 -9
  51. data/lib/steep/type_inference/context.rb +4 -0
  52. data/lib/steep/type_inference/context_array.rb +6 -6
  53. data/lib/steep/type_inference/logic_type_interpreter.rb +202 -68
  54. data/lib/steep/typing.rb +5 -4
  55. data/lib/steep/version.rb +1 -1
  56. data/lib/steep.rb +21 -14
  57. data/sample/Steepfile +1 -0
  58. data/sig/shims/bundler.rbs +3 -0
  59. data/sig/shims/language-server_protocol.rbs +151 -10
  60. data/sig/shims/parser/nodes.rbs +210 -0
  61. data/sig/shims/parser.rbs +10 -0
  62. data/sig/steep/ast/builtin.rbs +2 -2
  63. data/sig/steep/ast/node/type_application.rbs +2 -2
  64. data/sig/steep/ast/node/type_assertion.rbs +8 -2
  65. data/sig/steep/ast/types/factory.rbs +28 -1
  66. data/sig/steep/diagnostic/deprecated/else_on_exhaustive_case.rbs +13 -0
  67. data/sig/steep/diagnostic/lsp_formatter.rbs +5 -2
  68. data/sig/steep/diagnostic/ruby.rbs +76 -6
  69. data/sig/steep/drivers/annotations.rbs +5 -5
  70. data/sig/steep/drivers/check.rbs +11 -11
  71. data/sig/steep/drivers/diagnostic_printer.rbs +9 -9
  72. data/sig/steep/drivers/init.rbs +6 -6
  73. data/sig/steep/drivers/print_project.rbs +4 -4
  74. data/sig/steep/drivers/utils/driver_helper.rbs +8 -6
  75. data/sig/steep/drivers/validate.rbs +4 -4
  76. data/sig/steep/drivers/watch.rbs +1 -1
  77. data/sig/steep/expectations.rbs +72 -0
  78. data/sig/steep/index/signature_symbol_provider.rbs +22 -10
  79. data/sig/steep/interface/block.rbs +2 -0
  80. data/sig/steep/interface/function.rbs +2 -2
  81. data/sig/steep/node_helper.rbs +56 -0
  82. data/sig/steep/path_helper.rbs +1 -1
  83. data/sig/steep/project/options.rbs +1 -1
  84. data/sig/steep/range_extension.rbs +2 -2
  85. data/sig/steep/server/master.rbs +16 -2
  86. data/sig/steep/server/type_check_worker.rbs +5 -1
  87. data/sig/steep/server/worker_process.rbs +5 -1
  88. data/sig/steep/services/completion_provider.rbs +31 -1
  89. data/sig/steep/services/goto_service.rbs +80 -19
  90. data/sig/steep/source.rbs +27 -4
  91. data/sig/steep/subtyping/variable_variance.rbs +9 -9
  92. data/sig/steep/thread_waiter.rbs +13 -0
  93. data/sig/steep/type_construction.rbs +26 -9
  94. data/sig/steep/type_inference/block_params.rbs +13 -1
  95. data/sig/steep/type_inference/context.rbs +5 -1
  96. data/sig/steep/type_inference/context_array.rbs +16 -15
  97. data/sig/steep/type_inference/logic_type_interpreter.rbs +36 -6
  98. data/sig/steep/type_inference/type_env_builder.rbs +4 -0
  99. data/sig/steep/typing.rbs +22 -20
  100. data/sig/steep.rbs +14 -13
  101. data/smoke/and/a.rb +1 -1
  102. data/smoke/and/test_expectations.yml +5 -7
  103. data/smoke/diagnostics/incompatible_annotation.rb +1 -1
  104. data/smoke/diagnostics/test_expectations.yml +2 -2
  105. data/smoke/enumerator/a.rb +0 -7
  106. data/smoke/enumerator/b.rb +0 -2
  107. data/smoke/enumerator/test_expectations.yml +17 -105
  108. data/smoke/lambda/a.rb +0 -5
  109. data/smoke/lambda/test_expectations.yml +0 -22
  110. data/smoke/type_case/test_expectations.yml +10 -0
  111. data/steep.gemspec +2 -2
  112. metadata +16 -9
@@ -1,5 +1,136 @@
1
1
  module Steep
2
2
  class Expectations
3
+ class Diagnostic < Struct.new(:start_position, :end_position, :severity, :message, :code, keyword_init: true)
4
+ DiagnosticSeverity = LanguageServer::Protocol::Constant::DiagnosticSeverity
5
+
6
+ def self.from_hash(hash)
7
+ start_position = {
8
+ line: hash.dig("range", "start", "line") - 1,
9
+ character: hash.dig("range", "start", "character")
10
+ } #: position
11
+ end_position = {
12
+ line: hash.dig("range", "end", "line") - 1,
13
+ character: hash.dig("range", "end", "character")
14
+ } #: position
15
+
16
+ severity =
17
+ case hash["severity"] || "ERROR"
18
+ when "ERROR"
19
+ :error
20
+ when "WARNING"
21
+ :warning
22
+ when "INFORMATION"
23
+ :information
24
+ when "HINT"
25
+ :hint
26
+ end #: Diagnostic::LSPFormatter::severity
27
+
28
+ Diagnostic.new(
29
+ start_position: start_position,
30
+ end_position: end_position,
31
+ severity: severity,
32
+ message: hash["message"],
33
+ code: hash["code"]
34
+ )
35
+ end
36
+
37
+ def self.from_lsp(diagnostic)
38
+ start_position = {
39
+ line: diagnostic.dig(:range, :start, :line),
40
+ character: diagnostic.dig(:range, :start, :character)
41
+ } #: position
42
+ end_position = {
43
+ line: diagnostic.dig(:range, :end, :line),
44
+ character: diagnostic.dig(:range, :end, :character)
45
+ } #: position
46
+
47
+ severity =
48
+ case diagnostic[:severity]
49
+ when DiagnosticSeverity::ERROR
50
+ :error
51
+ when DiagnosticSeverity::WARNING
52
+ :warning
53
+ when DiagnosticSeverity::INFORMATION
54
+ :information
55
+ when DiagnosticSeverity::HINT
56
+ :hint
57
+ else
58
+ :error
59
+ end #: Diagnostic::LSPFormatter::severity
60
+
61
+ Diagnostic.new(
62
+ start_position: start_position,
63
+ end_position: end_position,
64
+ severity: severity,
65
+ message: diagnostic[:message],
66
+ code: diagnostic[:code]
67
+ )
68
+ end
69
+
70
+ def to_hash
71
+ {
72
+ "range" => {
73
+ "start" => {
74
+ "line" => start_position[:line] + 1,
75
+ "character" => start_position[:character]
76
+ },
77
+ "end" => {
78
+ "line" => end_position[:line] + 1,
79
+ "character" => end_position[:character]
80
+ }
81
+ },
82
+ "severity" => severity.to_s.upcase,
83
+ "message" => message,
84
+ "code" => code
85
+ }
86
+ end
87
+
88
+ def lsp_severity
89
+ case severity
90
+ when :error
91
+ DiagnosticSeverity::ERROR
92
+ when :warning
93
+ DiagnosticSeverity::WARNING
94
+ when :information
95
+ DiagnosticSeverity::INFORMATION
96
+ when :hint
97
+ DiagnosticSeverity::HINT
98
+ else
99
+ raise
100
+ end
101
+ end
102
+
103
+ def to_lsp
104
+ {
105
+ range: {
106
+ start: {
107
+ line: start_position[:line],
108
+ character: start_position[:character]
109
+ },
110
+ end: {
111
+ line: end_position[:line],
112
+ character: end_position[:character]
113
+ }
114
+ },
115
+ severity: lsp_severity,
116
+ message: message,
117
+ code: code
118
+ }
119
+ end
120
+
121
+ def sort_key
122
+ [
123
+ start_position[:line],
124
+ start_position[:character],
125
+ end_position[:line],
126
+ end_position[:character],
127
+ code,
128
+ severity,
129
+ message
130
+ ]
131
+ end
132
+ end
133
+
3
134
  class TestResult
4
135
  attr_reader :path
5
136
  attr_reader :expectation
@@ -21,17 +152,17 @@ module Steep
21
152
 
22
153
  def each_diagnostics
23
154
  if block_given?
24
- expected_set = Set.new(expectation)
25
- actual_set = Set.new(actual)
155
+ expected_set = Set.new(expectation) #: Set[Diagnostic]
156
+ actual_set = Set.new(actual) #: Set[Diagnostic]
26
157
 
27
- (expected_set + actual_set).sort_by {|a| Expectations.sort_key(a) }.each do |lsp|
158
+ (expected_set + actual_set).sort_by(&:sort_key).each do |diagnostic|
28
159
  case
29
- when expected_set.include?(lsp) && actual_set.include?(lsp)
30
- yield :expected, lsp
31
- when expected_set.include?(lsp)
32
- yield :missing, lsp
33
- when actual_set.include?(lsp)
34
- yield :unexpected, lsp
160
+ when expected_set.include?(diagnostic) && actual_set.include?(diagnostic)
161
+ yield [:expected, diagnostic]
162
+ when expected_set.include?(diagnostic)
163
+ yield [:missing, diagnostic]
164
+ when actual_set.include?(diagnostic)
165
+ yield [:unexpected, diagnostic]
35
166
  end
36
167
  end
37
168
  else
@@ -56,18 +187,6 @@ module Steep
56
187
 
57
188
  attr_reader :diagnostics
58
189
 
59
- def self.sort_key(hash)
60
- [
61
- hash.dig(:range, :start, :line),
62
- hash.dig(:range, :start, :character),
63
- hash.dig(:range, :end, :line),
64
- hash.dig(:range, :end, :character),
65
- hash[:code],
66
- hash[:severity],
67
- hash[:message]
68
- ]
69
- end
70
-
71
190
  def initialize()
72
191
  @diagnostics = {}
73
192
  end
@@ -81,14 +200,13 @@ module Steep
81
200
  end
82
201
 
83
202
  def to_yaml
84
- array = []
203
+ array = [] #: Array[{ "file" => String, "diagnostics" => Array[untyped] }]
85
204
 
86
205
  diagnostics.each_key.sort.each do |key|
87
206
  ds = diagnostics[key]
88
207
  array << {
89
208
  "file" => key.to_s,
90
- 'diagnostics' => ds.sort_by {|hash| Expectations.sort_key(hash) }
91
- .map { |d| Expectations.lsp_to_hash(d) }
209
+ 'diagnostics' => ds.sort_by(&:sort_key).map(&:to_hash)
92
210
  }
93
211
  end
94
212
 
@@ -100,60 +218,11 @@ module Steep
100
218
 
101
219
  YAML.load(content, filename: path.to_s).each do |entry|
102
220
  file = Pathname(entry["file"])
103
- expectations.diagnostics[file] = entry["diagnostics"]
104
- .map {|hash| hash_to_lsp(hash) }
105
- .sort_by! {|h| sort_key(h) }
221
+ expectations.diagnostics[file] =
222
+ entry["diagnostics"].map {|hash| Diagnostic.from_hash(hash) }.sort_by!(&:sort_key)
106
223
  end
107
224
 
108
225
  expectations
109
226
  end
110
-
111
- # Translate hash to LSP Diagnostic message
112
- def self.hash_to_lsp(hash)
113
- {
114
- range: {
115
- start: {
116
- line: hash.dig("range", "start", "line") - 1,
117
- character: hash.dig("range", "start", "character")
118
- },
119
- end: {
120
- line: hash.dig("range", "end", "line") - 1,
121
- character: hash.dig("range", "end", "character")
122
- }
123
- },
124
- severity: {
125
- "ERROR" => LSP::Constant::DiagnosticSeverity::ERROR,
126
- "WARNING" => LSP::Constant::DiagnosticSeverity::WARNING,
127
- "INFORMATION" => LSP::Constant::DiagnosticSeverity::INFORMATION,
128
- "HINT" => LSP::Constant::DiagnosticSeverity::HINT
129
- }[hash["severity"] || "ERROR"],
130
- message: hash["message"],
131
- code: hash["code"]
132
- }
133
- end
134
-
135
- # Translate LSP diagnostic message to hash
136
- def self.lsp_to_hash(lsp)
137
- {
138
- "range" => {
139
- "start" => {
140
- "line" => lsp.dig(:range, :start, :line) + 1,
141
- "character" => lsp.dig(:range, :start, :character)
142
- },
143
- "end" => {
144
- "line" => lsp.dig(:range, :end, :line) + 1,
145
- "character" => lsp.dig(:range, :end, :character)
146
- }
147
- },
148
- "severity" => {
149
- LSP::Constant::DiagnosticSeverity::ERROR => "ERROR",
150
- LSP::Constant::DiagnosticSeverity::WARNING => "WARNING",
151
- LSP::Constant::DiagnosticSeverity::INFORMATION => "INFORMATION",
152
- LSP::Constant::DiagnosticSeverity::HINT => "HINT"
153
- }[lsp[:severity] || LSP::Constant::DiagnosticSeverity::ERROR],
154
- "message" => lsp[:message],
155
- "code" => lsp[:code]
156
- }
157
- end
158
227
  end
159
228
  end
@@ -2,7 +2,9 @@ module Steep
2
2
  module Index
3
3
  class SignatureSymbolProvider
4
4
  LSP = LanguageServer::Protocol
5
- SymbolInformation = Struct.new(:name, :kind, :container_name, :location, keyword_init: true)
5
+
6
+ class SymbolInformation < Struct.new(:name, :kind, :container_name, :location, keyword_init: true)
7
+ end
6
8
 
7
9
  attr_reader :project
8
10
  attr_reader :indexes
@@ -48,7 +50,7 @@ module Steep
48
50
  end
49
51
 
50
52
  def query_symbol(query)
51
- symbols = []
53
+ symbols = [] #: Array[SymbolInformation]
52
54
 
53
55
  indexes.each do |index|
54
56
  index.each_entry do |entry|
@@ -60,34 +62,35 @@ module Steep
60
62
  name = entry.type_name.name.to_s
61
63
 
62
64
  entry.declarations.each do |decl|
63
- next unless assigned?(Pathname(decl.location.buffer.name))
65
+ location = decl.location or next
66
+ next unless assigned?(Pathname(location.buffer.name))
64
67
 
65
68
  case decl
66
69
  when RBS::AST::Declarations::Class
67
70
  symbols << SymbolInformation.new(
68
71
  name: name,
69
- location: decl.location,
72
+ location: location,
70
73
  kind: LSP::Constant::SymbolKind::CLASS,
71
74
  container_name: container_name
72
75
  )
73
76
  when RBS::AST::Declarations::Module
74
77
  symbols << SymbolInformation.new(
75
78
  name: name,
76
- location: decl.location,
79
+ location: location,
77
80
  kind: LSP::Constant::SymbolKind::MODULE,
78
81
  container_name: container_name
79
82
  )
80
83
  when RBS::AST::Declarations::Interface
81
84
  symbols << SymbolInformation.new(
82
85
  name: name,
83
- location: decl.location,
86
+ location: location,
84
87
  kind: LSP::Constant::SymbolKind::INTERFACE,
85
88
  container_name: container_name
86
89
  )
87
90
  when RBS::AST::Declarations::TypeAlias
88
91
  symbols << SymbolInformation.new(
89
92
  name: name,
90
- location: decl.location,
93
+ location: location,
91
94
  kind: LSP::Constant::SymbolKind::ENUM,
92
95
  container_name: container_name
93
96
  )
@@ -101,31 +104,34 @@ module Steep
101
104
  "##{entry.method_name.method_name}"
102
105
  when SingletonMethodName
103
106
  ".#{entry.method_name.method_name}"
107
+ else
108
+ raise
104
109
  end
105
110
  container_name = entry.method_name.type_name.relative!.to_s
106
111
 
107
112
  entry.declarations.each do |decl|
108
- next unless assigned?(Pathname(decl.location.buffer.name))
113
+ location = decl.location or next
114
+ next unless assigned?(Pathname(location.buffer.name))
109
115
 
110
116
  case decl
111
117
  when RBS::AST::Members::MethodDefinition
112
118
  symbols << SymbolInformation.new(
113
119
  name: name,
114
- location: decl.location,
120
+ location: location,
115
121
  kind: LSP::Constant::SymbolKind::METHOD,
116
122
  container_name: container_name
117
123
  )
118
124
  when RBS::AST::Members::Alias
119
125
  symbols << SymbolInformation.new(
120
126
  name: name,
121
- location: decl.location,
127
+ location: location,
122
128
  kind: LSP::Constant::SymbolKind::METHOD,
123
129
  container_name: container_name
124
130
  )
125
131
  when RBS::AST::Members::AttrAccessor, RBS::AST::Members::AttrReader, RBS::AST::Members::AttrWriter
126
132
  symbols << SymbolInformation.new(
127
133
  name: name,
128
- location: decl.location,
134
+ location: location,
129
135
  kind: LSP::Constant::SymbolKind::PROPERTY,
130
136
  container_name: container_name
131
137
  )
@@ -133,7 +139,7 @@ module Steep
133
139
  if decl.ivar_name
134
140
  symbols << SymbolInformation.new(
135
141
  name: decl.ivar_name.to_s,
136
- location: decl.location,
142
+ location: location,
137
143
  kind: LSP::Constant::SymbolKind::FIELD,
138
144
  container_name: container_name
139
145
  )
@@ -144,6 +150,7 @@ module Steep
144
150
  next unless SignatureSymbolProvider.test_const_name(query, entry.const_name)
145
151
 
146
152
  entry.declarations.each do |decl|
153
+ next unless decl.location
147
154
  next unless assigned?(Pathname(decl.location.buffer.name))
148
155
 
149
156
  symbols << SymbolInformation.new(
@@ -157,12 +164,14 @@ module Steep
157
164
  next unless SignatureSymbolProvider.test_global_name(query, entry.global_name)
158
165
 
159
166
  entry.declarations.each do |decl|
167
+ next unless decl.location
160
168
  next unless assigned?(Pathname(decl.location.buffer.name))
161
169
 
162
170
  symbols << SymbolInformation.new(
163
171
  name: decl.name.to_s,
164
172
  location: decl.location,
165
- kind: LSP::Constant::SymbolKind::VARIABLE
173
+ kind: LSP::Constant::SymbolKind::VARIABLE,
174
+ container_name: nil
166
175
  )
167
176
  end
168
177
  end
@@ -25,6 +25,8 @@ module Steep
25
25
 
26
26
  def value_node?(node)
27
27
  case node.type
28
+ when :self
29
+ true
28
30
  when :true, :false, :str, :sym, :int, :float, :nil
29
31
  true
30
32
  when :lvar
@@ -45,5 +47,175 @@ module Steep
45
47
  false
46
48
  end
47
49
  end
50
+
51
+ def deconstruct_if_node(node)
52
+ if node.type == :if
53
+ [
54
+ node.children[0],
55
+ node.children[1],
56
+ node.children[2],
57
+ _ = node.location
58
+ ]
59
+ end
60
+ end
61
+
62
+ def deconstruct_if_node!(node)
63
+ deconstruct_if_node(node) or raise
64
+ end
65
+
66
+ def test_if_node(node)
67
+ if (a, b, c, d = deconstruct_if_node(node))
68
+ yield(a, b, c, d)
69
+ else
70
+ false
71
+ end
72
+ end
73
+
74
+ def deconstruct_whileish_node(node)
75
+ case node.type
76
+ when :while, :until, :while_post, :until_post
77
+ [
78
+ node.children[0],
79
+ node.children[1],
80
+ _ = node.location
81
+ ]
82
+ end
83
+ end
84
+
85
+ def deconstruct_whileish_node!(node)
86
+ deconstruct_whileish_node(node) or raise
87
+ end
88
+
89
+ def test_whileish_node(node)
90
+ if (a, b, c = deconstruct_whileish_node(node))
91
+ yield(a, b, c)
92
+ else
93
+ false
94
+ end
95
+ end
96
+
97
+ def deconstruct_case_node(node)
98
+ case node.type
99
+ when :case
100
+ cond, *whens, else_ = node.children
101
+ [
102
+ cond,
103
+ whens,
104
+ else_,
105
+ _ = node.loc
106
+ ]
107
+ end
108
+ end
109
+
110
+ def deconstruct_case_node!(node)
111
+ deconstruct_case_node(node) or raise
112
+ end
113
+
114
+ def test_case_node(node)
115
+ if (a, b, c, d = deconstruct_case_node(node))
116
+ yield a, b, c, d
117
+ else
118
+ false
119
+ end
120
+ end
121
+
122
+ def deconstruct_when_node(node)
123
+ case node.type
124
+ when :when
125
+ *conds, body = node.children
126
+ [
127
+ conds,
128
+ body,
129
+ _ = node.loc
130
+ ]
131
+ end
132
+ end
133
+
134
+ def deconstruct_when_node!(node)
135
+ deconstruct_when_node(node) or raise
136
+ end
137
+
138
+ def test_when_node(node)
139
+ if (a, b, c = deconstruct_when_node(node))
140
+ yield a, b, c
141
+ else
142
+ false
143
+ end
144
+ end
145
+
146
+ def deconstruct_rescue_node(node)
147
+ case node.type
148
+ when :rescue
149
+ body, *resbodies, else_ = node.children
150
+
151
+ [
152
+ body,
153
+ resbodies,
154
+ else_,
155
+ _ = node.loc
156
+ ]
157
+ end
158
+ end
159
+
160
+ def deconstruct_rescue_node!(node)
161
+ deconstruct_rescue_node(node) or raise
162
+ end
163
+
164
+ def test_rescue_node(node)
165
+ if (a, b, c, d = deconstruct_rescue_node(node))
166
+ yield a, b, c, d
167
+ else
168
+ false
169
+ end
170
+ end
171
+
172
+ def deconstruct_resbody_node(node)
173
+ case node.type
174
+ when :resbody
175
+ [
176
+ node.children[0],
177
+ node.children[1],
178
+ node.children[2],
179
+ _ = node.loc
180
+ ]
181
+ end
182
+ end
183
+
184
+ def deconstruct_resbody_node!(node)
185
+ deconstruct_resbody_node(node) or raise
186
+ end
187
+
188
+ def test_resbody_node(node)
189
+ if (a, b, c, d = deconstruct_resbody_node(node))
190
+ yield a, b, c, d
191
+ else
192
+ false
193
+ end
194
+ end
195
+
196
+ def deconstruct_send_node(node)
197
+ case node.type
198
+ when :send, :csend
199
+ receiver, selector, *args = node.children
200
+ [
201
+ receiver,
202
+ selector,
203
+ args,
204
+ _ = node.loc
205
+ ]
206
+ end
207
+ end
208
+
209
+ def deconstruct_send_node!(node)
210
+ deconstruct_send_node(node) or raise
211
+ end
212
+
213
+ def test_send_node(node)
214
+ if (a, b, c, d = deconstruct_send_node(node))
215
+ yield a, b, c, d
216
+ else
217
+ false
218
+ end
219
+ end
48
220
  end
49
221
  end
@@ -40,6 +40,8 @@ module Steep
40
40
  def run
41
41
  tags = Steep.logger.formatter.current_tags.dup
42
42
  thread = Thread.new do
43
+ Thread.current.abort_on_exception = true
44
+
43
45
  Steep.logger.formatter.push_tags(*tags)
44
46
  Steep.logger.tagged "background" do
45
47
  while job = queue.pop
@@ -69,7 +71,6 @@ module Steep
69
71
  end
70
72
  end
71
73
  end
72
- thread.abort_on_exception = true
73
74
 
74
75
  Steep.logger.tagged "frontend" do
75
76
  begin
@@ -16,7 +16,7 @@ module Steep
16
16
  buffered_changes.clear
17
17
  copy
18
18
  end
19
-
19
+
20
20
  if block_given?
21
21
  yield changes
22
22
  else
@@ -41,21 +41,23 @@ module Steep
41
41
 
42
42
  def collect_changes(request)
43
43
  push_buffer do |changes|
44
- path = project.relative_path(Steep::PathHelper.to_pathname(request[:params][:textDocument][:uri]))
45
- version = request[:params][:textDocument][:version]
46
- Steep.logger.info { "Updating source: path=#{path}, version=#{version}..." }
44
+ if path = Steep::PathHelper.to_pathname(request[:params][:textDocument][:uri])
45
+ path = project.relative_path(path)
46
+ version = request[:params][:textDocument][:version]
47
+ Steep.logger.info { "Updating source: path=#{path}, version=#{version}..." }
47
48
 
48
- changes[path] ||= []
49
- request[:params][:contentChanges].each do |change|
50
- changes[path] << Services::ContentChange.new(
51
- range: change[:range]&.yield_self {|range|
52
- [
53
- range[:start].yield_self {|pos| Services::ContentChange::Position.new(line: pos[:line] + 1, column: pos[:character]) },
54
- range[:end].yield_self {|pos| Services::ContentChange::Position.new(line: pos[:line] + 1, column: pos[:character]) }
55
- ]
56
- },
57
- text: change[:text]
58
- )
49
+ changes[path] ||= []
50
+ request[:params][:contentChanges].each do |change|
51
+ changes[path] << Services::ContentChange.new(
52
+ range: change[:range]&.yield_self {|range|
53
+ [
54
+ range[:start].yield_self {|pos| Services::ContentChange::Position.new(line: pos[:line] + 1, column: pos[:character]) },
55
+ range[:end].yield_self {|pos| Services::ContentChange::Position.new(line: pos[:line] + 1, column: pos[:character]) }
56
+ ]
57
+ },
58
+ text: change[:text]
59
+ )
60
+ end
59
61
  end
60
62
  end
61
63
  end
@@ -361,6 +361,26 @@ module Steep
361
361
  new_text: item.identifier.to_s
362
362
  )
363
363
  )
364
+ when Services::CompletionProvider::TypeNameItem
365
+ kind =
366
+ case
367
+ when item.absolute_type_name.class?
368
+ LSP::Constant::CompletionItemKind::CLASS
369
+ when item.absolute_type_name.interface?
370
+ LSP::Constant::CompletionItemKind::INTERFACE
371
+ when item.absolute_type_name.alias?
372
+ LSP::Constant::CompletionItemKind::FIELD
373
+ end
374
+ LSP::Interface::CompletionItem.new(
375
+ label: item.relative_type_name.to_s,
376
+ kind: kind,
377
+ label_details: nil,
378
+ documentation: LSPFormatter.markup_content { LSPFormatter.format_completion_docs(item) },
379
+ text_edit: LSP::Interface::TextEdit.new(
380
+ range: range,
381
+ new_text: item.relative_type_name.to_s
382
+ )
383
+ )
364
384
  end
365
385
  end
366
386
 
@@ -231,6 +231,25 @@ module Steep
231
231
  format_method_item_doc(item.method_types, method_names, comments)
232
232
  when Services::CompletionProvider::GeneratedMethodNameItem
233
233
  format_method_item_doc(item.method_types, [], {}, "🤖 Generated method for receiver type")
234
+ when Services::CompletionProvider::TypeNameItem
235
+ io = StringIO.new
236
+
237
+ io.puts <<~MD
238
+ ```rbs
239
+ #{declaration_summary(item.decl)}
240
+ ```
241
+ MD
242
+
243
+ unless item.comments.empty?
244
+ io.puts "----"
245
+ io.puts format_comments(
246
+ item.comments.map {|comment|
247
+ [item.absolute_type_name.relative!.to_s, comment] #: [String, RBS::AST::Comment?]
248
+ }
249
+ )
250
+ end
251
+
252
+ io.string
234
253
  end
235
254
  end
236
255
 
@@ -296,7 +315,7 @@ module Steep
296
315
 
297
316
  io = StringIO.new
298
317
  if header
299
- io.puts "### 📚 #{header}"
318
+ io.puts "### 📚 #{header.gsub("_", "\\_")}"
300
319
  io.puts
301
320
  end
302
321
  io.puts comment.string.rstrip.gsub(/^[ \t]*<!--(?~-->)-->\n/, "").gsub(/\A([ \t]*\n)+/, "")