Skip to content

Commit 0b68056

Browse files
authored
Merge pull request #90 from austb/puppet5_ast
Support the Puppet 5 AST on the Language Server
2 parents aa57ffa + 2b3a899 commit 0b68056

File tree

4 files changed

+77
-34
lines changed

4 files changed

+77
-34
lines changed

appveyor.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ environment:
1818
- PUPPET_GEM_VERSION: "~> 5.0"
1919
RUBY_VER: 24-x64
2020
RAKE_TASK: test
21+
2122
# Ruby style
2223
- PUPPET_GEM_VERSION: "~> 4.0"
2324
RUBY_VER: 23-x64
@@ -36,10 +37,6 @@ matrix:
3637
- PUPPET_GEM_VERSION: "~> 4.0"
3738
RUBY_VER: 23-x64
3839
RAKE_TASK: rubocop
39-
# Latest Puppet
40-
- PUPPET_GEM_VERSION: "~> 5.0"
41-
RUBY_VER: 24-x64
42-
RAKE_TASK: test
4340

4441
install:
4542
- ps: |

server/lib/puppet-languageserver/completion_provider.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ def self.complete(content, line_num, char_num)
44
items = []
55
incomplete = false
66

7-
item = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num, true, [Puppet::Pops::Model::QualifiedName])
7+
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num, true, [Puppet::Pops::Model::QualifiedName])
88

9-
if item.nil?
9+
if result.nil?
1010
# We are in the root of the document.
1111

1212
# Add keywords
@@ -22,6 +22,8 @@ def self.complete(content, line_num, char_num)
2222
'items' => items)
2323
end
2424

25+
item = result[:model]
26+
2527
case item.class.to_s
2628
when 'Puppet::Pops::Model::VariableExpression'
2729
expr = item.expr.value

server/lib/puppet-languageserver/hover_provider.rb

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,44 @@
11
module PuppetLanguageServer
22
module HoverProvider
33
def self.resolve(content, line_num, char_num)
4-
item = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num, false)
5-
return LanguageServer::Hover.create_nil_response if item.nil?
4+
result = PuppetLanguageServer::PuppetParserHelper.object_under_cursor(content, line_num, char_num, false, [Puppet::Pops::Model::QualifiedName])
5+
return LanguageServer::Hover.create_nil_response if result.nil?
6+
7+
path = result[:path]
8+
item = result[:model]
69

710
content = nil
811
case item.class.to_s
912
when 'Puppet::Pops::Model::ResourceExpression'
1013
content = get_resource_expression_content(item)
1114
when 'Puppet::Pops::Model::LiteralString'
12-
if item.eContainer.class == Puppet::Pops::Model::AccessExpression
13-
expr = item.eContainer.left_expr.expr.value
15+
if path[-1].class == Puppet::Pops::Model::AccessExpression
16+
expr = path[-1].left_expr.expr.value
1417

15-
content = get_hover_content_for_access_expression(item, expr)
16-
elsif item.eContainer.class == Puppet::Pops::Model::ResourceBody
18+
content = get_hover_content_for_access_expression(path, expr)
19+
elsif path[-1].class == Puppet::Pops::Model::ResourceBody
1720
# We are hovering over the resource name
18-
content = get_resource_expression_content(item.eContainer.eContainer)
21+
content = get_resource_expression_content(path[-2])
1922
end
2023
when 'Puppet::Pops::Model::VariableExpression'
2124
expr = item.expr.value
2225

23-
content = get_hover_content_for_access_expression(item, expr)
24-
when 'Puppet::Pops::Model::QualifiedName'
25-
if !item.eContainer.nil? && item.eContainer.class.to_s == 'Puppet::Pops::Model::ResourceExpression'
26-
content = get_resource_expression_content(item.eContainer)
27-
elsif !item.eContainer.nil? && item.eContainer.class.to_s == 'Puppet::Pops::Model::CallNamedFunctionExpression'
28-
content = get_call_named_function_expression_content(item.eContainer)
29-
end
30-
26+
content = get_hover_content_for_access_expression(path, expr)
27+
when 'Puppet::Pops::Model::CallNamedFunctionExpression'
28+
content = get_call_named_function_expression_content(item)
3129
when 'Puppet::Pops::Model::AttributeOperation'
3230
# Get the parent resource class
33-
parent_klass = item.eContainer
31+
distance_up_ast = -1
32+
parent_klass = path[distance_up_ast]
3433
while !parent_klass.nil? && parent_klass.class.to_s != 'Puppet::Pops::Model::ResourceBody'
35-
parent_klass = parent_klass.eContainer
34+
distance_up_ast -= 1
35+
parent_klass = path[distance_up_ast]
3636
end
3737
raise "Unable to find suitable parent object for object of type #{item.class}" if parent_klass.nil?
3838

