Skip to content

Commit 99fce1d

Browse files
author
James Pogran
authored
Merge pull request #93 from glennsarti/gh-24-parseplans
(GH-24) Allow parsing in tasks mode
2 parents 8e3dc41 + 06c8b01 commit 99fce1d

21 files changed

+449
-45
lines changed

lib/puppet-languageserver-sidecar/puppet_monkey_patches.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def newfunction(name, options = {}, &block)
1616
# Append the caller information
1717
result[:source_location] = {
1818
:source => caller.absolute_path,
19-
:line => caller.lineno - 1, # Convert to a zero based line number system
19+
:line => caller.lineno - 1 # Convert to a zero based line number system
2020
}
2121
monkey_append_function_info(name, result)
2222

@@ -69,7 +69,7 @@ def newtype(name, options = {}, &block)
6969
if block_given? && !block.source_location.nil?
7070
result._source_location = {
7171
:source => block.source_location[0],
72-
:line => block.source_location[1] - 1, # Convert to a zero based line number system
72+
:line => block.source_location[1] - 1 # Convert to a zero based line number system
7373
}
7474
end
7575
result

lib/puppet-languageserver/document_store.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ def self.document_type(uri)
5252
end
5353
end
5454

55+
# Plan files https://puppet.com/docs/bolt/1.x/writing_plans.html#concept-4485
56+
# exist in modules (requires metadata.json) and are in the `/plans` directory
57+
def self.module_plan_file?(uri)
58+
return false unless store_has_module_metadata?
59+
relative_path = PuppetLanguageServer::UriHelper.relative_uri_path(PuppetLanguageServer::UriHelper.build_file_uri(store_root_path), uri, !windows?)
60+
return false if relative_path.nil?
61+
relative_path.start_with?('/plans/')
62+
end
63+
5564
# Workspace management
5665
WORKSPACE_CACHE_TTL_SECONDS = 60
5766
def self.initialize_store(options = {})
@@ -150,5 +159,12 @@ def self.dir_exist?(path)
150159
Dir.exist?(path)
151160
end
152161
private_class_method :dir_exist?
162+
163+
def self.windows?
164+
# Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard
165+
# library uses that to test what platform it's on.
166+
!!File::ALT_SEPARATOR # rubocop:disable Style/DoubleNegation
167+
end
168+
private_class_method :windows?
153169
end
154170
end

lib/puppet-languageserver/manifest/completion_provider.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
module PuppetLanguageServer
22
module Manifest
33
module CompletionProvider
4-
def self.complete(content, line_num, char_num)
4+
def self.complete(content, line_num, char_num, options = {})
5+
options = {
6+
:tasks_mode => false
7+
}.merge(options)
58
items = []
69
incomplete = false
710

8-
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num, true, [Puppet::Pops::Model::QualifiedName, Puppet::Pops::Model::BlockExpression])
9-
11+
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num,
12+
:multiple_attempts => true,
13+
:disallowed_classes => [Puppet::Pops::Model::QualifiedName, Puppet::Pops::Model::BlockExpression],
14+
:tasks_mode => options[:tasks_mode])
1015
if result.nil?
1116
# We are in the root of the document.
1217

1318
# Add keywords
1419
keywords(%w[class define node application site]) { |x| items << x }
20+
keywords(%w[plan]) { |x| items << x } if options[:tasks_mode]
1521

1622
# Add resources
1723
all_resources { |x| items << x }

lib/puppet-languageserver/manifest/definition_provider.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
module PuppetLanguageServer
22
module Manifest
33
module DefinitionProvider
4-
def self.find_definition(content, line_num, char_num)
5-
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num, false, [Puppet::Pops::Model::BlockExpression])
6-
4+
def self.find_definition(content, line_num, char_num, options = {})
5+
options = {
6+
:tasks_mode => false
7+
}.merge(options)
8+
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num,
9+
:disallowed_classes => [Puppet::Pops::Model::BlockExpression],
10+
:tasks_mode => options[:tasks_mode])
711
return nil if result.nil?
812

913
path = result[:path]

lib/puppet-languageserver/manifest/document_symbol_provider.rb

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def self.workspace_symbols(query)
1717
'fromline' => item.line,
1818
'fromchar' => 0, # Don't have char pos for types
1919
'toline' => item.line,
20-
'tochar' => 1024, # Don't have char pos for types
20+
'tochar' => 1024 # Don't have char pos for types
2121
)
2222
)
2323

@@ -30,7 +30,7 @@ def self.workspace_symbols(query)
3030
'fromline' => item.line,
3131
'fromchar' => 0, # Don't have char pos for functions
3232
'toline' => item.line,
33-
'tochar' => 1024, # Don't have char pos for functions
33+
'tochar' => 1024 # Don't have char pos for functions
3434
)
3535
)
3636

@@ -43,17 +43,20 @@ def self.workspace_symbols(query)
4343
'fromline' => item.line,
4444
'fromchar' => 0, # Don't have char pos for classes
4545
'toline' => item.line,
46-
'tochar' => 1024, # Don't have char pos for classes
46+
'tochar' => 1024 # Don't have char pos for classes
4747
)
4848
)
4949
end
5050
end
5151
result
5252
end
5353

54-
def self.extract_document_symbols(content)
54+
def self.extract_document_symbols(content, options = {})
55+
options = {
56+
:tasks_mode => false
57+
}.merge(options)
5558
parser = Puppet::Pops::Parser::Parser.new
56-
result = parser.parse_string(content, '')
59+
result = parser.singleton_parse_string(content, options[:tasks_mode], '')
5760

5861
if result.model.respond_to? :eAllContents
5962
# We are unable to build a document symbol tree for Puppet 4 AST
@@ -187,6 +190,28 @@ def self.recurse_document_symbols(object, path, parentsymbol, symbollist)
187190
'children' => []
188191
)
189192

193+
# Puppet Plan
194+
when 'Puppet::Pops::Model::PlanDefinition'
195+
this_symbol = LanguageServer::DocumentSymbol.create(
196+
'name' => object.name,
197+
'kind' => LanguageServer::SYMBOLKIND_CLASS,
198+
'detail' => object.name,
199+
'range' => create_range_array(object.offset, object.length, object.locator),
200+
'selectionRange' => create_range_array(object.offset, object.length, object.locator),
201+
'children' => []
202+
)
203+
# Load in the class parameters
204+
object.parameters.each do |param|
205+
param_symbol = LanguageServer::DocumentSymbol.create(
206+
'name' => '$' + param.name,
207+
'kind' => LanguageServer::SYMBOLKIND_PROPERTY,
208+
'detail' => '$' + param.name,
209+
'range' => create_range_array(param.offset, param.length, param.locator),
210+
'selectionRange' => create_range_array(param.offset, param.length, param.locator),
211+
'children' => []
212+
)
213+
this_symbol['children'].push(param_symbol)
214+
end
190215
end
191216

192217
object._pcore_contents do |item|

lib/puppet-languageserver/manifest/hover_provider.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
module PuppetLanguageServer
22
module Manifest
33
module HoverProvider
4-
def self.resolve(content, line_num, char_num)
5-
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num, false, [Puppet::Pops::Model::QualifiedName, Puppet::Pops::Model::BlockExpression])
4+
def self.resolve(content, line_num, char_num, options = {})
5+
options = {
6+
:tasks_mode => false
7+
}.merge(options)
8+
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num,
9+
:disallowed_classes => [Puppet::Pops::Model::QualifiedName, Puppet::Pops::Model::BlockExpression],
10+
:tasks_mode => options[:tasks_mode])
611
return LanguageServer::Hover.create_nil_response if result.nil?
712

813
path = result[:path]

lib/puppet-languageserver/manifest/validation_provider.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ def self.fix_validate_errors(content)
2828
[problems_fixed, linter.manifest]
2929
end
3030

