spoom 1.1.11 → 1.1.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,12 +23,16 @@ module Spoom
23
23
  const :contents, String
24
24
  const :range, T.nilable(Range)
25
25
 
26
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Hover) }
27
- def self.from_json(json)
28
- Hover.new(
29
- contents: json['contents']['value'],
30
- range: json['range'] ? Range.from_json(json['range']) : nil
31
- )
26
+ class << self
27
+ extend T::Sig
28
+
29
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Hover) }
30
+ def from_json(json)
31
+ Hover.new(
32
+ contents: json["contents"]["value"],
33
+ range: json["range"] ? Range.from_json(json["range"]) : nil
34
+ )
35
+ end
32
36
  end
33
37
 
34
38
  sig { override.params(printer: SymbolPrinter).void }
@@ -50,12 +54,16 @@ module Spoom
50
54
  const :line, Integer
51
55
  const :char, Integer
52
56
 
53
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Position) }
54
- def self.from_json(json)
55
- Position.new(
56
- line: json['line'].to_i,
57
- char: json['character'].to_i
58
- )
57
+ class << self
58
+ extend T::Sig
59
+
60
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Position) }
61
+ def from_json(json)
62
+ Position.new(
63
+ line: json["line"].to_i,
64
+ char: json["character"].to_i
65
+ )
66
+ end
59
67
  end
60
68
 
61
69
  sig { override.params(printer: SymbolPrinter).void }
@@ -76,12 +84,16 @@ module Spoom
76
84
  const :start, Position
77
85
  const :end, Position
78
86
 
79
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Range) }
80
- def self.from_json(json)
81
- Range.new(
82
- start: Position.from_json(json['start']),
83
- end: Position.from_json(json['end'])
84
- )
87
+ class << self
88
+ extend T::Sig
89
+
90
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Range) }
91
+ def from_json(json)
92
+ Range.new(
93
+ start: Position.from_json(json["start"]),
94
+ end: Position.from_json(json["end"])
95
+ )
96
+ end
85
97
  end
86
98
 
87
99
  sig { override.params(printer: SymbolPrinter).void }
@@ -104,12 +116,16 @@ module Spoom
104
116
  const :uri, String
105
117
  const :range, LSP::Range
106
118
 
107
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Location) }
108
- def self.from_json(json)
109
- Location.new(
110
- uri: json['uri'],
111
- range: Range.from_json(json['range'])
112
- )
119
+ class << self
120
+ extend T::Sig
121
+
122
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Location) }
123
+ def from_json(json)
124
+ Location.new(
125
+ uri: json["uri"],
126
+ range: Range.from_json(json["range"])
127
+ )
128
+ end
113
129
  end
114
130
 
115
131
  sig { override.params(printer: SymbolPrinter).void }
@@ -132,20 +148,24 @@ module Spoom
132
148
  const :doc, Object # TODO
133
149
  const :params, T::Array[T.untyped] # TODO
134
150
 
135
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(SignatureHelp) }
136
- def self.from_json(json)
137
- SignatureHelp.new(
138
- label: json['label'],
139
- doc: json['documentation'],
140
- params: json['parameters'],
141
- )
151
+ class << self
152
+ extend T::Sig
153
+
154
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(SignatureHelp) }
155
+ def from_json(json)
156
+ SignatureHelp.new(
157
+ label: json["label"],
158
+ doc: json["documentation"],
159
+ params: json["parameters"],
160
+ )
161
+ end
142
162
  end
143
163
 
144
164
  sig { override.params(printer: SymbolPrinter).void }
145
165
  def accept_printer(printer)
146
166
  printer.print(label)
147
167
  printer.print("(")
148
- printer.print(params.map { |l| "#{l['label']}: #{l['documentation']}" }.join(", "))
168
+ printer.print(params.map { |l| "#{l["label"]}: #{l["documentation"]}" }.join(", "))
149
169
  printer.print(")")
150
170
  end
151
171
 
@@ -164,14 +184,18 @@ module Spoom
164
184
  const :message, String
165
185
  const :informations, Object
166
186
 
