steep 0.10.0 → 0.11.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.
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