steep 0.39.0 → 0.40.0

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.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/CHANGELOG.md +6 -0
  4. data/Rakefile +5 -2
  5. data/bin/output_rebaseline.rb +49 -0
  6. data/bin/output_test.rb +93 -0
  7. data/lib/steep.rb +8 -3
  8. data/lib/steep/cli.rb +1 -1
  9. data/lib/steep/diagnostic/helper.rb +17 -0
  10. data/lib/steep/diagnostic/lsp_formatter.rb +16 -0
  11. data/lib/steep/diagnostic/ruby.rb +623 -0
  12. data/lib/steep/diagnostic/signature.rb +224 -0
  13. data/lib/steep/drivers/annotations.rb +13 -6
  14. data/lib/steep/drivers/check.rb +83 -60
  15. data/lib/steep/drivers/diagnostic_printer.rb +94 -0
  16. data/lib/steep/drivers/stats.rb +125 -29
  17. data/lib/steep/drivers/trace_printer.rb +5 -1
  18. data/lib/steep/drivers/validate.rb +13 -6
  19. data/lib/steep/drivers/watch.rb +26 -9
  20. data/lib/steep/drivers/worker.rb +5 -0
  21. data/lib/steep/project/options.rb +4 -4
  22. data/lib/steep/project/signature_file.rb +8 -2
  23. data/lib/steep/project/stats_calculator.rb +80 -0
  24. data/lib/steep/project/target.rb +64 -53
  25. data/lib/steep/range_extension.rb +29 -0
  26. data/lib/steep/server/base_worker.rb +42 -4
  27. data/lib/steep/server/code_worker.rb +37 -24
  28. data/lib/steep/server/interaction_worker.rb +1 -0
  29. data/lib/steep/server/master.rb +268 -82
  30. data/lib/steep/server/signature_worker.rb +7 -59
  31. data/lib/steep/server/worker_process.rb +9 -9
  32. data/lib/steep/signature/validator.rb +33 -9
  33. data/lib/steep/type_construction.rb +276 -194
  34. data/lib/steep/version.rb +1 -1
  35. data/smoke/alias/a.rb +0 -3
  36. data/smoke/alias/b.rb +0 -1
  37. data/smoke/alias/c.rb +0 -2
  38. data/smoke/alias/test.yaml +73 -0
  39. data/smoke/and/a.rb +0 -3
  40. data/smoke/and/test.yaml +24 -0
  41. data/smoke/array/a.rb +0 -3
  42. data/smoke/array/b.rb +0 -2
  43. data/smoke/array/c.rb +0 -1
  44. data/smoke/array/test.yaml +80 -0
  45. data/smoke/block/a.rb +0 -2
  46. data/smoke/block/b.rb +0 -2
  47. data/smoke/block/d.rb +0 -4
  48. data/smoke/block/test.yaml +96 -0
  49. data/smoke/broken/Steepfile +5 -0
  50. data/smoke/broken/broken.rb +0 -0
  51. data/smoke/broken/broken.rbs +0 -0
  52. data/smoke/broken/test.yaml +6 -0
  53. data/smoke/case/a.rb +0 -3
  54. data/smoke/case/test.yaml +36 -0
  55. data/smoke/class/a.rb +0 -3
  56. data/smoke/class/c.rb +0 -1
  57. data/smoke/class/f.rb +0 -1
  58. data/smoke/class/g.rb +0 -2
  59. data/smoke/class/i.rb +0 -2
  60. data/smoke/class/test.yaml +89 -0
  61. data/smoke/const/a.rb +0 -3
  62. data/smoke/const/b.rb +7 -0
  63. data/smoke/const/b.rbs +5 -0
  64. data/smoke/const/test.yaml +96 -0
  65. data/smoke/diagnostics-rbs-duplicated/Steepfile +5 -0
  66. data/smoke/diagnostics-rbs-duplicated/a.rbs +5 -0
  67. data/smoke/diagnostics-rbs-duplicated/test.yaml +10 -0
  68. data/smoke/diagnostics-rbs/Steepfile +5 -0
  69. data/smoke/diagnostics-rbs/duplicated-method-definition.rbs +20 -0
  70. data/smoke/diagnostics-rbs/generic-parameter-mismatch.rbs +7 -0
  71. data/smoke/diagnostics-rbs/invalid-method-overload.rbs +3 -0
  72. data/smoke/diagnostics-rbs/invalid-type-application.rbs +7 -0
  73. data/smoke/diagnostics-rbs/invalid_variance_annotation.rbs +3 -0
  74. data/smoke/diagnostics-rbs/recursive-alias.rbs +5 -0
  75. data/smoke/diagnostics-rbs/recursive-class.rbs +8 -0
  76. data/smoke/diagnostics-rbs/superclass-mismatch.rbs +7 -0
  77. data/smoke/diagnostics-rbs/test.yaml +142 -0
  78. data/smoke/diagnostics-rbs/unknown-method-alias.rbs +3 -0
  79. data/smoke/diagnostics-rbs/unknown-type-name.rbs +13 -0
  80. data/smoke/diagnostics/Steepfile +5 -0
  81. data/smoke/diagnostics/a.rbs +26 -0
  82. data/smoke/diagnostics/argument_type_mismatch.rb +1 -0
  83. data/smoke/diagnostics/block_body_type_mismatch.rb +1 -0
  84. data/smoke/diagnostics/block_type_mismatch.rb +3 -0
  85. data/smoke/diagnostics/break_type_mismatch.rb +1 -0
  86. data/smoke/diagnostics/else_on_exhaustive_case.rb +12 -0
  87. data/smoke/diagnostics/incompatible_annotation.rb +6 -0
  88. data/smoke/diagnostics/incompatible_argument.rb +1 -0
  89. data/smoke/diagnostics/incompatible_assignment.rb +8 -0
  90. data/smoke/diagnostics/method_arity_mismatch.rb +11 -0
  91. data/smoke/diagnostics/method_body_type_mismatch.rb +6 -0
  92. data/smoke/diagnostics/method_definition_missing.rb +2 -0
  93. data/smoke/diagnostics/method_return_type_annotation_mismatch.rb +7 -0
  94. data/smoke/diagnostics/missing_keyword.rb +1 -0
  95. data/smoke/diagnostics/no_method.rb +1 -0
  96. data/smoke/diagnostics/required_block_missing.rb +1 -0
  97. data/smoke/diagnostics/return_type_mismatch.rb +6 -0
  98. data/smoke/diagnostics/test.yaml +333 -0
  99. data/smoke/diagnostics/unexpected_block_given.rb +1 -0
  100. data/smoke/diagnostics/unexpected_dynamic_method.rb +3 -0
  101. data/smoke/diagnostics/unexpected_jump.rb +4 -0
  102. data/smoke/diagnostics/unexpected_jump_value.rb +3 -0
  103. data/smoke/diagnostics/unexpected_keyword.rb +1 -0
  104. data/smoke/diagnostics/unexpected_splat.rb +1 -0
  105. data/smoke/diagnostics/unexpected_yield.rb +6 -0
  106. data/smoke/diagnostics/unknown_constant_assigned.rb +7 -0
  107. data/smoke/diagnostics/unresolved_overloading.rb +1 -0
  108. data/smoke/diagnostics/unsatisfiable_constraint.rb +7 -0
  109. data/smoke/diagnostics/unsupported_syntax.rb +2 -0
  110. data/smoke/dstr/a.rb +0 -1
  111. data/smoke/dstr/test.yaml +10 -0
  112. data/smoke/ensure/a.rb +0 -4
  113. data/smoke/ensure/test.yaml +47 -0
  114. data/smoke/enumerator/a.rb +0 -6
  115. data/smoke/enumerator/b.rb +0 -3
  116. data/smoke/enumerator/test.yaml +100 -0
  117. data/smoke/extension/a.rb +0 -1
  118. data/smoke/extension/b.rb +0 -2
  119. data/smoke/extension/c.rb +0 -1
  120. data/smoke/extension/test.yaml +50 -0
  121. data/smoke/hash/b.rb +0 -1
  122. data/smoke/hash/c.rb +0 -3
  123. data/smoke/hash/d.rb +0 -1
  124. data/smoke/hash/e.rb +0 -1
  125. data/smoke/hash/test.yaml +62 -0
  126. data/smoke/hello/hello.rb +0 -2
  127. data/smoke/hello/test.yaml +18 -0
  128. data/smoke/if/a.rb +0 -2
  129. data/smoke/if/test.yaml +27 -0
  130. data/smoke/implements/a.rb +0 -2
  131. data/smoke/implements/test.yaml +16 -0
  132. data/smoke/initialize/test.yaml +4 -0
  133. data/smoke/integer/a.rb +0 -7
  134. data/smoke/integer/test.yaml +66 -0
  135. data/smoke/interface/a.rb +0 -2
  136. data/smoke/interface/test.yaml +16 -0
  137. data/smoke/kwbegin/a.rb +0 -1
  138. data/smoke/kwbegin/test.yaml +14 -0
  139. data/smoke/lambda/a.rb +1 -4
  140. data/smoke/lambda/test.yaml +28 -0
  141. data/smoke/literal/a.rb +0 -5
  142. data/smoke/literal/b.rb +0 -2
  143. data/smoke/literal/test.yaml +79 -0
  144. data/smoke/map/test.yaml +4 -0
  145. data/smoke/method/a.rb +0 -5
  146. data/smoke/method/b.rb +0 -1
  147. data/smoke/method/test.yaml +71 -0
  148. data/smoke/module/a.rb +0 -2
  149. data/smoke/module/b.rb +0 -2
  150. data/smoke/module/c.rb +0 -1
  151. data/smoke/module/d.rb +0 -1
  152. data/smoke/module/f.rb +0 -2
  153. data/smoke/module/test.yaml +51 -0
  154. data/smoke/regexp/a.rb +0 -38
  155. data/smoke/regexp/b.rb +0 -26
  156. data/smoke/regexp/test.yaml +372 -0
  157. data/smoke/regression/set_divide.rb +0 -4
  158. data/smoke/regression/test.yaml +38 -0
  159. data/smoke/rescue/a.rb +0 -5
  160. data/smoke/rescue/test.yaml +60 -0
  161. data/smoke/self/a.rb +0 -2
  162. data/smoke/self/test.yaml +16 -0
  163. data/smoke/skip/skip.rb +0 -2
  164. data/smoke/skip/test.yaml +16 -0
  165. data/smoke/stdout/test.yaml +4 -0
  166. data/smoke/super/a.rb +0 -4
  167. data/smoke/super/test.yaml +52 -0
  168. data/smoke/toplevel/a.rb +0 -1
  169. data/smoke/toplevel/test.yaml +12 -0
  170. data/smoke/tsort/a.rb +0 -3
  171. data/smoke/tsort/test.yaml +32 -0
  172. data/smoke/type_case/a.rb +0 -4
  173. data/smoke/type_case/test.yaml +33 -0
  174. data/smoke/yield/a.rb +0 -3
  175. data/smoke/yield/b.rb +6 -0
  176. data/smoke/yield/test.yaml +49 -0
  177. data/steep.gemspec +3 -3
  178. metadata +108 -17
  179. data/bin/smoke_runner.rb +0 -139
  180. data/lib/steep/drivers/signature_error_printer.rb +0 -25
  181. data/lib/steep/errors.rb +0 -594
  182. data/lib/steep/signature/errors.rb +0 -128
  183. data/lib/steep/type_assignability.rb +0 -367
