platformos-check 0.0.1

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.
Files changed (240) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +2 -0
  3. data/.gitignore +22 -0
  4. data/.rubocop.yml +555 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/CONTRIBUTING.md +209 -0
  8. data/Gemfile +33 -0
  9. data/Guardfile +7 -0
  10. data/LICENSE.md +10 -0
  11. data/Makefile +18 -0
  12. data/README.md +189 -0
  13. data/RELEASING.md +35 -0
  14. data/Rakefile +83 -0
  15. data/TROUBLESHOOTING.md +35 -0
  16. data/bin/platformos-check +29 -0
  17. data/bin/platformos-check-language-server +29 -0
  18. data/config/default.yml +98 -0
  19. data/config/nothing.yml +11 -0
  20. data/data/platformos_liquid/built_in_liquid_objects.json +66 -0
  21. data/data/platformos_liquid/deprecated_filters.json +22 -0
  22. data/data/platformos_liquid/documentation/filters.json +6 -0
  23. data/data/platformos_liquid/documentation/latest.json +2 -0
  24. data/data/platformos_liquid/documentation/objects.json +6 -0
  25. data/data/platformos_liquid/documentation/tags.json +6 -0
  26. data/docker/check.Dockerfile +3 -0
  27. data/docker/lsp.Dockerfile +21 -0
  28. data/docs/api/check.md +15 -0
  29. data/docs/api/html_check.md +46 -0
  30. data/docs/api/liquid_check.md +115 -0
  31. data/docs/api/yaml_check.md +19 -0
  32. data/docs/checks/TEMPLATE.md.erb +52 -0
  33. data/docs/checks/convert_include_to_render.md +48 -0
  34. data/docs/checks/deprecated_filter.md +30 -0
  35. data/docs/checks/html_parsing_error.md +50 -0
  36. data/docs/checks/img_lazy_loading.md +63 -0
  37. data/docs/checks/img_width_and_height.md +79 -0
  38. data/docs/checks/invalid_args.md +56 -0
  39. data/docs/checks/liquid_tag.md +65 -0
  40. data/docs/checks/missing_enable_comment.md +50 -0
  41. data/docs/checks/missing_template.md +65 -0
  42. data/docs/checks/parse_json_format.md +76 -0
  43. data/docs/checks/parser_blocking_javascript.md +97 -0
  44. data/docs/checks/required_layout_object.md +28 -0
  45. data/docs/checks/space_inside_braces.md +89 -0
  46. data/docs/checks/syntax_error.md +49 -0
  47. data/docs/checks/template_length.md +45 -0
  48. data/docs/checks/undefined_object.md +71 -0
  49. data/docs/checks/unknown_filter.md +46 -0
  50. data/docs/checks/unused_assign.md +63 -0
  51. data/docs/checks/unused_partial.md +32 -0
  52. data/docs/checks/valid_yaml.md +48 -0
  53. data/docs/flamegraph.svg +18488 -0
  54. data/docs/language_server/code-action-command-palette.png +0 -0
  55. data/docs/language_server/code-action-flow.png +0 -0
  56. data/docs/language_server/code-action-keyboard.png +0 -0
  57. data/docs/language_server/code-action-light-bulb.png +0 -0
  58. data/docs/language_server/code-action-problem.png +0 -0
  59. data/docs/language_server/code-action-quickfix.png +0 -0
  60. data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
  61. data/docs/preview.png +0 -0
  62. data/exe/platformos-check +6 -0
  63. data/exe/platformos-check-language-server +7 -0
  64. data/lib/platformos_check/analyzer.rb +178 -0
  65. data/lib/platformos_check/api_call_file.rb +9 -0
  66. data/lib/platformos_check/app.rb +138 -0
  67. data/lib/platformos_check/app_file.rb +89 -0
  68. data/lib/platformos_check/app_file_rewriter.rb +79 -0
  69. data/lib/platformos_check/asset_file.rb +34 -0
  70. data/lib/platformos_check/bug.rb +23 -0
  71. data/lib/platformos_check/check.rb +163 -0
  72. data/lib/platformos_check/checks/TEMPLATE.rb.erb +11 -0
  73. data/lib/platformos_check/checks/convert_include_to_render.rb +17 -0
  74. data/lib/platformos_check/checks/deprecated_filter.rb +123 -0
  75. data/lib/platformos_check/checks/html_parsing_error.rb +13 -0
  76. data/lib/platformos_check/checks/img_lazy_loading.rb +18 -0
  77. data/lib/platformos_check/checks/img_width_and_height.rb +46 -0
  78. data/lib/platformos_check/checks/invalid_args.rb +81 -0
  79. data/lib/platformos_check/checks/liquid_tag.rb +47 -0
  80. data/lib/platformos_check/checks/missing_enable_comment.rb +37 -0
  81. data/lib/platformos_check/checks/missing_template.rb +107 -0
  82. data/lib/platformos_check/checks/parse_json_format.rb +31 -0
  83. data/lib/platformos_check/checks/parser_blocking_javascript.rb +17 -0
  84. data/lib/platformos_check/checks/required_layout_object.rb +41 -0
  85. data/lib/platformos_check/checks/space_inside_braces.rb +150 -0
  86. data/lib/platformos_check/checks/syntax_error.rb +31 -0
  87. data/lib/platformos_check/checks/template_length.rb +20 -0
  88. data/lib/platformos_check/checks/undefined_object.rb +206 -0
  89. data/lib/platformos_check/checks/unknown_filter.rb +27 -0
  90. data/lib/platformos_check/checks/unused_assign.rb +101 -0
  91. data/lib/platformos_check/checks/unused_partial.rb +93 -0
  92. data/lib/platformos_check/checks/valid_yaml.rb +16 -0
  93. data/lib/platformos_check/checks.rb +73 -0
  94. data/lib/platformos_check/checks_tracking.rb +9 -0
  95. data/lib/platformos_check/cli.rb +239 -0
  96. data/lib/platformos_check/config.rb +219 -0
  97. data/lib/platformos_check/config_file.rb +6 -0
  98. data/lib/platformos_check/corrector.rb +68 -0
  99. data/lib/platformos_check/disabled_check.rb +44 -0
  100. data/lib/platformos_check/disabled_checks.rb +96 -0
  101. data/lib/platformos_check/email_file.rb +9 -0
  102. data/lib/platformos_check/exceptions.rb +36 -0
  103. data/lib/platformos_check/file_system_storage.rb +93 -0
  104. data/lib/platformos_check/graphql_file.rb +68 -0
  105. data/lib/platformos_check/html_check.rb +8 -0
  106. data/lib/platformos_check/html_node.rb +210 -0
  107. data/lib/platformos_check/html_visitor.rb +36 -0
  108. data/lib/platformos_check/in_memory_storage.rb +68 -0
  109. data/lib/platformos_check/json_file.rb +57 -0
  110. data/lib/platformos_check/json_helper.rb +73 -0
  111. data/lib/platformos_check/json_helpers.rb +24 -0
  112. data/lib/platformos_check/json_printer.rb +32 -0
  113. data/lib/platformos_check/language_server/bridge.rb +167 -0
  114. data/lib/platformos_check/language_server/channel.rb +69 -0
  115. data/lib/platformos_check/language_server/client_capabilities.rb +27 -0
  116. data/lib/platformos_check/language_server/code_action_engine.rb +32 -0
  117. data/lib/platformos_check/language_server/code_action_provider.rb +41 -0
  118. data/lib/platformos_check/language_server/code_action_providers/quickfix_code_action_provider.rb +85 -0
  119. data/lib/platformos_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +41 -0
  120. data/lib/platformos_check/language_server/completion_context.rb +52 -0
  121. data/lib/platformos_check/language_server/completion_engine.rb +32 -0
  122. data/lib/platformos_check/language_server/completion_helper.rb +26 -0
  123. data/lib/platformos_check/language_server/completion_provider.rb +53 -0
  124. data/lib/platformos_check/language_server/completion_providers/assignments_completion_provider.rb +40 -0
  125. data/lib/platformos_check/language_server/completion_providers/filter_completion_provider.rb +102 -0
  126. data/lib/platformos_check/language_server/completion_providers/object_attribute_completion_provider.rb +48 -0
  127. data/lib/platformos_check/language_server/completion_providers/object_completion_provider.rb +38 -0
  128. data/lib/platformos_check/language_server/completion_providers/render_snippet_completion_provider.rb +50 -0
  129. data/lib/platformos_check/language_server/completion_providers/tag_completion_provider.rb +41 -0
  130. data/lib/platformos_check/language_server/configuration.rb +89 -0
  131. data/lib/platformos_check/language_server/constants.rb +29 -0
  132. data/lib/platformos_check/language_server/diagnostic.rb +129 -0
  133. data/lib/platformos_check/language_server/diagnostics_engine.rb +131 -0
  134. data/lib/platformos_check/language_server/diagnostics_manager.rb +184 -0
  135. data/lib/platformos_check/language_server/document_change_corrector.rb +271 -0
  136. data/lib/platformos_check/language_server/document_link_engine.rb +21 -0
  137. data/lib/platformos_check/language_server/document_link_provider.rb +71 -0
  138. data/lib/platformos_check/language_server/document_link_providers/asset_document_link_provider.rb +11 -0
  139. data/lib/platformos_check/language_server/document_link_providers/include_document_link_provider.rb +11 -0
  140. data/lib/platformos_check/language_server/document_link_providers/render_document_link_provider.rb +11 -0
  141. data/lib/platformos_check/language_server/document_link_providers/section_document_link_provider.rb +11 -0
  142. data/lib/platformos_check/language_server/execute_command_engine.rb +19 -0
  143. data/lib/platformos_check/language_server/execute_command_provider.rb +30 -0
  144. data/lib/platformos_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
  145. data/lib/platformos_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +28 -0
  146. data/lib/platformos_check/language_server/handler.rb +310 -0
  147. data/lib/platformos_check/language_server/hover_engine.rb +32 -0
  148. data/lib/platformos_check/language_server/hover_provider.rb +53 -0
  149. data/lib/platformos_check/language_server/hover_providers/filter_hover_provider.rb +113 -0
  150. data/lib/platformos_check/language_server/io_messenger.rb +109 -0
  151. data/lib/platformos_check/language_server/messenger.rb +27 -0
  152. data/lib/platformos_check/language_server/protocol.rb +55 -0
  153. data/lib/platformos_check/language_server/server.rb +188 -0
  154. data/lib/platformos_check/language_server/tokens.rb +55 -0
  155. data/lib/platformos_check/language_server/type_helper.rb +22 -0
  156. data/lib/platformos_check/language_server/uri_helper.rb +39 -0
  157. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/node_handler.rb +87 -0
  158. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/scope.rb +60 -0
  159. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/scope_visitor.rb +44 -0
  160. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder.rb +76 -0
  161. data/lib/platformos_check/language_server/variable_lookup_finder/constants.rb +44 -0
  162. data/lib/platformos_check/language_server/variable_lookup_finder/liquid_fixer.rb +103 -0
  163. data/lib/platformos_check/language_server/variable_lookup_finder/potential_lookup.rb +10 -0
  164. data/lib/platformos_check/language_server/variable_lookup_finder/tolerant_parser.rb +94 -0
  165. data/lib/platformos_check/language_server/variable_lookup_finder.rb +262 -0
  166. data/lib/platformos_check/language_server/variable_lookup_traverser.rb +70 -0
  167. data/lib/platformos_check/language_server/versioned_in_memory_storage.rb +84 -0
  168. data/lib/platformos_check/language_server.rb +71 -0
  169. data/lib/platformos_check/layout_file.rb +15 -0
  170. data/lib/platformos_check/liquid_check.rb +10 -0
  171. data/lib/platformos_check/liquid_file.rb +102 -0
  172. data/lib/platformos_check/liquid_node.rb +570 -0
  173. data/lib/platformos_check/liquid_visitor.rb +39 -0
  174. data/lib/platformos_check/migration_file.rb +9 -0
  175. data/lib/platformos_check/node.rb +53 -0
  176. data/lib/platformos_check/offense.rb +228 -0
  177. data/lib/platformos_check/page_file.rb +9 -0
  178. data/lib/platformos_check/parsing_helpers.rb +21 -0
  179. data/lib/platformos_check/partial_file.rb +15 -0
  180. data/lib/platformos_check/platformos_liquid/deprecated_filter.rb +31 -0
  181. data/lib/platformos_check/platformos_liquid/documentation/markdown_template.rb +51 -0
  182. data/lib/platformos_check/platformos_liquid/documentation.rb +45 -0
  183. data/lib/platformos_check/platformos_liquid/filter.rb +19 -0
  184. data/lib/platformos_check/platformos_liquid/object.rb +15 -0
  185. data/lib/platformos_check/platformos_liquid/source_index/base_entry.rb +66 -0
  186. data/lib/platformos_check/platformos_liquid/source_index/base_state.rb +23 -0
  187. data/lib/platformos_check/platformos_liquid/source_index/filter_entry.rb +26 -0
  188. data/lib/platformos_check/platformos_liquid/source_index/filter_state.rb +11 -0
  189. data/lib/platformos_check/platformos_liquid/source_index/object_entry.rb +20 -0
  190. data/lib/platformos_check/platformos_liquid/source_index/object_state.rb +11 -0
  191. data/lib/platformos_check/platformos_liquid/source_index/parameter_entry.rb +25 -0
  192. data/lib/platformos_check/platformos_liquid/source_index/property_entry.rb +21 -0
  193. data/lib/platformos_check/platformos_liquid/source_index/return_type_entry.rb +41 -0
  194. data/lib/platformos_check/platformos_liquid/source_index/tag_entry.rb +24 -0
  195. data/lib/platformos_check/platformos_liquid/source_index/tag_state.rb +11 -0
  196. data/lib/platformos_check/platformos_liquid/source_index.rb +79 -0
  197. data/lib/platformos_check/platformos_liquid/source_manager.rb +116 -0
  198. data/lib/platformos_check/platformos_liquid/tag.rb +59 -0
  199. data/lib/platformos_check/platformos_liquid.rb +21 -0
  200. data/lib/platformos_check/position.rb +180 -0
  201. data/lib/platformos_check/position_helper.rb +57 -0
  202. data/lib/platformos_check/printer.rb +87 -0
  203. data/lib/platformos_check/regex_helpers.rb +21 -0
  204. data/lib/platformos_check/releaser.rb +43 -0
  205. data/lib/platformos_check/schema_file.rb +6 -0
  206. data/lib/platformos_check/sms_file.rb +9 -0
  207. data/lib/platformos_check/storage.rb +29 -0
  208. data/lib/platformos_check/string_helpers.rb +48 -0
  209. data/lib/platformos_check/tags/background.rb +67 -0
  210. data/lib/platformos_check/tags/base.rb +14 -0
  211. data/lib/platformos_check/tags/base_block.rb +14 -0
  212. data/lib/platformos_check/tags/base_tag_methods.rb +59 -0
  213. data/lib/platformos_check/tags/cache.rb +13 -0
  214. data/lib/platformos_check/tags/export.rb +30 -0
  215. data/lib/platformos_check/tags/form.rb +19 -0
  216. data/lib/platformos_check/tags/function.rb +58 -0
  217. data/lib/platformos_check/tags/graphql.rb +70 -0
  218. data/lib/platformos_check/tags/hash_assign.rb +75 -0
  219. data/lib/platformos_check/tags/log.rb +15 -0
  220. data/lib/platformos_check/tags/parse_json.rb +24 -0
  221. data/lib/platformos_check/tags/print.rb +20 -0
  222. data/lib/platformos_check/tags/redirect_to.rb +15 -0
  223. data/lib/platformos_check/tags/render.rb +60 -0
  224. data/lib/platformos_check/tags/response_headers.rb +20 -0
  225. data/lib/platformos_check/tags/response_status.rb +20 -0
  226. data/lib/platformos_check/tags/return.rb +20 -0
  227. data/lib/platformos_check/tags/session.rb +27 -0
  228. data/lib/platformos_check/tags/sign_in.rb +27 -0
  229. data/lib/platformos_check/tags/spam_protection.rb +15 -0
  230. data/lib/platformos_check/tags/theme_render.rb +58 -0
  231. data/lib/platformos_check/tags/try.rb +59 -0
  232. data/lib/platformos_check/tags.rb +65 -0
  233. data/lib/platformos_check/translation_file.rb +6 -0
  234. data/lib/platformos_check/user_schema_file.rb +6 -0
  235. data/lib/platformos_check/version.rb +5 -0
  236. data/lib/platformos_check/yaml_check.rb +11 -0
  237. data/lib/platformos_check/yaml_file.rb +57 -0
  238. data/lib/platformos_check.rb +106 -0
  239. data/platformos-check.gemspec +34 -0
  240. metadata +329 -0
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ module VariableLookupFinder
6
+ ##
7
+ # Attempt to turn the code of the token until the cursor position into
8
+ # valid liquid code.
9
+ #
10
+ class LiquidFixer
11
+ include Constants
12
+
13
+ attr_reader :content, :cursor
14
+
15
+ def initialize(content, cursor = nil)
16
+ @content = content
17
+ @cursor = cursor || content.size
18
+ end
19
+
20
+ def parsable
21
+ # Welcome to Hackcity
22
+ @markup = content[0...cursor]
23
+
24
+ catch(:empty_lookup_markup) do
25
+ # close open delimiters
26
+ @markup += "'" if @markup.count("'").odd?
27
+ @markup += '"' if @markup.count('"').odd?
28
+ @markup += "]" if UNCLOSED_SQUARE_BRACKET.match?(@markup)
29
+
30
+ @ends_with_blank_potential_lookup = @markup =~ ENDS_WITH_BLANK_POTENTIAL_LOOKUP
31
+ @markup = last_line if liquid_tag?
32
+
33
+ @markup = "{% #{@markup}" unless has_start_tag?
34
+
35
+ # close the tag
36
+ @markup += tag_end unless has_end_tag?
37
+
38
+ # close if statements
39
+ @markup += '{% endif %}' if tag?('if')
40
+
41
+ # close unless statements
42
+ @markup += '{% endunless %}' if tag?('unless')
43
+
44
+ # close elsif statements
45
+ @markup = "{% if x %}#{@markup}{% endif %}" if tag?('elsif')
46
+
47
+ # close case statements
48
+ @markup += '{% endcase %}' if tag?('case')
49
+
50
+ # close when statements
51
+ @markup = "{% case x %}#{@markup}{% endcase %}" if tag?('when')
52
+
53
+ # close for statements
54
+ @markup += '{% endfor %}' if tag?('for')
55
+
56
+ # close tablerow statements
57
+ @markup += '{% endtablerow %}' if tag?('tablerow')
58
+
59
+ @markup
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def tag?(tag_name)
66
+ if @markup&.match?(tag_regex(tag_name))
67
+ throw(:empty_lookup_markup, '') if @ends_with_blank_potential_lookup
68
+ true
69
+ else
70
+ false
71
+ end
72
+ end
73
+
74
+ def last_line
75
+ lines = @markup.rstrip.lines
76
+
77
+ last_line = lines.pop.lstrip while last_line.nil? || last_line =~ ANY_ENDING_TAG
78
+ last_line
79
+ end
80
+
81
+ def liquid_tag?
82
+ @markup =~ tag_regex('liquid')
83
+ end
84
+
85
+ def has_start_tag?
86
+ @markup =~ ANY_STARTING_TAG
87
+ end
88
+
89
+ def has_end_tag?
90
+ @markup =~ ANY_ENDING_TAG
91
+ end
92
+
93
+ def tag_end
94
+ VARIABLE_START.match?(@markup) ? ' }}' : ' %}'
95
+ end
96
+
97
+ def tag_regex(tag_name)
98
+ PlatformosLiquid::Tag.tag_regex(tag_name)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ module VariableLookupFinder
6
+ class PotentialLookup < Struct.new(:name, :lookups, :scope)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ module VariableLookupFinder
6
+ module TolerantParser
7
+ class Template
8
+ class << self
9
+ def parse(content)
10
+ ##
11
+ # The tolerant parser relies on a tolerant custom parse
12
+ # context to creates a new 'Template' object, even when
13
+ # a block is not closed.
14
+ Liquid::Template.parse(content, custom_parse_context)
15
+ end
16
+
17
+ private
18
+
19
+ def custom_parse_context
20
+ ParseContext.new
21
+ end
22
+ end
23
+ end
24
+
25
+ class ParseContext < Liquid::ParseContext
26
+ def new_block_body
27
+ BlockBody.new
28
+ end
29
+ end
30
+
31
+ class BlockBody < Liquid::BlockBody
32
+ ##
33
+ # The tags are statically defined and referenced at the
34
+ # 'Liquid::Template', so the TolerantParser just uses the
35
+ # redefined tags at this custom block body. Thus, there's
36
+ # no side-effects between the regular and the tolerant parsers.
37
+ def registered_tags
38
+ Tags.new(super)
39
+ end
40
+ end
41
+
42
+ class Tags
43
+ module TolerantBlockBody
44
+ ##
45
+ # This module defines the tolerant parse body that doesn't
46
+ # raise syntax errors when a block is not closed. Thus, the
47
+ # tolerant parser can build the AST for templates with this
48
+ # kind of error, which is quite common in language servers.
49
+ def parse_body(body, tokens)
50
+ super
51
+ rescue StandardError
52
+ false
53
+ end
54
+ end
55
+
56
+ class Case < Liquid::Case
57
+ include TolerantBlockBody
58
+ end
59
+
60
+ class For < Liquid::For
61
+ include TolerantBlockBody
62
+ end
63
+
64
+ class If < Liquid::If
65
+ include TolerantBlockBody
66
+ end
67
+
68
+ class TableRow < Liquid::TableRow
69
+ include TolerantBlockBody
70
+ end
71
+
72
+ class Unless < Liquid::Unless
73
+ include TolerantBlockBody
74
+ end
75
+
76
+ def initialize(standard_tags)
77
+ @standard_tags = standard_tags
78
+ @tolerant_tags = {
79
+ 'case' => Case,
80
+ 'for' => For,
81
+ 'if' => If,
82
+ 'tablerow' => TableRow,
83
+ 'unless' => Unless
84
+ }
85
+ end
86
+
87
+ def [](key)
88
+ @tolerant_tags[key] || @standard_tags[key]
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,262 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+
5
+ module PlatformosCheck
6
+ module LanguageServer
7
+ module VariableLookupFinder
8
+ include Constants
9
+ include TypeHelper
10
+ extend self
11
+
12
+ def lookup(context)
13
+ content = context.content
14
+ cursor = context.cursor
15
+
16
+ return if cursor_is_on_bracket_position_that_cant_be_completed(content, cursor)
17
+
18
+ variable_lookup = lookup_liquid_variable(content, cursor) || lookup_liquid_tag(content, cursor)
19
+
20
+ return variable_lookup if variable_lookup.is_a?(PotentialLookup)
21
+ return unless variable_lookup.is_a?(Liquid::VariableLookup)
22
+
23
+ potential_lookup(variable_lookup, context)
24
+ end
25
+
26
+ def lookup_literal(context)
27
+ lookup_liquid_variable(context.content, context.cursor)
28
+ end
29
+
30
+ private
31
+
32
+ def potential_lookup(variable, context)
33
+ return as_potential_lookup(variable) if context.buffer.nil? || context.buffer.empty?
34
+
35
+ buffer = context.buffer[0...context.absolute_cursor]
36
+ lookups = variable.lookups
37
+ assignments = find_assignments(buffer)
38
+ assignments_path = []
39
+
40
+ while assignments[variable.name] && !assignments_path.include?(assignments[variable.name])
41
+ variable = assignments[variable.name]
42
+ lookups = variable.lookups + lookups
43
+
44
+ assignments_path << variable
45
+ end
46
+
47
+ as_potential_lookup(variable, lookups:)
48
+ end
49
+
50
+ def find_assignments(buffer)
51
+ finder = AssignmentsFinder.new(buffer)
52
+ finder.find!
53
+ finder.assignments
54
+ end
55
+
56
+ def as_potential_lookup(variable, lookups: nil)
57
+ PotentialLookup.new(variable.name, lookups || variable.lookups)
58
+ end
59
+
60
+ def cursor_is_on_bracket_position_that_cant_be_completed(content, cursor)
61
+ content_before_cursor = content[0..cursor - 1]
62
+ return false unless /[\[\]]/.match?(content_before_cursor)
63
+
64
+ content_before_cursor =~ ENDS_IN_BRACKET_POSITION_THAT_CANT_BE_COMPLETED
65
+ end
66
+
67
+ def cursor_is_on_liquid_variable_lookup_position(content, cursor)
68
+ previous_char = content[cursor - 1]
69
+ is_liquid_variable = content =~ Liquid::VariableStart
70
+ is_in_variable_segment = previous_char =~ VARIABLE_LOOKUP_CHARACTERS
71
+ is_on_blank_variable_lookup_position = content[0..cursor - 1] =~ /[{:,-]\s+$/
72
+ (
73
+ is_liquid_variable && (
74
+ is_in_variable_segment ||
75
+ is_on_blank_variable_lookup_position
76
+ )
77
+ )
78
+ end
79
+
80
+ def lookup_liquid_variable(content, cursor)
81
+ return unless cursor_is_on_liquid_variable_lookup_position(content, cursor)
82
+
83
+ start_index = content.match(/#{Liquid::VariableStart}-?/o).end(0) + 1
84
+ end_index = cursor - 1
85
+
86
+ # We take the following content
87
+ # - start after the first two {{
88
+ # - end at cursor position
89
+ #
90
+ # That way, we'll have a partial liquid variable that
91
+ # can be parsed such that the "last" variable_lookup
92
+ # will be the one we're trying to complete.
93
+ markup = content[start_index..end_index]
94
+
95
+ # Early return for incomplete variables
96
+ return empty_lookup if /\s+$/.match?(markup)
97
+
98
+ # Now we go to hack city... The cursor might be in the middle
99
+ # of a string/square bracket lookup. We need to close those
100
+ # otherwise the variable parse won't work.
101
+ markup += "'" if markup.count("'").odd?
102
+ markup += '"' if markup.count('"').odd?
103
+ markup += "]" if UNCLOSED_SQUARE_BRACKET.match?(markup)
104
+
105
+ variable = variable_from_markup(markup)
106
+
107
+ variable_lookup_for_liquid_variable(variable)
108
+ end
109
+
110
+ def cursor_is_on_liquid_tag_lookup_position(content, cursor)
111
+ markup = content[0..cursor - 1]
112
+ is_liquid_tag = content.match?(Liquid::TagStart)
113
+ is_in_variable_segment = markup =~ ENDS_WITH_POTENTIAL_LOOKUP
114
+ is_on_blank_variable_lookup_position = markup =~ ENDS_WITH_BLANK_POTENTIAL_LOOKUP
115
+ (
116
+ is_liquid_tag && (
117
+ is_in_variable_segment ||
118
+ is_on_blank_variable_lookup_position
119
+ )
120
+ )
121
+ end
122
+
123
+ # Context:
124
+ #
125
+ # We know full well that the code as it is being typed is probably not
126
+ # something that can be parsed by liquid.
127
+ #
128
+ # How this works:
129
+ #
130
+ # 1. Attempt to turn the code of the token until the cursor position into
131
+ # valid liquid code with some hacks.
132
+ # 2. If the code ends in space at a "potential lookup" spot
133
+ # a. Then return an empty variable lookup
134
+ # 3. Parse the valid liquid code
135
+ # 4. Attempt to extract a VariableLookup from Liquid::Template
136
+ def lookup_liquid_tag(content, cursor)
137
+ return unless cursor_is_on_liquid_tag_lookup_position(content, cursor)
138
+
139
+ markup = parseable_markup(content, cursor)
140
+ return empty_lookup if markup.empty?
141
+
142
+ template = Liquid::Template.parse(markup)
143
+ current_tag = template.root.nodelist[0]
144
+
145
+ case current_tag&.tag_name
146
+ when "if", "unless"
147
+ variable_lookup_for_if_tag(current_tag)
148
+ when "case"
149
+ variable_lookup_for_case_tag(current_tag)
150
+ when "cycle"
151
+ variable_lookup_for_cycle_tag(current_tag)
152
+ when "for"
153
+ variable_lookup_for_for_tag(current_tag)
154
+ when "tablerow"
155
+ variable_lookup_for_tablerow_tag(current_tag)
156
+ when "render"
157
+ variable_lookup_for_render_tag(current_tag)
158
+ when "assign"
159
+ variable_lookup_for_assign_tag(current_tag)
160
+ when "echo"
161
+ variable_lookup_for_echo_tag(current_tag)
162
+ else
163
+ empty_lookup
164
+ end
165
+ rescue Liquid::SyntaxError
166
+ # We don't complete variable for liquid syntax errors
167
+ empty_lookup
168
+ end
169
+
170
+ def parseable_markup(content, cursor = nil)
171
+ LiquidFixer.new(content, cursor).parsable
172
+ end
173
+
174
+ def variable_lookup_for_if_tag(if_tag)
175
+ condition = if_tag.blocks.last
176
+ variable_lookup_for_condition(condition)
177
+ end
178
+
179
+ def variable_lookup_for_condition(condition)
180
+ return variable_lookup_for_condition(condition.child_condition) if condition.child_condition
181
+ return condition.right if condition.right
182
+
183
+ condition.left
184
+ end
185
+
186
+ def variable_lookup_for_case_tag(case_tag)
187
+ return variable_lookup_for_case_block(case_tag.blocks.last) unless case_tag.blocks.empty?
188
+
189
+ case_tag.left
190
+ end
191
+
192
+ def variable_lookup_for_case_block(condition)
193
+ condition.right
194
+ end
195
+
196
+ def variable_lookup_for_cycle_tag(cycle_tag)
197
+ cycle_tag.variables.last
198
+ end
199
+
200
+ def variable_lookup_for_for_tag(for_tag)
201
+ for_tag.collection_name
202
+ end
203
+
204
+ def variable_lookup_for_tablerow_tag(tablerow_tag)
205
+ tablerow_tag.collection_name
206
+ end
207
+
208
+ def variable_lookup_for_render_tag(render_tag)
209
+ return empty_lookup if /:\s*$/.match?(render_tag.raw)
210
+
211
+ render_tag.attributes.values.last
212
+ end
213
+
214
+ def variable_lookup_for_assign_tag(assign_tag)
215
+ variable_lookup_for_liquid_variable(assign_tag.from)
216
+ end
217
+
218
+ def variable_lookup_for_echo_tag(echo_tag)
219
+ variable_lookup_for_liquid_variable(echo_tag.variable)
220
+ end
221
+
222
+ def variable_lookup_for_liquid_variable(variable)
223
+ has_filters = !variable.filters.empty?
224
+
225
+ # Can complete after trailing comma or :
226
+ if has_filters && variable.raw =~ /[:,]\s*$/
227
+ empty_lookup
228
+ elsif has_filters
229
+ last_filter_argument(variable.filters)
230
+ elsif variable.name.nil?
231
+ empty_lookup
232
+ elsif variable.name.is_a?(Liquid::VariableLookup)
233
+ variable.name
234
+ else
235
+ PotentialLookup.new(input_type_of(variable.name), [])
236
+ end
237
+ end
238
+
239
+ def empty_lookup
240
+ Liquid::VariableLookup.parse('')
241
+ end
242
+
243
+ # We want the last thing in variable.filters which is at most
244
+ # an array that looks like [name, positional_args, hash_arg]
245
+ def last_filter_argument(filters)
246
+ filter = filters.last
247
+ return filter[2].values.last if filter.size == 3
248
+ return filter[1].last if filter.size == 2
249
+
250
+ nil
251
+ end
252
+
253
+ def variable_from_markup(markup, parse_context = Liquid::ParseContext.new)
254
+ Liquid::Variable.new(markup, parse_context)
255
+ end
256
+
257
+ def tag_regex(tag)
258
+ PlatformosLiquid::Tag.tag_regex(tag)
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ module VariableLookupTraverser
6
+ extend self
7
+
8
+ def lookup_object_and_property(potential_lookup)
9
+ object, generic_type = find_object_and_generic_type(potential_lookup)
10
+ property = nil
11
+
12
+ potential_lookup.lookups.each do |name|
13
+ prop = find_property(object, name)
14
+
15
+ next unless prop
16
+
17
+ generic_type = generic_type(prop) if generic_type?(prop)
18
+
19
+ property = prop
20
+ property.return_type = generic_type if prop.generic_type?
21
+ object = find_object(prop.return_type)
22
+ end
23
+
24
+ [object, property]
25
+ end
26
+
27
+ def find_object_and_generic_type(potential_lookup)
28
+ generic_type = nil
29
+ object = find_object(potential_lookup.name)
30
+
31
+ # Objects like 'product' are a complex structure with fields
32
+ # and their return type is not present.
33
+ #
34
+ # However, we also handle objects that have simple built-in types,
35
+ # like 'current_tags', which is an 'array'. So, we follow them until
36
+ # the source type:
37
+ while object&.return_type
38
+ generic_type = generic_type(object) if generic_type?(object)
39
+ object = find_object(object.return_type)
40
+ end
41
+
42
+ [object, generic_type]
43
+ end
44
+
45
+ # Currently, we're handling generic types only for arrays,
46
+ # so we get the array type
47
+ def generic_type(object)
48
+ object.array_type
49
+ end
50
+
51
+ # Currently, we're handling generic types only for arrays,
52
+ # so we check if it's an array type
53
+ def generic_type?(object)
54
+ object.array_type?
55
+ end
56
+
57
+ def find_property(object, property_name)
58
+ object
59
+ &.properties
60
+ &.find { |property| property.name == property_name }
61
+ end
62
+
63
+ def find_object(object_name)
64
+ PlatformosLiquid::SourceIndex
65
+ .objects
66
+ .find { |entry| entry.name == object_name }
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ class VersionedInMemoryStorage < InMemoryStorage
5
+ Version = Struct.new(:id, :version)
6
+
7
+ attr_reader :versions
8
+
9
+ def initialize(files, root = "/dev/null")
10
+ super(files, root)
11
+ @versions = {} # Hash<relative_path, number>
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+ # Motivations:
16
+ # - Need way for LanguageServer to know on which version of a file
17
+ # the check was run on, because we need to know where the
18
+ # TextEdit goes. If the text changed, our TextEdit might not be
19
+ # in the right spot. e.g.
20
+ #
21
+ # Example:
22
+ #
23
+ # ```
24
+ # Hi
25
+ # {{world}}
26
+ # ```
27
+ #
28
+ # Would produce two "SpaceInsideBrace" errors:
29
+ #
30
+ # - One after {{ at index 5 to 6
31
+ # - One before }} at index 10 to 11
32
+ #
33
+ # If the user goes in and changes Hi to Sup, and _then_
34
+ # right clicks to apply the code edit at index 5 to 6, he'd
35
+ # get the following:
36
+ #
37
+ # ```
38
+ # Sup
39
+ # { {world}}
40
+ # ```
41
+ #
42
+ # Which is not a fix at all.
43
+ #
44
+ # Solution:
45
+ # - Have the LanguageServer store the version on textDocument/did{Open,Change,Close}
46
+ # - Have AppFile store the version right after @storage.read.
47
+ # - Add version to the diagnostic meta data
48
+ # - Use diagnostic meta data to determine if we can make a code edit or not
49
+ # - Only offer fixes on "clean" files (or offer the change but specify the version so the editor knows what to do with it)
50
+ def write(relative_path, content, version)
51
+ @mutex.synchronize do
52
+ if version.nil?
53
+ @versions.delete(relative_path)
54
+ else
55
+ @versions[relative_path] = version
56
+ end
57
+ super(relative_path, content)
58
+ end
59
+ end
60
+
61
+ def read_version(relative_path)
62
+ @mutex.synchronize { [read(relative_path), version(relative_path)] }
63
+ end
64
+
65
+ def remove(relative_path)
66
+ @mutex.synchronize do
67
+ @versions.delete(relative_path)
68
+ super(relative_path)
69
+ end
70
+ end
71
+
72
+ def versioned?
73
+ true
74
+ end
75
+
76
+ def version(relative_path)
77
+ @versions[relative_path.to_s]
78
+ end
79
+
80
+ def opened_files
81
+ @versions.keys
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "language_server/protocol"
4
+ require_relative "language_server/constants"
5
+ require_relative "language_server/configuration"
6
+ require_relative "language_server/channel"
7
+ require_relative "language_server/messenger"
8
+ require_relative "language_server/io_messenger"
9
+ require_relative "language_server/bridge"
10
+ require_relative "language_server/uri_helper"
11
+ require_relative "language_server/type_helper"
12
+ require_relative "language_server/server"
13
+ require_relative "language_server/tokens"
14
+ require_relative "language_server/variable_lookup_finder/potential_lookup"
15
+ require_relative "language_server/variable_lookup_finder/tolerant_parser"
16
+ require_relative "language_server/variable_lookup_finder/assignments_finder/node_handler"
17
+ require_relative "language_server/variable_lookup_finder/assignments_finder/scope_visitor"
18
+ require_relative "language_server/variable_lookup_finder/assignments_finder/scope"
19
+ require_relative "language_server/variable_lookup_finder/assignments_finder"
20
+ require_relative "language_server/variable_lookup_finder/constants"
21
+ require_relative "language_server/variable_lookup_finder/liquid_fixer"
22
+ require_relative "language_server/variable_lookup_finder"
23
+ require_relative "language_server/variable_lookup_traverser"
24
+ require_relative "language_server/diagnostic"
25
+ require_relative "language_server/diagnostics_manager"
26
+ require_relative "language_server/diagnostics_engine"
27
+ require_relative "language_server/document_change_corrector"
28
+ require_relative "language_server/versioned_in_memory_storage"
29
+ require_relative "language_server/client_capabilities"
30
+
31
+ require_relative "language_server/completion_context"
32
+ require_relative "language_server/completion_helper"
33
+ require_relative "language_server/completion_provider"
34
+ require_relative "language_server/completion_engine"
35
+ Dir[__dir__ + "/language_server/completion_providers/*.rb"].each do |file|
36
+ require file
37
+ end
38
+
39
+ require_relative "language_server/hover_provider"
40
+ require_relative "language_server/hover_engine"
41
+ Dir[__dir__ + "/language_server/hover_providers/*.rb"].each do |file|
42
+ require file
43
+ end
44
+
45
+ require_relative "language_server/document_link_provider"
46
+ require_relative "language_server/document_link_engine"
47
+ Dir[__dir__ + "/language_server/document_link_providers/*.rb"].each do |file|
48
+ require file
49
+ end
50
+
51
+ require_relative "language_server/execute_command_provider"
52
+ require_relative "language_server/execute_command_engine"
53
+ Dir[__dir__ + "/language_server/execute_command_providers/*.rb"].each do |file|
54
+ require file
55
+ end
56
+
57
+ require_relative "language_server/code_action_provider"
58
+ require_relative "language_server/code_action_engine"
59
+ Dir[__dir__ + "/language_server/code_action_providers/*.rb"].each do |file|
60
+ require file
61
+ end
62
+
63
+ require_relative "language_server/handler"
64
+
65
+ module PlatformosCheck
66
+ module LanguageServer
67
+ def self.start
68
+ Server.new(messenger: IOMessenger.new).listen
69
+ end
70
+ end
71
+ end