puppet-editor-services 2.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +510 -0
- data/CODEOWNERS +2 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +54 -0
- data/Gemfile +53 -0
- data/LICENSE +201 -0
- data/README.md +308 -0
- data/Rakefile +185 -0
- data/bin/puppet-debugserver +8 -0
- data/bin/puppet-languageserver +7 -0
- data/bin/puppet-languageserver-sidecar +7 -0
- data/lib/dsp/dsp.rb +7 -0
- data/lib/dsp/dsp_base.rb +62 -0
- data/lib/dsp/dsp_protocol.rb +4619 -0
- data/lib/lsp/lsp.rb +10 -0
- data/lib/lsp/lsp_base.rb +63 -0
- data/lib/lsp/lsp_custom.rb +170 -0
- data/lib/lsp/lsp_enums.rb +143 -0
- data/lib/lsp/lsp_protocol.rb +2785 -0
- data/lib/lsp/lsp_protocol_callhierarchy.proposed.rb +239 -0
- data/lib/lsp/lsp_protocol_colorprovider.rb +100 -0
- data/lib/lsp/lsp_protocol_configuration.rb +82 -0
- data/lib/lsp/lsp_protocol_declaration.rb +73 -0
- data/lib/lsp/lsp_protocol_foldingrange.rb +129 -0
- data/lib/lsp/lsp_protocol_implementation.rb +75 -0
- data/lib/lsp/lsp_protocol_progress.rb +200 -0
- data/lib/lsp/lsp_protocol_selectionrange.rb +79 -0
- data/lib/lsp/lsp_protocol_sematictokens.proposed.rb +340 -0
- data/lib/lsp/lsp_protocol_typedefinition.rb +75 -0
- data/lib/lsp/lsp_protocol_workspacefolders.rb +174 -0
- data/lib/lsp/lsp_types.rb +1534 -0
- data/lib/puppet-debugserver/debug_session/break_points.rb +137 -0
- data/lib/puppet-debugserver/debug_session/flow_control.rb +161 -0
- data/lib/puppet-debugserver/debug_session/hook_handlers.rb +295 -0
- data/lib/puppet-debugserver/debug_session/puppet_session_run_mode.rb +66 -0
- data/lib/puppet-debugserver/debug_session/puppet_session_state.rb +122 -0
- data/lib/puppet-debugserver/hooks.rb +132 -0
- data/lib/puppet-debugserver/message_handler.rb +277 -0
- data/lib/puppet-debugserver/puppet_debug_session.rb +541 -0
- data/lib/puppet-debugserver/puppet_monkey_patches.rb +118 -0
- data/lib/puppet-languageserver/client_session_state.rb +119 -0
- data/lib/puppet-languageserver/crash_dump.rb +50 -0
- data/lib/puppet-languageserver/epp/validation_provider.rb +34 -0
- data/lib/puppet-languageserver/facter_helper.rb +25 -0
- data/lib/puppet-languageserver/global_queues/sidecar_queue.rb +205 -0
- data/lib/puppet-languageserver/global_queues/single_instance_queue.rb +126 -0
- data/lib/puppet-languageserver/global_queues/validation_queue.rb +102 -0
- data/lib/puppet-languageserver/global_queues.rb +16 -0
- data/lib/puppet-languageserver/manifest/completion_provider.rb +331 -0
- data/lib/puppet-languageserver/manifest/definition_provider.rb +99 -0
- data/lib/puppet-languageserver/manifest/document_symbol_provider.rb +228 -0
- data/lib/puppet-languageserver/manifest/folding_provider.rb +226 -0
- data/lib/puppet-languageserver/manifest/format_on_type_provider.rb +143 -0
- data/lib/puppet-languageserver/manifest/hover_provider.rb +221 -0
- data/lib/puppet-languageserver/manifest/signature_provider.rb +169 -0
- data/lib/puppet-languageserver/manifest/validation_provider.rb +127 -0
- data/lib/puppet-languageserver/message_handler.rb +462 -0
- data/lib/puppet-languageserver/providers.rb +18 -0
- data/lib/puppet-languageserver/puppet_helper.rb +108 -0
- data/lib/puppet-languageserver/puppet_lexer_helper.rb +55 -0
- data/lib/puppet-languageserver/puppet_monkey_patches.rb +39 -0
- data/lib/puppet-languageserver/puppet_parser_helper.rb +212 -0
- data/lib/puppet-languageserver/puppetfile/validation_provider.rb +185 -0
- data/lib/puppet-languageserver/server_capabilities.rb +48 -0
- data/lib/puppet-languageserver/session_state/document_store.rb +272 -0
- data/lib/puppet-languageserver/session_state/language_client.rb +239 -0
- data/lib/puppet-languageserver/session_state/object_cache.rb +162 -0
- data/lib/puppet-languageserver/sidecar_protocol.rb +532 -0
- data/lib/puppet-languageserver/uri_helper.rb +46 -0
- data/lib/puppet-languageserver-sidecar/cache/base.rb +36 -0
- data/lib/puppet-languageserver-sidecar/cache/filesystem.rb +111 -0
- data/lib/puppet-languageserver-sidecar/cache/null.rb +27 -0
- data/lib/puppet-languageserver-sidecar/facter_helper.rb +41 -0
- data/lib/puppet-languageserver-sidecar/puppet_environment_monkey_patches.rb +52 -0
- data/lib/puppet-languageserver-sidecar/puppet_helper.rb +281 -0
- data/lib/puppet-languageserver-sidecar/puppet_modulepath_monkey_patches.rb +146 -0
- data/lib/puppet-languageserver-sidecar/puppet_monkey_patches.rb +9 -0
- data/lib/puppet-languageserver-sidecar/puppet_parser_helper.rb +77 -0
- data/lib/puppet-languageserver-sidecar/puppet_strings_helper.rb +399 -0
- data/lib/puppet-languageserver-sidecar/puppet_strings_monkey_patches.rb +16 -0
- data/lib/puppet-languageserver-sidecar/sidecar_protocol_extensions.rb +16 -0
- data/lib/puppet-languageserver-sidecar/workspace.rb +89 -0
- data/lib/puppet_debugserver.rb +164 -0
- data/lib/puppet_editor_services/connection/base.rb +62 -0
- data/lib/puppet_editor_services/connection/stdio.rb +25 -0
- data/lib/puppet_editor_services/connection/tcp.rb +34 -0
- data/lib/puppet_editor_services/handler/base.rb +16 -0
- data/lib/puppet_editor_services/handler/debug_adapter.rb +63 -0
- data/lib/puppet_editor_services/handler/json_rpc.rb +133 -0
- data/lib/puppet_editor_services/logging.rb +45 -0
- data/lib/puppet_editor_services/protocol/base.rb +27 -0
- data/lib/puppet_editor_services/protocol/debug_adapter.rb +135 -0
- data/lib/puppet_editor_services/protocol/debug_adapter_messages.rb +171 -0
- data/lib/puppet_editor_services/protocol/json_rpc.rb +241 -0
- data/lib/puppet_editor_services/protocol/json_rpc_messages.rb +200 -0
- data/lib/puppet_editor_services/server/base.rb +42 -0
- data/lib/puppet_editor_services/server/stdio.rb +85 -0
- data/lib/puppet_editor_services/server/tcp.rb +349 -0
- data/lib/puppet_editor_services/server.rb +15 -0
- data/lib/puppet_editor_services/version.rb +36 -0
- data/lib/puppet_editor_services.rb +8 -0
- data/lib/puppet_languageserver.rb +263 -0
- data/lib/puppet_languageserver_sidecar.rb +361 -0
- data/puppet-debugserver +11 -0
- data/puppet-editor-services.gemspec +29 -0
- data/puppet-languageserver +15 -0
- data/puppet-languageserver-sidecar +14 -0
- metadata +240 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServer
|
|
4
|
+
module PuppetParserHelper
|
|
5
|
+
def self.remove_chars_starting_at(content, line_offsets, line_num, char_num, num_chars_to_remove)
|
|
6
|
+
line_offset = line_offsets[line_num]
|
|
7
|
+
raise if line_offset.nil?
|
|
8
|
+
|
|
9
|
+
# Remove the offending character
|
|
10
|
+
content.slice(0, line_offset + char_num - num_chars_to_remove) + content.slice(line_offset + char_num, content.length - num_chars_to_remove)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.remove_char_at(content, line_offsets, line_num, char_num)
|
|
14
|
+
remove_chars_starting_at(content, line_offsets, line_num, char_num, 1)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.get_char_at(content, line_offsets, line_num, char_num)
|
|
18
|
+
line_offset = line_offsets[line_num]
|
|
19
|
+
raise if line_offset.nil?
|
|
20
|
+
|
|
21
|
+
absolute_offset = line_offset + (char_num - 1)
|
|
22
|
+
|
|
23
|
+
content[absolute_offset]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.insert_text_at(content, line_offsets, line_num, char_num, text)
|
|
27
|
+
# Insert text after where the cursor is
|
|
28
|
+
# This helps due to syntax errors like `$facts[]` or `ensure =>`
|
|
29
|
+
line_offset = line_offsets[line_num]
|
|
30
|
+
raise if line_offset.nil?
|
|
31
|
+
|
|
32
|
+
# Insert the text
|
|
33
|
+
content.slice(0, line_offset + char_num) + text + content.slice(line_offset + char_num, content.length - 1)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.line_offsets(content)
|
|
37
|
+
# Calculate all of the offsets of \n in the file
|
|
38
|
+
line_offsets = [0]
|
|
39
|
+
line_offset = -1
|
|
40
|
+
loop do
|
|
41
|
+
line_offset = content.index("\n", line_offset + 1)
|
|
42
|
+
break if line_offset.nil?
|
|
43
|
+
|
|
44
|
+
line_offsets << (line_offset + 1)
|
|
45
|
+
end
|
|
46
|
+
line_offsets
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.get_line_at(content, line_offsets, line_num)
|
|
50
|
+
# Get the text of the designated line
|
|
51
|
+
start_index = line_offsets[line_num]
|
|
52
|
+
if line_offsets[line_num + 1].nil?
|
|
53
|
+
content.slice(start_index, content.length - start_index)
|
|
54
|
+
else
|
|
55
|
+
content.slice(start_index, line_offsets[line_num + 1] - start_index - 1)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.object_under_cursor(content, line_num, char_num, options)
|
|
60
|
+
options = {
|
|
61
|
+
multiple_attempts: false,
|
|
62
|
+
disallowed_classes: [],
|
|
63
|
+
tasks_mode: false,
|
|
64
|
+
remove_trigger_char: true
|
|
65
|
+
}.merge(options)
|
|
66
|
+
|
|
67
|
+
# Use Puppet to generate the AST
|
|
68
|
+
parser = Puppet::Pops::Parser::Parser.new
|
|
69
|
+
|
|
70
|
+
# Calculating the line offsets can be expensive and is only required
|
|
71
|
+
# if we're doing mulitple passes of parsing
|
|
72
|
+
line_offsets = line_offsets(content) if options[:multiple_attempts]
|
|
73
|
+
|
|
74
|
+
result = nil
|
|
75
|
+
move_offset = 0
|
|
76
|
+
%i[noop remove_word try_quotes try_quotes_and_comma remove_char].each do |method|
|
|
77
|
+
new_content = nil
|
|
78
|
+
case method
|
|
79
|
+
when :noop
|
|
80
|
+
new_content = content
|
|
81
|
+
when :remove_char
|
|
82
|
+
next if line_num.zero? && char_num.zero?
|
|
83
|
+
|
|
84
|
+
new_content = remove_char_at(content, line_offsets, line_num, char_num)
|
|
85
|
+
move_offset = -1
|
|
86
|
+
when :remove_word
|
|
87
|
+
next if line_num.zero? && char_num.zero?
|
|
88
|
+
|
|
89
|
+
next_char = get_char_at(content, line_offsets, line_num, char_num)
|
|
90
|
+
|
|
91
|
+
while /[[:word:]]/ =~ next_char
|
|
92
|
+
move_offset -= 1
|
|
93
|
+
next_char = get_char_at(content, line_offsets, line_num, char_num + move_offset)
|
|
94
|
+
|
|
95
|
+
break if char_num + move_offset < 0
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
new_content = remove_chars_starting_at(content, line_offsets, line_num, char_num, -move_offset)
|
|
99
|
+
when :try_quotes
|
|
100
|
+
# Perhaps try inserting double quotes. Useful in empty arrays or during variable assignment
|
|
101
|
+
# Grab the line up to the cursor character + 1
|
|
102
|
+
line = get_line_at(content, line_offsets, line_num).slice!(0, char_num + 1)
|
|
103
|
+
if line.strip.end_with?('=') || line.end_with?('[]') # rubocop:disable Style/IfUnlessModifier Nicer to read like this
|
|
104
|
+
new_content = insert_text_at(content, line_offsets, line_num, char_num, "''")
|
|
105
|
+
end
|
|
106
|
+
when :try_quotes_and_comma
|
|
107
|
+
# Perhaps try inserting double quotes with a comma. Useful resource properties and parameter assignments
|
|
108
|
+
# Grab the line up to the cursor character + 1
|
|
109
|
+
line = get_line_at(content, line_offsets, line_num).slice!(0, char_num + 1)
|
|
110
|
+
if line.strip.end_with?('=>') # rubocop:disable Style/IfUnlessModifier Nicer to read like this
|
|
111
|
+
new_content = insert_text_at(content, line_offsets, line_num, char_num, "'',")
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
raise("Unknown parsing method #{method}")
|
|
115
|
+
end
|
|
116
|
+
# if we have no content to parse, try the next method.
|
|
117
|
+
next if new_content.nil?
|
|
118
|
+
|
|
119
|
+
begin
|
|
120
|
+
result = parser.singleton_parse_string(new_content, options[:tasks_mode], '')
|
|
121
|
+
break
|
|
122
|
+
rescue Puppet::ParseErrorWithIssue
|
|
123
|
+
next if options[:multiple_attempts]
|
|
124
|
+
|
|
125
|
+
raise
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
raise('Unable to parse content') if result.nil?
|
|
129
|
+
|
|
130
|
+
# Convert line and char nums (base 0) to an absolute offset
|
|
131
|
+
# result.line_offsets contains an array of the offsets on a per line basis e.g.
|
|
132
|
+
# [0, 14, 34, 36] means line number 2 starts at absolute offset 34
|
|
133
|
+
# Once we know the line offset, we can simply add on the char_num to get the absolute offset
|
|
134
|
+
# If during paring we modified the source we may need to change the cursor location
|
|
135
|
+
line_offset = if result.respond_to?(:line_offsets)
|
|
136
|
+
result.line_offsets[line_num]
|
|
137
|
+
else
|
|
138
|
+
result['locator'].line_index[line_num]
|
|
139
|
+
end
|
|
140
|
+
abs_offset = line_offset + char_num + move_offset
|
|
141
|
+
# Typically we're completing after something was typed, so go back one char by default
|
|
142
|
+
abs_offset -= 1 if options[:remove_trigger_char]
|
|
143
|
+
|
|
144
|
+
# Enumerate the AST looking for items that span the line/char we want.
|
|
145
|
+
# Once we have all valid items, sort them by the smallest span. Typically the smallest span
|
|
146
|
+
# is the most specific object in the AST
|
|
147
|
+
#
|
|
148
|
+
# TODO: Should probably walk the AST and only look for the deepest child, but integer sorting
|
|
149
|
+
# is so much easier and faster.
|
|
150
|
+
model_path_locator_struct = Struct.new(:model, :path, :locator)
|
|
151
|
+
|
|
152
|
+
valid_models = []
|
|
153
|
+
if result.model.respond_to? :eAllContents
|
|
154
|
+
valid_models = result.model.eAllContents.select do |item|
|
|
155
|
+
check_for_valid_item(item, abs_offset, options[:disallowed_classes])
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
valid_models.sort! { |a, b| a.length - b.length }
|
|
159
|
+
else
|
|
160
|
+
path = []
|
|
161
|
+
result.model._pcore_all_contents(path) do |item|
|
|
162
|
+
if check_for_valid_item(item, abs_offset, options[:disallowed_classes]) # rubocop:disable Style/IfUnlessModifier Nicer to read like this
|
|
163
|
+
valid_models.push(model_path_locator_struct.new(item, path.dup))
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
valid_models.sort! { |a, b| a[:model].length - b[:model].length }
|
|
168
|
+
end
|
|
169
|
+
# nil means the root of the document
|
|
170
|
+
return nil if valid_models.empty?
|
|
171
|
+
|
|
172
|
+
response = valid_models[0]
|
|
173
|
+
|
|
174
|
+
if response.respond_to? :eAllContents # rubocop:disable Style/IfUnlessModifier Nicer to read like this
|
|
175
|
+
response = model_path_locator_struct.new(response, construct_path(response))
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
response.locator = result.model.locator
|
|
179
|
+
response
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def self.construct_path(item)
|
|
183
|
+
path = []
|
|
184
|
+
item = item.eContainer
|
|
185
|
+
while item.class != Puppet::Pops::Model::Program
|
|
186
|
+
path.unshift item
|
|
187
|
+
item = item.eContainer
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
path
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def self.check_for_valid_item(item, abs_offset, disallowed_classes)
|
|
194
|
+
item.respond_to?(:offset) && !item.offset.nil? && !item.length.nil? && abs_offset >= item.offset && abs_offset <= item.offset + item.length && !disallowed_classes.include?(item.class)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# This method is only required during development or debugging. Visualising the AST tree can be difficult
|
|
198
|
+
# so this method just prints it to the console.
|
|
199
|
+
# def self.recurse_showast(item, abs_offset, disallowed_classes, depth = 0)
|
|
200
|
+
# output = " " * depth
|
|
201
|
+
# output += check_for_valid_item(item, abs_offset, disallowed_classes) ? 'X ' : ' '
|
|
202
|
+
# output += "#{item.class.to_s} (#{item.object_id})"
|
|
203
|
+
# if item.respond_to?(:offset)
|
|
204
|
+
# output += " (Off-#{item.offset}:#{item.offset + item.length} Pos-#{item.line}:#{item.pos} Len-#{item.length}) ~#{item.locator.extract_text(item.offset, item.length)}~"
|
|
205
|
+
# end
|
|
206
|
+
# puts output
|
|
207
|
+
# item._pcore_contents do |child|
|
|
208
|
+
# recurse_showast(child, abs_offset, disallowed_classes, depth + 1)
|
|
209
|
+
# end
|
|
210
|
+
# end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lsp/lsp'
|
|
4
|
+
|
|
5
|
+
module PuppetLanguageServer
|
|
6
|
+
module Puppetfile
|
|
7
|
+
module ValidationProvider
|
|
8
|
+
def self.max_line_length
|
|
9
|
+
# TODO: ... need to figure out the actual line length
|
|
10
|
+
1000
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.validate(content, options = {})
|
|
14
|
+
options = {
|
|
15
|
+
max_problems: 100,
|
|
16
|
+
resolve_puppetfile: true,
|
|
17
|
+
module_path: [],
|
|
18
|
+
document_uri: '???'
|
|
19
|
+
}.merge(options)
|
|
20
|
+
|
|
21
|
+
result = []
|
|
22
|
+
# TODO: Need to implement max_problems
|
|
23
|
+
_problems = 0
|
|
24
|
+
|
|
25
|
+
require 'puppetfile-resolver'
|
|
26
|
+
require 'puppetfile-resolver/puppetfile/parser/r10k_eval'
|
|
27
|
+
parser = PuppetfileResolver::Puppetfile::Parser::R10KEval
|
|
28
|
+
|
|
29
|
+
# Attempt to parse the file
|
|
30
|
+
puppetfile = nil
|
|
31
|
+
begin
|
|
32
|
+
puppetfile = parser.parse(content)
|
|
33
|
+
rescue PuppetfileResolver::Puppetfile::Parser::ParserError => e
|
|
34
|
+
result << LSP::Diagnostic.new(
|
|
35
|
+
'severity' => LSP::DiagnosticSeverity::ERROR,
|
|
36
|
+
'range' => document_location_to_lsp_range(e.location),
|
|
37
|
+
'source' => 'Puppet',
|
|
38
|
+
'message' => e.to_s
|
|
39
|
+
)
|
|
40
|
+
puppetfile = nil
|
|
41
|
+
end
|
|
42
|
+
return result if puppetfile.nil?
|
|
43
|
+
|
|
44
|
+
puppetfile.validation_errors.each do |validation_error|
|
|
45
|
+
related_information = nil
|
|
46
|
+
|
|
47
|
+
if validation_error.is_a?(PuppetfileResolver::Puppetfile::DocumentDuplicateModuleError)
|
|
48
|
+
related_information = validation_error.duplicates.map do |dup_mod|
|
|
49
|
+
{
|
|
50
|
+
'location' => {
|
|
51
|
+
'uri' => options[:document_uri],
|
|
52
|
+
'range' => document_location_to_lsp_range(dup_mod.location)
|
|
53
|
+
},
|
|
54
|
+
'message' => validation_error.message
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
result << LSP::Diagnostic.new(
|
|
60
|
+
'severity' => LSP::DiagnosticSeverity::ERROR,
|
|
61
|
+
'range' => document_location_to_lsp_range(validation_error.puppet_module.location),
|
|
62
|
+
'source' => 'Puppet',
|
|
63
|
+
'message' => validation_error.message,
|
|
64
|
+
'relatedInformation' => related_information
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
return result unless options[:resolve_puppetfile] && puppetfile.valid?
|
|
69
|
+
|
|
70
|
+
result + validate_resolution(puppetfile, options[:document_uri], resolver_cache, options[:module_path], options[:puppet_version])
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.validate_resolution(puppetfile_document, document_uri, cache, module_path, puppet_version)
|
|
74
|
+
ui = nil
|
|
75
|
+
resolver = PuppetfileResolver::Resolver.new(puppetfile_document, puppet_version)
|
|
76
|
+
opts = { cache: cache, ui: ui, module_paths: module_path, allow_missing_modules: true }
|
|
77
|
+
begin
|
|
78
|
+
resolution = resolver.resolve(opts)
|
|
79
|
+
rescue PuppetfileResolver::Puppetfile::DocumentVersionConflictError,
|
|
80
|
+
PuppetfileResolver::Puppetfile::DocumentCircularDependencyError => e
|
|
81
|
+
return [document_error_to_diagnostic(document_uri, e)]
|
|
82
|
+
rescue PuppetfileResolver::Puppetfile::DocumentResolveError => e
|
|
83
|
+
return [LSP::Diagnostic.new(
|
|
84
|
+
'severity' => LSP::DiagnosticSeverity::ERROR,
|
|
85
|
+
'range' => LSP.create_range(0, 0, 0, max_line_length),
|
|
86
|
+
'source' => 'Puppet',
|
|
87
|
+
'message' => e.message
|
|
88
|
+
)]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
resolution.validation_errors.map do |error|
|
|
92
|
+
severity = case error
|
|
93
|
+
when PuppetfileResolver::Puppetfile::DocumentLatestVersionError
|
|
94
|
+
LSP::DiagnosticSeverity::INFORMATION
|
|
95
|
+
when PuppetfileResolver::Puppetfile::DocumentMissingModuleError
|
|
96
|
+
LSP::DiagnosticSeverity::HINT
|
|
97
|
+
else
|
|
98
|
+
LSP::DiagnosticSeverity::ERROR
|
|
99
|
+
end
|
|
100
|
+
LSP::Diagnostic.new(
|
|
101
|
+
'severity' => severity,
|
|
102
|
+
'range' => document_location_to_lsp_range(error.puppet_module.location),
|
|
103
|
+
'source' => 'Puppet',
|
|
104
|
+
'message' => error.message
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.find_dependencies(content)
|
|
110
|
+
require 'puppetfile-resolver'
|
|
111
|
+
require 'puppetfile-resolver/puppetfile/parser/r10k_eval'
|
|
112
|
+
parser = PuppetfileResolver::Puppetfile::Parser::R10KEval
|
|
113
|
+
|
|
114
|
+
result = []
|
|
115
|
+
puppetfile = parser.parse(content)
|
|
116
|
+
|
|
117
|
+
return result if puppetfile.nil?
|
|
118
|
+
|
|
119
|
+
raise 'Puppetfile is not valid' unless puppetfile.valid?
|
|
120
|
+
|
|
121
|
+
puppetfile.modules.select { |d| d.module_type == :forge }.each do |dep|
|
|
122
|
+
result << {
|
|
123
|
+
name: dep.name,
|
|
124
|
+
title: dep.title,
|
|
125
|
+
owner: dep.owner,
|
|
126
|
+
version: dep.version.to_s,
|
|
127
|
+
start_line: dep.location.start_line,
|
|
128
|
+
end_line: dep.location.end_line
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
result
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def self.resolver_cache
|
|
136
|
+
return @resolver_cache unless @resolver_cache.nil?
|
|
137
|
+
|
|
138
|
+
require 'puppetfile-resolver/cache/base'
|
|
139
|
+
# TODO: The cache should probably not cache local module information though
|
|
140
|
+
# Share a cache between resolution calls to speed-up lookups
|
|
141
|
+
@resolver_cache = PuppetfileResolver::Cache::Base.new(nil)
|
|
142
|
+
end
|
|
143
|
+
private_class_method :resolver_cache
|
|
144
|
+
|
|
145
|
+
def self.document_error_to_diagnostic(document_uri, error)
|
|
146
|
+
if error.puppetfile_modules.count.zero?
|
|
147
|
+
return LSP::Diagnostic.new(
|
|
148
|
+
'severity' => LSP::DiagnosticSeverity::ERROR,
|
|
149
|
+
'range' => LSP.create_range(0, 0, 0, max_line_length),
|
|
150
|
+
'source' => 'Puppet',
|
|
151
|
+
'message' => error.message
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
related_information = error.puppetfile_modules.slice(1..-1).map do |dup_mod|
|
|
156
|
+
{
|
|
157
|
+
'location' => {
|
|
158
|
+
'uri' => document_uri,
|
|
159
|
+
'range' => document_location_to_lsp_range(dup_mod.location)
|
|
160
|
+
},
|
|
161
|
+
'message' => "Module definition for #{dup_mod.name}"
|
|
162
|
+
}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
LSP::Diagnostic.new(
|
|
166
|
+
'severity' => LSP::DiagnosticSeverity::ERROR,
|
|
167
|
+
'range' => document_location_to_lsp_range(error.puppetfile_modules[0].location),
|
|
168
|
+
'source' => 'Puppet',
|
|
169
|
+
'message' => error.message,
|
|
170
|
+
'relatedInformation' => related_information.empty? ? nil : related_information
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
private_class_method :document_error_to_diagnostic
|
|
174
|
+
|
|
175
|
+
def self.document_location_to_lsp_range(location)
|
|
176
|
+
start_line = location.start_line
|
|
177
|
+
start_char = location.start_char.nil? ? 0 : location.start_char
|
|
178
|
+
end_line = location.end_line
|
|
179
|
+
end_char = location.end_char.nil? ? max_line_length : location.end_char
|
|
180
|
+
LSP.create_range(start_line, start_char, end_line, end_char)
|
|
181
|
+
end
|
|
182
|
+
private_class_method :document_location_to_lsp_range
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lsp/lsp'
|
|
4
|
+
|
|
5
|
+
module PuppetLanguageServer
|
|
6
|
+
module ServerCapabilites
|
|
7
|
+
def self.folding_provider_supported?
|
|
8
|
+
@folding_provider ||= PuppetLanguageServer::Manifest::FoldingProvider.supported?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.capabilities(options = {})
|
|
12
|
+
# https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialize-request
|
|
13
|
+
|
|
14
|
+
value = {
|
|
15
|
+
'textDocumentSync' => LSP::TextDocumentSyncKind::FULL,
|
|
16
|
+
'hoverProvider' => true,
|
|
17
|
+
'completionProvider' => {
|
|
18
|
+
'resolveProvider' => true,
|
|
19
|
+
'triggerCharacters' => ['>', '$', '[', '=']
|
|
20
|
+
},
|
|
21
|
+
'definitionProvider' => true,
|
|
22
|
+
'documentSymbolProvider' => true,
|
|
23
|
+
'workspaceSymbolProvider' => true,
|
|
24
|
+
'signatureHelpProvider' => {
|
|
25
|
+
'triggerCharacters' => ['(', ',']
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
value['documentOnTypeFormattingProvider'] = document_on_type_formatting_options if options[:documentOnTypeFormattingProvider]
|
|
29
|
+
value['foldingRangeProvider'] = folding_range_provider_options if options[:foldingRangeProvider]
|
|
30
|
+
value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.document_on_type_formatting_options
|
|
34
|
+
{
|
|
35
|
+
'firstTriggerCharacter' => '>'
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.folding_range_provider_options
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.no_capabilities
|
|
44
|
+
# Any empty hash denotes no capabilities at all
|
|
45
|
+
{}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|