platformos-check 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,2 @@
1
1
 
2
- {"revision":"949281126d705b543c2fa4e5f328c13ed411351a"}
2
+ {"revision":"008ef526d0cf8a5c42eb2c05938d91543996edf8"}
@@ -1,5 +1,6 @@
1
1
  FROM ruby:3.2-alpine
2
2
 
3
+ ARG VERSION
3
4
  ENV WORKDIR /app
4
5
  ENV PLATFORMOS_CHECK_DEBUG true
5
6
  ENV PLATFORMOS_CHECK_DEBUG_LOG_FILE /tmp/platformos-check-debug.log
@@ -9,13 +10,12 @@ RUN apk add --update bash git openssh build-base && mkdir $WORKDIR
9
10
  WORKDIR $WORKDIR
10
11
 
11
12
  RUN git clone https://github.com/Platform-OS/platformos-lsp.git && \
12
- touch $PLATFORMOS_CHECK_DEBUG_LOG_FILE && \
13
13
  cd platformos-lsp && \
14
14
  bundle install && \
15
- gem build && gem install platformos-check-0.0.0.gem
15
+ gem build && gem install platformos-check-$VERSION.gem
16
16
 
17
17
  RUN adduser --disabled-password --gecos '' platformos && chown platformos:platformos -R /app
18
18
 
19
- ENTRYPOINT ["/app/platformos-lsp/exe/platformos-check-language-server"]
19
+ ENTRYPOINT ["/app/platformos-lsp/bin/platformos-check-language-server"]
20
20
 
21
21
  USER platformos
@@ -34,6 +34,8 @@ module PlatformosCheck
34
34
  end
35
35
 
36
36
  def on_assign(node)
37
+ return if ignore_underscored?(node)
38
+
37
39
  @templates[node.app_file.name].assign_nodes[node.value.to] = node
38
40
  end
39
41
 
@@ -42,13 +44,13 @@ module PlatformosCheck
42
44
  end
43
45
 
44
46
  def on_function(node)
45
- return if node.value.to.start_with?('_')
47
+ return if ignore_underscored?(node)
46
48
 
47
49
  @templates[node.app_file.name].assign_nodes[node.value.to] = node
48
50
  end
49
51
 
50
52
  def on_graphql(node)
51
- return if node.value.to.start_with?('_')
53
+ return if ignore_underscored?(node)
52
54
 
53
55
  @templates[node.app_file.name].assign_nodes[node.value.to] = node
54
56
  end
@@ -101,5 +103,11 @@ module PlatformosCheck
101
103
  end
102
104
  end
103
105
  end
106
+
107
+ private
108
+
109
+ def ignore_underscored?(node)
110
+ node.value.to.start_with?('_')
111
+ end
104
112
  end
105
113
  end
@@ -75,6 +75,14 @@ module PlatformosCheck
75
75
  @defined_arguments ||= variables.map(&:name)
76
76
  end
77
77
 
78
+ def selections
79
+ definition.selections
80
+ end
81
+
82
+ def fragments
83
+ @fragments ||= parse.definitions.select { |d| d.is_a?(GraphQL::Language::Nodes::FragmentDefinition) }
84
+ end
85
+
78
86
  private
79
87
 
80
88
  def variables
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module PlatformosCheck
6
+ class GraphqlTraverser
7
+ def initialize(graphql_file)
8
+ @graphql_file = graphql_file
9
+ end
10
+
11
+ def fields
12
+ pickup_fields(@graphql_file.selections, '')
13
+ end
14
+
15
+ private
16
+
17
+ def pickup_fields(selections, path = '')
18
+ selections = expand_fragments(selections)
19
+ fields = {}
20
+ fields['/'] = map_names_from_selections(selections) if path.empty?
21
+ selections.map do |selection|
22
+ current_path = File.join(path, name_from_selection(selection))
23
+ fields[current_path] = map_names_from_selections(expand_fragments(selection.selections))
24
+
25
+ child_fields = pickup_fields(selection.selections, current_path)
26
+ fields = fields.merge(child_fields)
27
+ end
28
+ fields
29
+ end
30
+
31
+ def map_names_from_selections(selections)
32
+ selections.map { |selection| name_from_selection(selection) }
33
+ end
34
+
35
+ def name_from_selection(selection)
36
+ selection.alias || selection.name
37
+ end
38
+
39
+ def expand_fragments(selections)
40
+ selections.map do |selection|
41
+ if selection.is_a?(GraphQL::Language::Nodes::FragmentSpread)
42
+ find_fragment(selection.name).selections
43
+ else
44
+ selection
45
+ end
46
+ end.flatten.uniq { |s| name_from_selection(s) }
47
+ end
48
+
49
+ def find_fragment(fragment_name)
50
+ @graphql_file.fragments.detect { |definition| definition.name == fragment_name }
51
+ end
52
+ end
53
+ end
@@ -39,6 +39,10 @@ module PlatformosCheck
39
39
  @files.keys