167
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Diagnostic) }
168
- def self.from_json(json)
169
- Diagnostic.new(
170
- range: Range.from_json(json['range']),
171
- code: json['code'].to_i,
172
- message: json['message'],
173
- informations: json['relatedInformation']
174
- )
187
+ class << self
188
+ extend T::Sig
189
+
190
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Diagnostic) }
191
+ def from_json(json)
192
+ Diagnostic.new(
193
+ range: Range.from_json(json["range"]),
194
+ code: json["code"].to_i,
195
+ message: json["message"],
196
+ informations: json["relatedInformation"]
197
+ )
198
+ end
175
199
  end
176
200
 
177
201
  sig { override.params(printer: SymbolPrinter).void }
@@ -196,35 +220,40 @@ module Spoom
196
220
  const :range, T.nilable(Range)
197
221
  const :children, T::Array[DocumentSymbol]
198
222
 
199
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(DocumentSymbol) }
200
- def self.from_json(json)
201
- DocumentSymbol.new(
202
- name: json['name'],
203
- detail: json['detail'],
204
- kind: json['kind'],
205
- location: json['location'] ? Location.from_json(json['location']) : nil,
206
- range: json['range'] ? Range.from_json(json['range']) : nil,
207
- children: json['children'] ? json['children'].map { |symbol| DocumentSymbol.from_json(symbol) } : [],
208
- )
223
+ class << self
224
+ extend T::Sig
225
+
226
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(DocumentSymbol) }
227
+ def from_json(json)
228
+ DocumentSymbol.new(
229
+ name: json["name"],
230
+ detail: json["detail"],
231
+ kind: json["kind"],
232
+ location: json["location"] ? Location.from_json(json["location"]) : nil,
233
+ range: json["range"] ? Range.from_json(json["range"]) : nil,
234
+ children: json["children"] ? json["children"].map { |symbol| DocumentSymbol.from_json(symbol) } : [],
235
+ )
236
+ end
209
237
  end
210
238
 
211
239
  sig { override.params(printer: SymbolPrinter).void }
212
240
  def accept_printer(printer)
213
241
  h = serialize.hash
214
242
  return if printer.seen.include?(h)
243
+
215
244
  printer.seen.add(h)
216
245
 
217
246
  printer.printt
218
247
  printer.print(kind_string)
219
- printer.print(' ')
248
+ printer.print(" ")
220
249
  printer.print_colored(name, Color::BLUE, Color::BOLD)
221
- printer.print_colored(' (', Color::LIGHT_BLACK)
250
+ printer.print_colored(" (", Color::LIGHT_BLACK)
222
251
  if range
223
252
  printer.print_object(range)
224
253
  elsif location
225
254
  printer.print_object(location)
226
255
  end
227
- printer.print_colored(')', Color::LIGHT_BLACK)
256
+ printer.print_colored(")", Color::LIGHT_BLACK)
228
257
  printer.printn
229
258
  unless children.empty?
230
259
  printer.indent
@@ -303,6 +332,7 @@ module Spoom
303
332
  sig { params(object: T.nilable(PrintableSymbol)).void }
304
333
  def print_object(object)
305
334
  return unless object
335
+
306
336
  object.accept_printer(self)
307
337
  end
308
338
 
@@ -315,6 +345,7 @@ module Spoom
315
345
  def clean_uri(uri)
316
346
  prefix = self.prefix
317
347
  return uri unless prefix
348
+
318
349
  uri.delete_prefix(prefix)
319
350
  end
320
351
 
@@ -322,7 +353,7 @@ module Spoom
322
353
  def print_list(objects)
323
354
  objects.each do |object|
324
355
  printt
325
- print "* "
356
+ print("* ")
326
357
  print_object(object)
327
358
  printn
328
359
  end
@@ -1,12 +1,12 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'open3'
5
- require 'json'
4
+ require "open3"
5
+ require "json"
6
6
 
7
- require_relative 'lsp/base'
8
- require_relative 'lsp/structures'
9
- require_relative 'lsp/errors'
7
+ require_relative "lsp/base"
8
+ require_relative "lsp/structures"
9
+ require_relative "lsp/errors"
10
10
 