3939
# Get an instance of the type
40-
item_type = PuppetLanguageServer::PuppetHelper.get_type(parent_klass.eContainer.type_name.value)
41-
raise "#{parent_klass.eContainer.type_name.value} is not a valid puppet type" if item_type.nil?
40+
item_type = PuppetLanguageServer::PuppetHelper.get_type(path[distance_up_ast - 1].type_name.value)
41+
raise "#{path[distance_up_ast - 1].type_name.value} is not a valid puppet type" if item_type.nil?
4242
# Check if it's a property
4343
attribute = item_type.validproperty?(item.attribute_name)
4444
if attribute != false
@@ -58,12 +58,20 @@ def self.resolve(content, line_num, char_num)
5858
end
5959
end
6060

61-
def self.get_hover_content_for_access_expression(item, expr)
61+
def self.get_hover_content_for_access_expression(path, expr)
6262
if expr == 'facts'
6363
# We are dealing with the facts variable
6464
# Just get the first part of the array and display that
65-
if item.eContainer.eContents.length > 1
66-
factname = item.eContainer.eContents[1].value
65+
fact_array = path[-1]
66+
if fact_array.respond_to? :eContents
67+
fact_array_content = fact_array.eContents
68+
else
69+
fact_array_content = []
70+
fact_array._pcore_contents { |item| fact_array_content.push item }
71+
end
72+
73+
if fact_array_content.length > 1
74+
factname = fact_array_content[1].value
6775
content = get_fact_content(factname)
6876
end
6977
elsif expr.start_with?('::') && expr.rindex(':') == 1

server/lib/puppet-languageserver/puppet_parser_helper.rb

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,28 +120,64 @@ def self.object_under_cursor(content, line_num, char_num, multiple_attempts = fa
120120
# [0, 14, 34, 36] means line number 2 starts at absolute offset 34
121121
# Once we know the line offset, we can simply add on the char_num to get the absolute offset
122122
# If during paring we modified the source we may need to change the cursor location
123-
abs_offset = result.line_offsets[line_num] + char_num + move_offset
123+
begin
124+
line_offset = result.line_offsets[line_num]
125+
rescue
126+
line_offset = result['locator'].line_index[line_num]
127+
end
124128
# Typically we're completing after something was typed, so go back one char
125-
abs_offset -= 1
129+
abs_offset = line_offset + char_num + move_offset - 1
126130

127131
# Enumerate the AST looking for items that span the line/char we want.
128132
# Once we have all valid items, sort them by the smallest span. Typically the smallest span
129133
# is the most specific object in the AST
130134
#
131135
# TODO: Should probably walk the AST and only look for the deepest child, but integer sorting
132136
# is so much easier and faster.
133-
valid_models = result.model.eAllContents.select do |item|
134-
!item.offset.nil? && !item.length.nil? && abs_offset >= item.offset && abs_offset <= item.offset + item.length && !disallowed_classes.include?(item.class)
135-
end
136-
valid_models.sort! { |a, b| a.length - b.length }
137+
model_path_struct = Struct.new(:model, :path)
138+
valid_models = []
139+
if result.model.respond_to? :eAllContents
140+
valid_models = result.model.eAllContents.select do |item|
141+
check_for_valid_item(item, abs_offset, disallowed_classes)
142+
end
143+
144+
valid_models.sort! { |a, b| a.length - b.length }
145+
else
146+
path = []
147+
result.model._pcore_all_contents(path) do |item|
148+
if check_for_valid_item(item, abs_offset, disallowed_classes)
149+
valid_models.push(model_path_struct.new(item, path.dup))
150+
end
151+
end
137152

153+
valid_models.sort! { |a, b| a[:model].length - b[:model].length }
154+
end
138155
# nil means the root of the document
139156
return nil if valid_models.empty?
140157
item = valid_models[0]
141158

159+
if item.respond_to? :eAllContents
160+
item = model_path_struct.new(item, construct_path(item))
161+
end
162+
142163
item
143164
end
144165

166+
def self.construct_path(item)
167+
path = []
168+
item = item.eContainer
169+
while item.class != Puppet::Pops::Model::Program
170+
path.unshift item
171+
item = item.eContainer
172+
end
173+
174+
path
175+
end
176+
177+
def self.check_for_valid_item(item, abs_offset, disallowed_classes)
178+
item.respond_to?(:offset) && !item.offset.nil? && !item.length.nil? && abs_offset >= item.offset && abs_offset <= item.offset + item.length && !disallowed_classes.include?(item.class)
179+
end
180+
145181
# Reference - https://github.com/puppetlabs/puppet/blob/master/spec/lib/puppet_spec/compiler.rb
146182
def self.compile_to_catalog(string, node = Puppet::Node.new('test'))
147183
Puppet[:code] = string

0 commit comments

Comments
 (0)