40
40
  end
41
41
 
42
+ def files_with_content
43
+ @files
44
+ end
45
+
42
46
  def relative_path(absolute_path)
43
47
  Pathname.new(absolute_path).relative_path_from(@root).to_s
44
48
  end
@@ -3,7 +3,7 @@
3
3
  module PlatformosCheck
4
4
  module LanguageServer
5
5
  class ObjectAttributeCompletionProvider < CompletionProvider
6
- def completions(context)
6
+ def completions(context, child_lookup = nil)
7
7
  content = context.content
8
8
  cursor = context.cursor
9
9
 
@@ -11,19 +11,27 @@ module PlatformosCheck
11
11
  return [] unless (variable_lookup = VariableLookupFinder.lookup(context))
12
12
  return [] if content[cursor - 1] == "." && content[cursor - 2] == "."
13
13
 
14
- # Navigate through lookups until the last valid [object, property] level
15
- object, property = VariableLookupTraverser.lookup_object_and_property(variable_lookup)
14
+ variable_lookup.lookups = variable_lookup.lookups + child_lookup.lookups if child_lookup&.lookups&.any?
16
15
 
17
- # If the last lookup level is incomplete/invalid, use the partial term
18
- # to filter object properties.
19
- partial = partial_property_name(property, variable_lookup)
16
+ if variable_lookup.file_path
17
+ function_completion(variable_lookup)
18
+ elsif variable_lookup.lookups.first&.start_with?('graphql/')
19
+ graphql_completion(variable_lookup)
20
+ else
21
+ # Navigate through lookups until the last valid [object, property] level
22
+ object, property = VariableLookupTraverser.lookup_object_and_property(variable_lookup)
20
23
 
21
- return [] unless object
24
+ # If the last lookup level is incomplete/invalid, use the partial term
25
+ # to filter object properties.
26
+ partial = partial_property_name(property, variable_lookup)
22
27
 
23
- object
24
- .properties
25
- .select { |prop| partial.nil? || prop.name.start_with?(partial) }
26
- .map { |prop| property_to_completion(prop) }
28
+ return [] unless object
29
+
30
+ object
31
+ .properties
32
+ .select { |prop| partial.nil? || prop.name.start_with?(partial) }
33
+ .map { |prop| property_to_completion(prop) }
34
+ end
27
35
  end
28
36
 
29
37
  private
@@ -43,6 +51,47 @@ module PlatformosCheck
43
51
  **doc_hash(content)
44
52
  }
45
53
  end
54
+
55
+ def find_file(file_name)
56
+ @storage
57
+ .platformos_app
58
+ .all
59
+ .find { |t| t.name == file_name }
60
+ end
61
+
62
+ def graphql_completion(variable_lookup)
63
+ graphql_file_name = variable_lookup.lookups.first.sub("graphql/", '')
64
+ graphql_file = find_file(graphql_file_name)
65
+ fields = GraphqlTraverser.new(graphql_file).fields
66
+ variable_path = File.join('', variable_lookup.lookups.slice(1..-1))
67
+ variable_properties = fields[variable_path]
68
+
69
+ variable_properties.map { |property| graphql_to_property_completion(property) }
70
+ end
71
+
72
+ def graphql_to_property_completion(property)
73
+ hash = {
74
+ 'name' => property
75
+ }
76
+ object_entry = PlatformosLiquid::SourceIndex::ObjectEntry.new(hash)
77
+ property_to_completion(object_entry)
78
+ end
79
+
80
+ def function_completion(variable_lookup)
81
+ liquid_file = find_file(variable_lookup.file_path)
82
+ partial_content = liquid_file.source
83
+ lines = partial_content.split("\n")
84
+ partial_provider = ObjectAttributeCompletionProvider.new(@storage)
85
+
86
+ line_number_with_return = lines.rindex { |x| x.include?('return') }
87
+ partial_context = CompletionContext.new(
88
+ @storage,
89
+ liquid_file.relative_path.to_s,
90
+ line_number_with_return,
91
+ lines[line_number_with_return].size
92
+ )
93
+ partial_provider.completions(partial_context, variable_lookup)
94
+ end
46
95
  end