@@ -0,0 +1,224 @@
1
+ module Steep
2
+ module Diagnostic
3
+ module Signature
4
+ class Base
5
+ include Helper
6
+
7
+ attr_reader :location
8
+
9
+ def initialize(location:)
10
+ @location = location
11
+ end
12
+
13
+ def header_line
14
+ StringIO.new.tap do |io|
15
+ puts io
16
+ end.string
17
+ end
18
+
19
+ def detail_lines
20
+ nil
21
+ end
22
+
23
+ def diagnostic_code
24
+ "Ruby::#{error_name}"
25
+ end
26
+
27
+ def path
28
+ location.buffer.name
29
+ end
30
+ end
31
+
32
+ class SyntaxError < Base
33
+ attr_reader :exception
34
+
35
+ def initialize(exception, location:)
36
+ super(location: location)
37
+ @exception = exception
38
+ end
39
+
40
+ def header_line
41
+ "Syntax error: #{exception.message}"
42
+ end
43
+ end
44
+
45
+ class DuplicatedDeclaration < Base
46
+ attr_reader :type_name
47
+
48
+ def initialize(type_name:, location:)
49
+ super(location: location)
50
+ @type_name = type_name
51
+ end
52
+
53
+ def header_line
54
+ "Declaration of `#{type_name}` is duplicated"
55
+ end
56
+ end
57
+
58
+ class UnknownTypeName < Base
59
+ attr_reader :name
60
+
61
+ def initialize(name:, location:)
62
+ super(location: location)
63
+ @name = name
64
+ end
65
+
66
+ def header_line
67
+ "Cannot find type `#{name}`"
68
+ end
69
+ end
70
+
71
+ class InvalidTypeApplication < Base
72
+ attr_reader :name
73
+ attr_reader :args
74
+ attr_reader :params
75
+
76
+ def initialize(name:, args:, params:, location:)
77
+ super(location: location)
78
+ @name = name
79
+ @args = args
80
+ @params = params
81
+ end
82
+
83
+ def header_line
84
+ case
85
+ when params.empty?
86
+ "Type `#{name}` is not generic but used as a generic type with #{args.size} arguments"
87
+ when args.empty?
88
+ "Type `#{name}` is generic but used as a non generic type"
89
+ else
90
+ "Type `#{name}` expects #{params.size} arguments, but #{args.size} arguments are given"
91
+ end
92
+ end
93
+ end
94
+
95
+ class InvalidMethodOverload < Base
96
+ attr_reader :class_name
97
+ attr_reader :method_name
98
+
99
+ def initialize(class_name:, method_name:, location:)
100
+ super(location: location)
101
+ @class_name = class_name
102
+ @method_name = method_name
103
+ end
104
+
105
+ def header_line
106
+ "Cannot find a non-overloading definition of `#{method_name}` in `#{class_name}`"
107
+ end
108
+ end
109
+
110
+ class UnknownMethodAlias < Base
111
+ attr_reader :class_name
112
+ attr_reader :method_name
113
+
114
+ def initialize(class_name:, method_name:, location:)
115
+ super(location: location)
116
+ @class_name = class_name
117
+ @method_name = method_name
118
+ end
119
+
120
+ def header_line
121
+ "Cannot find the original method `#{method_name}` in `#{class_name}`"
122
+ end
123
+ end
124
+
125
+ class DuplicatedMethodDefinition < Base
126
+ attr_reader :class_name
127
+ attr_reader :method_name
128
+
129
+ def initialize(class_name:, method_name:, location:)
130
+ super(location: location)
131
+ @class_name = class_name
132
+ @method_name = method_name
133
+ end
134
+
135
+ def header_line
136
+ "Non-overloading method definition of `#{method_name}` in `#{class_name}` cannot be duplicated"
137
+ end
138
+ end
139
+
140
+ class RecursiveAlias < Base
141
+ attr_reader :class_name
142
+ attr_reader :names
143
+ attr_reader :location
144
+
145
+ def initialize(class_name:, names:, location:)
146
+ super(location: location)
147
+ @class_name = class_name
148
+ @names = names
149
+ end
150
+
151
+ def header_line
152
+ "Circular method alias is detected in `#{class_name}`: #{names.join(" -> ")}"
153
+ end
154
+ end
155
+
156
+ class RecursiveAncestor < Base
157
+ attr_reader :ancestors
158
+
159
+ def initialize(ancestors:, location:)
160
+ super(location: location)
161
+ @ancestors = ancestors
162
+ end
163
+
164
+ def header_line
165
+ names = ancestors.map do |ancestor|
166
+ case ancestor
167
+ when RBS::Definition::Ancestor::Singleton
168
+ "singleton(#{ancestor.name})"
169
+ when RBS::Definition::Ancestor::Instance
170
+ if ancestor.args.empty?
171
+ ancestor.name.to_s
172
+ else
173
+ "#{ancestor.name}[#{ancestor.args.join(", ")}]"
174
+ end
175
+ end
176
+ end
177
+
178
+ "Circular inheritance/mix-in is detected: #{names.join(" <: ")}"
179
+ end
180
+ end
181
+
182
+ class SuperclassMismatch < Base
183
+ attr_reader :name
184
+
185
+ def initialize(name:, location:)
186
+ super(location: location)
187
+ @name = name
188
+ end
189
+
190
+ def header_line
191
+ "Different superclasses are specified for `#{name}`"
192
+ end
193
+ end
194
+
195
+ class GenericParameterMismatch < Base
196
+ attr_reader :name
197
+
198
+ def initialize(name:, location:)
199
+ super(location: location)
200
+ @name = name
201
+ end
202
+
203
+ def header_line
204
+ "Different generic parameters are specified across definitions of `#{name}`"
205
+ end
206
+ end
207
+
208
+ class InvalidVarianceAnnotation < Base
209
+ attr_reader :name
210
+ attr_reader :param
211
+
212
+ def initialize(name:, param:, location:)
213
+ super(location: location)
214
+ @name = name
215
+ @param = param
216
+ end
217
+
218
+ def header_line
219
+ "The variance of type parameter `#{param.name}` is #{param.variance}, but used in incompatible position here"
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -39,12 +39,19 @@ module Steep
39
39
  end