11
11
  module Spoom
12
12
  module LSP
@@ -58,10 +58,10 @@ module Spoom
58
58
  json = JSON.parse(raw_string)
59
59
 
60
60
  # Handle error in the LSP protocol
61
- raise ResponseError.from_json(json['error']) if json['error']
61
+ raise ResponseError.from_json(json["error"]) if json["error"]
62
62
 
63
63
  # Handle typechecking errors
64
- raise Error::Diagnostics.from_json(json['params']) if json['method'] == "textDocument/publishDiagnostics"
64
+ raise Error::Diagnostics.from_json(json["params"]) if json["method"] == "textDocument/publishDiagnostics"
65
65
 
66
66
  json
67
67
  end
@@ -71,16 +71,17 @@ module Spoom
71
71
  sig { params(workspace_path: String).void }
72
72
  def open(workspace_path)
73
73
  raise Error::AlreadyOpen, "Error: CLI already opened" if @open
74
+
74
75
  send(Request.new(
75
76
  next_id,
76
- 'initialize',
77
+ "initialize",
77
78
  {
78
- 'rootPath' => workspace_path,
79
- 'rootUri' => "file://#{workspace_path}",
80
- 'capabilities' => {},
79
+ "rootPath" => workspace_path,
80
+ "rootUri" => "file://#{workspace_path}",
81
+ "capabilities" => {},
81
82
  },
82
83
  ))
83
- send(Notification.new('initialized', {}))
84
+ send(Notification.new("initialized", {}))
84
85
  @open = true
85
86
  end
86
87
 
@@ -88,133 +89,140 @@ module Spoom
88
89
  def hover(uri, line, column)