47
96
  end
48
97
  end
@@ -30,27 +30,7 @@ module PlatformosCheck
30
30
  end
31
31
 
32
32
  def on_function(node, scope)
33
- # When a variable is redefined in a new scope we
34
- # no longer can guarantee the type in the global scope
35
- #
36
- # Example:
37
- # ```liquid
38
- # {%- liquid
39
- # assign var1 = some_value
40
- #
41
- # if condition
42
- # function var1 = 'another_value'
43
- # ^^^^ from here we no longer can guarantee
44
- # the type of `var1` in the global scope
45
- # -%}
46
- # ```
47
- p_scope = scope
48
- while (p_scope = p_scope.parent)
49
- p_scope.variables.delete(node.value.to)
50
- end
51
-
52
- scope << node
53
- scope
33
+ on_assign(node, scope)
54
34
  end
55
35
 
56
36
  def on_graphql(node, scope)
@@ -21,9 +21,13 @@ module PlatformosCheck
21
21
  when Liquid::Assign
22
22
  variable_name = tag.to
23
23
  variables[variable_name] = as_potential_lookup(tag.from.name)
24
- when Tags::Function, Tags::Graphql
24
+ when Tags::Function
25
25
  variable_name = tag.to
26
- variables[variable_name] = literal_lookup(tag.from)
26
+ variables[variable_name] = as_potential_lookup_function_result(tag)
27
+ when Tags::Graphql
28
+ variable_name = tag.to
29
+ potential = as_potential_lookup_graphql(tag)
30
+ variables[variable_name] = potential
27
31
  when Liquid::For, Liquid::TableRow
28
32
  variable_name = tag.variable_name
29
33
  variables[variable_name] = as_potential_lookup(tag.collection_name, ['first'])
@@ -56,6 +60,19 @@ module PlatformosCheck
56
60
 
57
61
  PotentialLookup.new(name, lookups, variables)
58
62
  end
63
+
64
+ def as_potential_lookup_graphql(tag)
65
+ variable_lookup = tag.from.name
66
+ name = variable_lookup
67
+ variable_lookup.lookups.push
68
+
69
+ # TODO: this is smelly
70
+ PotentialLookup.new(name, ["graphql/#{tag.partial_name}"], variables)
71
+ end
72
+
73
+ def as_potential_lookup_function_result(tag)
74
+ PotentialLookup.new(tag.from, [], variables, tag.from)
75
+ end
59
76
  end
60
77
  end
61
78
  end
