Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/puppet-languageserver-sidecar/puppet_monkey_patches.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def newfunction(name, options = {}, &block)
# Append the caller information
result[:source_location] = {
:source => caller.absolute_path,
:line => caller.lineno - 1, # Convert to a zero based line number system
:line => caller.lineno - 1 # Convert to a zero based line number system
}
monkey_append_function_info(name, result)

Expand Down Expand Up @@ -69,7 +69,7 @@ def newtype(name, options = {}, &block)
if block_given? && !block.source_location.nil?
result._source_location = {
:source => block.source_location[0],
:line => block.source_location[1] - 1, # Convert to a zero based line number system
:line => block.source_location[1] - 1 # Convert to a zero based line number system
}
end
result
Expand Down
16 changes: 16 additions & 0 deletions lib/puppet-languageserver/document_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ def self.document_type(uri)
end
end

# Plan files https://puppet.com/docs/bolt/1.x/writing_plans.html#concept-4485
# exist in modules (requires metadata.json) and are in the `/plans` directory
def self.module_plan_file?(uri)
return false unless store_has_module_metadata?
relative_path = PuppetLanguageServer::UriHelper.relative_uri_path(PuppetLanguageServer::UriHelper.build_file_uri(store_root_path), uri, !windows?)
return false if relative_path.nil?
relative_path.start_with?('/plans/')
end

# Workspace management
WORKSPACE_CACHE_TTL_SECONDS = 60
def self.initialize_store(options = {})
Expand Down Expand Up @@ -150,5 +159,12 @@ def self.dir_exist?(path)
Dir.exist?(path)
end
private_class_method :dir_exist?

def self.windows?
# Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard
# library uses that to test what platform it's on.
!!File::ALT_SEPARATOR # rubocop:disable Style/DoubleNegation
end
private_class_method :windows?
end
end
12 changes: 9 additions & 3 deletions lib/puppet-languageserver/manifest/completion_provider.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
module PuppetLanguageServer
module Manifest
module CompletionProvider
def self.complete(content, line_num, char_num)
def self.complete(content, line_num, char_num, options = {})
options = {
:tasks_mode => false
}.merge(options)
items = []
incomplete = false

result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num, true, [Puppet::Pops::Model::QualifiedName, Puppet::Pops::Model::BlockExpression])

result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num,
:multiple_attempts => true,
:disallowed_classes => [Puppet::Pops::Model::QualifiedName, Puppet::Pops::Model::BlockExpression],
:tasks_mode => options[:tasks_mode])
if result.nil?
# We are in the root of the document.

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

# Add resources
all_resources { |x| items << x }
Expand Down
10 changes: 7 additions & 3 deletions lib/puppet-languageserver/manifest/definition_provider.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
module PuppetLanguageServer
module Manifest
module DefinitionProvider
def self.find_definition(content, line_num, char_num)
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num, false, [Puppet::Pops::Model::BlockExpression])

def self.find_definition(content, line_num, char_num, options = {})
options = {
:tasks_mode => false
}.merge(options)
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num,
:disallowed_classes => [Puppet::Pops::Model::BlockExpression],
:tasks_mode => options[:tasks_mode])
return nil if result.nil?

path = result[:path]
Expand Down
35 changes: 30 additions & 5 deletions lib/puppet-languageserver/manifest/document_symbol_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def self.workspace_symbols(query)
'fromline' => item.line,
'fromchar' => 0, # Don't have char pos for types
'toline' => item.line,
'tochar' => 1024, # Don't have char pos for types
'tochar' => 1024 # Don't have char pos for types
)
)

Expand All @@ -30,7 +30,7 @@ def self.workspace_symbols(query)
'fromline' => item.line,
'fromchar' => 0, # Don't have char pos for functions
'toline' => item.line,
'tochar' => 1024, # Don't have char pos for functions
'tochar' => 1024 # Don't have char pos for functions
)
)

Expand All @@ -43,17 +43,20 @@ def self.workspace_symbols(query)
'fromline' => item.line,
'fromchar' => 0, # Don't have char pos for classes
'toline' => item.line,
'tochar' => 1024, # Don't have char pos for classes
'tochar' => 1024 # Don't have char pos for classes
)
)
end
end
result
end

def self.extract_document_symbols(content)
def self.extract_document_symbols(content, options = {})
options = {
:tasks_mode => false
}.merge(options)
parser = Puppet::Pops::Parser::Parser.new
result = parser.parse_string(content, '')
result = parser.singleton_parse_string(content, options[:tasks_mode], '')

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

