steep 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63ee9143e226c595a96dd859109327d66f97ab2c428601c30f771a12a21a1603
4
- data.tar.gz: ec8d8d3cdcde5c0e37c73fdb70faeb0887b5b36ae1590d57dbab1feea95f86ad
3
+ metadata.gz: 5e0677b34af6d2b65ac33be3c60d126d689e10d3b9fb7eff598b33dabda2dc98
4
+ data.tar.gz: f126e4ed9b28c55bad922282b280b51a350e0cbc77d25192553437fe0f392917
5
5
  SHA512:
6
- metadata.gz: 5297a41597af66524e848e02f2c6f6b33d53d9fc52aac03280ec438845414e306dc3ce6a86b643608d34e89e1cabacfc75262013f7f78970661af88d4679f624
7
- data.tar.gz: 610747408983f5e98ee292b5a3795e7b61c8dd74b6d255e3768f5c3658676f63c7dd8157cf7cbba492c98d36806e9c08482dcf5d58474e7ffaaaf78ea93c1157
6
+ metadata.gz: '0940b5f73af0ae31a8b310825e402caf784e9327982122b5e2b19f29ce69ddd4dd4dadf7f07bf93d150cecae2c4ffcb3faa6b237ece6d3653482a1dce12930fe'
7
+ data.tar.gz: b3fa66639c3ae93d475e6d12b965b526f633c31e244043f22e1c32c5d140e51568602c5087cc85247068010547d0dc4353f78f61c9d7093aefd8933b4b5b2186
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
  /tmp/
10
10
  /lib/steep/parser.output
11
11
  /lib/steep/parser.rb
12
+ /log
data/.travis.yml CHANGED
@@ -2,5 +2,6 @@ sudo: false
2
2
  language: ruby
3
3
  rvm:
4
4
  - 2.5.3
5
+ - 2.6.1
5
6
  before_install: gem install bundler -v 1.13.7
