spoom 1.0.3 → 1.0.4

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.
@@ -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: