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.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +510 -0
  3. data/CODEOWNERS +2 -0
  4. data/CODE_OF_CONDUCT.md +46 -0
  5. data/CONTRIBUTING.md +54 -0
  6. data/Gemfile +53 -0
  7. data/LICENSE +201 -0
  8. data/README.md +308 -0
  9. data/Rakefile +185 -0
  10. data/bin/puppet-debugserver +8 -0
  11. data/bin/puppet-languageserver +7 -0
  12. data/bin/puppet-languageserver-sidecar +7 -0
  13. data/lib/dsp/dsp.rb +7 -0
  14. data/lib/dsp/dsp_base.rb +62 -0
  15. data/lib/dsp/dsp_protocol.rb +4619 -0
  16. data/lib/lsp/lsp.rb +10 -0
  17. data/lib/lsp/lsp_base.rb +63 -0
  18. data/lib/lsp/lsp_custom.rb +170 -0
  19. data/lib/lsp/lsp_enums.rb +143 -0
  20. data/lib/lsp/lsp_protocol.rb +2785 -0
  21. data/lib/lsp/lsp_protocol_callhierarchy.proposed.rb +239 -0
  22. data/lib/lsp/lsp_protocol_colorprovider.rb +100 -0
  23. data/lib/lsp/lsp_protocol_configuration.rb +82 -0
  24. data/lib/lsp/lsp_protocol_declaration.rb +73 -0
  25. data/lib/lsp/lsp_protocol_foldingrange.rb +129 -0
  26. data/lib/lsp/lsp_protocol_implementation.rb +75 -0
  27. data/lib/lsp/lsp_protocol_progress.rb +200 -0
  28. data/lib/lsp/lsp_protocol_selectionrange.rb +79 -0
  29. data/lib/lsp/lsp_protocol_sematictokens.proposed.rb +340 -0
  30. data/lib/lsp/lsp_protocol_typedefinition.rb +75 -0
  31. data/lib/lsp/lsp_protocol_workspacefolders.rb +174 -0
  32. data/lib/lsp/lsp_types.rb +1534 -0
  33. data/lib/puppet-debugserver/debug_session/break_points.rb +137 -0
  34. data/lib/puppet-debugserver/debug_session/flow_control.rb +161 -0
  35. data/lib/puppet-debugserver/debug_session/hook_handlers.rb +295 -0
  36. data/lib/puppet-debugserver/debug_session/puppet_session_run_mode.rb +66 -0
  37. data/lib/puppet-debugserver/debug_session/puppet_session_state.rb +122 -0
  38. data/lib/puppet-debugserver/hooks.rb +132 -0
  39. data/lib/puppet-debugserver/message_handler.rb +277 -0
  40. data/lib/puppet-debugserver/puppet_debug_session.rb +541 -0
  41. data/lib/puppet-debugserver/puppet_monkey_patches.rb +118 -0
  42. data/lib/puppet-languageserver/client_session_state.rb +119 -0
  43. data/lib/puppet-languageserver/crash_dump.rb +50 -0
  44. data/lib/puppet-languageserver/epp/validation_provider.rb +34 -0
  45. data/lib/puppet-languageserver/facter_helper.rb +25 -0
  46. data/lib/puppet-languageserver/global_queues/sidecar_queue.rb +205 -0
  47. data/lib/puppet-languageserver/global_queues/single_instance_queue.rb +126 -0
  48. data/lib/puppet-languageserver/global_queues/validation_queue.rb +102 -0
  49. data/lib/puppet-languageserver/global_queues.rb +16 -0
  50. data/lib/puppet-languageserver/manifest/completion_provider.rb +331 -0
  51. data/lib/puppet-languageserver/manifest/definition_provider.rb +99 -0
  52. data/lib/puppet-languageserver/manifest/document_symbol_provider.rb +228 -0
  53. data/lib/puppet-languageserver/manifest/folding_provider.rb +226 -0
  54. data/lib/puppet-languageserver/manifest/format_on_type_provider.rb +143 -0
  55. data/lib/puppet-languageserver/manifest/hover_provider.rb +221 -0
  56. data/lib/puppet-languageserver/manifest/signature_provider.rb +169 -0
  57. data/lib/puppet-languageserver/manifest/validation_provider.rb +127 -0
  58. data/lib/puppet-languageserver/message_handler.rb +462 -0
  59. data/lib/puppet-languageserver/providers.rb +18 -0
  60. data/lib/puppet-languageserver/puppet_helper.rb +108 -0
  61. data/lib/puppet-languageserver/puppet_lexer_helper.rb +55 -0
  62. data/lib/puppet-languageserver/puppet_monkey_patches.rb +39 -0
  63. data/lib/puppet-languageserver/puppet_parser_helper.rb +212 -0
  64. data/lib/puppet-languageserver/puppetfile/validation_provider.rb +185 -0
  65. data/lib/puppet-languageserver/server_capabilities.rb +48 -0
  66. data/lib/puppet-languageserver/session_state/document_store.rb +272 -0
  67. data/lib/puppet-languageserver/session_state/language_client.rb +239 -0
  68. data/lib/puppet-languageserver/session_state/object_cache.rb +162 -0
  69. data/lib/puppet-languageserver/sidecar_protocol.rb +532 -0
  70. data/lib/puppet-languageserver/uri_helper.rb +46 -0
  71. data/lib/puppet-languageserver-sidecar/cache/base.rb +36 -0
  72. data/lib/puppet-languageserver-sidecar/cache/filesystem.rb +111 -0
  73. data/lib/puppet-languageserver-sidecar/cache/null.rb +27 -0
  74. data/lib/puppet-languageserver-sidecar/facter_helper.rb +41 -0
  75. data/lib/puppet-languageserver-sidecar/puppet_environment_monkey_patches.rb +52 -0
  76. data/lib/puppet-languageserver-sidecar/puppet_helper.rb +281 -0
  77. data/lib/puppet-languageserver-sidecar/puppet_modulepath_monkey_patches.rb +146 -0
  78. data/lib/puppet-languageserver-sidecar/puppet_monkey_patches.rb +9 -0
  79. data/lib/puppet-languageserver-sidecar/puppet_parser_helper.rb +77 -0
  80. data/lib/puppet-languageserver-sidecar/puppet_strings_helper.rb +399 -0
  81. data/lib/puppet-languageserver-sidecar/puppet_strings_monkey_patches.rb +16 -0
  82. data/lib/puppet-languageserver-sidecar/sidecar_protocol_extensions.rb +16 -0
  83. data/lib/puppet-languageserver-sidecar/workspace.rb +89 -0
  84. data/lib/puppet_debugserver.rb +164 -0
  85. data/lib/puppet_editor_services/connection/base.rb +62 -0
  86. data/lib/puppet_editor_services/connection/stdio.rb +25 -0
  87. data/lib/puppet_editor_services/connection/tcp.rb +34 -0
  88. data/lib/puppet_editor_services/handler/base.rb +16 -0
  89. data/lib/puppet_editor_services/handler/debug_adapter.rb +63 -0
  90. data/lib/puppet_editor_services/handler/json_rpc.rb +133 -0
  91. data/lib/puppet_editor_services/logging.rb +45 -0
  92. data/lib/puppet_editor_services/protocol/base.rb +27 -0
  93. data/lib/puppet_editor_services/protocol/debug_adapter.rb +135 -0
  94. data/lib/puppet_editor_services/protocol/debug_adapter_messages.rb +171 -0
  95. data/lib/puppet_editor_services/protocol/json_rpc.rb +241 -0
  96. data/lib/puppet_editor_services/protocol/json_rpc_messages.rb +200 -0
  97. data/lib/puppet_editor_services/server/base.rb +42 -0
  98. data/lib/puppet_editor_services/server/stdio.rb +85 -0
  99. data/lib/puppet_editor_services/server/tcp.rb +349 -0
  100. data/lib/puppet_editor_services/server.rb +15 -0
  101. data/lib/puppet_editor_services/version.rb +36 -0
  102. data/lib/puppet_editor_services.rb +8 -0
  103. data/lib/puppet_languageserver.rb +263 -0
  104. data/lib/puppet_languageserver_sidecar.rb +361 -0
  105. data/puppet-debugserver +11 -0
  106. data/puppet-editor-services.gemspec +29 -0
  107. data/puppet-languageserver +15 -0
  108. data/puppet-languageserver-sidecar +14 -0
  109. 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