klenod-lsp 0.0.30 → 0.0.32

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: 4e64b40c339a0f3b2e2338120f4fce1a631202dad834e0ca30566048db1f5f0f
4
- data.tar.gz: 5458d0f7574457c17214c08e84db53ecb14677fa75f89dffe229de1f1dac8543
3
+ metadata.gz: f41f8fc46c0ec27d4ac70f84f6e60f9d3ad44f9fb50fafe7b4c13d71f935e071
4
+ data.tar.gz: 418a82a7691f2f70927806291379740e789d14795aa6e3afe8e2dbbee1825a74
5
5
  SHA512:
6
- metadata.gz: a448b50f1d21c57317f13893c48ee376560cd4324900b2d8fbd1ab841c7d849c6f4d1be99237e4f7893149dafbeb15137ab997220c5ad6c968fb8245ff627e4a
7
- data.tar.gz: 9093c27e1d87afee0d355d121371aa9c37ec555a03e1e9090dc52ee4a0e18f619406b2d8de7f83dfd81d8d0679991af3546f9c5865e5eccd6a04c982bb5eeea8
6
+ metadata.gz: 3bfa7e60f2ca9e16b1aa01398a3ef1f44cca62907436cb120e313b3fc79a9fb959f0e560f1a51a4dcc47021371de999a1a4d24755df517e658066815218a3822
7
+ data.tar.gz: 2bd0c26fe1062883602fc4ff9f91d17730e6327f40c9f33e0cc739833a313cb03a5384724faf88e459e57605809e2e73ff051270ba06ece03bdf60d94b871b77
data/README.md CHANGED
@@ -54,14 +54,17 @@ bundle exec klenod check src/components src/pages/Home.haml
54
54
  ```
55
55
 
56
56
  ```
57
- src/pages/Home.haml:4:3: warning: Card is imported but never used
58
- src/pages/Home.haml:9:12: error: Could not resolve "./Missing"
57
+ src/pages/Home.haml
58
+ 4:3 warning `Card` is imported but never used
59
+ 9:12 error Could not resolve "./Missing"
60
+
59
61
  1 error, 1 warning, 42 files checked.