40
40
  end
41
41
  end
42
- when Project::Target::SignatureSyntaxErrorStatus
43
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
44
- printer.print_syntax_errors(status.errors)
45
- when Project::Target::SignatureValidationErrorStatus
46
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
47
- printer.print_semantic_errors(status.errors)
42
+ when Project::Target::SignatureErrorStatus
43
+ formatter = Diagnostic::LSPFormatter.new
44
+ diagnostics = status.errors.group_by {|e| e.location.buffer }.transform_values do |errors|
45
+ errors.map {|error| formatter.format(error) }
46
+ end
47
+
48
+ diagnostics.each do |buffer, ds|
49
+ printer = DiagnosticPrinter.new(stdout: stdout, buffer: buffer)
50
+ ds.each do |d|
51
+ printer.print(d)
52
+ stdout.puts
53
+ end
54
+ end
48
55
  end
49
56
  end
50
57
  end
@@ -1,20 +1,18 @@
1
1
  module Steep
2
2
  module Drivers
3
3
  class Check
4
+ LSP = LanguageServer::Protocol
5
+
4
6
  attr_reader :stdout
5
7
  attr_reader :stderr
6
8
  attr_reader :command_line_patterns
7
9
 
8
- attr_accessor :dump_all_types
9
-
10
10
  include Utils::DriverHelper
