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,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServerSidecar
|
|
4
|
+
module PuppetParserHelper
|
|
5
|
+
def self.compile_node_graph(content)
|
|
6
|
+
result = PuppetLanguageServerSidecar::Protocol::PuppetNodeGraph.new
|
|
7
|
+
|
|
8
|
+
begin
|
|
9
|
+
node_graph = compile_to_pretty_relationship_graph(content)
|
|
10
|
+
if node_graph.vertices.count.zero?
|
|
11
|
+
result.set_error('There were no resources created in the node graph. Is there an include statement missing?')
|
|
12
|
+
return result
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
result.vertices = []
|
|
16
|
+
result.edges = []
|
|
17
|
+
|
|
18
|
+
node_graph.vertices.each do |vertex|
|
|
19
|
+
result.vertices << { label: vertex.to_s }
|
|
20
|
+
end
|
|
21
|
+
node_graph.edges.each do |edge|
|
|
22
|
+
result.edges << { source: edge.source.to_s, target: edge.target.to_s }
|
|
23
|
+
end
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
result.set_error("Error while parsing the file. #{e}")
|
|
26
|
+
rescue LoadError => e
|
|
27
|
+
result.set_error("Load error while parsing the file. #{e}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
result
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Reference - https://github.com/puppetlabs/puppet/blob/master/spec/lib/puppet_spec/compiler.rb
|
|
34
|
+
def self.compile_to_catalog(string, node = Puppet::Node.new('test'))
|
|
35
|
+
Puppet[:code] = string
|
|
36
|
+
# see lib/puppet/indirector/catalog/compiler.rb#filter
|
|
37
|
+
Puppet::Parser::Compiler.compile(node).filter(&:virtual?)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.compile_to_ral(manifest, node = Puppet::Node.new('test'))
|
|
41
|
+
# Add the node facts if they don't already exist
|
|
42
|
+
node.merge(Facter.to_hash) if node.facts.nil?
|
|
43
|
+
|
|
44
|
+
catalog = compile_to_catalog(manifest, node)
|
|
45
|
+
ral = catalog.to_ral
|
|
46
|
+
ral.finalize
|
|
47
|
+
ral
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.compile_to_relationship_graph(manifest, prioritizer = Puppet::Graph::SequentialPrioritizer.new)
|
|
51
|
+
ral = compile_to_ral(manifest)
|
|
52
|
+
graph = Puppet::Graph::RelationshipGraph.new(prioritizer)
|
|
53
|
+
graph.populate_from(ral)
|
|
54
|
+
graph
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.compile_to_pretty_relationship_graph(manifest, prioritizer = Puppet::Graph::SequentialPrioritizer.new)
|
|
58
|
+
graph = compile_to_relationship_graph(manifest, prioritizer)
|
|
59
|
+
|
|
60
|
+
# Remove vertexes which just clutter the graph
|
|
61
|
+
|
|
62
|
+
# Remove all of the Puppet::Type::Whit nodes. This is an internal only class
|
|
63
|
+
list = graph.vertices.select { |node| node.is_a?(Puppet::Type::Whit) }
|
|
64
|
+
list.each { |node| graph.remove_vertex!(node) }
|
|
65
|
+
|
|
66
|
+
# Remove all of the Puppet::Type::Schedule nodes
|
|
67
|
+
list = graph.vertices.select { |node| node.is_a?(Puppet::Type::Schedule) }
|
|
68
|
+
list.each { |node| graph.remove_vertex!(node) }
|
|
69
|
+
|
|
70
|
+
# Remove all of the Puppet::Type::Filebucket nodes
|
|
71
|
+
list = graph.vertices.select { |node| node.is_a?(Puppet::Type::Filebucket) }
|
|
72
|
+
list.each { |node| graph.remove_vertex!(node) }
|
|
73
|
+
|
|
74
|
+
graph
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServerSidecar
|
|
4
|
+
module PuppetStringsHelper
|
|
5
|
+
def self.instance
|
|
6
|
+
@instance ||= Helper.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.file_documentation(path, puppet_path, cache = nil)
|
|
10
|
+
instance.file_documentation(path, puppet_path, cache)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.require_puppet_strings
|
|
14
|
+
return @puppet_strings_loaded unless @puppet_strings_loaded.nil?
|
|
15
|
+
|
|
16
|
+
begin
|
|
17
|
+
require 'puppet-strings'
|
|
18
|
+
require 'puppet-strings/yard'
|
|
19
|
+
require 'puppet-strings/json'
|
|
20
|
+
|
|
21
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'puppet_strings_monkey_patches'))
|
|
22
|
+
@puppet_strings_loaded = true
|
|
23
|
+
rescue LoadError => e
|
|
24
|
+
PuppetLanguageServerSidecar.log_message(:error, "[PuppetStringsHelper::require_puppet_strings] Unable to load puppet-strings gem: #{e}")
|
|
25
|
+
@puppet_strings_loaded = false
|
|
26
|
+
end
|
|
27
|
+
@puppet_strings_loaded
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.setup_yard!
|
|
31
|
+
unless @yard_setup # rubocop:disable Style/GuardClause
|
|
32
|
+
::PuppetStrings::Yard.setup!
|
|
33
|
+
@yard_setup = true
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Helper
|
|
38
|
+
# Returns a FileDocumentation object for a given path
|
|
39
|
+
#
|
|
40
|
+
# @param [String] path The absolute path to the file that will be documented
|
|
41
|
+
# @param [PuppetLanguageServerSidecar::Cache] cache A Sidecar cache which stores already parsed documents as serialised FileDocumentation objects
|
|
42
|
+
# @return [FileDocumentation, nil] Returns the documentation for the path, or nil if it cannot be extracted
|
|
43
|
+
def file_documentation(path, puppet_path, cache = nil)
|
|
44
|
+
return nil unless PuppetLanguageServerSidecar::PuppetStringsHelper.require_puppet_strings
|
|
45
|
+
|
|
46
|
+
@helper_cache = FileDocumentationCache.new if @helper_cache.nil?
|
|
47
|
+
return @helper_cache.document(path) if @helper_cache.path_exists?(path)
|
|
48
|
+
|
|
49
|
+
# Load from the permanent cache
|
|
50
|
+
@helper_cache.populate_from_sidecar_cache!(path, cache) unless cache.nil? || !cache.active?
|
|
51
|
+
return @helper_cache.document(path) if @helper_cache.path_exists?(path)
|
|
52
|
+
|
|
53
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[PuppetStringsHelper::file_documentation] Fetching documentation for #{path}")
|
|
54
|
+
|
|
55
|
+
PuppetLanguageServerSidecar::PuppetStringsHelper.setup_yard!
|
|
56
|
+
|
|
57
|
+
# For now, assume a single file path
|
|
58
|
+
search_patterns = [path]
|
|
59
|
+
|
|
60
|
+
# Format the arguments to YARD
|
|
61
|
+
args = ['doc']
|
|
62
|
+
args << '--no-output'
|
|
63
|
+
args << '--quiet'
|
|
64
|
+
args << '--no-stats'
|
|
65
|
+
args << '--no-progress'
|
|
66
|
+
args << '--no-save'
|
|
67
|
+
args << '--api public'
|
|
68
|
+
args << '--api private'
|
|
69
|
+
args << '--no-api'
|
|
70
|
+
args += search_patterns
|
|
71
|
+
|
|
72
|
+
# Run YARD
|
|
73
|
+
::YARD::CLI::Yardoc.run(*args)
|
|
74
|
+
|
|
75
|
+
# Populate the documentation cache from the YARD information
|
|
76
|
+
@helper_cache.populate_from_yard_registry!(puppet_path)
|
|
77
|
+
|
|
78
|
+
# Save to the permanent cache
|
|
79
|
+
@helper_cache.save_to_sidecar_cache(path, cache) unless cache.nil? || !cache.active?
|
|
80
|
+
|
|
81
|
+
# Return the documentation details
|
|
82
|
+
@helper_cache.document(path)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class FileDocumentationCache
|
|
88
|
+
def initialize
|
|
89
|
+
# Hash of <[String] path, FileDocumentation> objects
|
|
90
|
+
@cache = {}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def path_exists?(path)
|
|
94
|
+
@cache.key?(path)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def document(path)
|
|
98
|
+
@cache[path]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def populate_from_yard_registry!(puppet_path)
|
|
102
|
+
# Extract all of the information
|
|
103
|
+
# Ref - https://github.com/puppetlabs/puppet-strings/blob/87a8e10f45bfeb7b6b8e766324bfb126de59f791/lib/puppet-strings/json.rb#L10-L16
|
|
104
|
+
populate_classes_from_yard_registry!
|
|
105
|
+
populate_data_types_from_yard_registry!
|
|
106
|
+
populate_functions_from_yard_registry!
|
|
107
|
+
populate_types_from_yard_registry!(puppet_path)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def populate_from_sidecar_cache!(path, cache)
|
|
111
|
+
cached_result = cache.load(path, PuppetLanguageServerSidecar::Cache::PUPPETSTRINGS_SECTION)
|
|
112
|
+
unless cached_result.nil? # rubocop:disable Style/GuardClause Reads better this way
|
|
113
|
+
begin
|
|
114
|
+
obj = FileDocumentation.new.from_json!(cached_result)
|
|
115
|
+
@cache[path] = obj
|
|
116
|
+
rescue StandardError => e
|
|
117
|
+
PuppetLanguageServerSidecar.log_message(:warn, "[FileDocumentationCache::populate_from_sidecar_cache!] Error while deserializing #{path} from cache: #{e}")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def save_to_sidecar_cache(path, cache)
|
|
123
|
+
cache.save(path, PuppetLanguageServerSidecar::Cache::PUPPETSTRINGS_SECTION, document(path).to_json) if cache.active?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def populate_classes_from_yard_registry!
|
|
129
|
+
%I[puppet_class puppet_defined_type].each do |yard_type|
|
|
130
|
+
YARD::Registry.all(yard_type).map(&:to_hash).each do |item|
|
|
131
|
+
source_path = item[:file]
|
|
132
|
+
class_name = item[:name].to_s
|
|
133
|
+
@cache[source_path] = FileDocumentation.new(source_path) if @cache[source_path].nil?
|
|
134
|
+
|
|
135
|
+
obj = PuppetLanguageServer::Sidecar::Protocol::PuppetClass.new
|
|
136
|
+
obj.key = class_name
|
|
137
|
+
obj.source = item[:file]
|
|
138
|
+
obj.calling_source = obj.source
|
|
139
|
+
obj.line = item[:line]
|
|
140
|
+
|
|
141
|
+
obj.doc = item[:docstring][:text]
|
|
142
|
+
obj.parameters = {}
|
|
143
|
+
# Extract the class parameters
|
|
144
|
+
unless item[:docstring][:tags].nil?
|
|
145
|
+
item[:docstring][:tags].select { |tag| tag[:tag_name] == 'param' && tag.key?(:types) }.each do |tag|
|
|
146
|
+
param_name = tag[:name]
|
|
147
|
+
obj.parameters[param_name] = {
|
|
148
|
+
doc: tag[:text],
|
|
149
|
+
type: tag[:types]&.join(', ')
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
@cache[source_path].classes << obj
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def populate_data_types_from_yard_registry!
|
|
160
|
+
::YARD::Registry.all(:puppet_data_type).map(&:to_hash).each do |item|
|
|
161
|
+
source_path = item[:file]
|
|
162
|
+
type_name = item[:name].to_s
|
|
163
|
+
@cache[source_path] = FileDocumentation.new(source_path) if @cache[source_path].nil?
|
|
164
|
+
|
|
165
|
+
obj = PuppetLanguageServer::Sidecar::Protocol::PuppetDataType.new
|
|
166
|
+
obj.key = type_name
|
|
167
|
+
obj.source = item[:file]
|
|
168
|
+
obj.calling_source = obj.source
|
|
169
|
+
obj.line = item[:line]
|
|
170
|
+
obj.doc = item[:docstring][:text]
|
|
171
|
+
obj.is_type_alias = false
|
|
172
|
+
obj.alias_of = nil
|
|
173
|
+
|
|
174
|
+
defaults = item[:defaults] || {}
|
|
175
|
+
unless item[:docstring][:tags].nil?
|
|
176
|
+
item[:docstring][:tags].select { |tag| tag[:tag_name] == 'param' }.each do |tag|
|
|
177
|
+
obj.attributes << PuppetLanguageServer::Sidecar::Protocol::PuppetDataTypeAttribute.new.from_h!(
|
|
178
|
+
'key' => tag[:name],
|
|
179
|
+
'default_value' => defaults[tag[:name]],
|
|
180
|
+
'doc' => tag[:text],
|
|
181
|
+
'types' => tag[:types].nil? ? nil : tag[:types].join(', ')
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
@cache[source_path].datatypes << obj
|
|
187
|
+
end
|
|
188
|
+
::YARD::Registry.all(:puppet_data_type_alias).map(&:to_hash).each do |item|
|
|
189
|
+
source_path = item[:file]
|
|
190
|
+
type_name = item[:name].to_s
|
|
191
|
+
@cache[source_path] = FileDocumentation.new(source_path) if @cache[source_path].nil?
|
|
192
|
+
|
|
193
|
+
obj = PuppetLanguageServer::Sidecar::Protocol::PuppetDataType.new
|
|
194
|
+
obj.key = type_name
|
|
195
|
+
obj.source = item[:file]
|
|
196
|
+
obj.calling_source = obj.source
|
|
197
|
+
obj.line = item[:line]
|
|
198
|
+
obj.doc = item[:docstring][:text]
|
|
199
|
+
obj.is_type_alias = true
|
|
200
|
+
obj.alias_of = item[:alias_of]
|
|
201
|
+
|
|
202
|
+
@cache[source_path].datatypes << obj
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def populate_functions_from_yard_registry!
|
|
207
|
+
::YARD::Registry.all(:puppet_function).map(&:to_hash).each do |item|
|
|
208
|
+
source_path = item[:file]
|
|
209
|
+
func_name = item[:name].to_s
|
|
210
|
+
@cache[source_path] = FileDocumentation.new(source_path) if @cache[source_path].nil?
|
|
211
|
+
|
|
212
|
+
obj = PuppetLanguageServer::Sidecar::Protocol::PuppetFunction.new
|
|
213
|
+
obj.key = func_name
|
|
214
|
+
obj.source = item[:file]
|
|
215
|
+
obj.calling_source = obj.source
|
|
216
|
+
obj.line = item[:line]
|
|
217
|
+
obj.doc = item[:docstring][:text]
|
|
218
|
+
# 'ruby3x' functions are version 3. 'ruby4x' and 'puppet' are version 4
|
|
219
|
+
obj.function_version = item[:type] == 'ruby3x' ? 3 : 4
|
|
220
|
+
|
|
221
|
+
# Try and determine the function call site from the source file
|
|
222
|
+
char = item[:source].index(":#{func_name}")
|
|
223
|
+
unless char.nil?
|
|
224
|
+
obj.char = char
|
|
225
|
+
obj.length = func_name.length + 1
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Note that puppet strings doesn't populate the method signatures for V3 functions
|
|
229
|
+
# Also, we don't have access to the arity of V3 functions so we can't reverse engineer the signature
|
|
230
|
+
item[:signatures].each do |signature|
|
|
231
|
+
sig = PuppetLanguageServer::Sidecar::Protocol::PuppetFunctionSignature.new
|
|
232
|
+
|
|
233
|
+
sig.key = signature[:signature]
|
|
234
|
+
sig.doc = signature[:docstring][:text]
|
|
235
|
+
|
|
236
|
+
unless signature[:docstring][:tags].nil?
|
|
237
|
+
signature[:docstring][:tags].each do |tag|
|
|
238
|
+
case tag[:tag_name]
|
|
239
|
+
when 'param'
|
|
240
|
+
sig.parameters << PuppetLanguageServer::Sidecar::Protocol::PuppetFunctionSignatureParameter.new.from_h!(
|
|
241
|
+
'name' => tag[:name],
|
|
242
|
+
'types' => tag[:types],
|
|
243
|
+
'doc' => tag[:text]
|
|
244
|
+
)
|
|
245
|
+
when 'return'
|
|
246
|
+
sig.return_types = tag[:types]
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
calculate_signature_parameter_locations!(sig)
|
|
251
|
+
obj.signatures << sig
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Extract other common information
|
|
255
|
+
# TODO: Other common tags include `example`, `overload`
|
|
256
|
+
pre_docs = ''
|
|
257
|
+
pre_docs += "This uses the legacy Ruby function API\n" if item[:type] == 'ruby3x'
|
|
258
|
+
since_tag = item[:docstring][:tags].find { |tag| tag[:tag_name] == 'since' }
|
|
259
|
+
pre_docs += "Since #{since_tag[:text]}\n" unless since_tag.nil?
|
|
260
|
+
obj.doc = "#{pre_docs}\n#{obj.doc}" unless pre_docs.empty?
|
|
261
|
+
|
|
262
|
+
@cache[source_path].functions << obj
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def populate_types_from_yard_registry!(puppet_path)
|
|
267
|
+
::YARD::Registry.all(:puppet_type).map(&:to_hash).each do |item|
|
|
268
|
+
type_name = item[:name].to_s
|
|
269
|
+
source_path = item[:file]
|
|
270
|
+
@cache[source_path] = FileDocumentation.new(source_path) if @cache[source_path].nil?
|
|
271
|
+
|
|
272
|
+
obj = PuppetLanguageServer::Sidecar::Protocol::PuppetType.new
|
|
273
|
+
obj.key = type_name
|
|
274
|
+
obj.source = item[:file]
|
|
275
|
+
obj.calling_source = obj.source
|
|
276
|
+
obj.line = item[:line]
|
|
277
|
+
obj.doc = item[:docstring][:text]
|
|
278
|
+
|
|
279
|
+
obj.attributes = {}
|
|
280
|
+
unless item[:properties].nil?
|
|
281
|
+
item[:properties].each do |prop|
|
|
282
|
+
obj.attributes[prop[:name]] = {
|
|
283
|
+
type: :property,
|
|
284
|
+
doc: prop[:description]
|
|
285
|
+
}
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
unless item[:parameters].nil?
|
|
289
|
+
item[:parameters].each do |prop|
|
|
290
|
+
obj.attributes[prop[:name]] = {
|
|
291
|
+
type: :param,
|
|
292
|
+
doc: prop[:description],
|
|
293
|
+
isnamevar?: prop[:isnamevar]
|
|
294
|
+
}
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
if obj.key == 'file'
|
|
299
|
+
# Special case for file type
|
|
300
|
+
# we need to set the source and calling_source to the correct file definition
|
|
301
|
+
path = File.join(puppet_path, 'lib/puppet/type')
|
|
302
|
+
obj.source = "#{path}/#{obj.key}.rb"
|
|
303
|
+
obj.calling_source = obj.source
|
|
304
|
+
end
|
|
305
|
+
@cache[source_path].types << obj
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def calculate_signature_parameter_locations!(sig)
|
|
310
|
+
# When Puppet Strings extracts the parameter name it differs from how it appears in the signature key
|
|
311
|
+
# This makes it hard for clients to determine where in the signature, the parameter actually is. So
|
|
312
|
+
# We need to calculate where in the signature key a parameter is
|
|
313
|
+
|
|
314
|
+
sig.parameters.each do |param|
|
|
315
|
+
name = param.name.dup # Don't want to modify the original object
|
|
316
|
+
# Munge the parameter name to what it appears in the signature key
|
|
317
|
+
# Ref - https://github.com/puppetlabs/puppet-strings/blob/2987558bb3170bc37e6077aab1b60efb17161eff/lib/puppet-strings/yard/handlers/ruby/function_handler.rb#L293-L317
|
|
318
|
+
if name.start_with?('*', '&')
|
|
319
|
+
name.insert(1, '$')
|
|
320
|
+
else
|
|
321
|
+
name = "$#{name}"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# We need to use terminating characters here due to substring matching e.g. $abc will incorrectly match in
|
|
325
|
+
# function([String] $abc123, [String] $abc)
|
|
326
|
+
idx = sig.key.index("#{name},")
|
|
327
|
+
idx = sig.key.index("#{name})") if idx.nil?
|
|
328
|
+
|
|
329
|
+
unless idx.nil?
|
|
330
|
+
param.signature_key_offset = idx
|
|
331
|
+
param.signature_key_length = name.length
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
class FileDocumentation
|
|
338
|
+
# The path to file that has been documented
|
|
339
|
+
attr_accessor :path
|
|
340
|
+
|
|
341
|
+
# PuppetLanguageServer::Sidecar::Protocol::PuppetClassList object holding all classes
|
|
342
|
+
attr_accessor :classes
|
|
343
|
+
|
|
344
|
+
# PuppetLanguageServer::Sidecar::Protocol::PuppetDataTypeList object holding all types
|
|
345
|
+
attr_accessor :datatypes
|
|
346
|
+
|
|
347
|
+
# PuppetLanguageServer::Sidecar::Protocol::PuppetFunctionList object holding all functions
|
|
348
|
+
attr_accessor :functions
|
|
349
|
+
|
|
350
|
+
# PuppetLanguageServer::Sidecar::Protocol::PuppetTypeList object holding all types
|
|
351
|
+
attr_accessor :types
|
|
352
|
+
|
|
353
|
+
def initialize(path = nil)
|
|
354
|
+
@path = path
|
|
355
|
+
@classes = PuppetLanguageServer::Sidecar::Protocol::PuppetClassList.new
|
|
356
|
+
@datatypes = PuppetLanguageServer::Sidecar::Protocol::PuppetDataTypeList.new
|
|
357
|
+
@functions = PuppetLanguageServer::Sidecar::Protocol::PuppetFunctionList.new
|
|
358
|
+
@types = PuppetLanguageServer::Sidecar::Protocol::PuppetTypeList.new
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Serialisation
|
|
362
|
+
def to_h
|
|
363
|
+
{
|
|
364
|
+
'path' => path,
|
|
365
|
+
'classes' => classes,
|
|
366
|
+
'datatypes' => datatypes,
|
|
367
|
+
'functions' => functions,
|
|
368
|
+
'types' => types
|
|
369
|
+
}
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def to_json(*options)
|
|
373
|
+
JSON.generate(to_h, options)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Deserialisation
|
|
377
|
+
def from_json!(json_string)
|
|
378
|
+
obj = JSON.parse(json_string)
|
|
379
|
+
|
|
380
|
+
obj.keys.each do |key|
|
|
381
|
+
case key
|
|
382
|
+
when 'path'
|
|
383
|
+
# Simple deserialised object types
|
|
384
|
+
self.instance_variable_set(:"@#{key}", obj[key]) # rubocop:disable Style/RedundantSelf Reads better this way
|
|
385
|
+
else
|
|
386
|
+
# Sidecar protocol list object types
|
|
387
|
+
prop = self.instance_variable_get(:"@#{key}") # rubocop:disable Style/RedundantSelf Reads better this way
|
|
388
|
+
|
|
389
|
+
obj[key].each do |child_hash|
|
|
390
|
+
child = prop.child_type.new
|
|
391
|
+
# Let the sidecar deserialise for us
|
|
392
|
+
prop << child.from_h!(child_hash)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
self
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yard/logging'
|
|
4
|
+
module YARD
|
|
5
|
+
class Logger < ::Logger
|
|
6
|
+
# Suppress ANY output
|
|
7
|
+
def self.instance(_pipe = $stdout)
|
|
8
|
+
@logger ||= new(nil)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Suppress ANY progress indicators
|
|
12
|
+
def show_progress
|
|
13
|
+
false
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'puppet-languageserver/sidecar_protocol'
|
|
4
|
+
|
|
5
|
+
module PuppetLanguageServerSidecar
|
|
6
|
+
module Protocol
|
|
7
|
+
class PuppetNodeGraph < PuppetLanguageServer::Sidecar::Protocol::PuppetNodeGraph
|
|
8
|
+
def set_error(message) # rubocop:disable Naming/AccessorMethodName
|
|
9
|
+
self.error_content = message
|
|
10
|
+
self.vertices = nil
|
|
11
|
+
self.edges = nil
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServerSidecar
|
|
4
|
+
module Workspace
|
|
5
|
+
@root_path = nil
|
|
6
|
+
@has_module_metadata = false
|
|
7
|
+
@has_environmentconf = false
|
|
8
|
+
|
|
9
|
+
def self.detect_workspace(path)
|
|
10
|
+
result = process_workspace(path)
|
|
11
|
+
@root_path = result[:root_path]
|
|
12
|
+
@has_module_metadata = result[:has_metadatajson]
|
|
13
|
+
@has_environmentconf = result[:has_environmentconf]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.root_path
|
|
17
|
+
@root_path
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.has_module_metadata? # rubocop:disable Naming/PredicateName
|
|
21
|
+
@has_module_metadata
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.has_environmentconf? # rubocop:disable Naming/PredicateName
|
|
25
|
+
@has_environmentconf
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Given a path, locate a metadata.json or environment.conf file to determine where the
|
|
29
|
+
# root of the module/control repo actually is
|
|
30
|
+
def self.find_root_path(path)
|
|
31
|
+
return nil if path.nil?
|
|
32
|
+
|
|
33
|
+
filepath = File.expand_path(path)
|
|
34
|
+
|
|
35
|
+
if dir_exist?(filepath)
|
|
36
|
+
directory = filepath
|
|
37
|
+
elsif file_exist?(filepath)
|
|
38
|
+
directory = File.dirname(filepath)
|
|
39
|
+
else
|
|
40
|
+
return nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
until directory.nil?
|
|
44
|
+
break if file_exist?(File.join(directory, 'metadata.json')) || file_exist?(File.join(directory, 'environment.conf'))
|
|
45
|
+
|
|
46
|
+
parent = File.dirname(directory)
|
|
47
|
+
# If the parent is the same as the original, then we've reached the end of the path chain
|
|
48
|
+
directory = if parent == directory
|
|
49
|
+
nil
|
|
50
|
+
else
|
|
51
|
+
parent
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
directory
|
|
56
|
+
end
|
|
57
|
+
private_class_method :find_root_path
|
|
58
|
+
|
|
59
|
+
def self.process_workspace(path)
|
|
60
|
+
result = {
|
|
61
|
+
root_path: nil,
|
|
62
|
+
has_environmentconf: false,
|
|
63
|
+
has_metadatajson: false
|
|
64
|
+
}
|
|
65
|
+
return result if path.nil?
|
|
66
|
+
|
|
67
|
+
root_path = find_root_path(path)
|
|
68
|
+
if root_path.nil?
|
|
69
|
+
result[:root_path] = path
|
|
70
|
+
else
|
|
71
|
+
result[:root_path] = root_path
|
|
72
|
+
result[:has_metadatajson] = file_exist?(File.join(root_path, 'metadata.json'))
|
|
73
|
+
result[:has_environmentconf] = file_exist?(File.join(root_path, 'environment.conf'))
|
|
74
|
+
end
|
|
75
|
+
result
|
|
76
|
+
end
|
|
77
|
+
private_class_method :process_workspace
|
|
78
|
+
|
|
79
|
+
def self.file_exist?(path)
|
|
80
|
+
File.exist?(path) && !File.directory?(path)
|
|
81
|
+
end
|
|
82
|
+
private_class_method :file_exist?
|
|
83
|
+
|
|
84
|
+
def self.dir_exist?(path)
|
|
85
|
+
Dir.exist?(path)
|
|
86
|
+
end
|
|
87
|
+
private_class_method :dir_exist?
|
|
88
|
+
end
|
|
89
|
+
end
|