# Puppet Plan
when 'Puppet::Pops::Model::PlanDefinition'
this_symbol = LanguageServer::DocumentSymbol.create(
'name' => object.name,
'kind' => LanguageServer::SYMBOLKIND_CLASS,
'detail' => object.name,
'range' => create_range_array(object.offset, object.length, object.locator),
'selectionRange' => create_range_array(object.offset, object.length, object.locator),
'children' => []
)
# Load in the class parameters
object.parameters.each do |param|
param_symbol = LanguageServer::DocumentSymbol.create(
'name' => '$' + param.name,
'kind' => LanguageServer::SYMBOLKIND_PROPERTY,
'detail' => '$' + param.name,
'range' => create_range_array(param.offset, param.length, param.locator),
'selectionRange' => create_range_array(param.offset, param.length, param.locator),
'children' => []
)
this_symbol['children'].push(param_symbol)
end
end

object._pcore_contents do |item|
Expand Down
9 changes: 7 additions & 2 deletions lib/puppet-languageserver/manifest/hover_provider.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
module PuppetLanguageServer
module Manifest
module HoverProvider
def self.resolve(content, line_num, char_num)
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num, false, [Puppet::Pops::Model::QualifiedName, Puppet::Pops::Model::BlockExpression])
def self.resolve(content, line_num, char_num, options = {})
options = {
:tasks_mode => false
}.merge(options)
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num,
:disallowed_classes => [Puppet::Pops::Model::QualifiedName, Puppet::Pops::Model::BlockExpression],
:tasks_mode => options[:tasks_mode])
return LanguageServer::Hover.create_nil_response if result.nil?

path = result[:path]
Expand Down
19 changes: 16 additions & 3 deletions lib/puppet-languageserver/manifest/validation_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ def self.fix_validate_errors(content)
[problems_fixed, linter.manifest]
end

def self.validate(content, _max_problems = 100)
def self.validate(content, options = {})
options = {
:max_problems => 100,
:tasks_mode => false
}.merge(options)

result = []
# TODO: Need to implement max_problems
problems = 0
Expand Down Expand Up @@ -89,8 +94,16 @@ def self.validate(content, _max_problems = 100)
Puppet.override({ loaders: loaders }, 'For puppet parser validate') do
begin
validation_environment = env
validation_environment.check_for_reparse
validation_environment.known_resource_types.clear
$PuppetParserMutex.synchronize do # rubocop:disable Style/GlobalVars
begin
original_taskmode = Puppet[:tasks] if Puppet.tasks_supported?
Puppet[:tasks] = options[:tasks_mode] if Puppet.tasks_supported?
validation_environment.check_for_reparse
validation_environment.known_resource_types.clear
ensure
Puppet[:tasks] = original_taskmode if Puppet.tasks_supported?
end
end
rescue StandardError => detail
# Sometimes the error is in the cause not the root object itself
detail = detail.cause if !detail.respond_to?(:line) && detail.respond_to?(:cause)
Expand Down
9 changes: 4 additions & 5 deletions lib/puppet-languageserver/message_router.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def receive_request(request)
begin
case documents.document_type(file_uri)
when :manifest
request.reply_result(PuppetLanguageServer::Manifest::CompletionProvider.complete(content, line_num, char_num))
request.reply_result(PuppetLanguageServer::Manifest::CompletionProvider.complete(content, line_num, char_num, :tasks_mode => PuppetLanguageServer::DocumentStore.module_plan_file?(file_uri)))
else
raise "Unable to provide completion on #{file_uri}"
end
Expand All @@ -123,7 +123,7 @@ def receive_request(request)
begin
case documents.document_type(file_uri)
when :manifest
request.reply_result(PuppetLanguageServer::Manifest::HoverProvider.resolve(content, line_num, char_num))
request.reply_result(PuppetLanguageServer::Manifest::HoverProvider.resolve(content, line_num, char_num, :tasks_mode => PuppetLanguageServer::DocumentStore.module_plan_file?(file_uri)))
else
raise "Unable to provide hover on #{file_uri}"
end
Expand All @@ -140,7 +140,7 @@ def receive_request(request)
begin
case documents.document_type(file_uri)
when :manifest
request.reply_result(PuppetLanguageServer::Manifest::DefinitionProvider.find_definition(content, line_num, char_num))
request.reply_result(PuppetLanguageServer::Manifest::DefinitionProvider.find_definition(content, line_num, char_num, :tasks_mode => PuppetLanguageServer::DocumentStore.module_plan_file?(file_uri)))
else
raise "Unable to provide definition on #{file_uri}"
end
Expand All @@ -155,8 +155,7 @@ def receive_request(request)
begin
case documents.document_type(file_uri)
when :manifest
result = PuppetLanguageServer::Manifest::DocumentSymbolProvider.extract_document_symbols(content)
request.reply_result(result)
request.reply_result(PuppetLanguageServer::Manifest::DocumentSymbolProvider.extract_document_symbols(content, :tasks_mode => PuppetLanguageServer::DocumentStore.module_plan_file?(file_uri)))
else
raise "Unable to provide definition on #{file_uri}"
end
Expand Down
32 changes: 32 additions & 0 deletions lib/puppet-languageserver/puppet_monkey_patches.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
# Monkey Patch the Puppet language parser so we can globally lock any changes to the
# global setting Puppet[:tasks]. We need to manage this so we can switch between
# parsing modes. Unfortunately we can't do this as method parameter, only via the
# global Puppet settings which is not thread safe
$PuppetParserMutex = Mutex.new # rubocop:disable Style/GlobalVars
module Puppet
module Pops
module Parser
class Parser
def singleton_parse_string(code, task_mode = false, path = nil)
$PuppetParserMutex.synchronize do # rubocop:disable Style/GlobalVars
begin
original_taskmode = Puppet[:tasks] if Puppet.tasks_supported?
Puppet[:tasks] = task_mode if Puppet.tasks_supported?
return parse_string(code, path)
ensure
Puppet[:tasks] = original_taskmode if Puppet.tasks_supported?
end
end
end
end
end
end
end