@@ -27,6 +27,8 @@ module PlatformosCheck
27
27
  \s(?:
28
28
  if|elsif|unless|and|or|#{Liquid::Condition.operators.keys.join("|")}
29
29
  |echo
30
+ |return
31
+ |log
30
32
  |paginate
31
33
  |case|when
32
34
  |cycle
@@ -3,7 +3,7 @@
3
3
  module PlatformosCheck
4
4
  module LanguageServer
5
5
  module VariableLookupFinder
6
- class PotentialLookup < Struct.new(:name, :lookups, :scope)
6
+ class PotentialLookup < Struct.new(:name, :lookups, :scope, :file_path)
7
7
  end
8
8
  end
9
9
  end
@@ -15,7 +15,7 @@ module PlatformosCheck
15
15
 
16
16
  return if cursor_is_on_bracket_position_that_cant_be_completed(content, cursor)
17
17
 
18
- variable_lookup = lookup_liquid_variable(content, cursor) || lookup_liquid_tag(content, cursor)
18
+ variable_lookup = lookup_liquid_variable(content, cursor) || lookup_liquid_variable_inside_liquid_tag(content, cursor) || lookup_liquid_tag(content, cursor)
19
19
 
20
20
  return variable_lookup if variable_lookup.is_a?(PotentialLookup)
21
21
  return unless variable_lookup.is_a?(Liquid::VariableLookup)
@@ -54,14 +54,20 @@ module PlatformosCheck
54
54
  end
55
55
 
56
56
  def as_potential_lookup(variable, lookups: nil)
57
- PotentialLookup.new(variable.name, lookups || variable.lookups)
57
+ PotentialLookup.new(
58
+ variable.name,
59
+ lookups || variable.lookups,
60
+ nil,
61
+ variable.respond_to?(:file_path) ? variable.file_path : nil
62
+ )
58
63
  end
59
64
 
60
65
  def cursor_is_on_bracket_position_that_cant_be_completed(content, cursor)
61
66
  content_before_cursor = content[0..cursor - 1]
62
67
  return false unless /[\[\]]/.match?(content_before_cursor)
63
68
 
64
- content_before_cursor =~ ENDS_IN_BRACKET_POSITION_THAT_CANT_BE_COMPLETED
69
+ line_with_cursor = content_before_cursor.split("\n").last
70
+ line_with_cursor =~ ENDS_IN_BRACKET_POSITION_THAT_CANT_BE_COMPLETED
65
71
  end
66
72
 
67
73
  def cursor_is_on_liquid_variable_lookup_position(content, cursor)
@@ -77,6 +83,50 @@ module PlatformosCheck
77
83
  )
78
84
  end
79
85
 
86
+ def lookup_liquid_variable_inside_liquid_tag(content, cursor)
87
+ return unless content.strip.start_with?(/{%\s*liquid/)
88
+
89
+ previous_char = content[cursor - 1]
90
+ is_in_variable_segment = previous_char =~ VARIABLE_LOOKUP_CHARACTERS
91
+ return unless is_in_variable_segment
92
+
93
+ start_index = content.slice(0, cursor).rindex("\n") + 1
94
+ end_index = cursor - 1
95
+
96
+ # We take the following content
97
+ # - start after the first two {{
98
+ # - end at cursor position
99
+ #
100
+ # That way, we'll have a partial liquid variable that
101
+ # can be parsed such that the "last" variable_lookup
102
+ # will be the one we're trying to complete.
103
+ markup = content[start_index..end_index]
104
+
105
+ # Early return for incomplete variables
106
+ return empty_lookup if /\s+$/.match?(markup)
107
+
108
+ # Now we go to hack city... The cursor might be in the middle
109
+ # of a string/square bracket lookup. We need to close those
110
+ # otherwise the variable parse won't work.
111
+ markup += "'" if markup.count("'").odd?
112
+ markup += '"' if markup.count('"').odd?
113
+ markup += "]" if UNCLOSED_SQUARE_BRACKET.match?(markup)
114
+
115
+ if markup.strip.split(' ').size > 1
116
+ begin
117
+ template = LiquidFile.parse(parseable_markup(content, cursor))
118
+ current_tag = template.root.nodelist[0]
119
+ return if current_tag.is_a?(Liquid::Tag)
120
+ rescue Liquid::SyntaxError
121
+ return
122
+ end
123
+ end
124
+
125
+ variable = variable_from_markup(markup)
126
+
127
+ variable_lookup_for_liquid_variable(variable)
128
+ end
129
+
80
130
  def lookup_liquid_variable(content, cursor)
81
131
  return unless cursor_is_on_liquid_variable_lookup_position(content, cursor)
82
132
 
@@ -139,7 +189,7 @@ module PlatformosCheck
139
189
  markup = parseable_markup(content, cursor)
140
190
  return empty_lookup if markup.empty?
141
191
 
142
- template = Liquid::Template.parse(markup)
192
+ template = LiquidFile.parse(markup)
143
193
  current_tag = template.root.nodelist[0]
144
194
 
145
195
  case current_tag&.tag_name
@@ -159,6 +209,12 @@ module PlatformosCheck
159
209
  variable_lookup_for_assign_tag(current_tag)
160
210
  when "echo"
161
211
  variable_lookup_for_echo_tag(current_tag)
212
+ when "function"
213
+ variable_lookup_for_function_tag(current_tag)
214
+ when "return"
215
+ variable_lookup_for_return_tag(current_tag)
216
+ when "log"
217
+ variable_lookup_for_log_tag(current_tag)
162
218
  else
163
219
  empty_lookup
164
220
  end
@@ -219,6 +275,20 @@ module PlatformosCheck
219
275
  variable_lookup_for_liquid_variable(echo_tag.variable)
220
276
  end
221
277
 
278
+ def variable_lookup_for_function_tag(function_tag)
279
+ return empty_lookup if /:\s*$/.match?(function_tag.raw)
280
+
281
+ function_tag.attributes.values.last
282
+ end
283
+
284
+ def variable_lookup_for_return_tag(return_tag)
285
+ return_tag.variable
286
+ end
287
+
288
+ def variable_lookup_for_log_tag(log_tag)
289
+ log_tag.variable
290
+ end
291
+
222
292
  def variable_lookup_for_liquid_variable(variable)
223
293
  has_filters = !variable.filters.empty?
224
294
 
@@ -16,9 +16,11 @@ module PlatformosCheck
16
16
 
17
17
  def description
18
18
  @descritpion = begin
19
- desc = hash['description']&.strip || ''
19
+ desc = hash['description'].is_a?(Array) ? hash['description'].first : hash['description']
20
+ desc = desc&.strip || ''
20
21
  desc = '' if desc == 'returns'
21
22
  if parameters.any?
23
+ desc += "\n\n" unless desc.empty?
22
24
  desc += "Parameters:"
23
25
  parameters.each { |p| desc += "\n- #{p.full_summary}" }
24
26
  end
@@ -3,6 +3,10 @@
3
3
  module PlatformosCheck
4
4
  module Tags
5
5
  class Log < Base
6
+ def variable
7
+ @value_expr
8
+ end
9
+
6
10
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
7
11
  def children
8
12
  [
@@ -10,6 +10,10 @@ module PlatformosCheck
10
10
  @var = Liquid::Variable.new(markup, options)
11
11
  end
12
12
 
13
+ def variable
14
+ @var.name
15
+ end
16
+
13
17
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
14
18
  def children
15
19
  [@node.var]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PlatformosCheck
4
- VERSION = "0.4.3"
4
+ VERSION = "0.4.4"
5
5
  end
@@ -14,6 +14,7 @@ require_relative "platformos_check/schema_file"
14
14
  require_relative "platformos_check/config_file"
15
15
  require_relative "platformos_check/user_schema_file"
16
16
  require_relative "platformos_check/graphql_file"
17
+ require_relative "platformos_check/graphql_traverser"
17
18
  require_relative "platformos_check/liquid_file"
18
19
  require_relative "platformos_check/page_file"
19
20
  require_relative "platformos_check/partial_file"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: platformos-check
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Bliszczyk
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2023-09-25 00:00:00.000000000 Z
13
+ date: 2023-12-04 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: graphql
@@ -93,6 +93,11 @@ files:
93
93
  - TROUBLESHOOTING.md
94
94
  - bin/platformos-check
95
95
  - bin/platformos-check-language-server
96
+ - build/windows/Gemfile
97
+ - build/windows/README.md
98
+ - build/windows/build.sh
99
+ - build/windows/lsp.exe
100
+ - build/windows/run.rb
96
101
  - config/default.yml
97
102
  - config/nothing.yml
98
103
  - data/platformos_liquid/built_in_liquid_objects.json
@@ -184,6 +189,7 @@ files:
184
189
  - lib/platformos_check/file_system_storage.rb
185
190
  - lib/platformos_check/form_file.rb
186
191
  - lib/platformos_check/graphql_file.rb
192
+ - lib/platformos_check/graphql_traverser.rb
187
193
  - lib/platformos_check/html_check.rb
188
194
  - lib/platformos_check/html_node.rb
189
195
  - lib/platformos_check/html_visitor.rb
@@ -349,7 +355,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
349
355
  - !ruby/object:Gem::Version
350
356
  version: '0'
351
357
  requirements: []
352
- rubygems_version: 3.4.18
358
+ rubygems_version: 3.4.22
353
359
  signing_key:
354
360
  specification_version: 4
355
361
  summary: A platformOS App Linter