11
11
 
12
12
  def initialize(stdout:, stderr:)
13
13
  @stdout = stdout
14
14
  @stderr = stderr
15
15
  @command_line_patterns = []
16
-
17
- self.dump_all_types = false
18
16
  end
19
17
 
20
18
  def run
@@ -24,73 +22,98 @@ module Steep
24
22
  loader.load_sources(command_line_patterns)
25
23
  loader.load_signatures()
26
24
 
27
- type_check(project)
28
-
29
- if self.dump_all_types
30
- project.targets.each do |target|
31
- case (status = target.status)
32
- when Project::Target::TypeCheckStatus
33
- target.source_files.each_value do |file|
34
- case (file_status = file.status)
35
- when Project::SourceFile::TypeCheckStatus
36
- output_types(file_status.typing)
37
- end
38
- end
39
- end
40
- end
25
+ stdout.puts Rainbow("# Type checking files:").bold
26
+ stdout.puts
27
+
28
+ client_read, server_write = IO.pipe
29
+ server_read, client_write = IO.pipe
30
+
31
+ client_reader = LanguageServer::Protocol::Transport::Io::Reader.new(client_read)
32
+ client_writer = LanguageServer::Protocol::Transport::Io::Writer.new(client_write)
33
+
34
+ server_reader = LanguageServer::Protocol::Transport::Io::Reader.new(server_read)
35
+ server_writer = LanguageServer::Protocol::Transport::Io::Writer.new(server_write)
36
+
37
+ interaction_worker = Server::WorkerProcess.spawn_worker(:interaction, name: "interaction", steepfile: project.steepfile_path, delay_shutdown: true)
38
+ signature_worker = Server::WorkerProcess.spawn_worker(:signature, name: "signature", steepfile: project.steepfile_path, delay_shutdown: true)
39
+ code_workers = Server::WorkerProcess.spawn_code_workers(steepfile: project.steepfile_path, delay_shutdown: true)
40
+
41
+ master = Server::Master.new(
42
+ project: project,
43
+ reader: server_reader,
44
+ writer: server_writer,
45
+ interaction_worker: interaction_worker,
46
+ signature_worker: signature_worker,
47
+ code_workers: code_workers
48
+ )
49
+
50
+ main_thread = Thread.start do
51
+ master.start()
41
52
  end