31-
def self.validate(content, _max_problems = 100)
31+
def self.validate(content, options = {})
32+
options = {
33+
:max_problems => 100,
34+
:tasks_mode => false
35+
}.merge(options)
36+
3237
result = []
3338
# TODO: Need to implement max_problems
3439
problems = 0
@@ -89,8 +94,16 @@ def self.validate(content, _max_problems = 100)
8994
Puppet.override({ loaders: loaders }, 'For puppet parser validate') do
9095
begin
9196
validation_environment = env
92-
validation_environment.check_for_reparse
93-
validation_environment.known_resource_types.clear
97+
$PuppetParserMutex.synchronize do # rubocop:disable Style/GlobalVars
98+
begin
99+
original_taskmode = Puppet[:tasks] if Puppet.tasks_supported?
100+
Puppet[:tasks] = options[:tasks_mode] if Puppet.tasks_supported?
101+
validation_environment.check_for_reparse
102+
validation_environment.known_resource_types.clear
103+
ensure
104+
Puppet[:tasks] = original_taskmode if Puppet.tasks_supported?
105+
end
106+
end
94107
rescue StandardError => detail
95108
# Sometimes the error is in the cause not the root object itself
96109
detail = detail.cause if !detail.respond_to?(:line) && detail.respond_to?(:cause)

lib/puppet-languageserver/message_router.rb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def receive_request(request)
9797
begin
9898
case documents.document_type(file_uri)
9999
when :manifest
100-
request.reply_result(PuppetLanguageServer::Manifest::CompletionProvider.complete(content, line_num, char_num))
100+
request.reply_result(PuppetLanguageServer::Manifest::CompletionProvider.complete(content, line_num, char_num, :tasks_mode => PuppetLanguageServer::DocumentStore.module_plan_file?(file_uri)))
101101
else
102102
raise "Unable to provide completion on #{file_uri}"
103103
end
@@ -123,7 +123,7 @@ def receive_request(request)
123123
begin
124124
case documents.document_type(file_uri)
125125
when :manifest
126-
request.reply_result(PuppetLanguageServer::Manifest::HoverProvider.resolve(content, line_num, char_num))
126+
request.reply_result(PuppetLanguageServer::Manifest::HoverProvider.resolve(content, line_num, char_num, :tasks_mode => PuppetLanguageServer::DocumentStore.module_plan_file?(file_uri)))
127127
else
128128
raise "Unable to provide hover on #{file_uri}"
129129
end
@@ -140,7 +140,7 @@ def receive_request(request)
140140
begin
141141
case documents.document_type(file_uri)
142142
when :manifest
143-
request.reply_result(PuppetLanguageServer::Manifest::DefinitionProvider.find_definition(content, line_num, char_num))
143+
request.reply_result(PuppetLanguageServer::Manifest::DefinitionProvider.find_definition(content, line_num, char_num, :tasks_mode => PuppetLanguageServer::DocumentStore.module_plan_file?(file_uri)))
144144
else
145145
raise "Unable to provide definition on #{file_uri}"
146146
end
@@ -155,8 +155,7 @@ def receive_request(request)
155155
begin
156156
case documents.document_type(file_uri)
157157
when :manifest
158-
result = PuppetLanguageServer::Manifest::DocumentSymbolProvider.extract_document_symbols(content)
159-
request.reply_result(result)
158+
request.reply_result(PuppetLanguageServer::Manifest::DocumentSymbolProvider.extract_document_symbols(content, :tasks_mode => PuppetLanguageServer::DocumentStore.module_plan_file?(file_uri)))
160159
else
161160
raise "Unable to provide definition on #{file_uri}"
162161
end

