spoom 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,58 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module LSP
6
+ # Base messaging
7
+ # We don't use T::Struct for those so we can subclass them
8
+
9
+ # A general message as defined by JSON-RPC.
10
+ #
11
+ # The language server protocol always uses `"2.0"` as the `jsonrpc` version.
12
+ class Message
13
+ attr_reader :jsonrpc
14
+
15
+ def initialize
16
+ @jsonrpc = '2.0'
17
+ end
18
+
19
+ def as_json
20
+ instance_variables.each_with_object({}) do |var, obj|
21
+ val = instance_variable_get(var)
22
+ obj[var.to_s.delete('@')] = val if val
23
+ end
24
+ end
25
+
26
+ def to_json(*args)
27
+ as_json.to_json(*args)
28
+ end
29
+ end
30
+
31
+ # A request message to describe a request between the client and the server.
32
+ #
33
+ # Every processed request must send a response back to the sender of the request.
34
+ class Request < Message
35
+ attr_reader :id, :method, :params
36
+
37
+ def initialize(id, method, params)
38
+ super()
39
+ @id = id
40
+ @method = method
41
+ @params = params
42
+ end
43
+ end
44
+
45
+ # A notification message.
46
+ #
47
+ # A processed notification message must not send a response back. They work like events.
48
+ class Notification < Message
49
+ attr_reader :method, :params
50
+
51
+ def initialize(method, params)
52
+ super()
53
+ @method = method
54
+ @params = params
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,45 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module LSP
6
+ class Error < StandardError
7
+ class AlreadyOpen < Error; end
8
+ class BadHeaders < Error; end
9
+
10
+ class Diagnostics < Error
11
+ attr_reader :uri, :diagnostics
12
+
13
+ def self.from_json(json)
14
+ Diagnostics.new(
15
+ json['uri'],
16
+ json['diagnostics'].map { |d| Diagnostic.from_json(d) }
17
+ )
18
+ end
19
+
20
+ def initialize(uri, diagnostics)
21
+ @uri = uri
22
+ @diagnostics = diagnostics
23
+ end
24
+ end
25
+ end
26
+
27
+ class ResponseError < Error
28
+ attr_reader :code, :message, :data
29
+
30
+ def self.from_json(json)
31
+ ResponseError.new(
32
+ json['code'],
33
+ json['message'],
34
+ json['data']
35
+ )
36
+ end
37
+
38
+ def initialize(code, message, data)
39
+ @code = code
40
+ @message = message
41
+ @data = data
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,218 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module LSP
6
+ class Hover < T::Struct
7
+ const :contents, String
8
+ const :range, T.nilable(Range)
9
+
10
+ def self.from_json(json)
11
+ Hover.new(
12
+ contents: json['contents']['value'],
13
+ range: json['range'] ? Range.from_json(json['range']) : nil
14
+ )
15
+ end
16
+
17
+ def accept_printer(printer)
18
+ printer.print("#{contents}\n")
19
+ printer.visit(range) if range
20
+ end
21
+
22
+ def to_s
23
+ "#{contents} (#{range})."
24
+ end
25
+ end
26
+
27
+ class Position < T::Struct
28
+ const :line, Integer
29
+ const :char, Integer
30
+
31
+ def self.from_json(json)
32
+ Position.new(
33
+ line: json['line'].to_i,
34
+ char: json['character'].to_i
35
+ )
36
+ end
37
+
38
+ def accept_printer(printer)
39
+ printer.print("#{line}:#{char}".light_black)
40
+ end
41
+
42
+ def to_s
43
+ "#{line}:#{char}"
44
+ end
45
+ end
46
+
47
+ class Range < T::Struct
48
+ const :start, Position
49
+ const :end, Position
50
+
51
+ def self.from_json(json)
52
+ Range.new(
53
+ start: Position.from_json(json['start']),
54
+ end: Position.from_json(json['end'])
55
+ )
56
+ end
57
+
58
+ def accept_printer(printer)
59
+ printer.visit(start)
60
+ printer.print("-".light_black)
61
+ printer.visit(self.end)
62
+ end
63
+
64
+ def to_s
65
+ "#{start}-#{self.end}"
66
+ end
67
+ end
68
+
69
+ class Location < T::Struct
70
+ const :uri, String
71
+ const :range, LSP::Range
72
+
73
+ def self.from_json(json)
74
+ Location.new(
75
+ uri: json['uri'],
76
+ range: Range.from_json(json['range'])
77
+ )
78
+ end
79
+
80
+ def accept_printer(printer)
81
+ printer.print("#{uri.from_uri}:".light_black)
82
+ printer.visit(range)
83
+ end
84
+
85
+ def to_s
86
+ "#{uri}:#{range})."
87
+ end
88
+ end
89
+
90
+ class SignatureHelp < T::Struct
91
+ const :label, T.nilable(String)
92
+ const :doc, Object # TODO
93
+ const :params, T::Array[T.untyped] # TODO
94
+
95
+ def self.from_json(json)
96
+ SignatureHelp.new(
97
+ label: json['label'],
98
+ doc: json['documentation'],
99
+ params: json['parameters'],
100
+ )
101
+ end
102
+
103
+ def accept_printer(printer)
104
+ printer.print(label)
105
+ printer.print("(")
106
+ printer.print(params.map { |l| "#{l['label']}: #{l['documentation']}" }.join(", "))
107
+ printer.print(")")
108
+ end
109
+
110
+ def to_s
111
+ "#{label}(#{params})."
112
+ end
113
+ end
114
+
115
+ class Diagnostic < T::Struct
116
+ const :range, LSP::Range
117
+ const :code, Integer
118
+ const :message, String
119
+ const :informations, Object
120
+
121
+ def self.from_json(json)
122
+ Diagnostic.new(
123
+ range: Range.from_json(json['range']),
124
+ code: json['code'].to_i,
125
+ message: json['message'],
126
+ informations: json['relatedInformation']
127
+ )
128
+ end
129
+
130
+ def to_s
131
+ "Error: #{message} (#{code})."
132
+ end
133
+ end
134
+
135
+ class DocumentSymbol < T::Struct
136
+ const :name, String
137
+ const :detail, T.nilable(String)
138
+ const :kind, Integer
139
+ const :location, T.nilable(Location)
140
+ const :range, T.nilable(Range)
141
+ const :children, T::Array[DocumentSymbol]
142
+
143
+ def self.from_json(json)
144
+ DocumentSymbol.new(
145
+ name: json['name'],
146
+ detail: json['detail'],
147
+ kind: json['kind'],
148
+ location: json['location'] ? Location.from_json(json['location']) : nil,
149
+ range: json['range'] ? Range.from_json(json['range']) : nil,
150
+ children: json['children'] ? json['children'].map { |symbol| DocumentSymbol.from_json(symbol) } : [],
151
+ )
152
+ end
153
+
154
+ def accept_printer(printer)
155
+ h = serialize.hash
156
+ return if printer.seen.include?(h)
157
+ printer.seen.add(h)
158
+
159
+ printer.printt
160
+ printer.print(kind_string)
161
+ printer.print(' ')
162
+ printer.print(name.blue.bold)
163
+ printer.print(' ('.light_black)
164
+ if range
165
+ printer.visit(range)
166
+ elsif location
167
+ printer.visit(location)
168
+ end
169
+ printer.print(')'.light_black)
170
+ printer.printn
171
+ unless children.empty?
172
+ printer.indent
173
+ printer.visit(children)
174
+ printer.dedent
175
+ end
176
+ # TODO: also display details?
177
+ end
178
+
179
+ def to_s
180
+ "#{name} (#{range})"
181
+ end
182
+
183
+ def kind_string
184
+ return "<unknown:#{kind}>" unless SYMBOL_KINDS.key?(kind)
185
+ SYMBOL_KINDS[kind]
186
+ end
187
+
188
+ SYMBOL_KINDS = {
189
+ 1 => "file",
190
+ 2 => "module",
191
+ 3 => "namespace",
192
+ 4 => "package",
193
+ 5 => "class",
194
+ 6 => "def",
195
+ 7 => "property",
196
+ 8 => "field",
197
+ 9 => "constructor",
198
+ 10 => "enum",
199
+ 11 => "interface",
200
+ 12 => "function",
201
+ 13 => "variable",
202
+ 14 => "const",
203
+ 15 => "string",
204
+ 16 => "number",
205
+ 17 => "boolean",
206
+ 18 => "array",
207
+ 19 => "object",
208
+ 20 => "key",
209
+ 21 => "null",
210
+ 22 => "enum_member",
211
+ 23 => "struct",
212
+ 24 => "event",
213
+ 25 => "operator",
214
+ 26 => "type_parameter",
215
+ }
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,102 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Sorbet
6
+ class Metrics < T::Struct
7
+ extend T::Sig
8
+
9
+ DEFAULT_PREFIX = "ruby_typer.unknown.."
10
+ SIGILS = T.let(["ignore", "false", "true", "strict", "strong", "__STDLIB_INTERNAL"], T::Array[String])
11
+
12
+ const :repo, String
13
+ const :sha, String
14
+ const :status, String
15
+ const :branch, String
16
+ const :timestamp, Integer
17
+ const :uuid, String
18
+ const :metrics, T::Hash[String, T.nilable(Integer)]
19
+
20
+ sig { params(path: String, prefix: String).returns(Metrics) }
21
+ def self.parse_file(path, prefix = DEFAULT_PREFIX)
22
+ parse_string(File.read(path), prefix)
23
+ end
24
+
25
+ sig { params(string: String, prefix: String).returns(Metrics) }
26
+ def self.parse_string(string, prefix = DEFAULT_PREFIX)
27
+ parse_hash(JSON.parse(string), prefix)
28
+ end
29
+
30
+ sig { params(obj: T::Hash[String, T.untyped], prefix: String).returns(Metrics) }
31
+ def self.parse_hash(obj, prefix = DEFAULT_PREFIX)
32
+ Metrics.new(
33
+ repo: obj.fetch("repo"),
34
+ sha: obj.fetch("sha"),
35
+ status: obj.fetch("status"),
36
+ branch: obj.fetch("branch"),
37
+ timestamp: obj.fetch("timestamp").to_i,
38
+ uuid: obj.fetch("uuid"),
39
+ metrics: obj["metrics"].each_with_object({}) do |metric, all|
40
+ name = metric["name"]
41
+ name = name.sub(prefix, '')
42
+ all[name] = metric["value"].to_i
43
+ end,
44
+ )
45
+ end
46
+
47
+ sig { returns(T::Hash[String, T.nilable(Integer)]) }
48
+ def files_by_strictness
49
+ SIGILS.each_with_object({}) do |sigil, map|
50
+ map[sigil] = metrics["types.input.files.sigil.#{sigil}"]
51
+ end
52
+ end
53
+
54
+ sig { returns(Integer) }
55
+ def files_count
56
+ files_by_strictness.values.compact.sum
57
+ end
58
+
59
+ sig { params(key: String).returns(T.nilable(Integer)) }
60
+ def [](key)
61
+ metrics[key]
62
+ end
63
+
64
+ sig { returns(String) }
65
+ def to_s
66
+ "Metrics<#{repo}-#{timestamp}-#{status}>"
67
+ end
68
+
69
+ sig { params(out: T.any(IO, StringIO)).void }
70
+ def show(out = $stdout)
71
+ files = files_count
72
+
73
+ out.puts "Sigils:"
74
+ out.puts " files: #{files}"
75
+ files_by_strictness.each do |sigil, value|
76
+ next unless value
77
+ out.puts " #{sigil}: #{value}#{percent(value, files)}"
78
+ end
79
+
80
+ out.puts "\nMethods:"
81
+ m = metrics['types.input.methods.total']
82
+ s = metrics['types.sig.count']
83
+ out.puts " methods: #{m}"
84
+ out.puts " signatures: #{s}#{percent(s, m)}"
85
+
86
+ out.puts "\nSends:"
87
+ t = metrics['types.input.sends.typed']
88
+ s = metrics['types.input.sends.total']
89
+ out.puts " sends: #{s}"
90
+ out.puts " typed: #{t}#{percent(t, s)}"
91
+ end
92
+
93
+ private
94
+
95
+ sig { params(value: T.nilable(Integer), total: T.nilable(Integer)).returns(String) }
96
+ def percent(value, total)
97
+ return "" if value.nil? || total.nil? || total == 0
98
+ " (#{value * 100 / total}%)"
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,5 +1,6 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Spoom
4
- VERSION = "1.0.3"
5
+ VERSION = "1.0.4"
5
6
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spoom
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandre Terrasa
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-07 00:00:00.000000000 Z
11
+ date: 2020-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: sorbet-runtime
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: bundler
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +52,20 @@ dependencies:
66
52
  - - "~>"