53
+ main_thread.abort_on_exception = true
42
54
 
43
- project.targets.each do |target|
44
- Steep.logger.tagged "target=#{target.name}" do
45
- case (status = target.status)
46
- when Project::Target::SignatureSyntaxErrorStatus
47
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
48
- printer.print_syntax_errors(status.errors)
49
- when Project::Target::SignatureValidationErrorStatus
50
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
51
- printer.print_semantic_errors(status.errors)
52
- when Project::Target::TypeCheckStatus
53
- status.type_check_sources.each do |source_file|
54
- case source_file.status
55
- when Project::SourceFile::TypeCheckStatus
56
- source_file.errors.select {|error| target.options.error_to_report?(error) }.each do |error|
57
- error.print_to stdout
58
- end
59
- when Project::SourceFile::TypeCheckErrorStatus
60
- Steep.log_error source_file.status.error
61
- end
62
- end
63
- when Project::Target::SignatureOtherErrorStatus
64
- Steep.log_error status.error
55
+ client_writer.write({ method: :initialize, id: 0 })
56
+
57
+ shutdown_id = -1
58
+ client_writer.write({ method: :shutdown, id: shutdown_id })
59
+
60
+ responses = []
61
+ error_messages = []
62
+ client_reader.read do |response|
63
+ case
64
+ when response[:method] == "textDocument/publishDiagnostics"
65
+ ds = response[:params][:diagnostics]
66
+ if ds.empty?
67
+ stdout.print "."
65
68
  else
