theme-check 0.3.1 → 0.6.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/.rubocop.yml +13 -3
- data/CHANGELOG.md +61 -0
- data/CONTRIBUTING.md +5 -2
- data/Gemfile +5 -3
- data/README.md +11 -4
- data/RELEASING.md +2 -2
- data/config/default.yml +14 -0
- data/data/shopify_liquid/tags.yml +27 -0
- data/data/shopify_translation_keys.yml +850 -0
- data/docs/checks/CHECK_DOCS_TEMPLATE.md +47 -0
- data/docs/checks/asset_size_css.md +52 -0
- data/docs/checks/asset_size_javascript.md +79 -0
- data/docs/checks/convert_include_to_render.md +48 -0
- data/docs/checks/default_locale.md +46 -0
- data/docs/checks/deprecated_filter.md +46 -0
- data/docs/checks/img_width_and_height.md +79 -0
- data/docs/checks/liquid_tag.md +65 -0
- data/docs/checks/matching_schema_translations.md +93 -0
- data/docs/checks/matching_translations.md +72 -0
- data/docs/checks/missing_enable_comment.md +50 -0
- data/docs/checks/missing_required_template_files.md +26 -0
- data/docs/checks/missing_template.md +40 -0
- data/docs/checks/nested_snippet.md +69 -0
- data/docs/checks/parser_blocking_javascript.md +97 -0
- data/docs/checks/required_directories.md +25 -0
- data/docs/checks/required_layout_theme_object.md +28 -0
- data/docs/checks/space_inside_braces.md +63 -0
- data/docs/checks/syntax_error.md +49 -0
- data/docs/checks/template_length.md +50 -0
- data/docs/checks/translation_key_exists.md +63 -0
- data/docs/checks/undefined_object.md +53 -0
- data/docs/checks/unknown_filter.md +45 -0
- data/docs/checks/unused_assign.md +47 -0
- data/docs/checks/unused_snippet.md +32 -0
- data/docs/checks/valid_html_translation.md +53 -0
- data/docs/checks/valid_json.md +60 -0
- data/docs/checks/valid_schema.md +50 -0
- data/lib/theme_check.rb +3 -0
- data/lib/theme_check/asset_file.rb +34 -0
- data/lib/theme_check/check.rb +19 -9
- data/lib/theme_check/checks/asset_size_css.rb +89 -0
- data/lib/theme_check/checks/asset_size_javascript.rb +68 -0
- data/lib/theme_check/checks/convert_include_to_render.rb +1 -1
- data/lib/theme_check/checks/default_locale.rb +1 -0
- data/lib/theme_check/checks/deprecated_filter.rb +1 -1
- data/lib/theme_check/checks/img_width_and_height.rb +74 -0
- data/lib/theme_check/checks/liquid_tag.rb +3 -3
- data/lib/theme_check/checks/matching_schema_translations.rb +1 -0
- data/lib/theme_check/checks/matching_translations.rb +1 -0
- data/lib/theme_check/checks/missing_enable_comment.rb +1 -0
- data/lib/theme_check/checks/missing_required_template_files.rb +1 -2
- data/lib/theme_check/checks/missing_template.rb +1 -0
- data/lib/theme_check/checks/nested_snippet.rb +1 -0
- data/lib/theme_check/checks/parser_blocking_javascript.rb +7 -14
- data/lib/theme_check/checks/required_directories.rb +1 -1
- data/lib/theme_check/checks/required_layout_theme_object.rb +1 -1
- data/lib/theme_check/checks/space_inside_braces.rb +1 -0
- data/lib/theme_check/checks/syntax_error.rb +1 -0
- data/lib/theme_check/checks/template_length.rb +1 -0
- data/lib/theme_check/checks/translation_key_exists.rb +17 -1
- data/lib/theme_check/checks/undefined_object.rb +29 -10
- data/lib/theme_check/checks/unknown_filter.rb +1 -0
- data/lib/theme_check/checks/unused_assign.rb +5 -3
- data/lib/theme_check/checks/unused_snippet.rb +1 -0
- data/lib/theme_check/checks/valid_html_translation.rb +1 -0
- data/lib/theme_check/checks/valid_json.rb +1 -0
- data/lib/theme_check/checks/valid_schema.rb +1 -0
- data/lib/theme_check/cli.rb +39 -12
- data/lib/theme_check/config.rb +5 -2
- data/lib/theme_check/in_memory_storage.rb +11 -3
- data/lib/theme_check/language_server.rb +12 -0
- data/lib/theme_check/language_server/completion_engine.rb +38 -0
- data/lib/theme_check/language_server/completion_helper.rb +25 -0
- data/lib/theme_check/language_server/completion_provider.rb +28 -0
- data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +51 -0
- data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +31 -0
- data/lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb +43 -0
- data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +31 -0
- data/lib/theme_check/language_server/constants.rb +10 -0
- data/lib/theme_check/language_server/document_link_engine.rb +47 -0
- data/lib/theme_check/language_server/handler.rb +93 -6
- data/lib/theme_check/language_server/position_helper.rb +27 -0
- data/lib/theme_check/language_server/protocol.rb +41 -0
- data/lib/theme_check/language_server/server.rb +8 -2
- data/lib/theme_check/language_server/tokens.rb +55 -0
- data/lib/theme_check/liquid_check.rb +11 -0
- data/lib/theme_check/offense.rb +51 -14
- data/lib/theme_check/regex_helpers.rb +15 -0
- data/lib/theme_check/remote_asset_file.rb +44 -0
- data/lib/theme_check/shopify_liquid.rb +1 -0
- data/lib/theme_check/shopify_liquid/deprecated_filter.rb +4 -0
- data/lib/theme_check/shopify_liquid/tag.rb +16 -0
- data/lib/theme_check/theme.rb +7 -1
- data/lib/theme_check/version.rb +1 -1
- metadata +52 -2
data/lib/theme_check/cli.rb
CHANGED
|
@@ -7,12 +7,14 @@ module ThemeCheck
|
|
|
7
7
|
Usage: theme-check [options] /path/to/your/theme
|
|
8
8
|
|
|
9
9
|
Options:
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
10
|
+
--init Generate a .theme-check.yml file in the current directory
|
|
11
|
+
-C, --config <path> Use the config provided, overriding .theme-check.yml if present
|
|
12
|
+
-c, --category <category> Only run this category of checks
|
|
13
|
+
-x, --exclude-category <category> Exclude this category of checks
|
|
14
|
+
-l, --list List enabled checks
|
|
15
|
+
-a, --auto-correct Automatically fix offenses
|
|
16
|
+
-h, --help Show this. Hi!
|
|
17
|
+
-v, --version Print Theme Check version
|
|
16
18
|
|
|
17
19
|
Description:
|
|
18
20
|
Theme Check helps you follow Shopify Themes & Liquid best practices by analyzing the
|
|
@@ -22,12 +24,13 @@ module ThemeCheck
|
|
|
22
24
|
END
|
|
23
25
|
|
|
24
26
|
def run(argv)
|
|
25
|
-
path = "."
|
|
27
|
+
@path = "."
|
|
26
28
|
|
|
27
29
|
command = :check
|
|
28
30
|
only_categories = []
|
|
29
31
|
exclude_categories = []
|
|
30
32
|
auto_correct = false
|
|
33
|
+
config_path = nil
|
|
31
34
|
|
|
32
35
|
args = argv.dup
|
|
33
36
|
while (arg = args.shift)
|
|
@@ -36,6 +39,8 @@ module ThemeCheck
|
|
|
36
39
|
raise Abort, USAGE
|
|
37
40
|
when "--version", "-v"
|
|
38
41
|
command = :version
|
|
42
|
+
when "--config", "-C"
|
|
43
|
+
config_path = Pathname.new(args.shift)
|
|
39
44
|
when "--category", "-c"
|
|
40
45
|
only_categories << args.shift.to_sym
|
|
41
46
|
when "--exclude-category", "-x"
|
|
@@ -44,15 +49,26 @@ module ThemeCheck
|
|
|
44
49
|
command = :list
|
|
45
50
|
when "--auto-correct", "-a"
|
|
46
51
|
auto_correct = true
|
|
52
|
+
when "--init"
|
|
53
|
+
command = :init
|
|
47
54
|
else
|
|
48
|
-
path = arg
|
|
55
|
+
@path = arg
|
|
49
56
|
end
|
|
50
57
|
end
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
unless [:version, :init].include?(command)
|
|
60
|
+
@config = if config_path.present?
|
|
61
|
+
ThemeCheck::Config.new(
|
|
62
|
+
root: @path,
|
|
63
|
+
configuration: ThemeCheck::Config.load_file(config_path)
|
|
64
|
+
)
|
|
65
|
+
else
|
|
66
|
+
ThemeCheck::Config.from_path(@path)
|
|
67
|
+
end
|
|
68
|
+
@config.only_categories = only_categories
|
|
69
|
+
@config.exclude_categories = exclude_categories
|
|
70
|
+
@config.auto_correct = auto_correct
|
|
71
|
+
end
|
|
56
72
|
|
|
57
73
|
send(command)
|
|
58
74
|
end
|
|
@@ -75,6 +91,17 @@ module ThemeCheck
|
|
|
75
91
|
puts ThemeCheck::VERSION
|
|
76
92
|
end
|
|
77
93
|
|
|
94
|
+
def init
|
|
95
|
+
dotfile_path = ThemeCheck::Config.find(@path)
|
|
96
|
+
if dotfile_path.nil?
|
|
97
|
+
File.write(File.join(@path, ThemeCheck::Config::DOTFILE), File.read(ThemeCheck::Config::DEFAULT_CONFIG))
|
|
98
|
+
|
|
99
|
+
puts "Writing new #{ThemeCheck::Config::DOTFILE} to #{@path}"
|
|
100
|
+
else
|
|
101
|
+
raise Abort, "#{ThemeCheck::Config::DOTFILE} already exists at #{@path}."
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
78
105
|
def check
|
|
79
106
|
puts "Checking #{@config.root} ..."
|
|
80
107
|
storage = ThemeCheck::FileSystemStorage.new(@config.root, ignored_patterns: @config.ignored_patterns)
|
data/lib/theme_check/config.rb
CHANGED
|
@@ -10,6 +10,8 @@ module ThemeCheck
|
|
|
10
10
|
attr_accessor :only_categories, :exclude_categories, :auto_correct
|
|
11
11
|
|
|
12
12
|
class << self
|
|
13
|
+
attr_reader :last_loaded_config
|
|
14
|
+
|
|
13
15
|
def from_path(path)
|
|
14
16
|
if (filename = find(path))
|
|
15
17
|
new(root: filename.dirname, configuration: load_file(filename))
|
|
@@ -36,6 +38,7 @@ module ThemeCheck
|
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
def load_file(absolute_path)
|
|
41
|
+
@last_loaded_config = absolute_path
|
|
39
42
|
YAML.load_file(absolute_path)
|
|
40
43
|
end
|
|
41
44
|
|
|
@@ -83,8 +86,8 @@ module ThemeCheck
|
|
|
83
86
|
|
|
84
87
|
check_class = ThemeCheck.const_get(check_name)
|
|
85
88
|
|
|
86
|
-
next if exclude_categories.include?(
|
|
87
|
-
next if only_categories.any? &&
|
|
89
|
+
next if check_class.categories.any? { |category| exclude_categories.include?(category) }
|
|
90
|
+
next if only_categories.any? && check_class.categories.none? { |category| only_categories.include?(category) }
|
|
88
91
|
|
|
89
92
|
options_for_check = options.transform_keys(&:to_sym)
|
|
90
93
|
options_for_check.delete(:enabled)
|
|
@@ -6,20 +6,28 @@
|
|
|
6
6
|
# as a big hash already, leave it like that and save yourself some IO.
|
|
7
7
|
module ThemeCheck
|
|
8
8
|
class InMemoryStorage < Storage
|
|
9
|
-
def initialize(files)
|
|
9
|
+
def initialize(files = {}, root = nil)
|
|
10
10
|
@files = files
|
|
11
|
+
@root = root
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def path(name)
|
|
15
|
+
return File.join(@root, name) unless @root.nil?
|
|
16
|
+
name
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def relative_path(name)
|
|
20
|
+
path = Pathname.new(name)
|
|
21
|
+
return path.relative_path_from(@root).to_s unless path.relative? || @root.nil?
|
|
14
22
|
name
|
|
15
23
|
end
|
|
16
24
|
|
|
17
25
|
def read(name)
|
|
18
|
-
@files[name]
|
|
26
|
+
@files[relative_path(name)]
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
def write(name, content)
|
|
22
|
-
@files[name] = content
|
|
30
|
+
@files[relative_path(name)] = content
|
|
23
31
|
end
|
|
24
32
|
|
|
25
33
|
def files
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require_relative "language_server/protocol"
|
|
3
|
+
require_relative "language_server/constants"
|
|
2
4
|
require_relative "language_server/handler"
|
|
3
5
|
require_relative "language_server/server"
|
|
6
|
+
require_relative "language_server/tokens"
|
|
7
|
+
require_relative "language_server/position_helper"
|
|
8
|
+
require_relative "language_server/completion_helper"
|
|
9
|
+
require_relative "language_server/completion_provider"
|
|
10
|
+
require_relative "language_server/completion_engine"
|
|
11
|
+
require_relative "language_server/document_link_engine"
|
|
12
|
+
|
|
13
|
+
Dir[__dir__ + "/language_server/completion_providers/*.rb"].each do |file|
|
|
14
|
+
require file
|
|
15
|
+
end
|
|
4
16
|
|
|
5
17
|
module ThemeCheck
|
|
6
18
|
module LanguageServer
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
class CompletionEngine
|
|
6
|
+
include PositionHelper
|
|
7
|
+
|
|
8
|
+
def initialize(storage)
|
|
9
|
+
@storage = storage
|
|
10
|
+
@providers = CompletionProvider.all.map { |x| x.new(storage) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def completions(name, line, col)
|
|
14
|
+
buffer = @storage.read(name)
|
|
15
|
+
cursor = from_line_column_to_index(buffer, line, col)
|
|
16
|
+
token = find_token(buffer, cursor)
|
|
17
|
+
return [] if token.nil?
|
|
18
|
+
|
|
19
|
+
@providers.flat_map do |p|
|
|
20
|
+
p.completions(
|
|
21
|
+
token.content,
|
|
22
|
+
cursor - token.start
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def find_token(buffer, cursor)
|
|
28
|
+
Tokens.new(buffer).find do |token|
|
|
29
|
+
# Here we include the next character and exclude the first
|
|
30
|
+
# one becase when we want to autocomplete inside a token
|
|
31
|
+
# and at most 1 outside it since the cursor could be placed
|
|
32
|
+
# at the end of the token.
|
|
33
|
+
token.start < cursor && cursor <= token.end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
module CompletionHelper
|
|
6
|
+
WORD = /\w+/
|
|
7
|
+
|
|
8
|
+
def cursor_on_start_content?(content, cursor, regex)
|
|
9
|
+
content.slice(0, cursor).match?(/#{regex}(?:\s|\n)*$/m)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def cursor_on_first_word?(content, cursor)
|
|
13
|
+
word = content.match(WORD)
|
|
14
|
+
return false if word.nil?
|
|
15
|
+
word_start = word.begin(0)
|
|
16
|
+
word_end = word.end(0)
|
|
17
|
+
word_start <= cursor && cursor <= word_end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def first_word(content)
|
|
21
|
+
return content.match(WORD)[0] if content.match?(WORD)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
class CompletionProvider
|
|
6
|
+
include CompletionHelper
|
|
7
|
+
include RegexHelpers
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def all
|
|
11
|
+
@all ||= []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def inherited(subclass)
|
|
15
|
+
all << subclass
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(storage = InMemoryStorage.new)
|
|
20
|
+
@storage = storage
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def completions(content, cursor)
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
class FilterCompletionProvider < CompletionProvider
|
|
6
|
+
NAMED_FILTER = /#{Liquid::FilterSeparator}\s*(\w+)/o
|
|
7
|
+
|
|
8
|
+
def completions(content, cursor)
|
|
9
|
+
return [] unless can_complete?(content, cursor)
|
|
10
|
+
available_labels
|
|
11
|
+
.select { |w| w.starts_with?(partial(content, cursor)) }
|
|
12
|
+
.map { |filter| filter_to_completion(filter) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def can_complete?(content, cursor)
|
|
16
|
+
content.match?(Liquid::FilterSeparator) && (
|
|
17
|
+
cursor_on_start_content?(content, cursor, Liquid::FilterSeparator) ||
|
|
18
|
+
cursor_on_filter?(content, cursor)
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def available_labels
|
|
25
|
+
@labels ||= ShopifyLiquid::Filter.labels - ShopifyLiquid::DeprecatedFilter.labels
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def cursor_on_filter?(content, cursor)
|
|
29
|
+
return false unless content.match?(NAMED_FILTER)
|
|
30
|
+
matches(content, NAMED_FILTER).any? do |match|
|
|
31
|
+
match.begin(1) <= cursor && cursor < match.end(1) + 1 # including next character
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def partial(content, cursor)
|
|
36
|
+
return '' unless content.match?(NAMED_FILTER)
|
|
37
|
+
partial_match = matches(content, NAMED_FILTER).find do |match|
|
|
38
|
+
match.begin(1) <= cursor && cursor < match.end(1) + 1 # including next character
|
|
39
|
+
end
|
|
40
|
+
partial_match[1]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def filter_to_completion(filter)
|
|
44
|
+
{
|
|
45
|
+
label: filter,
|
|
46
|
+
kind: CompletionItemKinds::FUNCTION,
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
class ObjectCompletionProvider < CompletionProvider
|
|
6
|
+
def completions(content, cursor)
|
|
7
|
+
return [] unless can_complete?(content, cursor)
|
|
8
|
+
partial = first_word(content) || ''
|
|
9
|
+
ShopifyLiquid::Object.labels
|
|
10
|
+
.select { |w| w.starts_with?(partial) }
|
|
11
|
+
.map { |object| object_to_completion(object) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def can_complete?(content, cursor)
|
|
15
|
+
content.match?(Liquid::VariableStart) && (
|
|
16
|
+
cursor_on_first_word?(content, cursor) ||
|
|
17
|
+
cursor_on_start_content?(content, cursor, Liquid::VariableStart)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def object_to_completion(object)
|
|
24
|
+
{
|
|
25
|
+
label: object,
|
|
26
|
+
kind: CompletionItemKinds::VARIABLE,
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
class RenderSnippetCompletionProvider < CompletionProvider
|
|
6
|
+
def completions(content, cursor)
|
|
7
|
+
return [] unless cursor_on_quoted_argument?(content, cursor)
|
|
8
|
+
partial = snippet(content) || ''
|
|
9
|
+
snippets
|
|
10
|
+
.select { |x| x.starts_with?(partial) }
|
|
11
|
+
.map { |x| snippet_to_completion(x) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def cursor_on_quoted_argument?(content, cursor)
|
|
17
|
+
match = content.match(PARTIAL_RENDER)
|
|
18
|
+
return false if match.nil?
|
|
19
|
+
match.begin(:partial) <= cursor && cursor <= match.end(:partial)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def snippet(content)
|
|
23
|
+
match = content.match(PARTIAL_RENDER)
|
|
24
|
+
return if match.nil?
|
|
25
|
+
match[:partial]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def snippets
|
|
29
|
+
@storage
|
|
30
|
+
.files
|
|
31
|
+
.select { |x| x.include?('snippets/') }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def snippet_to_completion(file)
|
|
35
|
+
{
|
|
36
|
+
label: File.basename(file, '.liquid'),
|
|
37
|
+
kind: CompletionItemKinds::SNIPPET,
|
|
38
|
+
detail: file,
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
class TagCompletionProvider < CompletionProvider
|
|
6
|
+
def completions(content, cursor)
|
|
7
|
+
return [] unless can_complete?(content, cursor)
|
|
8
|
+
partial = first_word(content) || ''
|
|
9
|
+
ShopifyLiquid::Tag.labels
|
|
10
|
+
.select { |w| w.starts_with?(partial) }
|
|
11
|
+
.map { |tag| tag_to_completion(tag) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def can_complete?(content, cursor)
|
|
15
|
+
content.starts_with?(Liquid::TagStart) && (
|
|
16
|
+
cursor_on_first_word?(content, cursor) ||
|
|
17
|
+
cursor_on_start_content?(content, cursor, Liquid::TagStart)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def tag_to_completion(tag)
|
|
24
|
+
{
|
|
25
|
+
label: tag,
|
|
26
|
+
kind: CompletionItemKinds::KEYWORD,
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|