lib/puppet-languageserver/puppet_monkey_patches.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
1+
# Monkey Patch the Puppet language parser so we can globally lock any changes to the
2+
# global setting Puppet[:tasks]. We need to manage this so we can switch between
3+
# parsing modes. Unfortunately we can't do this as method parameter, only via the
4+
# global Puppet settings which is not thread safe
5+
$PuppetParserMutex = Mutex.new # rubocop:disable Style/GlobalVars
6+
module Puppet
7+
module Pops
8+
module Parser
9+
class Parser
10+
def singleton_parse_string(code, task_mode = false, path = nil)
11+
$PuppetParserMutex.synchronize do # rubocop:disable Style/GlobalVars
12+
begin
13+
original_taskmode = Puppet[:tasks] if Puppet.tasks_supported?
14+
Puppet[:tasks] = task_mode if Puppet.tasks_supported?
15+
return parse_string(code, path)
16+
ensure
17+
Puppet[:tasks] = original_taskmode if Puppet.tasks_supported?
18+
end
19+
end
20+
end
21+
end
22+
end
23+
end
24+
end
25+
26+
module Puppet
27+
# Tasks first appeared in Puppet 5.4.0
28+
def self.tasks_supported?
29+
Gem::Version.new(Puppet.version) >= Gem::Version.new('5.4.0')
30+
end
31+
end
32+
133
# MUST BE LAST!!!!!!
234
# Suppress any warning messages to STDOUT. It can pollute stdout when running in STDIO mode
335
Puppet::Util::Log.newdesttype :null_logger do

lib/puppet-languageserver/puppet_parser_helper.rb

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,19 @@ def self.get_line_at(content, line_offsets, line_num)
5656
end
5757
end
5858

59-
def self.object_under_cursor(content, line_num, char_num, multiple_attempts = false, disallowed_classes = [])
59+
def self.object_under_cursor(content, line_num, char_num, options)
60+
options = {
61+
:multiple_attempts => false,
62+
:disallowed_classes => [],
63+
:tasks_mode => false
64+
}.merge(options)
65+
6066
# Use Puppet to generate the AST
6167
parser = Puppet::Pops::Parser::Parser.new
6268

6369
# Calculating the line offsets can be expensive and is only required
6470
# if we're doing mulitple passes of parsing
65-
line_offsets = line_offsets(content) if multiple_attempts
71+
line_offsets = line_offsets(content) if options[:multiple_attempts]
6672

6773
result = nil
6874
move_offset = 0
@@ -72,9 +78,11 @@ def self.object_under_cursor(content, line_num, char_num, multiple_attempts = fa
7278
when :noop
7379
new_content = content
7480
when :remove_char
81+
next if line_num.zero? && char_num.zero?
7582
new_content = remove_char_at(content, line_offsets, line_num, char_num)
7683
move_offset = -1
7784
when :remove_word
85+
next if line_num.zero? && char_num.zero?
7886
next_char = get_char_at(content, line_offsets, line_num, char_num)
7987

8088
while /[[:word:]]/ =~ next_char
@@ -106,10 +114,10 @@ def self.object_under_cursor(content, line_num, char_num, multiple_attempts = fa
106114
next if new_content.nil?
107115

108116
begin
109-
result = parser.parse_string(new_content, '')
117+
result = parser.singleton_parse_string(new_content, options[:tasks_mode], '')
110118
break
111119
rescue Puppet::ParseErrorWithIssue => _exception
112-
next if multiple_attempts
120+
next if options[:multiple_attempts]
113121
raise
114122
end
115123
end
@@ -138,14 +146,14 @@ def self.object_under_cursor(content, line_num, char_num, multiple_attempts = fa
138146
valid_models = []
139147
if result.model.respond_to? :eAllContents
140148
valid_models = result.model.eAllContents.select do |item|
141-
check_for_valid_item(item, abs_offset, disallowed_classes)
149+
check_for_valid_item(item, abs_offset, options[:disallowed_classes])
142150
end
143151

144152
valid_models.sort! { |a, b| a.length - b.length }
145153
else
146154
path = []
147155
result.model._pcore_all_contents(path) do |item|
148-
if check_for_valid_item(item, abs_offset, disallowed_classes) # rubocop:disable Style/IfUnlessModifier Nicer to read like this
156+
if check_for_valid_item(item, abs_offset, options[:disallowed_classes]) # rubocop:disable Style/IfUnlessModifier Nicer to read like this
149157
valid_models.push(model_path_struct.new(item, path.dup))
150158
end
151159
end

0 commit comments

Comments
 (0)