66
- Steep.logger.error { "Unexpected status: #{status.class}" }
69
+ stdout.print "F"
70
+ end
71
+ responses << response[:params]
72
+ stdout.flush
73
+ when response[:method] == "window/showMessage"
74
+ # Assuming ERROR message means unrecoverable error.
75
+ message = response[:params]
76
+ if message[:type] == LSP::Constant::MessageType::ERROR
77
+ error_messages << message[:message]
67
78
  end
79
+ when response[:id] == shutdown_id
80
+ break
68
81
  end
69
82
  end
70
83
 
71
- if project.targets.all? {|target| target.status.is_a?(Project::Target::TypeCheckStatus) && target.no_error? && target.errors.empty? }
72
- Steep.logger.info "No type error found"
73
- return 0
74
- end
84
+ client_writer.write({ method: :exit })
85
+ client_writer.io.close()
75
86
 
76
- 1
77
- end
87
+ main_thread.join()
78
88
 
79
- def output_types(typing)
80
- lines = []
89
+ stdout.puts
90
+ stdout.puts
81
91
 
82
- typing.each_typing do |node, _|
83
- begin
84
- type = typing.type_of(node: node)
85
- lines << [node.loc.expression.source_buffer.name, [node.loc.last_line,node.loc.last_column], [node.loc.first_line, node.loc.column], node, type]
86
- rescue
87
- lines << [node.loc.expression.source_buffer.name, [node.loc.last_line,node.loc.last_column], [node.loc.first_line, node.loc.column], node, nil]
88
- end
89
- end
92
+ case
93
+ when responses.all? {|res| res[:diagnostics].empty? } && error_messages.empty?
94
+ emoji = %w(🫖 🫖 🫖 🫖 🫖 🫖 🫖 🫖 🍵 🧋 🧉).sample
95
+ stdout.puts Rainbow("No type error detected. #{emoji}").green.bold
96
+ 0
97
+ when !error_messages.empty?
98
+ stdout.puts Rainbow("Unexpected error reported. 🚨").red.bold
99
+ 1
100
+ else
101
+ errors = responses.reject {|res| res[:diagnostics].empty? }
102
+ total = errors.sum {|res| res[:diagnostics].size }
103
+ stdout.puts Rainbow("Detected #{total} problems from #{errors.size} files").red.bold
104
+ stdout.puts
105
+
106
+ errors.each do |resp|
107
+ path = project.relative_path(Pathname(URI.parse(resp[:uri]).path))
108
+ buffer = RBS::Buffer.new(name: path, content: path.read)
109
+ printer = DiagnosticPrinter.new(buffer: buffer, stdout: stdout)
90
110
 