89
90
  json = send(Request.new(
90
91
  next_id,
91
- 'textDocument/hover',
92
+ "textDocument/hover",
92
93
  {
93
- 'textDocument' => {
94
- 'uri' => uri,
94
+ "textDocument" => {
95
+ "uri" => uri,
95
96
  },
96
- 'position' => {
97
- 'line' => line,
98
- 'character' => column,
97
+ "position" => {
98
+ "line" => line,
99
+ "character" => column,
99
100
  },
100
101
  }
101
102
  ))
102
103
 
103
- return nil unless json && json['result']
104
- Hover.from_json(json['result'])
104
+ return nil unless json && json["result"]
105
+
106
+ Hover.from_json(json["result"])
105
107
  end
106
108
 
107
109
  sig { params(uri: String, line: Integer, column: Integer).returns(T::Array[SignatureHelp]) }
108
110
  def signatures(uri, line, column)
109
111
  json = send(Request.new(
110
112
  next_id,
111
- 'textDocument/signatureHelp',
113
+ "textDocument/signatureHelp",
112
114
  {
113
- 'textDocument' => {
114
- 'uri' => uri,
115
+ "textDocument" => {
116
+ "uri" => uri,
115
117
  },
116
- 'position' => {
117
- 'line' => line,
118
- 'character' => column,
118
+ "position" => {
119
+ "line" => line,
120
+ "character" => column,
119
121
  },
120
122
  }
121
123
  ))
122
124
 
123
- return [] unless json && json['result'] && json['result']['signatures']
124
- json['result']['signatures'].map { |loc| SignatureHelp.from_json(loc) }
125
+ return [] unless json && json["result"] && json["result"]["signatures"]
126
+
127
+ json["result"]["signatures"].map { |loc| SignatureHelp.from_json(loc) }
125
128
  end
126
129
 
127
130
  sig { params(uri: String, line: Integer, column: Integer).returns(T::Array[Location]) }
128
131
  def definitions(uri, line, column)
129
132
  json = send(Request.new(
130
133
  next_id,
131
- 'textDocument/definition',
134
+ "textDocument/definition",
132
135
  {
133
- 'textDocument' => {
134
- 'uri' => uri,
136
+ "textDocument" => {
137
+ "uri" => uri,
135
138
  },
136
- 'position' => {
137
- 'line' => line,
138
- 'character' => column,
139
+ "position" => {
140
+ "line" => line,
141
+ "character" => column,
139
142
  },
140
143
  }
141
144
  ))
142
145
 
143
- return [] unless json && json['result']
144
- json['result'].map { |loc| Location.from_json(loc) }
146
+ return [] unless json && json["result"]
147
+
148
+ json["result"].map { |loc| Location.from_json(loc) }
145
149
  end
146
150
 
147
151
  sig { params(uri: String, line: Integer, column: Integer).returns(T::Array[Location]) }
148
152
  def type_definitions(uri, line, column)
149
153
  json = send(Request.new(
150
154
  next_id,
151
- 'textDocument/typeDefinition',
155
+ "textDocument/typeDefinition",
152
156
  {
153
- 'textDocument' => {
154
- 'uri' => uri,
157
+ "textDocument" => {
158
+ "uri" => uri,
155
159
  },
156
- 'position' => {
157
- 'line' => line,
158
- 'character' => column,
160
+ "position" => {
161
+ "line" => line,
162
+ "character" => column,
159
163
  },
160
164
  }
161
165
  ))
162
166
 
163
- return [] unless json && json['result']
164
- json['result'].map { |loc| Location.from_json(loc) }
167
+ return [] unless json && json["result"]
168
+
169
+ json["result"].map { |loc| Location.from_json(loc) }
165
170
  end
166
171
 
167
172
  sig { params(uri: String, line: Integer, column: Integer, include_decl: T::Boolean).returns(T::Array[Location]) }
168
173
  def references(uri, line, column, include_decl = true)
169
174
  json = send(Request.new(
170
175
  next_id,
171
- 'textDocument/references',
176
+ "textDocument/references",
172
177
  {
173
- 'textDocument' => {
174
- 'uri' => uri,
178
+ "textDocument" => {
179
+ "uri" => uri,
175
180
  },
176
- 'position' => {
177
- 'line' => line,
178
- 'character' => column,
181
+ "position" => {
182
+ "line" => line,
183
+ "character" => column,
179
184
  },
180
- 'context' => {
181
- 'includeDeclaration' => include_decl,
185
+ "context" => {
186
+ "includeDeclaration" => include_decl,
182
187
  },
183
188
  }
184
189
  ))
185
190
 
186
- return [] unless json && json['result']
187
- json['result'].map { |loc| Location.from_json(loc) }
191
+ return [] unless json && json["result"]
192
+
193
+ json["result"].map { |loc| Location.from_json(loc) }
188
194
  end
189
195
 
190
196
  sig { params(query: String).returns(T::Array[DocumentSymbol]) }
191
197
  def symbols(query)
192
198
  json = send(Request.new(
193
199
  next_id,
194
- 'workspace/symbol',
200
+ "workspace/symbol",
195
201
  {
196
- 'query' => query,
202
+ "query" => query,
197
203
  }
198
204
  ))
199
205
 
200
- return [] unless json && json['result']
201
- json['result'].map { |loc| DocumentSymbol.from_json(loc) }
206
+ return [] unless json && json["result"]
207
+
208
+ json["result"].map { |loc| DocumentSymbol.from_json(loc) }
202
209
  end
203
210
 
204
211
  sig { params(uri: String).returns(T::Array[DocumentSymbol]) }
205
212
  def document_symbols(uri)
206
213
  json = send(Request.new(
207
214
  next_id,
208
- 'textDocument/documentSymbol',
215
+ "textDocument/documentSymbol",
209
216
  {
210
- 'textDocument' => {
211
- 'uri' => uri,
217
+ "textDocument" => {
218
+ "uri" => uri,
212
219
  },
213
220
  }
214
221
  ))
215
222
 
216
- return [] unless json && json['result']
217
- json['result'].map { |loc| DocumentSymbol.from_json(loc) }
223
+ return [] unless json && json["result"]
224
+
225
+ json["result"].map { |loc| DocumentSymbol.from_json(loc) }
218
226
  end
219
227
 
220
228
  sig { void }
@@ -6,26 +6,28 @@ require_relative "sigils"
6
6
  module Spoom
7
7
  module Sorbet
8
8
  module MetricsParser
9
- extend T::Sig
10
-
11
9
  DEFAULT_PREFIX = "ruby_typer.unknown.."
12
10
 
13
- sig { params(path: String, prefix: String).returns(T::Hash[String, Integer]) }
14
- def self.parse_file(path, prefix = DEFAULT_PREFIX)
15
- parse_string(File.read(path), prefix)
16
- end
11
+ class << self
12
+ extend T::Sig
17
13
 
18
- sig { params(string: String, prefix: String).returns(T::Hash[String, Integer]) }
19
- def self.parse_string(string, prefix = DEFAULT_PREFIX)
20
- parse_hash(JSON.parse(string), prefix)
21
- end
14
+ sig { params(path: String, prefix: String).returns(T::Hash[String, Integer]) }
15
+ def parse_file(path, prefix = DEFAULT_PREFIX)
16
+ parse_string(File.read(path), prefix)
17
+ end
18
+
19
+ sig { params(string: String, prefix: String).returns(T::Hash[String, Integer]) }
20
+ def parse_string(string, prefix = DEFAULT_PREFIX)
21
+ parse_hash(JSON.parse(string), prefix)
22
+ end
22
23
 
23
- sig { params(obj: T::Hash[String, T.untyped], prefix: String).returns(T::Hash[String, Integer]) }
24
- def self.parse_hash(obj, prefix = DEFAULT_PREFIX)
25
- obj["metrics"].each_with_object(Hash.new(0)) do |metric, metrics|
26
- name = metric["name"]
27
- name = name.sub(prefix, '')
28
- metrics[name] = metric["value"] || 0
24
+ sig { params(obj: T::Hash[String, T.untyped], prefix: String).returns(T::Hash[String, Integer]) }
25
+ def parse_hash(obj, prefix = DEFAULT_PREFIX)
26
+ obj["metrics"].each_with_object(Hash.new(0)) do |metric, metrics|
27
+ name = metric["name"]
28
+ name = name.sub(prefix, "")
29
+ metrics[name] = metric["value"] || 0
30
+ end
29
31
  end
30
32
  end
31
33
  end
@@ -27,70 +27,75 @@ module Spoom
27
27
 
28
28
  SIGIL_REGEXP = T.let(/^#[\ t]*typed[\ t]*:[ \t]*(\w*)[ \t]*/.freeze, Regexp)
29
29
 
30
- # returns the full sigil comment string for the passed strictness
31
- sig { params(strictness: String).returns(String) }
32
- def self.sigil_string(strictness)
33
- "# typed: #{strictness}"
34
- end
30
+ class << self
31
+ extend T::Sig
35
32
 
36
- # returns true if the passed string is a valid strictness (else false)
37
- sig { params(strictness: String).returns(T::Boolean) }
38
- def self.valid_strictness?(strictness)
39
- VALID_STRICTNESS.include?(strictness.strip)
40
- end
33
+ # returns the full sigil comment string for the passed strictness
34
+ sig { params(strictness: String).returns(String) }
35
+ def sigil_string(strictness)
36
+ "# typed: #{strictness}"
37
+ end
41
38
 
42
- # returns the strictness of a sigil in the passed file content string (nil if no sigil)
43
- sig { params(content: String).returns(T.nilable(String)) }
44
- def self.strictness_in_content(content)
45
- SIGIL_REGEXP.match(content)&.[](1)
46
- end
39
+ # returns true if the passed string is a valid strictness (else false)
40
+ sig { params(strictness: String).returns(T::Boolean) }
41
+ def valid_strictness?(strictness)
42
+ VALID_STRICTNESS.include?(strictness.strip)
43
+ end
47
44
 
48
- # returns a string which is the passed content but with the sigil updated to a new strictness
49
- sig { params(content: String, new_strictness: String).returns(String) }
50
- def self.update_sigil(content, new_strictness)
51
- content.sub(SIGIL_REGEXP, sigil_string(new_strictness))
52
- end
45
+ # returns the strictness of a sigil in the passed file content string (nil if no sigil)
46
+ sig { params(content: String).returns(T.nilable(String)) }
47
+ def strictness_in_content(content)
48
+ SIGIL_REGEXP.match(content)&.[](1)
49
+ end
53
50
 
54
- # returns a string containing the strictness of a sigil in a file at the passed path
55
- # * returns nil if no sigil
56
- sig { params(path: T.any(String, Pathname)).returns(T.nilable(String)) }
57
- def self.file_strictness(path)
58
- return nil unless File.file?(path)
59
- content = File.read(path, encoding: Encoding::ASCII_8BIT)
60
- strictness_in_content(content)
61
- end
51
+ # returns a string which is the passed content but with the sigil updated to a new strictness
52
+ sig { params(content: String, new_strictness: String).returns(String) }
53
+ def update_sigil(content, new_strictness)
54
+ content.sub(SIGIL_REGEXP, sigil_string(new_strictness))
55
+ end
62
56
 
63
- # changes the sigil in the file at the passed path to the specified new strictness
64
- sig { params(path: T.any(String, Pathname), new_strictness: String).returns(T::Boolean) }
65
- def self.change_sigil_in_file(path, new_strictness)
66
- content = File.read(path, encoding: Encoding::ASCII_8BIT)
67
- new_content = update_sigil(content, new_strictness)
57
+ # returns a string containing the strictness of a sigil in a file at the passed path
58
+ # * returns nil if no sigil
59
+ sig { params(path: T.any(String, Pathname)).returns(T.nilable(String)) }
60
+ def file_strictness(path)
61
+ return nil unless File.file?(path)
68
62
 
69
- File.write(path, new_content, encoding: Encoding::ASCII_8BIT)
63
+ content = File.read(path, encoding: Encoding::ASCII_8BIT)
64
+ strictness_in_content(content)
65
+ end
70
66
 
71
- strictness_in_content(new_content) == new_strictness
72
- end
67
+ # changes the sigil in the file at the passed path to the specified new strictness
68
+ sig { params(path: T.any(String, Pathname), new_strictness: String).returns(T::Boolean) }
69
+ def change_sigil_in_file(path, new_strictness)
70
+ content = File.read(path, encoding: Encoding::ASCII_8BIT)
71
+ new_content = update_sigil(content, new_strictness)
73
72
 
74
- # changes the sigil to have a new strictness in a list of files
75
- sig { params(path_list: T::Array[String], new_strictness: String).returns(T::Array[String]) }
76
- def self.change_sigil_in_files(path_list, new_strictness)
77
- path_list.filter do |path|
78
- change_sigil_in_file(path, new_strictness)
73
+ File.write(path, new_content, encoding: Encoding::ASCII_8BIT)
74
+
75
+ strictness_in_content(new_content) == new_strictness
79
76
  end
80
- end
81
77
 
82
- # finds all files in the specified directory with the passed strictness
83
- sig do
84
- params(
85
- directory: T.any(String, Pathname),
86
- strictness: String,
87
- extension: String
88
- ).returns(T::Array[String])
89
- end
90
- def self.files_with_sigil_strictness(directory, strictness, extension: ".rb")
91
- paths = Dir.glob("#{File.expand_path(directory)}/**/*#{extension}").sort.uniq
92
- paths.filter do |path|
93
- file_strictness(path) == strictness
78
+ # changes the sigil to have a new strictness in a list of files
79
+ sig { params(path_list: T::Array[String], new_strictness: String).returns(T::Array[String]) }
80
+ def change_sigil_in_files(path_list, new_strictness)
81
+ path_list.filter do |path|
82
+ change_sigil_in_file(path, new_strictness)
83
+ end
84
+ end
85
+
86
+ # finds all files in the specified directory with the passed strictness
87
+ sig do
88
+ params(
89
+ directory: T.any(String, Pathname),
90
+ strictness: String,
91
+ extension: String
92
+ ).returns(T::Array[String])
93
+ end
94
+ def files_with_sigil_strictness(directory, strictness, extension: ".rb")
95
+ paths = Dir.glob("#{File.expand_path(directory)}/**/*#{extension}").sort.uniq
96
+ paths.filter do |path|
97
+ file_strictness(path) == strictness
98
+ end
94
99
  end
95
100
  end
96
101
  end