60
62
  ```
61
63
 
62
64
  It collects the whole module graph first, so cross-file diagnostics such as
63
65
  unknown props and scoped classes are included. Paths limit which files are
64
66
  reported. The command exits with status 1 when it reports any diagnostic.
67
+ Output is colored in a terminal; set `NO_COLOR` to turn colors off.
65
68
 
66
69
  Configure the editor to run that command for `.haml` files. Ruby and CSS files
67
70
  can also use it alongside their usual language servers.
@@ -26,6 +26,20 @@ module Klenod
26
26
  Constant::DiagnosticSeverity::HINT => "hint"
27
27
  }.freeze
28
28
 
29
+ COLORS = {
30
+ path: "\e[4m",
31
+ location: "\e[2m",
32
+ error: "\e[31m",
33
+ warning: "\e[33m",
34
+ info: "\e[1;34m",
35
+ hint: "\e[2m",
36
+ detail: "\e[2m",
37
+ code: "\e[36m",
38
+ success: "\e[1;32m"
39
+ }.freeze
40
+ RESET = "\e[0m"
41
+ CODE = /`([^`\n]+)`/
42
+
29
43
  # One checked file and its diagnostics, which may be empty.
30
44
  Result = Data.define(:path, :diagnostics)
31
45
 
@@ -58,38 +72,78 @@ module Klenod
58
72
  end
59
73
  end
60
74
 
61
- # Prints one `path:line:column: severity: message` entry per diagnostic
62
- # with paths relative to `root`, then a summary. Returns the number of
63
- # diagnostics.
64
- def self.report(results, output:, root: Dir.pwd)
65
- diagnostics = results.flat_map do |result|
66
- result.diagnostics
75
+ # Prints the diagnostics grouped by file, like ESLint's stylish format,
76
+ # with paths relative to `root`, then a summary:
77
+ #
78
+ # src/pages/Home.haml
79
+ # 4:3 warning `Card` is imported but never used
80
+ # 9:12 error Could not resolve "./Missing"
81
+ #
82
+ # 1 error, 1 warning, 42 files checked.
83
+ #
84
+ # Columns share one width across all files, so every row lines up.
85
+ # Returns the number of diagnostics. Colors default to on for a terminal
86
+ # unless NO_COLOR is set.
87
+ def self.report(results, output:, root: Dir.pwd, color: color?(output))
88
+ paint = color ? ->(name, text) { "#{COLORS.fetch(name)}#{text}#{RESET}" } : ->(_name, text) { text }
89
+ files = results.reject { |result| result.diagnostics.empty? }.map do |result|
90
+ rows = result.diagnostics
67
91
  .sort_by { |diagnostic| [diagnostic.range.start.line, diagnostic.range.start.character] }
68
- .map { |diagnostic| [display_path(result.path, root), diagnostic] }
92
+ .map do |diagnostic|
93
+ start = diagnostic.range.start
94
+ [start.line + 1, start.character + 1, SEVERITY_NAMES.fetch(diagnostic.severity), diagnostic.message]
95
+ end
96
+ [result.path, rows]
69
97
  end
70
-
71
- diagnostics.each do |path, diagnostic|
72
- start = diagnostic.range.start
73
- first_line, *rest = diagnostic.message.lines(chomp: true)
74
- output.puts "#{path}:#{start.line + 1}:#{start.character + 1}: #{SEVERITY_NAMES.fetch(diagnostic.severity)}: #{first_line}"
75
- rest.each { |line| output.puts " #{line}" }
98
+ rows = files.flat_map(&:last)
99
+
100
+ # Line numbers align right and columns left, so the colons line up.
101
+ line_width = rows.map { |line, _, _, _| line.to_s.length }.max
102
+ column_width = rows.map { |_, column, _, _| column.to_s.length }.max
103
+ severity_width = rows.map { |_, _, severity, _| severity.length }.max
104
+ indent = " " * (2 + line_width.to_i + 1 + column_width.to_i + 2 + severity_width.to_i + 2)
105
+
106
+ files.each do |path, file_rows|
107
+ output.puts paint.call(:path, display_path(path, root))
108
+ file_rows.each do |line, column, severity, message|
109
+ location = "#{line.to_s.rjust(line_width)}:#{column.to_s.ljust(column_width)}"
110
+ first_line, *rest = message.lines(chomp: true)
111
+ first_line = highlight(first_line) if color
112
+ output.puts " #{paint.call(:location, location)} #{paint.call(severity.to_sym, severity.ljust(severity_width))} #{first_line}"
113
+ rest.each { |text| output.puts "#{indent}#{color ? highlight(text, COLORS.fetch(:detail)) : text}" }
114
+ end
115
+ output.puts
76
116
  end
77
117
 
78
- output.puts summary(results, diagnostics.map(&:last))
118
+ diagnostics = results.flat_map(&:diagnostics)
119
+ output.puts summary(results, diagnostics, paint)
79
120
  diagnostics.length
80
121
  end
81
122
 
82
- def self.summary(results, diagnostics)
123
+ def self.summary(results, diagnostics, paint = ->(_name, text) { text })
83
124
  files = "#{results.length} #{(results.length == 1) ? "file" : "files"} checked"
84
- return "No problems found, #{files}." if diagnostics.empty?
125
+ return "#{paint.call(:success, "No problems found")}, #{files}." if diagnostics.empty?
85
126
 
86
127
  counts = diagnostics.group_by(&:severity).sort.map do |severity, group|
87
128
  name = SEVERITY_NAMES.fetch(severity)
88
- "#{group.length} #{(group.length == 1) ? name : "#{name}s"}"
129
+ paint.call(name.to_sym, "#{group.length} #{(group.length == 1) ? name : "#{name}s"}")
89
130
  end
90
131
  "#{counts.join(", ")}, #{files}."
91
132
  end
92
133
 
134
+ # Colors backticked names such as `Card` in place of their backticks.
135
+ # `base` is the color of the surrounding text, restored after each name.
136
+ def self.highlight(text, base = nil)
137
+ highlighted = text.gsub(CODE) { "#{COLORS.fetch(:code)}#{Regexp.last_match(1)}#{RESET}#{base}" }
138
+ base ? "#{base}#{highlighted}#{RESET}" : highlighted
139
+ end
140
+
141
+ def self.color?(output, env = ENV)
142
+ return false if env.key?("NO_COLOR")
143
+
144
+ output.respond_to?(:tty?) && output.tty?
145
+ end
146
+
93
147
  def self.display_path(path, root)
94
148
  relative = Pathname.new(path).relative_path_from(Pathname.new(root)).to_s
95
149
  relative.start_with?("../") ? path : relative
@@ -22,7 +22,7 @@ module Klenod
22
22
  SHORTHAND_PREFIX = /\A\s*(?:%[A-Za-z][\w:-]*)?(?:[.#][\w-]+)*\.(?<partial>[\w-]*)\z/
23
23
  LOOKUP_PREFIX = /ClassNames\[:(?<partial>[\w-]*)\z/
24
24
  STYLE_KINDS = %i[companion_style inline_style].freeze
25
- FILTER_HEAD = /\A\s*:\w/
25
+ FILTER_HEAD = /\A\s*:(?<name>\w+)/
26
26
 
27
27
  Occurrence = Data.define(:name, :span)
28
28
  Definition = Data.define(:module_id, :generated)
@@ -92,14 +92,16 @@ module Klenod
92
92
  # Indexes of the lines inside filters: every line after a `:name`
93
93
  # line that is blank or indented deeper than it, as Haml reads them.
94
94
  # Indentation alone also works while the document does not parse.
95
- def filter_body_lines(lines)
95
+ # Filters named in `except` are left out.
96
+ def filter_body_lines(lines, except: [])
96
97
  filter_indent = nil
97
98
  lines.each_with_index.with_object(Set.new) do |(line_text, index), body|
98
99
  indent = line_text[/\A\s*/].length
99
100
  if filter_indent && (line_text.strip.empty? || indent > filter_indent)
100
101
  body << index
101
102
  else
102
- filter_indent = line_text.match?(FILTER_HEAD) ? indent : nil
103
+ head = FILTER_HEAD.match(line_text)
104
+ filter_indent = (head && !except.include?(head[:name])) ? indent : nil
103
105
  end
104
106
  end
105
107
  end
@@ -117,7 +119,7 @@ module Klenod
117
119
  range: occurrence.span.to_range,
118
120
  severity: Constant::DiagnosticSeverity::WARNING,
119
121
  source: "klenod",
120
- message: "Unknown CSS class #{occurrence.name.inspect}: not defined in #{style_names(map, analysis).join(" or ")}"
122
+ message: "Unknown CSS class `#{occurrence.name}`: not defined in #{style_names(map, analysis).map { |name| "`#{name}`" }.join(" or ")}"
121
123
  )
122
124
  end
123
125
  end
@@ -5,6 +5,7 @@ require "language_server-protocol"
5
5
 
6
6
  require_relative "../../text"
7
7
  require_relative "../imports"
8
+ require_relative "classes"
8
9
  require_relative "ruby_regions"
9
10
 
10
11
  module Klenod
@@ -13,9 +14,9 @@ module Klenod
13
14
  class Haml
14
15
  # The props a `%Component` tag passes, checked against the props the
15
16
  # component reads. Only keys written out on the tag line count:
16
- # `%Foo(bar="1"){ baz: 2 }`. A splat makes the tag unverifiable, and
17
- # a component that reads no `$props`, or reads them all with `$*`,
18
- # is left alone.
17
+ # `%Foo(bar="1"){ baz: 2 }`. A splat or attributes spanning several
18
+ # lines make the tag unverifiable, and a component that reads no
19
+ # `$props`, or reads them all with `$*`, is left alone.
19
20
  module Props
20
21
  Interface = LanguageServer::Protocol::Interface
21
22
  Constant = LanguageServer::Protocol::Constant
@@ -31,7 +32,10 @@ module Klenod
31
32
  /(?<=[{,]|\A)\s*"(?<key>[^"]+)"\s*=>/
32
33
  ].freeze
33
34
  SPLAT = /\*\*/
34
- IMPLICIT = %w[children].freeze
35
+ CLOSERS = {"{" => "}", "(" => ")", "[" => "]"}.freeze
36
+ # Props every component accepts: Haml passes `children`, `%Foo[item]`
37
+ # passes `key`, and `slot` places the component in its parent's slot.
38
+ IMPLICIT = %w[children key slot].freeze
35
39
 
36
40
  Key = Data.define(:name, :span)
37
41
 
@@ -39,10 +43,13 @@ module Klenod
39
43
 
40
44
  def diagnostics(analysis, workspace, index)
41
45
  lines = analysis.lines
42
- bindings = Imports.bindings(lines)
46
+ bindings = Haml.bindings(lines)
43
47
  props_cache = {}
48
+ filter_body = Classes.filter_body_lines(lines)
44
49
 
45
50
  lines.each_with_index.flat_map do |line_text, line_index|
51
+ next [] if filter_body.include?(line_index)
52
+
46
53
  tag = TAG_LINE.match(line_text)
47
54
  next [] unless tag
48
55
 
@@ -57,16 +64,23 @@ module Klenod
57
64
  next [] unless props && !props.splat && !props.names.empty?
58
65
 
59
66
  allowed = props.names + IMPLICIT
60
- keys.reject { |key| allowed.include?(key.name) }.map { |key| diagnostic(key, tag[:name], allowed) }
67
+ keys.reject { |key| allowed.include?(prop_name(key.name)) }.map { |key| diagnostic(key, tag[:name], allowed) }
61
68
  end
62
69
  end
63
70
 
64
- # Keys from the tag's attribute regions, or nil when a splat makes
65
- # them unknowable.
71
+ # Components receive `video-id` as `$video_id`, like `merge_props`
72
+ # in the Haml plugin's helper source.
73
+ def prop_name(key)
74
+ key.tr("-", "_")
75
+ end
76
+
77
+ # Keys from the tag's attribute regions, or nil when a splat or an
78
+ # attribute list continuing on the next lines makes them unknowable.
66
79
  def explicit_keys(rest, line_index, offset)
67
80
  keys = []
68
81
  RubyRegions.attribute_regions(rest).each do |region_offset, region|
69
82
  return nil if region.start_with?("{") && region.match?(SPLAT)
83
+ return nil unless region.end_with?(CLOSERS.fetch(region[0]))
70
84
 
71
85
  patterns = region.start_with?("(") ? [HTML_KEY] : HASH_KEYS
72
86
  inner = region[1...-1]
@@ -93,9 +107,9 @@ module Klenod
93
107
  end
94
108
 
95
109
  def diagnostic(key, tag_name, allowed)
96
- suggestion = DidYouMean::SpellChecker.new(dictionary: allowed).correct(key.name).first
97
- message = "Unknown prop #{key.name.inspect} for #{tag_name}"
98
- message += "; did you mean #{suggestion.inspect}?" if suggestion
110
+ suggestion = DidYouMean::SpellChecker.new(dictionary: allowed).correct(prop_name(key.name)).first
111
+ message = "Unknown prop `#{key.name}` for `#{tag_name}`"
112
+ message += "; did you mean `#{suggestion}`?" if suggestion
99
113
 
100
114
  Interface::Diagnostic.new(
101
115
  range: key.span.to_range,
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../../text"
4
4
  require_relative "../imports"
5
+ require_relative "classes"
5
6
  require_relative "ruby_regions"
6
7
 
7
8
  module Klenod
@@ -9,9 +10,9 @@ module Klenod
9
10
  module Languages
10
11
  class Haml
11
12
  # Where a bound constant appears in a Haml document: the `%Name` tags
12
- # and the whole-word uses inside Ruby, meaning `:ruby` filters, script
13
- # lines, tag attributes, and printed tag content. Plain text keeps
14
- # its words.
13
+ # outside filters and the whole-word uses inside Ruby, meaning `:ruby`
14
+ # filters, script lines, tag attributes, and printed tag content.
15
+ # Plain text keeps its words.
15
16
  module Rename
16
17
  module_function
17
18
 
@@ -25,7 +26,10 @@ module Klenod
25
26
  end
26
27
  end
27
28
 
29
+ filter_body = Classes.filter_body_lines(lines)
28
30
  lines.each_with_index do |line_text, index|
31
+ next if filter_body.include?(index)
32
+
29
33
  Text.each_match(line_text, index, Haml::COMPONENT_TAG, group: :name) do |match, span|
30
34
  next unless match[:name].split("::").first == name
31
35
 
@@ -9,7 +9,8 @@ module Klenod
9
9
  # Executable Ruby regions in a Haml document: Ruby filters, script
10
10
  # lines, tag attributes, and printed tag content.
11
11
  module RubyRegions
12
- TAG_HEAD = /%[A-Za-z][\w:-]*(?:[.#][\w-]+)*/
12
+ # `%name.class#id`, or `.class#id` for an implicit `%div`.
13
+ TAG_HEAD = /\A\s*(?:%[A-Za-z][\w:-]*(?:[.#][\w-]+)*|(?:[.#][\w-]+)+)/
13
14
 
14
15
  module_function
15
16
 
@@ -35,22 +36,50 @@ module Klenod
35
36
  when :script, :silent_script
36
37
  yield line_index, 0, lines[line_index].to_s
37
38
  when :tag
38
- tag(node, lines[line_index].to_s, line_index, &block)
39
+ tag(node, lines, line_index, &block)
39
40
  end
40
41
  each_node(node.children, lines, &block) unless node.type == :filter
41
42
  end
42
43
  end
43
44
 
44
- def tag(node, line_text, line_index)
45
+ def tag(node, lines, line_index, &block)
46
+ line_text = lines[line_index].to_s
45
47
  head = TAG_HEAD.match(line_text)
46
48
  return unless head
47
49
 
48
50
  rest_start = head.end(0)
49
51
  rest = line_text[rest_start..].to_s
52
+ regions = attribute_regions(rest)
50
53
  if node.value[:parse]
51
54
  yield line_index, rest_start, rest
52
55
  else
53
- attribute_regions(rest).each { |offset, text| yield line_index, rest_start + offset, text }
56
+ regions.each { |offset, text| yield line_index, rest_start + offset, text }
57
+ end
58
+ continuation(lines, line_index + 1, regions.last&.last, parse: node.value[:parse], &block)
59
+ end
60
+
61
+ # The lines of an attribute region left open on the tag line, up to
62
+ # and including its closer. A tag printing Ruby content keeps the
63
+ # rest of the closing line too: `}= value`.
64
+ def continuation(lines, line_index, region, parse: false)
65
+ return unless region
66
+
67
+ opener = region[0]
68
+ closer = {"{" => "}", "(" => ")", "[" => "]"}.fetch(opener)
69
+ depth = region.count(opener) - region.count(closer)
70
+ while depth.positive? && line_index < lines.length
71
+ line_text = lines[line_index].to_s
72
+ length = line_text.length
73
+ line_text.each_char.with_index do |char, index|
74
+ depth += 1 if char == opener
75
+ depth -= 1 if char == closer
76
+ next unless depth.zero?
77
+
78
+ length = index + 1 unless parse
79
+ break
80
+ end
81
+ yield line_index, 0, line_text[0...length]
82
+ line_index += 1
54
83
  end
55
84
  end
56
85
 
@@ -68,13 +68,22 @@ module Klenod
68
68
 
69
69
  # `%Details` compiles to the constant `Details`, bound by an import in
70
70
  # the leading `:ruby` filter such as `Details = import("/components/Details")`.
71
+ # Imports in other filters, such as code samples in `:markdown`, are
72
+ # text.
71
73
  def self.bindings(lines)
72
- Imports.bindings(lines)
74
+ text = Classes.filter_body_lines(lines, except: %w[ruby])
75
+ Imports.bindings(lines.reject.with_index { |_line_text, index| text.include?(index) })
76
+ end
77
+
78
+ def bindings(lines)
79
+ self.class.bindings(lines)
73
80
  end
74
81
 
75
82
  private
76
83
 
77
84
  def component_target_at(line_text, position, lines)
85
+ return nil if Classes.filter_body_lines(lines).include?(position.line)
86
+
78
87
  Text.each_match(line_text, position.line, COMPONENT_TAG, group: :name) do |match, span|
79
88
  next unless span.include?(position)
80
89
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "language_server-protocol"
4
+ require "prism"
4
5
 
5
6
  require "klenod/build/module_id"
6
7
 
@@ -116,6 +117,7 @@ module Klenod
116
117
  tokens = []
117
118
  slots = []
118
119
  Haml::RubyRegions.each(source, lines) do |_line_index, _start_character, ruby_source|
120
+ ruby_source = without_comment(ruby_source)
119
121
  tokens.concat(ruby_source.scan(PROP_TOKEN).flatten)
120
122
  slots.concat(ruby_source.scan(SLOT_TOKEN).flatten)
121
123
  end
@@ -126,6 +128,13 @@ module Klenod
126
128
  )
127
129
  end
128
130
 
131
+ # The Ruby before a trailing `# comment`, so commented-out code reads
132
+ # no props. Prism tells a comment from `#` inside a string.
133
+ def without_comment(ruby_source)
134
+ comment = Prism.lex(ruby_source).value.find { |token, _state| token.type == :COMMENT }&.first
135
+ comment ? ruby_source.byteslice(0, comment.location.start_offset) : ruby_source
136
+ end
137
+
129
138
  # Completion items for the path being typed inside an import literal,
130
139
  # or nil when the line prefix is not inside one.
131
140
  def completion_items(prefix, position, analysis, workspace, syntax: Syntax::Ruby)
@@ -209,6 +218,11 @@ module Klenod
209
218
  Syntax::Ruby
210
219
  end
211
220
 
221
+ # Constant names bound to imports in a document, keyed by name.
222
+ def bindings(lines)
223
+ Imports.bindings(lines)
224
+ end
225
+
212
226
  def definition(analysis, position, workspace, _index = nil)
213
227
  target = target_at(analysis, position)
214
228
  module_id = target && Imports.resolve(target, analysis, workspace)
@@ -367,11 +381,14 @@ module Klenod
367
381
  def usage_spans(importer_id, lines, specifiers)
368
382
  return [] unless importer_id.extname == ".haml"
369
383
 
370
- names = Imports.bindings(lines).select { |_name, specifier| specifiers.include?(specifier) }.keys
384
+ names = Languages::Haml.bindings(lines).select { |_name, specifier| specifiers.include?(specifier) }.keys
371
385
  return [] if names.empty?
372
386
 
373
387
  spans = []
388
+ filter_body = Languages::Haml::Classes.filter_body_lines(lines)
374
389
  lines.each_with_index do |line_text, index|
390
+ next if filter_body.include?(index)
391
+
375
392
  Text.each_match(line_text, index, Languages::Haml::COMPONENT_TAG, group: :name) do |match, span|
376
393
  spans << span if names.include?(match[:name].split("::").first)
377
394
  end
@@ -382,7 +399,7 @@ module Klenod
382
399
  # Bindings whose constant is never used in the file. Languages give
383
400
  # the spans a constant occupies, the binding included.
384
401
  def unused_binding_diagnostics(analysis)
385
- Imports.bindings(analysis.lines).filter_map do |name, _specifier|
402
+ bindings(analysis.lines).filter_map do |name, _specifier|
386
403
  spans = rename_spans(analysis, name)
387
404
  next if spans.length > 1
388
405
 
@@ -391,7 +408,7 @@ module Klenod
391
408
  range: span.to_range,
392
409
  severity: Imports::Constant::DiagnosticSeverity::WARNING,
393
410
  source: "klenod",
394
- message: "#{name} is imported but never used",
411
+ message: "`#{name}` is imported but never used",
395
412
  tags: [Imports::Constant::DiagnosticTag::UNNECESSARY]
396
413
  )
397
414
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Klenod
4
4
  module LSP
5
- VERSION = "0.0.30"
5
+ VERSION = "0.0.32"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: klenod-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.30
4
+ version: 0.0.32
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrés Alin
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.0.30
18
+ version: 0.0.32
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.0.30
25
+ version: 0.0.32
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: language_server-protocol
28
28
  requirement: !ruby/object:Gem::Requirement