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