module Puppet
# Tasks first appeared in Puppet 5.4.0
def self.tasks_supported?
Gem::Version.new(Puppet.version) >= Gem::Version.new('5.4.0')
end
end

# MUST BE LAST!!!!!!
# Suppress any warning messages to STDOUT. It can pollute stdout when running in STDIO mode
Puppet::Util::Log.newdesttype :null_logger do
Expand Down
20 changes: 14 additions & 6 deletions lib/puppet-languageserver/puppet_parser_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,19 @@ def self.get_line_at(content, line_offsets, line_num)
end
end

def self.object_under_cursor(content, line_num, char_num, multiple_attempts = false, disallowed_classes = [])
def self.object_under_cursor(content, line_num, char_num, options)
options = {
:multiple_attempts => false,
:disallowed_classes => [],
:tasks_mode => false
}.merge(options)

# Use Puppet to generate the AST
parser = Puppet::Pops::Parser::Parser.new

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

result = nil
move_offset = 0
Expand All @@ -72,9 +78,11 @@ def self.object_under_cursor(content, line_num, char_num, multiple_attempts = fa
when :noop
new_content = content
when :remove_char
next if line_num.zero? && char_num.zero?
new_content = remove_char_at(content, line_offsets, line_num, char_num)
move_offset = -1
when :remove_word
next if line_num.zero? && char_num.zero?
next_char = get_char_at(content, line_offsets, line_num, char_num)

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

begin
result = parser.parse_string(new_content, '')
result = parser.singleton_parse_string(new_content, options[:tasks_mode], '')
break
rescue Puppet::ParseErrorWithIssue => _exception
next if multiple_attempts
next if options[:multiple_attempts]
raise
end
end
Expand Down Expand Up @@ -138,14 +146,14 @@ def self.object_under_cursor(content, line_num, char_num, multiple_attempts = fa
valid_models = []
if result.model.respond_to? :eAllContents
valid_models = result.model.eAllContents.select do |item|
check_for_valid_item(item, abs_offset, disallowed_classes)
check_for_valid_item(item, abs_offset, options[:disallowed_classes])
end

valid_models.sort! { |a, b| a.length - b.length }
else
path = []
result.model._pcore_all_contents(path) do |item|
if check_for_valid_item(item, abs_offset, disallowed_classes) # rubocop:disable Style/IfUnlessModifier Nicer to read like this
if check_for_valid_item(item, abs_offset, options[:disallowed_classes]) # rubocop:disable Style/IfUnlessModifier Nicer to read like this
valid_models.push(model_path_struct.new(item, path.dup))
end
end
Expand Down
30 changes: 29 additions & 1 deletion lib/puppet-languageserver/uri_helper.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
require 'uri'
require 'puppet'

module PuppetLanguageServer
module UriHelper
def self.build_file_uri(path)
path.start_with?('/') ? 'file://' + path : 'file:///' + path
'file://' + Puppet::Util.uri_encode(path.start_with?('/') ? path : '/' + path)
end

# Compares two URIs and returns the relative path
#
# @param root_uri [String] The root URI to compare to
# @param uri [String] The URI to compare to the root
# @param case_sensitive [Boolean] Whether the path comparison is case senstive or not. Default is true
# @return [String] Returns the relative path string if the URI is indeed a child of the root, otherwise returns nil
def self.relative_uri_path(root_uri, uri, case_sensitive = true)
actual_root = URI(root_uri)
actual_uri = URI(uri)
return nil unless actual_root.scheme == actual_uri.scheme

# CGI.unescape doesn't handle space rules properly in uri paths
# URI.unescape does, but returns strings in their original encoding
# Mostly safe here as we're only worried about file based URIs
root_path = URI.unescape(actual_root.path) # rubocop:disable Lint/UriEscapeUnescape
uri_path = URI.unescape(actual_uri.path) # rubocop:disable Lint/UriEscapeUnescape
if case_sensitive
return nil unless uri_path.slice(0, root_path.length) == root_path
else
return nil unless uri_path.slice(0, root_path.length).casecmp(root_path).zero?
end

uri_path.slice(root_path.length..-1)
end
end
end
Loading