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,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'stringio'
5
+ require 'timeout'
6
+
7
+ module PlatformosCheck
8
+ module LanguageServer
9
+ class DoneStreaming < StandardError; end
10
+
11
+ class IncompatibleStream < StandardError; end
12
+
13
+ class Server
14
+ attr_reader :handler, :should_raise_errors
15
+
16
+ def initialize(
17
+ messenger:,
18
+ should_raise_errors: false,
19
+ number_of_threads: 2
20
+ )
21
+ # This is what does the IO
22
+ @messenger = messenger
23
+
24
+ # This is what you use to communicate with the language client
25
+ @bridge = Bridge.new(@messenger)
26
+
27
+ # The handler handles messages from the language client
28
+ @handler = Handler.new(@bridge)
29
+
30
+ # The queue holds the JSON RPC messages
31
+ @queue = Queue.new
32
+
33
+ # The JSON RPC thread pushes messages onto the queue
34
+ @json_rpc_thread = nil
35
+
36
+ # The handler threads read messages from the queue
37
+ @number_of_threads = number_of_threads
38
+ @handlers = []
39
+
40
+ # The error queue holds blocks the main thread. When filled, we exit the program.
41
+ @error = SizedQueue.new(number_of_threads)
42
+
43
+ @should_raise_errors = should_raise_errors
44
+ end
45
+
46
+ def listen
47
+ start_handler_threads
48
+ start_json_rpc_thread
49
+ err = @error.pop
50
+ status_code = status_code_from_error(err)
51
+
52
+ if status_code > 0
53
+ # For a reason I can't comprehend, this hangs but prints
54
+ # anyway. So it's wrapped in this ugly timeout...
55
+ Timeout.timeout(1) do
56
+ warn err.full_message
57
+ end
58
+
59
+ # Warn user of error, otherwise server might restart
60
+ # without telling you.
61
+ @bridge.send_notification("window/showMessage", {
62
+ type: 1,
63
+ message: "A platformos-check-language-server error has occurred, search OUTPUT logs for details."
64
+ })
65
+ end
66
+
67
+ cleanup(status_code)
68
+ rescue SignalException
69
+ 0
70
+ rescue StandardError
71
+ 2
72
+ end
73
+
74
+ def start_json_rpc_thread
75
+ @json_rpc_thread = Thread.new do
76
+ loop do
77
+ message = @bridge.read_message
78
+ if message[:method] == 'initialize'
79
+ handle_message(message)
80
+ elsif message.key?(:result)
81
+ # Responses are handled on the main thread to prevent
82
+ # a potential deadlock caused by all handlers waiting
83
+ # for a responses.
84
+ handle_response(message)
85
+ else
86
+ @queue << message
87
+ end
88
+ end
89
+ rescue Exception => e # rubocop:disable Lint/RescueException
90
+ @bridge.log("rescuing #{e.class} in jsonrpc thread")
91
+ @error << e
92
+ end
93
+ end
94
+
95
+ def start_handler_threads
96
+ @number_of_threads.times do
97
+ @handlers << Thread.new do
98
+ handle_messages
99
+ end
100
+ end
101
+ end
102
+
103
+ def handle_messages
104
+ loop do
105
+ message = @queue.pop
106
+ return if @queue.closed? && @queue.empty?
107
+
108
+ handle_message(message)
109
+ end
110
+ rescue Exception => e # rubocop:disable Lint/RescueException
111
+ @bridge.log("rescuing #{e} in handler thread")
112
+ @error << e
113
+ end
114
+
115
+ def status_code_from_error(e)
116
+ raise e
117
+
118
+ # support ctrl+c and stuff
119
+ rescue SignalException, DoneStreaming
120
+ 0
121
+ rescue Exception => e # rubocop:disable Lint/RescueException
122
+ raise e if should_raise_errors
123
+
124
+ @bridge.log("Fatal #{e.class}")
125
+ 2
126
+ end
127
+
128
+ private
129
+
130
+ def handle_message(message)
131
+ id = message[:id]
132
+ method_name = message[:method]
133
+ method_name &&= "on_#{to_snake_case(method_name)}"
134
+ params = message[:params]
135
+
136
+ @handler.send(method_name, id, params) if @handler.respond_to?(method_name)
137
+ rescue DoneStreaming => e
138
+ raise e
139
+ rescue StandardError => e
140
+ is_request = id
141
+ raise e unless is_request
142
+
143
+ # Errors obtained in request handlers should be sent
144
+ # back as internal errors instead of closing the program.
145
+ @bridge.send_internal_error(id, e)
146
+ end
147
+
148
+ def handle_response(message)
149
+ id = message[:id]
150
+ result = message[:result]
151
+ @bridge.receive_response(id, result)
152
+ end
153
+
154
+ def to_snake_case(method_name)
155
+ StringHelpers.underscore(method_name.gsub(/[^\w]/, '_'))
156
+ end
157
+
158
+ def cleanup(status_code)
159
+ @bridge.log("Closing server... status code = #{status_code}")
160
+ # Stop listenting to RPC calls
161
+ @messenger.close_input
162
+ # Wait for rpc loop to close
163
+ @json_rpc_thread&.join if @json_rpc_thread&.alive?
164
+ # Close the queue
165
+ @queue.close unless @queue.closed?
166
+ # Give 10 seconds for the handlers to wrap up what they were
167
+ # doing/emptying the queue. 👀 unit tests.
168
+ @handlers.each { |thread| thread.join(10) if thread.alive? }
169
+
170
+ # Hijack the status_code if an error occurred while cleaning up.
171
+ # 👀 unit tests.
172
+ until @error.empty?
173
+ code = status_code_from_error(@error.pop)
174
+ # Promote the status_code to ERROR if one of the threads
175
+ # resulted in an error, otherwise leave the status_code as
176
+ # is. That's because one thread could end successfully in a
177
+ # DoneStreaming error while the other failed with an
178
+ # internal error. If we had an internal error, we should
179
+ # return with a status_code that fits.
180
+ status_code = code if code > status_code
181
+ end
182
+ status_code
183
+ ensure
184
+ @messenger.close_output
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ Token = Struct.new(
5
+ :content,
6
+ :start, # inclusive
7
+ :end # exclusive
8
+ )
9
+
10
+ TAG_START = Liquid::TagStart
11
+ TAG_END = Liquid::TagEnd
12
+ VARIABLE_START = Liquid::VariableStart
13
+ VARIABLE_END = Liquid::VariableEnd
14
+ SPLITTER = %r{
15
+ (?=(?:#{TAG_START}|#{VARIABLE_START}))| # positive lookahead on tag/variable start
16
+ (?<=(?:#{TAG_END}|#{VARIABLE_END})) # positive lookbehind on tag/variable end
17
+ }xom
18
+
19
+ # Implemented as an Enumerable so we stop iterating on the find once
20
+ # we have what we want. Kind of a perf thing.
21
+ class Tokens
22
+ include Enumerable
23
+
24
+ def initialize(buffer)
25
+ @buffer = buffer
26
+ end
27
+
28
+ def each(&block)
29
+ return to_enum(:each) unless block
30
+
31
+ chunks = @buffer.split(SPLITTER)
32
+ chunks.shift if chunks[0] && chunks[0].empty?
33
+
34
+ prev = Token.new('', 0, 0)
35
+ curr = Token.new('', 0, 0)
36
+
37
+ while (content = chunks.shift)
38
+
39
+ curr.start = prev.end
40
+ curr.end = curr.start + content.size
41
+
42
+ yield(Token.new(
43
+ content,
44
+ curr.start,
45
+ curr.end
46
+ ))
47
+
48
+ # recycling structs
49
+ tmp = prev
50
+ prev = curr
51
+ curr = tmp
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ module TypeHelper
6
+ def input_type_of(literal)
7
+ case literal
8
+ when String
9
+ 'string'
10
+ when Numeric
11
+ 'number'
12
+ when TrueClass, FalseClass
13
+ 'boolean'
14
+ when NilClass
15
+ 'nil'
16
+ else
17
+ 'untyped'
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+ require "uri"
5
+ require "cgi"
6
+
7
+ module PlatformosCheck
8
+ module LanguageServer
9
+ module URIHelper
10
+ # Will URI.encode a string the same way VS Code would. There are two things
11
+ # to watch out for:
12
+ #
13
+ # 1. VS Code still uses the outdated '%20' for spaces
14
+ # 2. VS Code prefixes Windows paths with / (so /C:/Users/... is expected)
15
+ #
16
+ # Exists because of https://github.com/Platform-OS/platformos-lsp/issues/360
17
+ def file_uri(absolute_path)
18
+ return if absolute_path.nil?
19
+
20
+ "file://" + absolute_path
21
+ .to_s
22
+ .split('/')
23
+ .map { |x| CGI.escape(x).gsub('+', '%20') }
24
+ .join('/')
25
+ .sub(%r{^/?}, '/') # Windows paths should be prefixed by /c:
26
+ end
27
+
28
+ def file_path(uri_string)
29
+ return if uri_string.nil?
30
+
31
+ uri = URI(uri_string)
32
+ path = CGI.unescape(uri.path)
33
+ # On Windows, VS Code sends the URLs as file:///C:/...
34
+ # /C:/1234 is not a valid path in ruby. So we strip the slash.
35
+ path.sub(%r{^/([a-z]:/)}i, '\1')
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ module VariableLookupFinder
6
+ class AssignmentsFinder
7
+ class NodeHandler
8
+ def on_assign(node, scope)
9
+ # When a variable is redefined in a new scope we
10
+ # no longer can guarantee the type in the global scope
11
+ #
12
+ # Example:
13
+ # ```liquid
14
+ # {%- liquid
15
+ # assign var1 = some_value
16
+ #
17
+ # if condition
18
+ # assign var1 = another_value
19
+ # ^^^^ from here we no longer can guarantee
20
+ # the type of `var1` in the global scope
21
+ # -%}
22
+ # ```
23
+ p_scope = scope
24
+ while (p_scope = p_scope.parent)
25
+ p_scope.variables.delete(node.value.to)
26
+ end
27
+
28
+ scope << node
29
+ scope
30
+ end
31
+
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
54
+ end
55
+
56
+ ##
57
+ # Table row tags do not rely on blocks to define scopes,
58
+ # so we index their value here
59
+ def on_table_row(node, scope)
60
+ scope = scope.new_child
61
+
62
+ scope << node
63
+ scope
64
+ end
65
+
66
+ ##
67
+ # Define a new scope every time a new block is created
68
+ def on_block_body(node, scope)
69
+ scope = scope.new_child
70
+
71
+ ##
72
+ # 'for' tags handle blocks flattenly and differently
73
+ # than the other tags (if, unless, case).
74
+ #
75
+ # The scope of 'for' tags exists only in the first
76
+ # block, as the following one refers to the else
77
+ # statement of the iteration.
78
+ parent = node.parent
79
+
80
+ scope << parent if parent.type_name == :for && parent.children.first == node
81
+ scope
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ module VariableLookupFinder
6
+ class AssignmentsFinder
7
+ class Scope < Struct.new(:variables, :parent)
8
+ include TypeHelper
9
+
10
+ def new_child
11
+ child_scope = dup
12
+ child_scope.variables = variables.dup
13
+ child_scope.parent = self
14
+ child_scope
15
+ end
16
+
17
+ def <<(node)
18
+ tag = node.value
19
+
20
+ case tag
21
+ when Liquid::Assign
22
+ variable_name = tag.to
23
+ variables[variable_name] = as_potential_lookup(tag.from.name)
24
+ when Liquid::For, Liquid::TableRow
25
+ variable_name = tag.variable_name
26
+ variables[variable_name] = as_potential_lookup(tag.collection_name, ['first'])
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def as_potential_lookup(variable_lookup, default_lookups = [])
33
+ case variable_lookup
34
+ when Liquid::VariableLookup
35
+ potential_lookup(variable_lookup, default_lookups)
36
+ when Liquid::RangeLookup
37
+ as_potential_lookup(variable_lookup.start_obj)
38
+ when Enumerable
39
+ as_potential_lookup(variable_lookup.first)
40
+ else
41
+ literal_lookup(variable_lookup)
42
+ end
43
+ end
44
+
45
+ def literal_lookup(variable_lookup)
46
+ name = input_type_of(variable_lookup)
47
+ PotentialLookup.new(name, [], variables)
48
+ end
49
+
50
+ def potential_lookup(variable_lookup, default_lookups)
51
+ name = variable_lookup.name
52
+ lookups = variable_lookup.lookups.concat(default_lookups)
53
+
54
+ PotentialLookup.new(name, lookups, variables)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ module VariableLookupFinder
6
+ class AssignmentsFinder
7
+ class ScopeVisitor
8
+ SCOPE_UNAWARE_NODES = %i[range range_lookup variable variable_lookup]
9
+
10
+ attr_reader :global_scope, :current_scope
11
+
12
+ def initialize
13
+ @node_handler = NodeHandler.new
14
+ @global_scope = Scope.new({})
15
+ @current_scope = Scope.new({})
16
+ end
17
+
18
+ def visit_template(template)
19
+ return unless template
20
+
21
+ visit(liquid_node(template), global_scope)
22
+ end
23
+
24
+ private
25
+
26
+ def visit(node, scope)
27
+ return if SCOPE_UNAWARE_NODES.include?(node.type_name)
28
+
29
+ method = :"on_#{node.type_name}"
30
+ scope = @node_handler.send(method, node, scope) if @node_handler.respond_to?(method)
31
+
32
+ @current_scope = scope
33
+
34
+ node.children.each { |child| visit(child, scope) }
35
+ end
36
+
37
+ def liquid_node(template)
38
+ LiquidNode.new(template.root, nil, template)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ module VariableLookupFinder
6
+ class AssignmentsFinder
7
+ include RegexHelpers
8
+
9
+ attr_reader :content, :scope_visitor
10
+
11
+ def initialize(content)
12
+ @content = close_tag(content)
13
+ @scope_visitor = ScopeVisitor.new
14
+ end
15
+
16
+ def find!
17
+ template = parse(content)
18
+
19
+ if template
20
+ visit_template(template)
21
+ return
22
+ end
23
+
24
+ liquid_tags.each do |tag|
25
+ visit_template(last_line_parse(tag))
26
+ end
27
+ end
28
+
29
+ def assignments
30
+ current_scope = scope_visitor.current_scope
31
+ current_scope.variables
32
+ end
33
+
34
+ private
35
+
36
+ def visit_template(template)
37
+ scope_visitor.visit_template(template)
38
+ end
39
+
40
+ def liquid_tags
41
+ matches(content, LIQUID_TAG_OR_VARIABLE)
42
+ .flat_map { |match| match[0] }
43
+ end
44
+
45
+ def parse(content)
46
+ regular_parse(content) || tolerant_parse(content)
47
+ end
48
+
49
+ def regular_parse(content)
50
+ Liquid::Template.parse(content)
51
+ rescue Liquid::SyntaxError
52
+ # Ignore syntax errors at the regular parse phase
53
+ end
54
+
55
+ def tolerant_parse(content)
56
+ TolerantParser::Template.parse(content)
57
+ rescue StandardError
58
+ # Ignore any error at the tolerant parse phase
59
+ end
60
+
61
+ def last_line_parse(content)
62
+ parsable_content = LiquidFixer.new(content).parsable
63
+
64
+ regular_parse(parsable_content)
65
+ end
66
+
67
+ def close_tag(content)
68
+ lines = content.lines
69
+ end_tag = VARIABLE_START.match?(lines.last) ? ' }}' : ' %}'
70
+
71
+ content + end_tag
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ module VariableLookupFinder
6
+ module Constants
7
+ ANY_STARTING_TAG = /\s*#{Liquid::AnyStartingTag}/
8
+ ANY_ENDING_TAG = /#{Liquid::TagEnd}|#{Liquid::VariableEnd}\s*^/om
9
+
10
+ UNCLOSED_SQUARE_BRACKET = /\[[^\]]*\Z/
11
+ ENDS_IN_BRACKET_POSITION_THAT_CANT_BE_COMPLETED = /
12
+ (
13
+ # quotes not preceded by a [
14
+ (?<!\[)['"]|
15
+ # closing ]
16
+ \]|
17
+ # opening [
18
+ \[
19
+ )$
20
+ /x
21
+
22
+ VARIABLE_START = /\s*#{Liquid::VariableStart}/
23
+ VARIABLE_LOOKUP_CHARACTERS = /[a-z0-9_.'"\]\[]/i
24
+ VARIABLE_LOOKUP = /#{VARIABLE_LOOKUP_CHARACTERS}+/o
25
+ SYMBOLS_PRECEDING_POTENTIAL_LOOKUPS = /
26
+ (?:
27
+ \s(?:
28
+ if|elsif|unless|and|or|#{Liquid::Condition.operators.keys.join("|")}
29
+ |echo
30
+ |paginate
31
+ |case|when
32
+ |cycle
33
+ |in
34
+ )
35
+ |[:,=]
36
+ )
37
+ \s+
38
+ /omix
39
+ ENDS_WITH_BLANK_POTENTIAL_LOOKUP = /#{SYMBOLS_PRECEDING_POTENTIAL_LOOKUPS}$/oimx
40
+ ENDS_WITH_POTENTIAL_LOOKUP = /#{SYMBOLS_PRECEDING_POTENTIAL_LOOKUPS}#{VARIABLE_LOOKUP}$/oimx
41
+ end
42
+ end
43
+ end
44
+ end