platformos-check 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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