steep 0.39.0 → 0.40.0

Sign up to get free protection for your applications and to get access to all the features.
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