theme-check 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/CHANGELOG.md +7 -0
- data/CONTRIBUTING.md +82 -0
- data/README.md +4 -0
- data/Rakefile +7 -0
- data/TROUBLESHOOTING.md +65 -0
- data/data/shopify_liquid/built_in_liquid_objects.json +60 -0
- data/data/shopify_liquid/documentation/filters.json +5528 -0
- data/data/shopify_liquid/documentation/latest.json +1 -0
- data/data/shopify_liquid/documentation/objects.json +19272 -0
- data/data/shopify_liquid/documentation/tags.json +1252 -0
- data/lib/theme_check/checks/undefined_object.rb +4 -0
- data/lib/theme_check/language_server/completion_context.rb +52 -0
- data/lib/theme_check/language_server/completion_engine.rb +15 -21
- data/lib/theme_check/language_server/completion_provider.rb +16 -1
- data/lib/theme_check/language_server/completion_providers/assignments_completion_provider.rb +36 -0
- data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +49 -6
- data/lib/theme_check/language_server/completion_providers/object_attribute_completion_provider.rb +47 -0
- data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +10 -7
- data/lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb +5 -1
- data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +8 -1
- data/lib/theme_check/language_server/handler.rb +3 -1
- data/lib/theme_check/language_server/protocol.rb +9 -0
- data/lib/theme_check/language_server/type_helper.rb +22 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder/node_handler.rb +63 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder/scope.rb +57 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder/scope_visitor.rb +42 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder.rb +76 -0
- data/lib/theme_check/language_server/variable_lookup_finder/constants.rb +43 -0
- data/lib/theme_check/language_server/variable_lookup_finder/liquid_fixer.rb +103 -0
- data/lib/theme_check/language_server/variable_lookup_finder/potential_lookup.rb +10 -0
- data/lib/theme_check/language_server/variable_lookup_finder/tolerant_parser.rb +94 -0
- data/lib/theme_check/language_server/variable_lookup_finder.rb +60 -100
- data/lib/theme_check/language_server/variable_lookup_traverser.rb +70 -0
- data/lib/theme_check/language_server.rb +12 -0
- data/lib/theme_check/remote_asset_file.rb +13 -7
- data/lib/theme_check/shopify_liquid/documentation/markdown_template.rb +51 -0
- data/lib/theme_check/shopify_liquid/documentation.rb +44 -0
- data/lib/theme_check/shopify_liquid/filter.rb +4 -0
- data/lib/theme_check/shopify_liquid/object.rb +4 -0
- data/lib/theme_check/shopify_liquid/source_index/base_entry.rb +60 -0
- data/lib/theme_check/shopify_liquid/source_index/base_state.rb +23 -0
- data/lib/theme_check/shopify_liquid/source_index/filter_entry.rb +18 -0
- data/lib/theme_check/shopify_liquid/source_index/filter_state.rb +11 -0
- data/lib/theme_check/shopify_liquid/source_index/object_entry.rb +14 -0
- data/lib/theme_check/shopify_liquid/source_index/object_state.rb +11 -0
- data/lib/theme_check/shopify_liquid/source_index/parameter_entry.rb +21 -0
- data/lib/theme_check/shopify_liquid/source_index/property_entry.rb +9 -0
- data/lib/theme_check/shopify_liquid/source_index/return_type_entry.rb +37 -0
- data/lib/theme_check/shopify_liquid/source_index/tag_entry.rb +20 -0
- data/lib/theme_check/shopify_liquid/source_index/tag_state.rb +11 -0
- data/lib/theme_check/shopify_liquid/source_index.rb +56 -0
- data/lib/theme_check/shopify_liquid/source_manager.rb +111 -0
- data/lib/theme_check/shopify_liquid/tag.rb +4 -0
- data/lib/theme_check/shopify_liquid.rb +17 -1
- data/lib/theme_check/version.rb +1 -1
- data/shipit.rubygems.yml +3 -0
- data/theme-check.gemspec +3 -1
- metadata +37 -2
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
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 = lines.last =~ VARIABLE_START ? ' }}' : ' %}'
|
70
|
+
|
71
|
+
content + end_tag
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
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 = %r{
|
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 = %r{
|
26
|
+
(?:
|
27
|
+
\s(?:
|
28
|
+
if|elsif|unless|and|or|#{Liquid::Condition.operators.keys.join("|")}
|
29
|
+
|echo
|
30
|
+
|case|when
|
31
|
+
|cycle
|
32
|
+
|in
|
33
|
+
)
|
34
|
+
|[:,=]
|
35
|
+
)
|
36
|
+
\s+
|
37
|
+
}omix
|
38
|
+
ENDS_WITH_BLANK_POTENTIAL_LOOKUP = /#{SYMBOLS_PRECEDING_POTENTIAL_LOOKUPS}$/oimx
|
39
|
+
ENDS_WITH_POTENTIAL_LOOKUP = /#{SYMBOLS_PRECEDING_POTENTIAL_LOOKUPS}#{VARIABLE_LOOKUP}$/oimx
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
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 @markup =~ UNCLOSED_SQUARE_BRACKET
|
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 =~ 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
|
+
@markup =~ VARIABLE_START ? ' }}' : ' %}'
|
95
|
+
end
|
96
|
+
|
97
|
+
def tag_regex(tag_name)
|
98
|
+
ShopifyLiquid::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 ThemeCheck
|
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
|
@@ -1,53 +1,60 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'ostruct'
|
4
|
+
|
3
5
|
module ThemeCheck
|
4
6
|
module LanguageServer
|
5
7
|
module VariableLookupFinder
|
8
|
+
include Constants
|
9
|
+
include TypeHelper
|
6
10
|
extend self
|
7
11
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# quotes not preceded by a [
|
12
|
-
(?<!\[)['"]|
|
13
|
-
# closing ]
|
14
|
-
\]|
|
15
|
-
# opening [
|
16
|
-
\[
|
17
|
-
)$
|
18
|
-
}x
|
19
|
-
|
20
|
-
VARIABLE_LOOKUP_CHARACTERS = /[a-z0-9_.'"\]\[]/i
|
21
|
-
VARIABLE_LOOKUP = /#{VARIABLE_LOOKUP_CHARACTERS}+/o
|
22
|
-
SYMBOLS_PRECEDING_POTENTIAL_LOOKUPS = %r{
|
23
|
-
(?:
|
24
|
-
\s(?:
|
25
|
-
if|elsif|unless|and|or|#{Liquid::Condition.operators.keys.join("|")}
|
26
|
-
|echo
|
27
|
-
|case|when
|
28
|
-
|cycle
|
29
|
-
|in
|
30
|
-
)
|
31
|
-
|[:,=]
|
32
|
-
)
|
33
|
-
\s+
|
34
|
-
}omix
|
35
|
-
ENDS_WITH_BLANK_POTENTIAL_LOOKUP = /#{SYMBOLS_PRECEDING_POTENTIAL_LOOKUPS}$/oimx
|
36
|
-
ENDS_WITH_POTENTIAL_LOOKUP = /#{SYMBOLS_PRECEDING_POTENTIAL_LOOKUPS}#{VARIABLE_LOOKUP}$/oimx
|
12
|
+
def lookup(context)
|
13
|
+
content = context.content
|
14
|
+
cursor = context.cursor
|
37
15
|
|
38
|
-
def lookup(content, cursor)
|
39
16
|
return if cursor_is_on_bracket_position_that_cant_be_completed(content, cursor)
|
40
|
-
potential_lookup = lookup_liquid_variable(content, cursor) || lookup_liquid_tag(content, cursor)
|
41
17
|
|
42
|
-
|
43
|
-
|
44
|
-
|
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)
|
45
28
|
end
|
46
29
|
|
47
30
|
private
|
48
31
|
|
32
|
+
def potential_lookup(variable, context)
|
33
|
+
return 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
|
+
|
39
|
+
while assignments[variable.name]
|
40
|
+
variable = assignments[variable.name]
|
41
|
+
lookups = variable.lookups + lookups
|
42
|
+
end
|
43
|
+
|
44
|
+
PotentialLookup.new(variable.name, lookups)
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_assignments(buffer)
|
48
|
+
finder = AssignmentsFinder.new(buffer)
|
49
|
+
finder.find!
|
50
|
+
finder.assignments
|
51
|
+
end
|
52
|
+
|
49
53
|
def cursor_is_on_bracket_position_that_cant_be_completed(content, cursor)
|
50
|
-
content[0..cursor - 1]
|
54
|
+
content_before_cursor = content[0..cursor - 1]
|
55
|
+
return false unless /[\[\]]/.match?(content_before_cursor)
|
56
|
+
|
57
|
+
content_before_cursor =~ ENDS_IN_BRACKET_POSITION_THAT_CANT_BE_COMPLETED
|
51
58
|
end
|
52
59
|
|
53
60
|
def cursor_is_on_liquid_variable_lookup_position(content, cursor)
|
@@ -65,6 +72,7 @@ module ThemeCheck
|
|
65
72
|
|
66
73
|
def lookup_liquid_variable(content, cursor)
|
67
74
|
return unless cursor_is_on_liquid_variable_lookup_position(content, cursor)
|
75
|
+
|
68
76
|
start_index = content.match(/#{Liquid::VariableStart}-?/o).end(0) + 1
|
69
77
|
end_index = cursor - 1
|
70
78
|
|
@@ -78,14 +86,14 @@ module ThemeCheck
|
|
78
86
|
markup = content[start_index..end_index]
|
79
87
|
|
80
88
|
# Early return for incomplete variables
|
81
|
-
return empty_lookup if
|
89
|
+
return empty_lookup if /\s+$/.match?(markup)
|
82
90
|
|
83
91
|
# Now we go to hack city... The cursor might be in the middle
|
84
92
|
# of a string/square bracket lookup. We need to close those
|
85
93
|
# otherwise the variable parse won't work.
|
86
94
|
markup += "'" if markup.count("'").odd?
|
87
95
|
markup += '"' if markup.count('"').odd?
|
88
|
-
markup += "]" if markup
|
96
|
+
markup += "]" if UNCLOSED_SQUARE_BRACKET.match?(markup)
|
89
97
|
|
90
98
|
variable = variable_from_markup(markup)
|
91
99
|
|
@@ -122,12 +130,12 @@ module ThemeCheck
|
|
122
130
|
return unless cursor_is_on_liquid_tag_lookup_position(content, cursor)
|
123
131
|
|
124
132
|
markup = parseable_markup(content, cursor)
|
125
|
-
return empty_lookup if markup
|
133
|
+
return empty_lookup if markup.empty?
|
126
134
|
|
127
135
|
template = Liquid::Template.parse(markup)
|
128
136
|
current_tag = template.root.nodelist[0]
|
129
137
|
|
130
|
-
case current_tag
|
138
|
+
case current_tag&.tag_name
|
131
139
|
when "if", "unless"
|
132
140
|
variable_lookup_for_if_tag(current_tag)
|
133
141
|
when "case"
|
@@ -144,70 +152,16 @@ module ThemeCheck
|
|
144
152
|
variable_lookup_for_assign_tag(current_tag)
|
145
153
|
when "echo"
|
146
154
|
variable_lookup_for_echo_tag(current_tag)
|
155
|
+
else
|
156
|
+
empty_lookup
|
147
157
|
end
|
148
|
-
|
149
|
-
# rubocop:disable Style/RedundantReturn
|
150
158
|
rescue Liquid::SyntaxError
|
151
159
|
# We don't complete variable for liquid syntax errors
|
152
|
-
|
160
|
+
empty_lookup
|
153
161
|
end
|
154
|
-
# rubocop:enable Style/RedundantReturn
|
155
|
-
|
156
|
-
def parseable_markup(content, cursor)
|
157
|
-
start_index = 0
|
158
|
-
end_index = cursor - 1
|
159
|
-
markup = content[start_index..end_index]
|
160
162
|
|
161
|
-
|
162
|
-
|
163
|
-
markup += '"' if markup.count('"').odd?
|
164
|
-
markup += "]" if markup =~ UNCLOSED_SQUARE_BRACKET
|
165
|
-
|
166
|
-
# Now check if it's a liquid tag
|
167
|
-
is_liquid_tag = markup =~ tag_regex('liquid')
|
168
|
-
ends_with_blank_potential_lookup = markup =~ ENDS_WITH_BLANK_POTENTIAL_LOOKUP
|
169
|
-
last_line = markup.rstrip.lines.last
|
170
|
-
markup = "{% #{last_line}" if is_liquid_tag
|
171
|
-
|
172
|
-
# Close the tag
|
173
|
-
markup += ' %}'
|
174
|
-
|
175
|
-
# if statements
|
176
|
-
is_if_tag = markup =~ tag_regex('if')
|
177
|
-
return :empty_lookup_markup if is_if_tag && ends_with_blank_potential_lookup
|
178
|
-
markup += '{% endif %}' if is_if_tag
|
179
|
-
|
180
|
-
# unless statements
|
181
|
-
is_unless_tag = markup =~ tag_regex('unless')
|
182
|
-
return :empty_lookup_markup if is_unless_tag && ends_with_blank_potential_lookup
|
183
|
-
markup += '{% endunless %}' if is_unless_tag
|
184
|
-
|
185
|
-
# elsif statements
|
186
|
-
is_elsif_tag = markup =~ tag_regex('elsif')
|
187
|
-
return :empty_lookup_markup if is_elsif_tag && ends_with_blank_potential_lookup
|
188
|
-
markup = '{% if x %}' + markup + '{% endif %}' if is_elsif_tag
|
189
|
-
|
190
|
-
# case statements
|
191
|
-
is_case_tag = markup =~ tag_regex('case')
|
192
|
-
return :empty_lookup_markup if is_case_tag && ends_with_blank_potential_lookup
|
193
|
-
markup += "{% endcase %}" if is_case_tag
|
194
|
-
|
195
|
-
# when
|
196
|
-
is_when_tag = markup =~ tag_regex('when')
|
197
|
-
return :empty_lookup_markup if is_when_tag && ends_with_blank_potential_lookup
|
198
|
-
markup = "{% case x %}" + markup + "{% endcase %}" if is_when_tag
|
199
|
-
|
200
|
-
# for statements
|
201
|
-
is_for_tag = markup =~ tag_regex('for')
|
202
|
-
return :empty_lookup_markup if is_for_tag && ends_with_blank_potential_lookup
|
203
|
-
markup += "{% endfor %}" if is_for_tag
|
204
|
-
|
205
|
-
# tablerow statements
|
206
|
-
is_tablerow_tag = markup =~ tag_regex('tablerow')
|
207
|
-
return :empty_lookup_markup if is_tablerow_tag && ends_with_blank_potential_lookup
|
208
|
-
markup += "{% endtablerow %}" if is_tablerow_tag
|
209
|
-
|
210
|
-
markup
|
163
|
+
def parseable_markup(content, cursor = nil)
|
164
|
+
LiquidFixer.new(content, cursor).parsable
|
211
165
|
end
|
212
166
|
|
213
167
|
def variable_lookup_for_if_tag(if_tag)
|
@@ -218,11 +172,13 @@ module ThemeCheck
|
|
218
172
|
def variable_lookup_for_condition(condition)
|
219
173
|
return variable_lookup_for_condition(condition.child_condition) if condition.child_condition
|
220
174
|
return condition.right if condition.right
|
175
|
+
|
221
176
|
condition.left
|
222
177
|
end
|
223
178
|
|
224
179
|
def variable_lookup_for_case_tag(case_tag)
|
225
180
|
return variable_lookup_for_case_block(case_tag.blocks.last) unless case_tag.blocks.empty?
|
181
|
+
|
226
182
|
case_tag.left
|
227
183
|
end
|
228
184
|
|
@@ -243,7 +199,8 @@ module ThemeCheck
|
|
243
199
|
end
|
244
200
|
|
245
201
|
def variable_lookup_for_render_tag(render_tag)
|
246
|
-
return empty_lookup if render_tag.raw
|
202
|
+
return empty_lookup if /:\s*$/.match?(render_tag.raw)
|
203
|
+
|
247
204
|
render_tag.attributes.values.last
|
248
205
|
end
|
249
206
|
|
@@ -265,8 +222,10 @@ module ThemeCheck
|
|
265
222
|
last_filter_argument(variable.filters)
|
266
223
|
elsif variable.name.nil?
|
267
224
|
empty_lookup
|
268
|
-
|
225
|
+
elsif variable.name.is_a?(Liquid::VariableLookup)
|
269
226
|
variable.name
|
227
|
+
else
|
228
|
+
PotentialLookup.new(input_type_of(variable.name), [])
|
270
229
|
end
|
271
230
|
end
|
272
231
|
|
@@ -280,6 +239,7 @@ module ThemeCheck
|
|
280
239
|
filter = filters.last
|
281
240
|
return filter[2].values.last if filter.size == 3
|
282
241
|
return filter[1].last if filter.size == 2
|
242
|
+
|
283
243
|
nil
|
284
244
|
end
|
285
245
|
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
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
|
+
ShopifyLiquid::SourceIndex
|
65
|
+
.objects
|
66
|
+
.find { |entry| entry.name == object_name }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative "language_server/protocol"
|
3
4
|
require_relative "language_server/constants"
|
4
5
|
require_relative "language_server/configuration"
|
@@ -7,9 +8,19 @@ require_relative "language_server/messenger"
|
|
7
8
|
require_relative "language_server/io_messenger"
|
8
9
|
require_relative "language_server/bridge"
|
9
10
|
require_relative "language_server/uri_helper"
|
11
|
+
require_relative "language_server/type_helper"
|
10
12
|
require_relative "language_server/server"
|
11
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"
|
12
22
|
require_relative "language_server/variable_lookup_finder"
|
23
|
+
require_relative "language_server/variable_lookup_traverser"
|
13
24
|
require_relative "language_server/diagnostic"
|
14
25
|
require_relative "language_server/diagnostics_manager"
|
15
26
|
require_relative "language_server/diagnostics_engine"
|
@@ -17,6 +28,7 @@ require_relative "language_server/document_change_corrector"
|
|
17
28
|
require_relative "language_server/versioned_in_memory_storage"
|
18
29
|
require_relative "language_server/client_capabilities"
|
19
30
|
|
31
|
+
require_relative "language_server/completion_context"
|
20
32
|
require_relative "language_server/completion_helper"
|
21
33
|
require_relative "language_server/completion_provider"
|
22
34
|
require_relative "language_server/completion_engine"
|