platformos-check 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.dockerignore +2 -0
- data/.gitignore +22 -0
- data/.rubocop.yml +555 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +209 -0
- data/Gemfile +33 -0
- data/Guardfile +7 -0
- data/LICENSE.md +10 -0
- data/Makefile +18 -0
- data/README.md +189 -0
- data/RELEASING.md +35 -0
- data/Rakefile +83 -0
- data/TROUBLESHOOTING.md +35 -0
- data/bin/platformos-check +29 -0
- data/bin/platformos-check-language-server +29 -0
- data/config/default.yml +98 -0
- data/config/nothing.yml +11 -0
- data/data/platformos_liquid/built_in_liquid_objects.json +66 -0
- data/data/platformos_liquid/deprecated_filters.json +22 -0
- data/data/platformos_liquid/documentation/filters.json +6 -0
- data/data/platformos_liquid/documentation/latest.json +2 -0
- data/data/platformos_liquid/documentation/objects.json +6 -0
- data/data/platformos_liquid/documentation/tags.json +6 -0
- data/docker/check.Dockerfile +3 -0
- data/docker/lsp.Dockerfile +21 -0
- data/docs/api/check.md +15 -0
- data/docs/api/html_check.md +46 -0
- data/docs/api/liquid_check.md +115 -0
- data/docs/api/yaml_check.md +19 -0
- data/docs/checks/TEMPLATE.md.erb +52 -0
- data/docs/checks/convert_include_to_render.md +48 -0
- data/docs/checks/deprecated_filter.md +30 -0
- data/docs/checks/html_parsing_error.md +50 -0
- data/docs/checks/img_lazy_loading.md +63 -0
- data/docs/checks/img_width_and_height.md +79 -0
- data/docs/checks/invalid_args.md +56 -0
- data/docs/checks/liquid_tag.md +65 -0
- data/docs/checks/missing_enable_comment.md +50 -0
- data/docs/checks/missing_template.md +65 -0
- data/docs/checks/parse_json_format.md +76 -0
- data/docs/checks/parser_blocking_javascript.md +97 -0
- data/docs/checks/required_layout_object.md +28 -0
- data/docs/checks/space_inside_braces.md +89 -0
- data/docs/checks/syntax_error.md +49 -0
- data/docs/checks/template_length.md +45 -0
- data/docs/checks/undefined_object.md +71 -0
- data/docs/checks/unknown_filter.md +46 -0
- data/docs/checks/unused_assign.md +63 -0
- data/docs/checks/unused_partial.md +32 -0
- data/docs/checks/valid_yaml.md +48 -0
- data/docs/flamegraph.svg +18488 -0
- data/docs/language_server/code-action-command-palette.png +0 -0
- data/docs/language_server/code-action-flow.png +0 -0
- data/docs/language_server/code-action-keyboard.png +0 -0
- data/docs/language_server/code-action-light-bulb.png +0 -0
- data/docs/language_server/code-action-problem.png +0 -0
- data/docs/language_server/code-action-quickfix.png +0 -0
- data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
- data/docs/preview.png +0 -0
- data/exe/platformos-check +6 -0
- data/exe/platformos-check-language-server +7 -0
- data/lib/platformos_check/analyzer.rb +178 -0
- data/lib/platformos_check/api_call_file.rb +9 -0
- data/lib/platformos_check/app.rb +138 -0
- data/lib/platformos_check/app_file.rb +89 -0
- data/lib/platformos_check/app_file_rewriter.rb +79 -0
- data/lib/platformos_check/asset_file.rb +34 -0
- data/lib/platformos_check/bug.rb +23 -0
- data/lib/platformos_check/check.rb +163 -0
- data/lib/platformos_check/checks/TEMPLATE.rb.erb +11 -0
- data/lib/platformos_check/checks/convert_include_to_render.rb +17 -0
- data/lib/platformos_check/checks/deprecated_filter.rb +123 -0
- data/lib/platformos_check/checks/html_parsing_error.rb +13 -0
- data/lib/platformos_check/checks/img_lazy_loading.rb +18 -0
- data/lib/platformos_check/checks/img_width_and_height.rb +46 -0
- data/lib/platformos_check/checks/invalid_args.rb +81 -0
- data/lib/platformos_check/checks/liquid_tag.rb +47 -0
- data/lib/platformos_check/checks/missing_enable_comment.rb +37 -0
- data/lib/platformos_check/checks/missing_template.rb +107 -0
- data/lib/platformos_check/checks/parse_json_format.rb +31 -0
- data/lib/platformos_check/checks/parser_blocking_javascript.rb +17 -0
- data/lib/platformos_check/checks/required_layout_object.rb +41 -0
- data/lib/platformos_check/checks/space_inside_braces.rb +150 -0
- data/lib/platformos_check/checks/syntax_error.rb +31 -0
- data/lib/platformos_check/checks/template_length.rb +20 -0
- data/lib/platformos_check/checks/undefined_object.rb +206 -0
- data/lib/platformos_check/checks/unknown_filter.rb +27 -0
- data/lib/platformos_check/checks/unused_assign.rb +101 -0
- data/lib/platformos_check/checks/unused_partial.rb +93 -0
- data/lib/platformos_check/checks/valid_yaml.rb +16 -0
- data/lib/platformos_check/checks.rb +73 -0
- data/lib/platformos_check/checks_tracking.rb +9 -0
- data/lib/platformos_check/cli.rb +239 -0
- data/lib/platformos_check/config.rb +219 -0
- data/lib/platformos_check/config_file.rb +6 -0
- data/lib/platformos_check/corrector.rb +68 -0
- data/lib/platformos_check/disabled_check.rb +44 -0
- data/lib/platformos_check/disabled_checks.rb +96 -0
- data/lib/platformos_check/email_file.rb +9 -0
- data/lib/platformos_check/exceptions.rb +36 -0
- data/lib/platformos_check/file_system_storage.rb +93 -0
- data/lib/platformos_check/graphql_file.rb +68 -0
- data/lib/platformos_check/html_check.rb +8 -0
- data/lib/platformos_check/html_node.rb +210 -0
- data/lib/platformos_check/html_visitor.rb +36 -0
- data/lib/platformos_check/in_memory_storage.rb +68 -0
- data/lib/platformos_check/json_file.rb +57 -0
- data/lib/platformos_check/json_helper.rb +73 -0
- data/lib/platformos_check/json_helpers.rb +24 -0
- data/lib/platformos_check/json_printer.rb +32 -0
- data/lib/platformos_check/language_server/bridge.rb +167 -0
- data/lib/platformos_check/language_server/channel.rb +69 -0
- data/lib/platformos_check/language_server/client_capabilities.rb +27 -0
- data/lib/platformos_check/language_server/code_action_engine.rb +32 -0
- data/lib/platformos_check/language_server/code_action_provider.rb +41 -0
- data/lib/platformos_check/language_server/code_action_providers/quickfix_code_action_provider.rb +85 -0
- data/lib/platformos_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +41 -0
- data/lib/platformos_check/language_server/completion_context.rb +52 -0
- data/lib/platformos_check/language_server/completion_engine.rb +32 -0
- data/lib/platformos_check/language_server/completion_helper.rb +26 -0
- data/lib/platformos_check/language_server/completion_provider.rb +53 -0
- data/lib/platformos_check/language_server/completion_providers/assignments_completion_provider.rb +40 -0
- data/lib/platformos_check/language_server/completion_providers/filter_completion_provider.rb +102 -0
- data/lib/platformos_check/language_server/completion_providers/object_attribute_completion_provider.rb +48 -0
- data/lib/platformos_check/language_server/completion_providers/object_completion_provider.rb +38 -0
- data/lib/platformos_check/language_server/completion_providers/render_snippet_completion_provider.rb +50 -0
- data/lib/platformos_check/language_server/completion_providers/tag_completion_provider.rb +41 -0
- data/lib/platformos_check/language_server/configuration.rb +89 -0
- data/lib/platformos_check/language_server/constants.rb +29 -0
- data/lib/platformos_check/language_server/diagnostic.rb +129 -0
- data/lib/platformos_check/language_server/diagnostics_engine.rb +131 -0
- data/lib/platformos_check/language_server/diagnostics_manager.rb +184 -0
- data/lib/platformos_check/language_server/document_change_corrector.rb +271 -0
- data/lib/platformos_check/language_server/document_link_engine.rb +21 -0
- data/lib/platformos_check/language_server/document_link_provider.rb +71 -0
- data/lib/platformos_check/language_server/document_link_providers/asset_document_link_provider.rb +11 -0
- data/lib/platformos_check/language_server/document_link_providers/include_document_link_provider.rb +11 -0
- data/lib/platformos_check/language_server/document_link_providers/render_document_link_provider.rb +11 -0
- data/lib/platformos_check/language_server/document_link_providers/section_document_link_provider.rb +11 -0
- data/lib/platformos_check/language_server/execute_command_engine.rb +19 -0
- data/lib/platformos_check/language_server/execute_command_provider.rb +30 -0
- data/lib/platformos_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
- data/lib/platformos_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +28 -0
- data/lib/platformos_check/language_server/handler.rb +310 -0
- data/lib/platformos_check/language_server/hover_engine.rb +32 -0
- data/lib/platformos_check/language_server/hover_provider.rb +53 -0
- data/lib/platformos_check/language_server/hover_providers/filter_hover_provider.rb +113 -0
- data/lib/platformos_check/language_server/io_messenger.rb +109 -0
- data/lib/platformos_check/language_server/messenger.rb +27 -0
- data/lib/platformos_check/language_server/protocol.rb +55 -0
- data/lib/platformos_check/language_server/server.rb +188 -0
- data/lib/platformos_check/language_server/tokens.rb +55 -0
- data/lib/platformos_check/language_server/type_helper.rb +22 -0
- data/lib/platformos_check/language_server/uri_helper.rb +39 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/node_handler.rb +87 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/scope.rb +60 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/scope_visitor.rb +44 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder.rb +76 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/constants.rb +44 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/liquid_fixer.rb +103 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/potential_lookup.rb +10 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/tolerant_parser.rb +94 -0
- data/lib/platformos_check/language_server/variable_lookup_finder.rb +262 -0
- data/lib/platformos_check/language_server/variable_lookup_traverser.rb +70 -0
- data/lib/platformos_check/language_server/versioned_in_memory_storage.rb +84 -0
- data/lib/platformos_check/language_server.rb +71 -0
- data/lib/platformos_check/layout_file.rb +15 -0
- data/lib/platformos_check/liquid_check.rb +10 -0
- data/lib/platformos_check/liquid_file.rb +102 -0
- data/lib/platformos_check/liquid_node.rb +570 -0
- data/lib/platformos_check/liquid_visitor.rb +39 -0
- data/lib/platformos_check/migration_file.rb +9 -0
- data/lib/platformos_check/node.rb +53 -0
- data/lib/platformos_check/offense.rb +228 -0
- data/lib/platformos_check/page_file.rb +9 -0
- data/lib/platformos_check/parsing_helpers.rb +21 -0
- data/lib/platformos_check/partial_file.rb +15 -0
- data/lib/platformos_check/platformos_liquid/deprecated_filter.rb +31 -0
- data/lib/platformos_check/platformos_liquid/documentation/markdown_template.rb +51 -0
- data/lib/platformos_check/platformos_liquid/documentation.rb +45 -0
- data/lib/platformos_check/platformos_liquid/filter.rb +19 -0
- data/lib/platformos_check/platformos_liquid/object.rb +15 -0
- data/lib/platformos_check/platformos_liquid/source_index/base_entry.rb +66 -0
- data/lib/platformos_check/platformos_liquid/source_index/base_state.rb +23 -0
- data/lib/platformos_check/platformos_liquid/source_index/filter_entry.rb +26 -0
- data/lib/platformos_check/platformos_liquid/source_index/filter_state.rb +11 -0
- data/lib/platformos_check/platformos_liquid/source_index/object_entry.rb +20 -0
- data/lib/platformos_check/platformos_liquid/source_index/object_state.rb +11 -0
- data/lib/platformos_check/platformos_liquid/source_index/parameter_entry.rb +25 -0
- data/lib/platformos_check/platformos_liquid/source_index/property_entry.rb +21 -0
- data/lib/platformos_check/platformos_liquid/source_index/return_type_entry.rb +41 -0
- data/lib/platformos_check/platformos_liquid/source_index/tag_entry.rb +24 -0
- data/lib/platformos_check/platformos_liquid/source_index/tag_state.rb +11 -0
- data/lib/platformos_check/platformos_liquid/source_index.rb +79 -0
- data/lib/platformos_check/platformos_liquid/source_manager.rb +116 -0
- data/lib/platformos_check/platformos_liquid/tag.rb +59 -0
- data/lib/platformos_check/platformos_liquid.rb +21 -0
- data/lib/platformos_check/position.rb +180 -0
- data/lib/platformos_check/position_helper.rb +57 -0
- data/lib/platformos_check/printer.rb +87 -0
- data/lib/platformos_check/regex_helpers.rb +21 -0
- data/lib/platformos_check/releaser.rb +43 -0
- data/lib/platformos_check/schema_file.rb +6 -0
- data/lib/platformos_check/sms_file.rb +9 -0
- data/lib/platformos_check/storage.rb +29 -0
- data/lib/platformos_check/string_helpers.rb +48 -0
- data/lib/platformos_check/tags/background.rb +67 -0
- data/lib/platformos_check/tags/base.rb +14 -0
- data/lib/platformos_check/tags/base_block.rb +14 -0
- data/lib/platformos_check/tags/base_tag_methods.rb +59 -0
- data/lib/platformos_check/tags/cache.rb +13 -0
- data/lib/platformos_check/tags/export.rb +30 -0
- data/lib/platformos_check/tags/form.rb +19 -0
- data/lib/platformos_check/tags/function.rb +58 -0
- data/lib/platformos_check/tags/graphql.rb +70 -0
- data/lib/platformos_check/tags/hash_assign.rb +75 -0
- data/lib/platformos_check/tags/log.rb +15 -0
- data/lib/platformos_check/tags/parse_json.rb +24 -0
- data/lib/platformos_check/tags/print.rb +20 -0
- data/lib/platformos_check/tags/redirect_to.rb +15 -0
- data/lib/platformos_check/tags/render.rb +60 -0
- data/lib/platformos_check/tags/response_headers.rb +20 -0
- data/lib/platformos_check/tags/response_status.rb +20 -0
- data/lib/platformos_check/tags/return.rb +20 -0
- data/lib/platformos_check/tags/session.rb +27 -0
- data/lib/platformos_check/tags/sign_in.rb +27 -0
- data/lib/platformos_check/tags/spam_protection.rb +15 -0
- data/lib/platformos_check/tags/theme_render.rb +58 -0
- data/lib/platformos_check/tags/try.rb +59 -0
- data/lib/platformos_check/tags.rb +65 -0
- data/lib/platformos_check/translation_file.rb +6 -0
- data/lib/platformos_check/user_schema_file.rb +6 -0
- data/lib/platformos_check/version.rb +5 -0
- data/lib/platformos_check/yaml_check.rb +11 -0
- data/lib/platformos_check/yaml_file.rb +57 -0
- data/lib/platformos_check.rb +106 -0
- data/platformos-check.gemspec +34 -0
- 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
|
data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/node_handler.rb
ADDED
@@ -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
|
data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/scope_visitor.rb
ADDED
@@ -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
|