67
53
  - !ruby/object:Gem::Version
68
54
  version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sorbet-runtime
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: sorbet
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.5.5
83
+ - !ruby/object:Gem::Dependency
84
+ name: thor
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 0.19.2
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 0.19.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: colorize
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description:
84
112
  email:
85
113
  - alexandre.terrasa@shopify.com
@@ -93,7 +121,21 @@ files:
93
121
  - Rakefile
94
122
  - exe/spoom
95
123
  - lib/spoom.rb
124
+ - lib/spoom/cli.rb
125
+ - lib/spoom/cli/commands/base.rb
126
+ - lib/spoom/cli/commands/config.rb
127
+ - lib/spoom/cli/commands/lsp.rb
128
+ - lib/spoom/cli/commands/run.rb
129
+ - lib/spoom/cli/symbol_printer.rb
130
+ - lib/spoom/config.rb
131
+ - lib/spoom/sorbet.rb
96
132
  - lib/spoom/sorbet/config.rb
133
+ - lib/spoom/sorbet/errors.rb
134
+ - lib/spoom/sorbet/lsp.rb
135
+ - lib/spoom/sorbet/lsp/base.rb
136
+ - lib/spoom/sorbet/lsp/errors.rb
137
+ - lib/spoom/sorbet/lsp/structures.rb
138
+ - lib/spoom/sorbet/metrics.rb
97
139
  - lib/spoom/version.rb
98
140
  homepage: https://github.com/Shopify/spoom
99
141
  licenses: