klenod-lsp 0.0.16 → 0.0.18

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: 6c19da186ca2391c2bec38dfa3aa24e17458b7925d9dbb92b3f444e5782297a3
4
- data.tar.gz: dfda5e93c14feb280fb601d1f0234e5f9c25718e9f30eb644845911e3ebfda6e
3
+ metadata.gz: d759a6c56f2fae5f3fc2508aefc25cbb2c710728b6b25e9967b5f981931d26bc
4
+ data.tar.gz: eba78d09c5055a0a93b8daa7433b836e83c32d95832f7dca444285cbf683b9e1
5
5
  SHA512:
6
- metadata.gz: 427d0cc60eaaafcc8418d3456e23f610806ad12327a56e9ae1f8ab0999c016c6bacc2c22c15cd6ff905e0e18895826218b109c6b851e0b581f5ea232e117f239
7
- data.tar.gz: b0b4fac3a4632a9db230f2089b6395a650971cfef7de75b7b7773ac828ad5c3640c571fb924e5dd191c3f0f43ed732f9ce5be36c254f4c2d9651b5382f913a5f
6
+ metadata.gz: 987861aafeb188197a09e899ab5258c75a6655b65a0cf14da361217dd6dabec5c64cc1baf92df64e9562ef71dbf5d5598d13474928596d271d3c501b78d542c9
7
+ data.tar.gz: cf9bfad79548f6ce194490cd26c4524918fe25033c381d0e80078d24918315e5a439415849b8a44e0f0e62648c856b454cd40b23b7b7be2f30c7fdb91e604af9
@@ -8,7 +8,7 @@ require_relative "text"
8
8
  module Klenod
9
9
  module LSP
10
10
  # Turns the errors in an Analysis into LSP diagnostics on the original
11
- # document. Every diagnostic is an error: Klenod has no warnings yet.
11
+ # document.
12
12
  module Diagnostics
13
13
  Interface = LanguageServer::Protocol::Interface
14
14
  Constant = LanguageServer::Protocol::Constant
@@ -58,6 +58,29 @@ module Klenod
58
58
  }.uniq { |diagnostic| [diagnostic.range.start.line, diagnostic.message] }
59
59
  end
60
60
 
61
+ # A failure the graph index recorded while collecting this module that
62
+ # analysis alone cannot see, such as a named import of a constant the
63
+ # target does not define: analysis only transforms and resolves, it never
64
+ # collects the target's record. The index refreshes on save, so the
65
+ # diagnostic is only shown while the document still contains the import
66
+ # it refers to.
67
+ def index_failure(analysis, index, syntax: Languages::Syntax::Ruby)
68
+ error = index&.failed&.[](analysis.module_id.to_s)
69
+ return [] unless error.is_a?(Klenod::Build::MissingExportError)
70
+ return [] unless error.module_id.to_s == analysis.module_id.to_s
71
+
72
+ lines = analysis.lines
73
+ report = error.cause
74
+ span = literal_spans(report.dependency.specifier.to_s, lines, syntax: syntax).find do |candidate|
75
+ named_import_span?(candidate, report.name, lines)
76
+ end
77
+ return [] unless span
78
+
79
+ message = "#{error.kind}: #{error.detail}"
80
+ message = "#{message}\n#{error.hints.join("\n")}" unless error.hints.empty?
81
+ [diagnostic(span, message)]
82
+ end
83
+
61
84
  def resolve_errors(analysis, lines, syntax)
62
85
  analysis.resolve_errors.map do |error|
63
86
  diagnostic(resolve_error_span(error, lines, syntax), error.message)
@@ -93,10 +116,26 @@ module Klenod
93
116
  spans
94
117
  end
95
118
 
119
+ # The same module can be imported more than once with different names.
120
+ # Match the name immediately following this literal rather than selecting
121
+ # the first matching specifier and inspecting its whole line.
122
+ def named_import_span?(span, name, lines)
123
+ suffix = lines[span.start.line].to_s[span.end.character..]
124
+ suffix&.match?(%r{\A["']\s*,\s*:#{Regexp.escape(name.to_s)}(?=\s*\))})
125
+ end
126
+
96
127
  def diagnostic(span, message)
128
+ diagnostic_with_severity(span, message, Constant::DiagnosticSeverity::ERROR)
129
+ end
130
+
131
+ def warning(span, message)
132
+ diagnostic_with_severity(span, message, Constant::DiagnosticSeverity::WARNING)
133
+ end
134
+
135
+ def diagnostic_with_severity(span, message, severity)
97
136
  Interface::Diagnostic.new(
98
137
  range: span.to_range,
99
- severity: Constant::DiagnosticSeverity::ERROR,
138
+ severity: severity,
100
139
  source: SOURCE,
101
140
  message: message
102
141
  )
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Klenod
4
+ module LSP
5
+ module Languages
6
+ class Haml
7
+ # Detects scripts whose nested Haml will not produce output. Haml's
8
+ # parser retains the body as children of the script node, which avoids
9
+ # guessing from indentation or warning about ordinary Ruby source in
10
+ # a filter.
11
+ module Iteration
12
+ module_function
13
+
14
+ ITERATOR = /\.(?<method>each|map)\b(?:\s*\([^\n]*\))?\s+do\b/
15
+
16
+ def diagnostics(analysis)
17
+ root = Klenod::Build::Plugins::HamlPlugin.parse_haml(analysis.source, module_id: analysis.module_id)
18
+ nodes = root.children.dup
19
+ diagnostics = []
20
+
21
+ until nodes.empty?
22
+ node = nodes.shift
23
+ nodes.concat(node.children)
24
+ case node.type
25
+ when :script
26
+ match = ITERATOR.match(node.value.fetch(:text))
27
+ diagnostics << each_diagnostic(node, match, analysis.lines) if match&.[](:method) == "each" && rendered_content?(node)
28
+ when :silent_script
29
+ diagnostics << silent_script_diagnostic(node, analysis.lines) if rendered_content?(node)
30
+ end
31
+ end
32
+
33
+ diagnostics
34
+ rescue Klenod::Build::Plugins::HamlPlugin::ParseError
35
+ []
36
+ end
37
+
38
+ def rendered_content?(node)
39
+ node.children.any? do |child|
40
+ [:plain, :script, :tag].include?(child.type) || (child.type == :filter && child.value.fetch(:name) != "ruby") || rendered_content?(child)
41
+ end
42
+ end
43
+
44
+ def each_diagnostic(node, match, lines)
45
+ line = node.line - 1
46
+ line_text = lines.fetch(line, "")
47
+ method_start = line_text.index(".#{match[:method]}") || 0
48
+ span = Text::Span.new(line, method_start, method_start + match[:method].length + 1)
49
+
50
+ Diagnostics.warning(span, "`= ...each do` returns the original collection, not the rendered Haml children; use `map` instead")
51
+ end
52
+
53
+ def silent_script_diagnostic(node, lines)
54
+ line = node.line - 1
55
+ line_text = lines.fetch(line, "")
56
+ start_character = line_text.index("-") || 0
57
+ span = Text::Span.new(line, start_character, start_character + 1)
58
+
59
+ Diagnostics.warning(span, "A silent `-` script discards its nested Haml content; use `=` when it should render")
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -6,6 +6,7 @@ require_relative "../text"
6
6
  require_relative "imports"
7
7
  require_relative "haml/classes"
8
8
  require_relative "haml/completion"
9
+ require_relative "haml/iteration"
9
10
  require_relative "haml/props"
10
11
  require_relative "haml/rename"
11
12
 
@@ -23,9 +24,12 @@ module Klenod
23
24
 
24
25
  def diagnostics(analysis, workspace = nil, index = nil)
25
26
  diagnostics = Diagnostics.for_analysis(analysis)
27
+ diagnostics.concat(Diagnostics.index_failure(analysis, index))
26
28
  diagnostics.concat(Classes.diagnostics(analysis, workspace, index)) if workspace && index
27
29
  diagnostics.concat(Props.diagnostics(analysis, workspace, index)) if workspace && index
28
30
  diagnostics.concat(unused_binding_diagnostics(analysis))
31
+ diagnostics.concat(Iteration.diagnostics(analysis)) unless analysis.build_error
32
+ diagnostics
29
33
  end
30
34
 
31
35
  def completion(analysis, position, workspace, index = nil)
@@ -15,8 +15,8 @@ module Klenod
15
15
 
16
16
  Interface = LanguageServer::Protocol::Interface
17
17
 
18
- def diagnostics(analysis, _workspace = nil, _index = nil)
19
- Diagnostics.for_analysis(analysis) + unused_binding_diagnostics(analysis)
18
+ def diagnostics(analysis, _workspace = nil, index = nil)
19
+ Diagnostics.for_analysis(analysis) + Diagnostics.index_failure(analysis, index) + unused_binding_diagnostics(analysis)
20
20
  end
21
21
 
22
22
  def document_symbols(analysis)
@@ -371,11 +371,11 @@ module Klenod
371
371
 
372
372
  @pending_collections.delete(document.uri)&.stop
373
373
  @pending_collections[document.uri] =
374
- @task.async do |task|
375
- task.sleep(DIAGNOSTICS_DEBOUNCE)
374
+ @task.async do
375
+ Kernel.sleep(DIAGNOSTICS_DEBOUNCE)
376
376
  publish_diagnostics(document)
377
377
  overlay(document)
378
- task.sleep(COLLECTION_DEBOUNCE - DIAGNOSTICS_DEBOUNCE)
378
+ Kernel.sleep(COLLECTION_DEBOUNCE - DIAGNOSTICS_DEBOUNCE)
379
379
  @pending_collections.delete(document.uri)
380
380
  @index.ensure_collected(document.module_id, force: true)
381
381
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Klenod
4
4
  module LSP
5
- VERSION = "0.0.16"
5
+ VERSION = "0.0.18"
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.16
4
+ version: 0.0.18
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.16
18
+ version: 0.0.18
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.16
25
+ version: 0.0.18
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: language_server-protocol
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -57,6 +57,7 @@ files:
57
57
  - lib/klenod/lsp/languages/haml.rb
58
58
  - lib/klenod/lsp/languages/haml/classes.rb
59
59
  - lib/klenod/lsp/languages/haml/completion.rb
60
+ - lib/klenod/lsp/languages/haml/iteration.rb
60
61
  - lib/klenod/lsp/languages/haml/props.rb
61
62
  - lib/klenod/lsp/languages/haml/rename.rb
62
63
  - lib/klenod/lsp/languages/haml/ruby_regions.rb