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,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'puppet-languageserver/puppet_lexer_helper'
|
|
4
|
+
require 'lsp/lsp'
|
|
5
|
+
|
|
6
|
+
module PuppetLanguageServer
|
|
7
|
+
module Manifest
|
|
8
|
+
class FoldingProvider
|
|
9
|
+
class << self
|
|
10
|
+
def instance
|
|
11
|
+
@instance ||= new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def supported?
|
|
15
|
+
# Folding is only supported on Puppet 6.3.0 and above
|
|
16
|
+
# Requires - https://github.com/puppetlabs/puppet/commit/6d375ab4d735779031d49ab8631bd9d161a9c3e3
|
|
17
|
+
@supported ||= Gem::Version.new(Puppet.version) >= Gem::Version.new('6.3.0')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
REGION_NONE = nil
|
|
22
|
+
REGION_COMMENT = 'comment'
|
|
23
|
+
REGION_REGION = 'region'
|
|
24
|
+
|
|
25
|
+
def start_region?(text)
|
|
26
|
+
!(text =~ /^#\s*region\b/).nil?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def end_region?(text)
|
|
30
|
+
!(text =~ /^#\s*endregion\b/).nil?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def folding_ranges(tokens, show_last_line = false)
|
|
34
|
+
return nil unless self.class.supported?
|
|
35
|
+
|
|
36
|
+
ranges = {}
|
|
37
|
+
|
|
38
|
+
brace_stack = []
|
|
39
|
+
brack_stack = []
|
|
40
|
+
comment_stack = []
|
|
41
|
+
|
|
42
|
+
index = 0
|
|
43
|
+
until index > tokens.length - 1
|
|
44
|
+
case tokens[index][0]
|
|
45
|
+
# Find comments
|
|
46
|
+
when :TOKEN_COMMENT
|
|
47
|
+
if block_comment?(index, tokens)
|
|
48
|
+
comment = tokens[index][1].locator.extract_text(tokens[index][1].offset, tokens[index][1].length)
|
|
49
|
+
if start_region?(comment) # rubocop:disable Metrics/BlockNesting
|
|
50
|
+
comment_stack.push(tokens[index][1])
|
|
51
|
+
elsif end_region?(comment) && !comment_stack.empty? # rubocop:disable Metrics/BlockNesting
|
|
52
|
+
add_range!(create_range_span_tokens(comment_stack.pop, tokens[index][1], REGION_REGION), ranges)
|
|
53
|
+
else
|
|
54
|
+
index = process_block_comment!(index, tokens, ranges)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Find old style comments /* -> */
|
|
59
|
+
when :TOKEN_MLCOMMENT
|
|
60
|
+
add_range!(create_range_whole_token(tokens[index][1], REGION_COMMENT), ranges)
|
|
61
|
+
|
|
62
|
+
# Find matching braces { -> } and select brace ?{ -> }
|
|
63
|
+
when :LBRACE, :SELBRACE
|
|
64
|
+
brace_stack.push(tokens[index][1])
|
|
65
|
+
when :RBRACE
|
|
66
|
+
add_range!(create_range_span_tokens(brace_stack.pop, tokens[index][1], REGION_NONE), ranges) unless brace_stack.empty?
|
|
67
|
+
|
|
68
|
+
# Find matching braces [ -> ], list and index
|
|
69
|
+
when :LISTSTART, :LBRACK
|
|
70
|
+
brack_stack.push(tokens[index][1])
|
|
71
|
+
when :RBRACK
|
|
72
|
+
add_range!(create_range_span_tokens(brack_stack.pop, tokens[index][1], REGION_NONE), ranges) unless brack_stack.empty?
|
|
73
|
+
|
|
74
|
+
# Find matching Heredoc and heredoc sublocations
|
|
75
|
+
when :HEREDOC
|
|
76
|
+
# Need to check if the next token is :SUBLOCATE
|
|
77
|
+
if index < tokens.length - 2 && tokens[index + 1][0] == :SUBLOCATE # rubocop:disable Style/IfUnlessModifier
|
|
78
|
+
add_range!(create_range_heredoc(tokens[index][1], tokens[index + 1][1], REGION_NONE), ranges)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
index += 1
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# If we are showing the last line then decrement the EndLine by one, if possible
|
|
86
|
+
if show_last_line
|
|
87
|
+
ranges.values.each do |range|
|
|
88
|
+
range.endLine = [range.startLine, range.endLine - 1].max
|
|
89
|
+
range.endCharacter = 0 # We don't know where the previous line actually ends so set it to zero
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
ranges.values
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
# region Internal Helper methods to call locator methods on Locators or SubLocators
|
|
99
|
+
def line_for_offset(token, offset = nil)
|
|
100
|
+
locator_method_with_offset(token, :line_for_offset, offset || token.offset)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def pos_on_line(token, offset = nil)
|
|
104
|
+
locator_method_with_offset(token, :pos_on_line, offset || token.offset)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def locator_method_with_offset(token, method_name, offset)
|
|
108
|
+
if token.locator.is_a?(Puppet::Pops::Parser::Locator::SubLocator)
|
|
109
|
+
global_offset, = token.locator.to_global(offset, token.length)
|
|
110
|
+
token.locator.locator.send(method_name, global_offset)
|
|
111
|
+
else
|
|
112
|
+
token.locator.send(method_name, offset)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def extract_text(token)
|
|
117
|
+
if token.locator.is_a?(Puppet::Pops::Parser::Locator::SubLocator)
|
|
118
|
+
global_offset, global_length = token.locator.to_global(token.offset, token.length)
|
|
119
|
+
token.locator.locator.extract_text(global_offset, global_length)
|
|
120
|
+
else
|
|
121
|
+
token.locator.extract_text(token.offset, token.length)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
# endregion
|
|
125
|
+
|
|
126
|
+
# Return nil if not valid range
|
|
127
|
+
def create_range_span_tokens(start_token, end_token, kind)
|
|
128
|
+
start_line = line_for_offset(start_token) - 1
|
|
129
|
+
end_line = line_for_offset(end_token) - 1
|
|
130
|
+
return nil if start_line == end_line
|
|
131
|
+
|
|
132
|
+
LSP::FoldingRange.new({
|
|
133
|
+
'startLine' => start_line,
|
|
134
|
+
'startCharacter' => pos_on_line(start_token) - 1,
|
|
135
|
+
'endLine' => end_line,
|
|
136
|
+
'endCharacter' => pos_on_line(end_token, end_token.offset + end_token.length) - 1,
|
|
137
|
+
'kind' => kind
|
|
138
|
+
})
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Return nil if not valid range
|
|
142
|
+
def create_range_whole_token(token, kind)
|
|
143
|
+
start_line = line_for_offset(token) - 1
|
|
144
|
+
end_line = line_for_offset(token, token.offset + token.length) - 1
|
|
145
|
+
return nil if start_line == end_line
|
|
146
|
+
|
|
147
|
+
LSP::FoldingRange.new({
|
|
148
|
+
'startLine' => start_line,
|
|
149
|
+
'startCharacter' => pos_on_line(token) - 1,
|
|
150
|
+
'endLine' => end_line,
|
|
151
|
+
'endCharacter' => pos_on_line(token, token.offset + token.length) - 1,
|
|
152
|
+
'kind' => kind
|
|
153
|
+
})
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Return nil if not valid range
|
|
157
|
+
def create_range_heredoc(heredoc_token, subloc_token, kind)
|
|
158
|
+
start_line = line_for_offset(heredoc_token) - 1
|
|
159
|
+
# The lexer does not output the end heredoc_token. Instead we
|
|
160
|
+
# use the heredoc sublocator endline and add one
|
|
161
|
+
end_line = line_for_offset(heredoc_token, heredoc_token.offset + heredoc_token.length + subloc_token.length)
|
|
162
|
+
return nil if start_line == end_line
|
|
163
|
+
|
|
164
|
+
LSP::FoldingRange.new({
|
|
165
|
+
'startLine' => start_line,
|
|
166
|
+
'startCharacter' => pos_on_line(heredoc_token) - 1,
|
|
167
|
+
'endLine' => end_line,
|
|
168
|
+
# We don't know where the end token for the Heredoc is, so just assume it's at the start of the line
|
|
169
|
+
'endCharacter' => 0,
|
|
170
|
+
'kind' => kind
|
|
171
|
+
})
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Adds a FoldingReference to the list and enforces ordering rules e.g. Only one fold per start line
|
|
175
|
+
def add_range!(range, ranges)
|
|
176
|
+
# Make sure the arguments are correct
|
|
177
|
+
return nil if range.nil? || ranges.nil?
|
|
178
|
+
|
|
179
|
+
# Ignore the range if there is an existing one which is bigger
|
|
180
|
+
return nil unless ranges[range.startLine].nil? || ranges[range.startLine].endLine < range.endLine
|
|
181
|
+
|
|
182
|
+
ranges[range.startLine] = range
|
|
183
|
+
nil
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Returns new index position
|
|
187
|
+
def process_block_comment!(index, tokens, ranges)
|
|
188
|
+
start_index = index
|
|
189
|
+
line_num = line_for_offset(tokens[index][1])
|
|
190
|
+
while index < tokens.length - 2
|
|
191
|
+
break unless tokens[index + 1][0] == :TOKEN_COMMENT
|
|
192
|
+
|
|
193
|
+
next_line = line_for_offset(tokens[index + 1][1])
|
|
194
|
+
# Tokens must be on contiguous lines
|
|
195
|
+
break unless next_line == line_num + 1
|
|
196
|
+
|
|
197
|
+
# Must not be a region comment
|
|
198
|
+
comment = extract_text(tokens[index + 1][1])
|
|
199
|
+
break if start_region?(comment) || end_region?(comment)
|
|
200
|
+
|
|
201
|
+
# It's a block comment
|
|
202
|
+
line_num = next_line
|
|
203
|
+
index += 1
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
return index if start_index == index
|
|
207
|
+
|
|
208
|
+
add_range!(create_range_span_tokens(tokens[start_index][1], tokens[index][1], REGION_COMMENT), ranges)
|
|
209
|
+
index
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def block_comment?(index, tokens)
|
|
213
|
+
# Has to be a comment token
|
|
214
|
+
return false unless tokens[index][0] == :TOKEN_COMMENT
|
|
215
|
+
# If it's the first token then it has to be at the start of a line
|
|
216
|
+
return true if index.zero?
|
|
217
|
+
|
|
218
|
+
# It has to be the first token on this line
|
|
219
|
+
this_token_line = line_for_offset(tokens[index][1])
|
|
220
|
+
prev_token_line = line_for_offset(tokens[index - 1][1])
|
|
221
|
+
|
|
222
|
+
this_token_line != prev_token_line
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'puppet-lint'
|
|
4
|
+
|
|
5
|
+
module PuppetLanguageServer
|
|
6
|
+
module Manifest
|
|
7
|
+
class FormatOnTypeProvider
|
|
8
|
+
class << self
|
|
9
|
+
def instance
|
|
10
|
+
@instance ||= new
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def format(content, line, char, trigger_character, formatting_options, max_filesize = 4096)
|
|
15
|
+
result = []
|
|
16
|
+
# Abort if the user has pressed something other than `>`
|
|
17
|
+
return result unless trigger_character == '>'
|
|
18
|
+
# Abort if the formatting is tab based. Can't do that yet
|
|
19
|
+
return result unless formatting_options['insertSpaces'] == true
|
|
20
|
+
# Abort if content is too big
|
|
21
|
+
return result if !max_filesize.zero? && (content.length > max_filesize)
|
|
22
|
+
|
|
23
|
+
lexer = PuppetLint::Lexer.new
|
|
24
|
+
tokens = lexer.tokenise(content)
|
|
25
|
+
|
|
26
|
+
# Find where in the manifest the cursor is
|
|
27
|
+
cursor_token = find_token_by_location(tokens, line, char)
|
|
28
|
+
return result if cursor_token.nil?
|
|
29
|
+
# The cursor should be at the end of a hashrocket, otherwise exit
|
|
30
|
+
return result unless cursor_token.type == :FARROW
|
|
31
|
+
|
|
32
|
+
# Find the start of the hash (or semicolon for multi-resource definitions) with respect to the cursor
|
|
33
|
+
start_brace = cursor_token.prev_token_of(%i[LBRACE SEMIC], skip_blocks: true)
|
|
34
|
+
# Find the end of the hash (or semicolon for multi-resource definitions) with respect to the cursor
|
|
35
|
+
end_brace = cursor_token.next_token_of(%i[RBRACE SEMIC], skip_blocks: true)
|
|
36
|
+
|
|
37
|
+
# The line count between the start and end brace needs to be at least 2 lines. Otherwise there's nothing to align to
|
|
38
|
+
return result if end_brace.nil? || start_brace.nil? || end_brace.line - start_brace.line <= 2
|
|
39
|
+
|
|
40
|
+
# Find all hashrockets '=>' between the hash braces, ignoring nested hashes
|
|
41
|
+
farrows = []
|
|
42
|
+
farrow_token = start_brace
|
|
43
|
+
lines = []
|
|
44
|
+
loop do
|
|
45
|
+
farrow_token = farrow_token.next_token_of(:FARROW, skip_blocks: true)
|
|
46
|
+
# if there are no more hashrockets, or we've gone past the end_brace, we can exit the loop
|
|
47
|
+
break if farrow_token.nil? || farrow_token.line > end_brace.line
|
|
48
|
+
# if there's a hashrocket AFTER the closing brace (why?) then we can also exit the loop
|
|
49
|
+
break if farrow_token.line == end_brace.line && farrow_token.character > end_brace.character
|
|
50
|
+
# Check for multiple hashrockets on the same line. If we find some, then we can't do any automated indentation
|
|
51
|
+
return result if lines.include?(farrow_token.line)
|
|
52
|
+
|
|
53
|
+
lines << farrow_token.line
|
|
54
|
+
farrows << { token: farrow_token }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Now we have a list of farrows, time for figure out the indentation marks
|
|
58
|
+
farrows.each do |item|
|
|
59
|
+
item.merge!(calculate_indentation_info(item[:token]))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Now we have the list of indentations we can find the biggest
|
|
63
|
+
max_indent = -1
|
|
64
|
+
farrows.each do |info|
|
|
65
|
+
max_indent = info[:indent] if info[:indent] > max_indent
|
|
66
|
+
end
|
|
67
|
+
# No valid indentations found
|
|
68
|
+
return result if max_indent == -1
|
|
69
|
+
|
|
70
|
+
# Now we have the indent size, generate all of the required TextEdits
|
|
71
|
+
farrows.each do |info|
|
|
72
|
+
# Ignore invalid hashrockets
|
|
73
|
+
next if info[:indent] == -1
|
|
74
|
+
|
|
75
|
+
end_name_token = info[:name_token].column + info[:name_token].to_manifest.length
|
|
76
|
+
begin_farrow_token = info[:token].column
|
|
77
|
+
new_whitespace = max_indent - end_name_token
|
|
78
|
+
# If the whitespace is already what we want, then ignore it.
|
|
79
|
+
next if begin_farrow_token - end_name_token == new_whitespace
|
|
80
|
+
|
|
81
|
+
# Create the TextEdit
|
|
82
|
+
result << LSP::TextEdit.new.from_h!(
|
|
83
|
+
'newText' => ' ' * new_whitespace,
|
|
84
|
+
'range' => LSP.create_range(info[:token].line - 1, end_name_token - 1, info[:token].line - 1, begin_farrow_token - 1)
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
result
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
VALID_TOKEN_TYPES = %i[NAME STRING SSTRING].freeze
|
|
93
|
+
|
|
94
|
+
def find_token_by_location(tokens, line, character)
|
|
95
|
+
return nil if tokens.empty?
|
|
96
|
+
|
|
97
|
+
# Puppet Lint uses base 1, but LSP is base 0, so adjust accordingly
|
|
98
|
+
cursor_line = line + 1
|
|
99
|
+
cursor_column = character + 1
|
|
100
|
+
idx = -1
|
|
101
|
+
while idx < tokens.count
|
|
102
|
+
idx += 1
|
|
103
|
+
# if the token is on previous lines keep looking...
|
|
104
|
+
next if tokens[idx].line < cursor_line
|
|
105
|
+
# return nil if we skipped over the line we need
|
|
106
|
+
return nil if tokens[idx].line > cursor_line
|
|
107
|
+
# return nil if we skipped over the character position we need
|
|
108
|
+
return nil if tokens[idx].column > cursor_column
|
|
109
|
+
# return the token if it starts on the cursor column we are interested in
|
|
110
|
+
return tokens[idx] if tokens[idx].column == cursor_column
|
|
111
|
+
|
|
112
|
+
end_column = tokens[idx].column + tokens[idx].to_manifest.length
|
|
113
|
+
# return the token it the cursor column is within the token string
|
|
114
|
+
return tokens[idx] if cursor_column <= end_column
|
|
115
|
+
# otherwise, keep on searching
|
|
116
|
+
end
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def calculate_indentation_info(farrow_token)
|
|
121
|
+
result = { indent: -1 }
|
|
122
|
+
# This is not a valid hashrocket if there's no previous tokens
|
|
123
|
+
return result if farrow_token.prev_token.nil?
|
|
124
|
+
|
|
125
|
+
if VALID_TOKEN_TYPES.include?(farrow_token.prev_token.type)
|
|
126
|
+
# Someone forgot the whitespace! e.g. ensure=>
|
|
127
|
+
result[:indent] = farrow_token.column + 1
|
|
128
|
+
result[:name_token] = farrow_token.prev_token
|
|
129
|
+
return result
|
|
130
|
+
end
|
|
131
|
+
if farrow_token.prev_token.type == :WHITESPACE
|
|
132
|
+
# If the whitespace has no previous token (which shouldn't happen) or the thing before the whitespace is not a property name this it not a valid hashrocket
|
|
133
|
+
return result if farrow_token.prev_token.prev_token.nil?
|
|
134
|
+
return result unless VALID_TOKEN_TYPES.include?(farrow_token.prev_token.prev_token.type)
|
|
135
|
+
|
|
136
|
+
result[:name_token] = farrow_token.prev_token.prev_token
|
|
137
|
+
result[:indent] = farrow_token.prev_token.column + 1 # The indent is the whitespace column + 1
|
|
138
|
+
end
|
|
139
|
+
result
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServer
|
|
4
|
+
module Manifest
|
|
5
|
+
module HoverProvider
|
|
6
|
+
def self.resolve(session_state, content, line_num, char_num, options = {})
|
|
7
|
+
options = {
|
|
8
|
+
tasks_mode: false
|
|
9
|
+
}.merge(options)
|
|
10
|
+
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num,
|
|
11
|
+
disallowed_classes: [Puppet::Pops::Model::BlockExpression],
|
|
12
|
+
tasks_mode: options[:tasks_mode])
|
|
13
|
+
return nil if result.nil?
|
|
14
|
+
|
|
15
|
+
path = result[:path]
|
|
16
|
+
item = result[:model]
|
|
17
|
+
|
|
18
|
+
# Munge the item
|
|
19
|
+
# We're not really interested in the Name of thing yet, we want the "thing" it's a name of
|
|
20
|
+
item = path.pop if item.instance_of?(Puppet::Pops::Model::QualifiedName) && !path.empty?
|
|
21
|
+
|
|
22
|
+
content = nil
|
|
23
|
+
case item.class.to_s
|
|
24
|
+
when 'Puppet::Pops::Model::ResourceExpression'
|
|
25
|
+
content = get_resource_expression_content(session_state, item)
|
|
26
|
+
when 'Puppet::Pops::Model::LiteralString'
|
|
27
|
+
if path[-1].instance_of?(Puppet::Pops::Model::AccessExpression)
|
|
28
|
+
expr = path[-1].left_expr.expr.value
|
|
29
|
+
|
|
30
|
+
content = get_hover_content_for_access_expression(session_state, path, expr)
|
|
31
|
+
elsif path[-1].instance_of?(Puppet::Pops::Model::ResourceBody)
|
|
32
|
+
# We are hovering over the resource name
|
|
33
|
+
content = get_resource_expression_content(session_state, path[-2])
|
|
34
|
+
end
|
|
35
|
+
when 'Puppet::Pops::Model::VariableExpression'
|
|
36
|
+
expr = item.expr.value
|
|
37
|
+
|
|
38
|
+
content = get_hover_content_for_access_expression(session_state, path, expr)
|
|
39
|
+
when 'Puppet::Pops::Model::CallNamedFunctionExpression'
|
|
40
|
+
content = get_call_named_function_expression_content(session_state, item, options[:tasks_mode])
|
|
41
|
+
when 'Puppet::Pops::Model::AttributeOperation'
|
|
42
|
+
# Get the parent resource class
|
|
43
|
+
distance_up_ast = -1
|
|
44
|
+
parent_klass = path[distance_up_ast]
|
|
45
|
+
while !parent_klass.nil? && parent_klass.class.to_s != 'Puppet::Pops::Model::ResourceBody'
|
|
46
|
+
distance_up_ast -= 1
|
|
47
|
+
parent_klass = path[distance_up_ast]
|
|
48
|
+
end
|
|
49
|
+
raise "Unable to find suitable parent object for object of type #{item.class}" if parent_klass.nil?
|
|
50
|
+
|
|
51
|
+
resource_type_name = path[distance_up_ast - 1].type_name.value
|
|
52
|
+
# Check if it's a Puppet Type
|
|
53
|
+
resource_object = PuppetLanguageServer::PuppetHelper.get_type(session_state, resource_type_name)
|
|
54
|
+
unless resource_object.nil?
|
|
55
|
+
# Check if it's a property
|
|
56
|
+
attribute = resource_object.attributes[item.attribute_name.intern]
|
|
57
|
+
case attribute[:type]
|
|
58
|
+
when :property
|
|
59
|
+
content = get_attribute_type_property_content(resource_object, item.attribute_name.intern)
|
|
60
|
+
when :param
|
|
61
|
+
content = get_attribute_type_parameter_content(resource_object, item.attribute_name.intern)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
# Check if it's a Puppet Class / Defined Type
|
|
65
|
+
if resource_object.nil?
|
|
66
|
+
resource_object = PuppetLanguageServer::PuppetHelper.get_class(session_state, resource_type_name)
|
|
67
|
+
content = get_attribute_class_parameter_content(resource_object, item.attribute_name) unless resource_object.nil?
|
|
68
|
+
end
|
|
69
|
+
raise "#{resource_type_name} is not a valid puppet type, class or defined type" if resource_object.nil?
|
|
70
|
+
when 'Puppet::Pops::Model::QualifiedReference'
|
|
71
|
+
# https://github.com/puppetlabs/puppet-specifications/blob/master/language/names.md#names
|
|
72
|
+
# Datatypes have to start with uppercase and can be fully qualified
|
|
73
|
+
if /^[A-Z][a-zA-Z:0-9]*$/.match?(item.cased_value) # rubocop:disable Style/GuardClause
|
|
74
|
+
content = get_puppet_datatype_content(session_state, item, options[:tasks_mode])
|
|
75
|
+
else
|
|
76
|
+
raise "#{item.cased_value} is an unknown QualifiedReference"
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
raise "Unable to generate Hover information for object of type #{item.class}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
return nil if content.nil?
|
|
83
|
+
|
|
84
|
+
LSP::Hover.new('contents' => content)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.get_hover_content_for_access_expression(session_state, path, expr)
|
|
88
|
+
if expr == 'facts'
|
|
89
|
+
# We are dealing with the facts variable
|
|
90
|
+
# Just get the first part of the array and display that
|
|
91
|
+
fact_array = path[-1]
|
|
92
|
+
if fact_array.respond_to? :eContents
|
|
93
|
+
fact_array_content = fact_array.eContents
|
|
94
|
+
else
|
|
95
|
+
fact_array_content = []
|
|
96
|
+
fact_array._pcore_contents { |item| fact_array_content.push item }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if fact_array_content.length > 1
|
|
100
|
+
factname = fact_array_content[1].value
|
|
101
|
+
content = get_fact_content(session_state, factname)
|
|
102
|
+
end
|
|
103
|
+
elsif expr.start_with?('::') && expr.rindex(':') == 1
|
|
104
|
+
# We are dealing with a top local scope variable - Possible fact name
|
|
105
|
+
factname = expr.slice(2, expr.length - 2)
|
|
106
|
+
content = get_fact_content(session_state, factname)
|
|
107
|
+
else
|
|
108
|
+
# Could be a flatout fact name. May not *shrugs*. That method of access is deprecated
|
|
109
|
+
content = get_fact_content(session_state, expr)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
content
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Content generation functions
|
|
116
|
+
def self.get_fact_content(session_state, factname)
|
|
117
|
+
fact = PuppetLanguageServer::FacterHelper.fact(session_state, factname)
|
|
118
|
+
return nil if fact.nil?
|
|
119
|
+
|
|
120
|
+
value = fact.value
|
|
121
|
+
content = "**#{factname}** Fact\n\n"
|
|
122
|
+
|
|
123
|
+
if value.is_a?(Hash)
|
|
124
|
+
content = "#{content}```\n#{JSON.pretty_generate(value)}\n```"
|
|
125
|
+
else
|
|
126
|
+
content += value.to_s
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
content
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def self.get_attribute_type_parameter_content(item_type, param)
|
|
133
|
+
param_type = item_type.attributes[param]
|
|
134
|
+
content = "**#{param}** Parameter"
|
|
135
|
+
content += "\n\n#{param_type[:doc]}" unless param_type[:doc].nil?
|
|
136
|
+
content
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.get_attribute_type_property_content(item_type, property)
|
|
140
|
+
prop_type = item_type.attributes[property]
|
|
141
|
+
content = "**#{property}** Property"
|
|
142
|
+
content += "\n\n(_required_)" if prop_type[:required?]
|
|
143
|
+
content += "\n\n#{prop_type[:doc]}" unless prop_type[:doc].nil?
|
|
144
|
+
content
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def self.get_attribute_class_parameter_content(item_class, param)
|
|
148
|
+
param_type = item_class.parameters[param]
|
|
149
|
+
return nil if param_type.nil?
|
|
150
|
+
|
|
151
|
+
content = "**#{param}** Parameter"
|
|
152
|
+
content += "\n\n#{param_type[:doc]}" unless param_type[:doc].nil?
|
|
153
|
+
content
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def self.get_call_named_function_expression_content(session_state, item, tasks_mode)
|
|
157
|
+
func_name = item.functor_expr.value
|
|
158
|
+
|
|
159
|
+
func_info = PuppetLanguageServer::PuppetHelper.function(session_state, func_name, tasks_mode)
|
|
160
|
+
raise "Function #{func_name} does not exist" if func_info.nil?
|
|
161
|
+
|
|
162
|
+
content = "**#{func_name}** Function"
|
|
163
|
+
content += "\n\n#{func_info.doc}" unless func_info.doc.nil?
|
|
164
|
+
|
|
165
|
+
content
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def self.get_resource_expression_content(session_state, item)
|
|
169
|
+
name = item.type_name.value
|
|
170
|
+
# Strip top scope colons if found
|
|
171
|
+
name = name[2..-1] if name.start_with?('::')
|
|
172
|
+
# Get an instance of the type
|
|
173
|
+
item_object = PuppetLanguageServer::PuppetHelper.get_type(session_state, name)
|
|
174
|
+
return get_puppet_type_content(item_object) unless item_object.nil?
|
|
175
|
+
|
|
176
|
+
item_object = PuppetLanguageServer::PuppetHelper.get_class(session_state, name)
|
|
177
|
+
return get_puppet_class_content(item_object) unless item_object.nil?
|
|
178
|
+
|
|
179
|
+
raise "#{name} is not a valid puppet type"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def self.get_puppet_type_content(item_type)
|
|
183
|
+
content = "**#{item_type.key}** Resource\n\n"
|
|
184
|
+
content += "\n\n#{item_type.doc}" unless item_type.doc.nil?
|
|
185
|
+
content += "\n\n---\n"
|
|
186
|
+
item_type.attributes.keys.sort.each do |attr|
|
|
187
|
+
content += "* #{attr}\n"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
content
|
|
191
|
+
end
|
|
192
|
+
private_class_method :get_puppet_type_content
|
|
193
|
+
|
|
194
|
+
def self.get_puppet_class_content(item_class)
|
|
195
|
+
content = "**#{item_class.key}** Resource"
|
|
196
|
+
content += "\n\n#{item_class.doc}" unless item_class.doc.nil?
|
|
197
|
+
unless item_class.parameters.count.zero?
|
|
198
|
+
content += "\n\n---\n"
|
|
199
|
+
item_class.parameters.sort.each do |name, _param|
|
|
200
|
+
content += "* #{name}\n"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
content.strip
|
|
204
|
+
end
|
|
205
|
+
private_class_method :get_puppet_class_content
|
|
206
|
+
|
|
207
|
+
def self.get_puppet_datatype_content(session_state, item, tasks_mode)
|
|
208
|
+
dt_info = PuppetLanguageServer::PuppetHelper.datatype(session_state, item.cased_value, tasks_mode)
|
|
209
|
+
raise "DataType #{item.cased_value} does not exist" if dt_info.nil?
|
|
210
|
+
|
|
211
|
+
content = "**#{item.cased_value}** Data Type"
|
|
212
|
+
content += ' Alias' if dt_info.is_type_alias
|
|
213
|
+
content += "\n\n#{dt_info.doc}" unless dt_info.doc.nil?
|
|
214
|
+
|
|
215
|
+
content += "\n\nAlias of `#{dt_info.alias_of}`" if dt_info.is_type_alias
|
|
216
|
+
content
|
|
217
|
+
end
|
|
218
|
+
private_class_method :get_puppet_datatype_content
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|