6
7
  script: bundle exec rake test smoke
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.11.0 (2019-05-18)
6
+
7
+ * Skip `alias` nodes type checking (#85)
8
+ * Add experimental LSP support (#79. #83)
9
+ * Fix logging (#81)
10
+
5
11
  ## 0.10.0 (2019-03-05)
6
12
 
7
13
  * Add `watch` subcommand (#77)
data/lib/steep.rb CHANGED
@@ -9,6 +9,7 @@ require "active_support/tagged_logging"
9
9
  require "rainbow"
10
10
  require "listen"
11
11
  require 'pry'
12
+ require 'language_server-protocol'
12
13
 
13
14
  require "steep/ast/namespace"
14
15
  require "steep/names"
@@ -85,6 +86,7 @@ require "steep/drivers/annotations"
85
86
  require "steep/drivers/scaffold"
86
87
  require "steep/drivers/print_interface"
87
88
  require "steep/drivers/watch"
89
+ require "steep/drivers/langserver"
88
90
 
89
91
  if ENV["NO_COLOR"]
90
92
  Rainbow.enabled = false
@@ -92,12 +94,16 @@ end
92
94
 
93
95
  module Steep
94
96
  def self.logger
95
- unless @logger
96
- @logger = ActiveSupport::TaggedLogging.new(Logger.new(STDERR))
97
- @logger.push_tags "Steep #{VERSION}"
98
- @logger.level = Logger::WARN
99
- end
97
+ self.log_output = STDERR unless @logger
100
98
 
101
99
  @logger
102
100
  end
101
+
102
+ def self.log_output=(output)
103
+ prev_level = @logger&.level
104
+
105
+ @logger = ActiveSupport::TaggedLogging.new(Logger.new(output))
106
+ @logger.push_tags "Steep #{VERSION}"
107
+ @logger.level = prev_level || Logger::WARN
108
+ end
103
109
  end
data/lib/steep/cli.rb CHANGED
@@ -132,7 +132,7 @@ module Steep
132
132
  end
133
133
 
134
134
  def self.available_commands
135
- [:check, :validate, :annotations, :scaffold, :interface, :version, :paths, :watch]
135
+ [:check, :validate, :annotations, :scaffold, :interface, :version, :paths, :watch, :langserver]
136
136
  end
137
137
 
138
138
  def process_global_options
@@ -141,7 +141,9 @@ module Steep
141
141
  process_version
142
142
  exit 0
143
143
  end
144
- end.order!
144
+
145
+ handle_logging_options(opts)
146
+ end.order!(argv)
145
147
 
146
148
  true
147
149
  end
@@ -164,6 +166,28 @@ module Steep
164
166
  __send__(:"process_#{command}")
165
167
  end
166
168
 
169
+ def handle_logging_options(opts)
170
+ opts.on("--verbose") do
171
+ Steep.logger.level = Logger::DEBUG
172
+ end
173
+
174
+ opts.on("--log-level=[debug,info,warn,error,fatal]") do |level|
175
+ lv = {
176
+ "debug" => Logger::DEBUG,
177
+ "info" => Logger::INFO,
178
+ "warn" => Logger::WARN,
179
+ "error" => Logger::ERROR,
180
+ "fatal" => Logger::FATAL
181
+ }[level.downcase] or raise "Unknown error level: #{level}"
182
+
183
+ Steep.logger.level = lv
184
+ end
185
+
186
+ opts.on("--log-output=[PATH]") do |file|
187
+ Steep.log_output = file
188
+ end
189
+ end
190
+
167
191
  def handle_dir_options(opts, options)
168
192
  opts.on("-I [PATH]") {|path| options << Pathname(path) }
169
193
  opts.on("-G [GEM]") {|gem| options << gem }
@@ -183,14 +207,13 @@ module Steep
183
207
 
184
208
  def process_check
185
209
  with_signature_options do |signature_options|
186
- verbose = false
187
210
  dump_all_types = false
188
211
  fallback_any_is_error = false
189
212
  strict = false
190
213
 
191
214
  OptionParser.new do |opts|
215
+ handle_logging_options opts
192
216
  handle_dir_options opts, signature_options
193
- opts.on("--verbose") { verbose = true }
194
217
  opts.on("--dump-all-types") { dump_all_types = true }
195
218
  opts.on("--strict") { strict = true }
196
219
  opts.on("--fallback-any-is-error") { fallback_any_is_error = true }
@@ -202,7 +225,6 @@ module Steep
202
225
  end
203
226
 
204
227
  Drivers::Check.new(source_paths: source_paths, signature_dirs: signature_options.paths, stdout: stdout, stderr: stderr).tap do |check|
205
- check.verbose = verbose
206
228
  check.dump_all_types = dump_all_types
207
229
  check.fallback_any_is_error = fallback_any_is_error || strict
208
230
  check.allow_missing_definitions = false if strict
@@ -212,25 +234,29 @@ module Steep
212
234
 
213
235
  def process_validate
214
236
  with_signature_options do |signature_options|
215
- verbose = false
216
-
217
237
  OptionParser.new do |opts|
238
+ handle_logging_options opts
218
239
  handle_dir_options opts, signature_options
219
- opts.on("--verbose") { verbose = true }
220
240
  end.parse!(argv)
221
241
 
222
- Drivers::Validate.new(signature_dirs: signature_options.paths, stdout: stdout, stderr: stderr).tap do |validate|
223
- validate.verbose = verbose
224
- end.run
242
+ Drivers::Validate.new(signature_dirs: signature_options.paths, stdout: stdout, stderr: stderr).run
225
243
  end
226
244
  end
227
245
 
228
246
  def process_annotations
247
+ OptionParser.new do |opts|
248
+ handle_logging_options opts
249
+ end.parse!(argv)
250
+
229
251
  source_paths = argv.map {|file| Pathname(file) }
230
252
  Drivers::Annotations.new(source_paths: source_paths, stdout: stdout, stderr: stderr).run
231
253
  end
232
254
 
233
255
  def process_scaffold
256
+ OptionParser.new do |opts|
257
+ handle_logging_options opts
258
+ end.parse!(argv)
259
+
234
260
  source_paths = argv.map {|file| Pathname(file) }
235
261
  Drivers::Scaffold.new(source_paths: source_paths, stdout: stdout, stderr: stderr).run
236
262
  end
@@ -238,6 +264,7 @@ module Steep
238
264
  def process_interface
239
265
  with_signature_options do |signature_options|
240
266
  OptionParser.new do |opts|
267
+ handle_logging_options opts
241
268
  handle_dir_options opts, signature_options
242
269
  end.parse!(argv)
243
270
 
@@ -251,6 +278,7 @@ module Steep
251
278
  fallback_any_is_error = false
252
279
 
253
280
  OptionParser.new do |opts|
281
+ handle_logging_options opts
254
282
  handle_dir_options opts, signature_options
255
283
  opts.on("--strict") { strict = true }
256
284
  opts.on("--fallback-any-is-error") { fallback_any_is_error = true }
@@ -270,6 +298,32 @@ module Steep
270
298
  end
271
299
  end
272
300
 
301
+ def process_langserver
302
+ with_signature_options do |signature_options|
303
+ strict = false
304
+ fallback_any_is_error = false
305
+
306
+ OptionParser.new do |opts|
307
+ handle_logging_options opts
308
+ handle_dir_options opts, signature_options
309
+ opts.on("--strict") { strict = true }
310
+ opts.on("--fallback-any-is-error") { fallback_any_is_error = true }
311
+ end.parse!(argv)
312
+
313
+ source_dirs = argv.map { |path| Pathname(path) }
314
+ if source_dirs.empty?
315
+ source_dirs << Pathname(".")
316
+ end
317
+
318
+ Drivers::Langserver.new(source_dirs: source_dirs, signature_dirs: signature_options.paths).tap do |driver|
319
+ driver.options.fallback_any_is_error = fallback_any_is_error || strict
320
+ driver.options.allow_missing_definitions = false if strict
321
+ end.run
322
+
323
+ 0
324
+ end
325
+ end
326
+
273
327
  def process_version
274
328
  stdout.puts Steep::VERSION
275
329
  0
@@ -278,6 +332,7 @@ module Steep
278
332
  def process_paths
279
333
  with_signature_options do |signature_options|
280
334
  OptionParser.new do |opts|
335
+ handle_logging_options opts
281
336
  handle_dir_options opts, signature_options
282
337
  end.parse!(argv)
283
338
 
@@ -6,7 +6,6 @@ module Steep
6
6
  attr_reader :stdout
7
7
  attr_reader :stderr
8
8
 
9
- attr_accessor :verbose
10
9
  attr_accessor :accept_implicit_any
11
10
  attr_accessor :dump_all_types
12
11
  attr_accessor :fallback_any_is_error
@@ -22,7 +21,6 @@ module Steep
22
21
  @stdout = stdout
23
22
  @stderr = stderr
24
23
 
25
- self.verbose = false
26
24
  self.accept_implicit_any = false
27
25
  self.dump_all_types = false
28
26
  self.fallback_any_is_error = false
@@ -37,8 +35,6 @@ module Steep
37
35
  end
38
36
 
39
37
  def run
40
- Steep.logger.level = Logger::DEBUG if verbose
41
-
42
38
  project = Project.new(Project::SyntaxErrorRaisingListener.new)
43
39
 
44
40
  source_paths.each do |path|
@@ -0,0 +1,166 @@
1
+ module Steep
2
+ module Drivers
3
+ class Langserver
4
+ attr_reader :source_dirs
5
+ attr_reader :signature_dirs
6
+ attr_reader :options
7
+ attr_reader :subscribers
8
+
9
+ include Utils::EachSignature
10
+
11
+ def initialize(source_dirs:, signature_dirs:)
12
+ @source_dirs = source_dirs
13
+ @signature_dirs = signature_dirs
14
+ @options = Project::Options.new
15
+ @subscribers = {}
16
+
17
+ subscribe :initialize do |request:, notifier:|
18
+ LanguageServer::Protocol::Interface::InitializeResult.new(
19
+ capabilities: LanguageServer::Protocol::Interface::ServerCapabilities.new(
20
+ text_document_sync: LanguageServer::Protocol::Interface::TextDocumentSyncOptions.new(
21
+ open_close: true,
22
+ change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::FULL,
23
+ ),
24
+ hover_provider: true
25
+ ),
26
+ )
27
+ end
28
+
29
+ subscribe :shutdown do |request:, notifier:|
30
+ Steep.logger.warn "Shutting down the server..."
31
+ exit
32
+ end
33
+
34
+ subscribe :"textDocument/didOpen" do |request:, notifier:|
35
+ uri = URI.parse(request[:params][:textDocument][:uri])
36
+ text = request[:params][:textDocument][:text]
37
+ synchronize_project(uri: uri, text: text, notifier: notifier)
38
+ end
39
+
40
+ subscribe :"textDocument/didChange" do |request:, notifier:|
41
+ uri = URI.parse(request[:params][:textDocument][:uri])
42
+ text = request[:params][:contentChanges][0][:text]
43
+ synchronize_project(uri: uri, text: text, notifier: notifier)
44
+ end
45
+
46
+ subscribe :"textDocument/hover" do |request:, notifier:|
47
+ Steep.logger.warn request.inspect
48
+ uri = URI.parse(request[:params][:textDocument][:uri])
49
+ line = request[:params][:position][:line]
50
+ column = request[:params][:position][:character]
51
+ respond_to_hover(uri: uri, line: line, column: column, notifier: notifier, id: request[:id])
52
+ end
53
+ end
54
+
55
+ def respond_to_hover(uri:, line:, column:, notifier:, id:)
56
+ path = Pathname(uri.path).relative_path_from(Pathname.pwd)
57
+
58
+ if path.extname == ".rb"
59
+ # line in LSP is zero-origin
60
+ project.type_of(path: path, line: line + 1, column: column) do |type, node|
61
+ Steep.logger.warn "type = #{type.to_s}"
62
+
63
+ start_position = { line: node.location.line - 1, character: node.location.column }
64
+ end_position = { line: node.location.last_line - 1, character: node.location.last_column }
65
+ range = { start: start_position, end: end_position }
66
+
67
+ Steep.logger.warn "node = #{node.type}"
68
+ Steep.logger.warn "range = #{range.inspect}"
69
+
70
+ LanguageServer::Protocol::Interface::Hover.new(
71
+ contents: { kind: "markdown", value: "`#{type}`" },
72
+ range: range
73
+ )
74
+ end
75
+ end
76
+ end
77
+
78
+ def subscribe(method, &callback)
79
+ @subscribers[method] = callback
80
+ end
81
+
82
+ def project
83
+ @project ||= Project.new.tap do |project|
84
+ source_dirs.each do |path|
85
+ each_file_in_path(".rb", path) do |file_path|
86
+ file = Project::SourceFile.new(path: file_path, options: options)
87
+ file.content = file_path.read
88
+ project.source_files[file_path] = file
89
+ end
90
+ end
91
+
92
+ signature_dirs.each do |path|
93
+ each_file_in_path(".rbi", path) do |file_path|
94
+ file = Project::SignatureFile.new(path: file_path)
95
+ file.content = file_path.read
96
+ project.signature_files[file_path] = file
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def run
103
+ writer = LanguageServer::Protocol::Transport::Stdio::Writer.new
104
+ reader = LanguageServer::Protocol::Transport::Stdio::Reader.new
105
+ notifier = Proc.new { |method:, params: {}| writer.write(method: method, params: params) }
106
+
107
+ reader.read do |request|
108
+ id = request[:id]
109
+ method = request[:method].to_sym
110
+ Steep.logger.warn "Received event: #{method}"
111
+ subscriber = subscribers[method]
112
+ if subscriber
113
+ result = subscriber.call(request: request, notifier: notifier)
114
+ if id && result
115
+ writer.write(id: id, result: result)
116
+ end
117
+ else
118
+ Steep.logger.warn "Ignored event: #{method}"
119
+ end
120
+ end
121
+ end
122
+
123
+ def synchronize_project(uri:, text:, notifier:)
124
+ path = Pathname(uri.path).relative_path_from(Pathname.pwd)
125
+
126
+ case path.extname
127
+ when ".rb"
128
+ file = project.source_files[path] || Project::SourceFile.new(path: path, options: options)
129
+ file.content = text
130
+ project.source_files[path] = file
131
+ project.type_check
132
+
133
+ diags = (file.errors || []).map do |error|
134
+ LanguageServer::Protocol::Interface::Diagnostic.new(
135
+ message: error.to_s,
136
+ severity: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
137
+ range: LanguageServer::Protocol::Interface::Range.new(
138
+ start: LanguageServer::Protocol::Interface::Position.new(
139
+ line: error.node.loc.line - 1,
140
+ character: error.node.loc.column,
141
+ ),
142
+ end: LanguageServer::Protocol::Interface::Position.new(
143
+ line: error.node.loc.last_line - 1,
144
+ character: error.node.loc.last_column,
145
+ ),
146
+ )
147
+ )
148
+ end
149
+
150
+ notifier.call(
151
+ method: :"textDocument/publishDiagnostics",
152
+ params: LanguageServer::Protocol::Interface::PublishDiagnosticsParams.new(
153
+ uri: uri,
154
+ diagnostics: diags,
155
+ ),
156
+ )
157
+ when ".rbi"
158
+ file = project.signature_files[path] || Project::SignatureFile.new(path: path)
159
+ file.content = text
160
+ project.signature_files[path] = file
161
+ project.type_check
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -4,21 +4,16 @@ module Steep
4
4
  attr_reader :signature_dirs
5
5
  attr_reader :stdout
6
6
  attr_reader :stderr
7
- attr_accessor :verbose
8
7
 
9
8
  def initialize(signature_dirs:, stdout:, stderr:)
10
9
  @signature_dirs = signature_dirs
11
10
  @stdout = stdout
12
11
  @stderr = stderr
13
-
14
- self.verbose = false
15
12
  end
16
13
 
17
14
  include Utils::EachSignature
18
15
 
19
16
  def run
20
- Steep.logger.level = Logger::DEBUG if verbose
21
-
22
17
  project = Project.new
23
18
 
24
19
  signature_dirs.each do |path|
data/lib/steep/project.rb CHANGED
@@ -227,5 +227,28 @@ module Steep
227
227
 
228
228
  errors
229
229
  end
230
+
231
+ def type_of(path:, line:, column:)
232
+ if source_file = source_files[path]
233
+ case source = source_file.source
234
+ when Source
235
+ if typing = source_file.typing
236
+ node = source.find_node(line: line, column: column)
237
+
238
+ type = begin
239
+ typing.type_of(node: node)
240
+ rescue RuntimeError
241
+ AST::Builtin.any_type
242
+ end
243
+
244
+ if block_given?
245
+ yield type, node
246
+ else
247
+ type
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
230
253
  end
231
254
  end
@@ -42,6 +42,9 @@ module Steep
42
42
  rescue ::Parser::SyntaxError => exn
43
43
  Steep.logger.warn { "Syntax error on #{path}: #{exn.inspect}" }
44
44
  exn
45
+ rescue EncodingError => exn
46
+ Steep.logger.warn { "Encoding error on #{path}: #{exn.inspect}" }
47
+ exn
45
48
  end
46
49
  end
47
50
 
data/lib/steep/source.rb CHANGED
@@ -288,5 +288,29 @@ module Steep
288
288
  enum_for :each_annotation
289
289
  end
290
290
  end
291
+
292
+ # @type method find_node: (line: Integer, column: Integer, ?node: any, ?position: Integer?) -> any
293
+ def find_node(line:, column:, node: self.node, position: nil)
294
+ position ||= (line-1).times.sum do |i|
295
+ node.location.expression.source_buffer.source_line(i+1).size + 1
296
+ end + column
297
+
298
+ range = node.location.expression&.yield_self do |r|
299
+ r.begin_pos..r.end_pos
300
+ end
301
+
302
+ if range
303
+ if range === position
304
+ Source.each_child_node(node) do |child|
305
+ n = find_node(line: line, column: column, node: child, position: position)
306
+ if n
307
+ return n
308
+ end
309
+ end
310
+
311
+ node
312
+ end
313
+ end
314
+ end
291
315
  end
292
316
  end
@@ -1559,9 +1559,9 @@ module Steep
1559
1559
  typing.add_typing node, AST::Builtin.any_type
1560
1560
  end
1561
1561
 
1562
- when :splat, :sclass
1562
+ when :splat, :sclass, :alias
1563
1563
  yield_self do
1564
- Steep.logger.error "Unsupported node #{node.type}"
1564
+ Steep.logger.error "Unsupported node #{node.type} (#{node.location.expression.source_buffer.name}:#{node.location.expression.line})"
1565
1565
 
1566
1566
  each_child_node node do |child|
1567
1567
  synthesize(child)
data/lib/steep/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Steep
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.0"
3
3
  end
data/sig/project.rbi CHANGED
@@ -13,6 +13,7 @@ class Steep::Source
13
13
  def self.parse: (String, path: String, labeling: any) -> instance
14
14
  def annotations: (block: any, builder: any, current_module: any) -> Array<annotation>
15
15
  def node: -> any
16
+ def find_node: (line: Integer, column: Integer) -> any
16
17
  end
17
18
 
18
19
  class Steep::Typing
@@ -103,4 +104,6 @@ class Steep::Project
103
104
  def signature_updated?: -> bool
104
105
  def reload_signature: -> void
105
106
  def validate_signature: (any) -> Array<any>
107
+
108
+ def type_of: (path: Pathname, line: Integer, column: Integer) -> any
106
109
  end
data/steep.gemspec CHANGED
@@ -35,4 +35,5 @@ Gem::Specification.new do |spec|
35
35
  spec.add_runtime_dependency "rainbow", "~> 2.2.2", "< 4.0"
36
36
  spec.add_runtime_dependency "listen", "~> 3.1"
37
37
  spec.add_runtime_dependency "pry", "~> 0.12.2"
38
+ spec.add_runtime_dependency "language_server-protocol", "~> 3.14.0"
38
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: steep
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soutaro Matsumoto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-03-04 00:00:00.000000000 Z
11
+ date: 2019-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -156,6 +156,20 @@ dependencies:
156
156
  - - "~>"
157
157
  - !ruby/object:Gem::Version
158
158
  version: 0.12.2
159
+ - !ruby/object:Gem::Dependency
160
+ name: language_server-protocol
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: 3.14.0
166
+ type: :runtime
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: 3.14.0
159
173
  description: Gradual Typing for Ruby
160
174
  email:
161
175
  - matsumoto@soutaro.com
@@ -214,6 +228,7 @@ files:
214
228
  - lib/steep/cli.rb
215
229
  - lib/steep/drivers/annotations.rb
216
230
  - lib/steep/drivers/check.rb
231
+ - lib/steep/drivers/langserver.rb
217
232
  - lib/steep/drivers/print_interface.rb
218
233
  - lib/steep/drivers/scaffold.rb
219
234
  - lib/steep/drivers/utils/each_signature.rb