91
- lines.sort {|x,y| y <=> x }.reverse_each do |line|
92
- source = line[3].loc.expression.source
93
- stdout.puts "#{line[0]}:(#{line[2].join(",")}):(#{line[1].join(",")}):\t#{line[3].type}:\t#{line[4]}\t(#{source.split(/\n/).first})"
111
+ resp[:diagnostics].each do |diag|
112
+ printer.print(diag)
113
+ stdout.puts
114
+ end
115
+ end
116
+ 1
94
117
  end
95
118
  end
96
119
  end
@@ -0,0 +1,94 @@
1
+ module Steep
2
+ module Drivers
3
+ class DiagnosticPrinter
4
+ LSP = LanguageServer::Protocol
5
+
6
+ attr_reader :stdout
7
+ attr_reader :buffer
8
+
9
+ def initialize(stdout:, buffer:)
10
+ @stdout = stdout
11
+ @buffer = buffer
12
+ end
13
+
14
+ def path
15
+ buffer.name
16
+ end
17
+
18
+ def color_severity(string, severity:)
19
+ s = Rainbow(string)
20
+
21
+ case severity
22
+ when LSP::Constant::DiagnosticSeverity::ERROR
23
+ s.red
24
+ when LSP::Constant::DiagnosticSeverity::WARNING
25
+ s.yellow
26
+ when LSP::Constant::DiagnosticSeverity::INFORMATION
27
+ s.blue
28
+ else
29
+ s
30
+ end
31
+ end
32
+
33
+ def severity_message(severity)
34
+ string = case severity
35
+ when LSP::Constant::DiagnosticSeverity::ERROR
36
+ "error"
37
+ when LSP::Constant::DiagnosticSeverity::WARNING
38
+ "warning"
39
+ when LSP::Constant::DiagnosticSeverity::INFORMATION
40
+ "information"
41
+ when LSP::Constant::DiagnosticSeverity::HINT
42
+ "hint"
43
+ end
44
+
45
+ color_severity(string, severity: severity)
46
+ end
47
+
48
+ def location(diagnostic)
49
+ start = diagnostic[:range][:start]
50
+ Rainbow("#{path}:#{start[:line]+1}:#{start[:character]}").magenta
51
+ end
52
+
53
+ def print(diagnostic)
54
+ header, *rest = diagnostic[:message].split(/\n/)
55
+
56
+ stdout.puts "#{location(diagnostic)}: [#{severity_message(diagnostic[:severity])}] #{Rainbow(header).underline}"
57
+
58
+ unless rest.empty?
59
+ rest.each do |message|
60
+ stdout.puts "│ #{message}"
61
+ end
62
+ end
63
+
64
+ if diagnostic[:code]
65
+ stdout.puts "│" unless rest.empty?
66
+ stdout.puts "│ Diagnostic ID: #{diagnostic[:code]}"
67
+ end
68
+
69
+ stdout.puts "│"
70
+
71
+ print_source_line(diagnostic)
72
+ end
73
+
74
+ def print_source_line(diagnostic)
75
+ start_pos = diagnostic[:range][:start]
76
+ end_pos = diagnostic[:range][:end]
77
+
78
+ line = buffer.lines[start_pos[:line]]
79
+
80
+ leading = line[0...start_pos[:character]]
81
+ if start_pos[:line] == end_pos[:line]
82
+ subject = line[start_pos[:character]...end_pos[:character]]
83
+ trailing = line[end_pos[:character]...].chomp
84
+ else
85
+ subject = line[start_pos[:character]...].chomp
86
+ trailing = ""
87
+ end
88
+
89
+ stdout.puts "└ #{leading}#{color_severity(subject, severity: diagnostic[:severity])}#{trailing}"
90
+ stdout.puts " #{" " * leading.size}#{"~" * subject.size}"
91
+ end
92
+ end
93
+ end
94
+ end