theme-check 0.3.3 → 0.4.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/CHANGELOG.md +7 -0
- data/README.md +0 -2
- data/data/shopify_liquid/tags.yml +26 -0
- data/lib/theme_check/cli.rb +22 -6
- data/lib/theme_check/in_memory_storage.rb +1 -1
- data/lib/theme_check/language_server.rb +10 -0
- data/lib/theme_check/language_server/completion_engine.rb +38 -0
- data/lib/theme_check/language_server/completion_helper.rb +35 -0
- data/lib/theme_check/language_server/completion_provider.rb +23 -0
- data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +47 -0
- data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +31 -0
- data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +31 -0
- data/lib/theme_check/language_server/handler.rb +51 -3
- 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/tokens.rb +55 -0
- data/lib/theme_check/offense.rb +51 -16
- data/lib/theme_check/shopify_liquid.rb +1 -0
- data/lib/theme_check/shopify_liquid/tag.rb +16 -0
- data/lib/theme_check/version.rb +1 -1
- metadata +13 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5543690a150f2a864a5a9900feca80286ffe4c00d545885ee31dc02c9d389b71
|
4
|
+
data.tar.gz: c1c518e2013a1106dc7ace3a14ef5158853e06929694503777a37c0fc695025a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 405832fbd93d17f3412abb83181b8c76c0aee43e2405bc7329b7ace746c20887c32ab638bf49c99adf4598532fc92d5840a46dc24d2a20b89909849b8c2813a3
|
7
|
+
data.tar.gz: 40316910f51d646017059d4256cba23dfcb9e20822a0987b65a04ab79c40c160c882d4bdf7e0cc4d646b08971ba04bb58999a7c5b93a4741233a8ce1c5ce4ad9
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
|
2
|
+
v0.4.0 / 2021-02-25
|
3
|
+
==================
|
4
|
+
|
5
|
+
* Add Completion Engine ([#161](https://github.com/shopify/theme-check/issues/161))
|
6
|
+
* Add init command to CLI ([#174](https://github.com/shopify/theme-check/issues/174))
|
7
|
+
* Refactor start and end Position logic ([#172](https://github.com/shopify/theme-check/issues/172))
|
8
|
+
|
2
9
|
v0.3.3 / 2021-02-18
|
3
10
|
==================
|
4
11
|
|
data/README.md
CHANGED
@@ -8,8 +8,6 @@ Theme Check is also available [inside some code editors](https://github.com/Shop
|
|
8
8
|
|
9
9
|

|
10
10
|
|
11
|
-
_Disclaimer: This tool is not supported as part of the Partners program._
|
12
|
-
|
13
11
|
## Supported Checks
|
14
12
|
|
15
13
|
Theme Check currently checks for the following:
|
@@ -0,0 +1,26 @@
|
|
1
|
+
---
|
2
|
+
- assign
|
3
|
+
- break
|
4
|
+
- capture
|
5
|
+
- case
|
6
|
+
- comment
|
7
|
+
- continue
|
8
|
+
- cycle
|
9
|
+
- decrement
|
10
|
+
- echo
|
11
|
+
- for
|
12
|
+
- form
|
13
|
+
- if
|
14
|
+
- ifchanged
|
15
|
+
- increment
|
16
|
+
- javascript
|
17
|
+
- layout
|
18
|
+
- paginate
|
19
|
+
- raw
|
20
|
+
- render
|
21
|
+
- schema
|
22
|
+
- section
|
23
|
+
- style
|
24
|
+
- stylesheet
|
25
|
+
- tablerow
|
26
|
+
- unless
|
data/lib/theme_check/cli.rb
CHANGED
@@ -11,6 +11,7 @@ module ThemeCheck
|
|
11
11
|
-x, [--exclude-category] # Exclude this category of checks
|
12
12
|
-l, [--list] # List enabled checks
|
13
13
|
-a, [--auto-correct] # Automatically fix offenses
|
14
|
+
--init # Generate a .theme-check.yml file in the current directory
|
14
15
|
-h, [--help] # Show this. Hi!
|
15
16
|
-v, [--version] # Print Theme Check version
|
16
17
|
|
@@ -22,7 +23,7 @@ module ThemeCheck
|
|
22
23
|
END
|
23
24
|
|
24
25
|
def run(argv)
|
25
|
-
path = "."
|
26
|
+
@path = "."
|
26
27
|
|
27
28
|
command = :check
|
28
29
|
only_categories = []
|
@@ -44,15 +45,19 @@ module ThemeCheck
|
|
44
45
|
command = :list
|
45
46
|
when "--auto-correct", "-a"
|
46
47
|
auto_correct = true
|
48
|
+
when "--init"
|
49
|
+
command = :init
|
47
50
|
else
|
48
|
-
path = arg
|
51
|
+
@path = arg
|
49
52
|
end
|
50
53
|
end
|
51
54
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
unless [:version, :init].include?(command)
|
56
|
+
@config = ThemeCheck::Config.from_path(@path)
|
57
|
+
@config.only_categories = only_categories
|
58
|
+
@config.exclude_categories = exclude_categories
|
59
|
+
@config.auto_correct = auto_correct
|
60
|
+
end
|
56
61
|
|
57
62
|
send(command)
|
58
63
|
end
|
@@ -75,6 +80,17 @@ module ThemeCheck
|
|
75
80
|
puts ThemeCheck::VERSION
|
76
81
|
end
|
77
82
|
|
83
|
+
def init
|
84
|
+
dotfile_path = ThemeCheck::Config.find(@path)
|
85
|
+
if dotfile_path.nil?
|
86
|
+
File.write(File.join(@path, ThemeCheck::Config::DOTFILE), File.read(ThemeCheck::Config::DEFAULT_CONFIG))
|
87
|
+
|
88
|
+
puts "Writing new #{ThemeCheck::Config::DOTFILE} to #{@path}"
|
89
|
+
else
|
90
|
+
raise Abort, "#{ThemeCheck::Config::DOTFILE} already exists at #{@path}."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
78
94
|
def check
|
79
95
|
puts "Checking #{@config.root} ..."
|
80
96
|
storage = ThemeCheck::FileSystemStorage.new(@config.root, ignored_patterns: @config.ignored_patterns)
|
@@ -1,6 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require_relative "language_server/protocol"
|
2
3
|
require_relative "language_server/handler"
|
3
4
|
require_relative "language_server/server"
|
5
|
+
require_relative "language_server/tokens"
|
6
|
+
require_relative "language_server/position_helper"
|
7
|
+
require_relative "language_server/completion_helper"
|
8
|
+
require_relative "language_server/completion_provider"
|
9
|
+
require_relative "language_server/completion_engine"
|
10
|
+
|
11
|
+
Dir[__dir__ + "/language_server/completion_providers/*.rb"].each do |file|
|
12
|
+
require file
|
13
|
+
end
|
4
14
|
|
5
15
|
module ThemeCheck
|
6
16
|
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(&:new)
|
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,35 @@
|
|
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
|
+
|
24
|
+
def matches(s, re)
|
25
|
+
start_at = 0
|
26
|
+
matches = []
|
27
|
+
while (m = s.match(re, start_at))
|
28
|
+
matches.push(m)
|
29
|
+
start_at = m.end(0)
|
30
|
+
end
|
31
|
+
matches
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module LanguageServer
|
5
|
+
class CompletionProvider
|
6
|
+
include CompletionHelper
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def all
|
10
|
+
@all ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
def inherited(subclass)
|
14
|
+
all << subclass
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def completions(content, cursor)
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
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
|
+
ShopifyLiquid::Filter.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 cursor_on_filter?(content, cursor)
|
25
|
+
return false unless content.match?(NAMED_FILTER)
|
26
|
+
matches(content, NAMED_FILTER).any? do |match|
|
27
|
+
match.begin(1) <= cursor && cursor < match.end(1) + 1 # including next character
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def partial(content, cursor)
|
32
|
+
return '' unless content.match?(NAMED_FILTER)
|
33
|
+
partial_match = matches(content, NAMED_FILTER).find do |match|
|
34
|
+
match.begin(1) <= cursor && cursor < match.end(1) + 1 # including next character
|
35
|
+
end
|
36
|
+
partial_match[1]
|
37
|
+
end
|
38
|
+
|
39
|
+
def filter_to_completion(filter)
|
40
|
+
{
|
41
|
+
label: filter,
|
42
|
+
kind: CompletionItemKinds::FUNCTION,
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
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
|
@@ -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
|
@@ -4,9 +4,13 @@ module ThemeCheck
|
|
4
4
|
module LanguageServer
|
5
5
|
class Handler
|
6
6
|
CAPABILITIES = {
|
7
|
+
completionProvider: {
|
8
|
+
triggerCharacters: ['.', '{{ ', '{% '],
|
9
|
+
context: true,
|
10
|
+
},
|
7
11
|
textDocumentSync: {
|
8
12
|
openClose: true,
|
9
|
-
change:
|
13
|
+
change: TextDocumentSyncKind::FULL,
|
10
14
|
willSave: false,
|
11
15
|
save: true,
|
12
16
|
},
|
@@ -15,6 +19,8 @@ module ThemeCheck
|
|
15
19
|
def initialize(server)
|
16
20
|
@server = server
|
17
21
|
@previously_reported_files = Set.new
|
22
|
+
@storage = InMemoryStorage.new
|
23
|
+
@completion_engine = CompletionEngine.new(@storage)
|
18
24
|
end
|
19
25
|
|
20
26
|
def on_initialize(id, params)
|
@@ -31,14 +37,52 @@ module ThemeCheck
|
|
31
37
|
def on_exit(_id, _params)
|
32
38
|
close!
|
33
39
|
end
|
40
|
+
alias_method :on_shutdown, :on_exit
|
41
|
+
|
42
|
+
def on_text_document_did_change(_id, params)
|
43
|
+
uri = text_document_uri(params)
|
44
|
+
@storage.write(uri, content_changes_text(params))
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_text_document_did_close(_id, params)
|
48
|
+
uri = text_document_uri(params)
|
49
|
+
@storage.write(uri, nil)
|
50
|
+
end
|
34
51
|
|
35
52
|
def on_text_document_did_open(_id, params)
|
36
|
-
|
53
|
+
uri = text_document_uri(params)
|
54
|
+
@storage.write(uri, text_document_text(params))
|
55
|
+
analyze_and_send_offenses(uri)
|
56
|
+
end
|
57
|
+
|
58
|
+
def on_text_document_did_save(_id, params)
|
59
|
+
analyze_and_send_offenses(text_document_uri(params))
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_text_document_completion(id, params)
|
63
|
+
uri = text_document_uri(params)
|
64
|
+
line = params.dig('position', 'line')
|
65
|
+
col = params.dig('position', 'character')
|
66
|
+
send_response(
|
67
|
+
id: id,
|
68
|
+
result: completions(uri, line, col)
|
69
|
+
)
|
37
70
|
end
|
38
|
-
alias_method :on_text_document_did_save, :on_text_document_did_open
|
39
71
|
|
40
72
|
private
|
41
73
|
|
74
|
+
def text_document_uri(params)
|
75
|
+
params.dig('textDocument', 'uri').sub('file://', '')
|
76
|
+
end
|
77
|
+
|
78
|
+
def text_document_text(params)
|
79
|
+
params.dig('textDocument', 'text')
|
80
|
+
end
|
81
|
+
|
82
|
+
def content_changes_text(params)
|
83
|
+
params.dig('contentChanges', 0, 'text')
|
84
|
+
end
|
85
|
+
|
42
86
|
def analyze_and_send_offenses(file_path)
|
43
87
|
root = ThemeCheck::Config.find(file_path) || @root_path
|
44
88
|
config = ThemeCheck::Config.from_path(root)
|
@@ -60,6 +104,10 @@ module ThemeCheck
|
|
60
104
|
analyzer.offenses
|
61
105
|
end
|
62
106
|
|
107
|
+
def completions(uri, line, col)
|
108
|
+
@completion_engine.completions(uri, line, col)
|
109
|
+
end
|
110
|
+
|
63
111
|
def send_diagnostics(offenses)
|
64
112
|
reported_files = Set.new
|
65
113
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Note: Everything is 0-indexed here.
|
3
|
+
|
4
|
+
module ThemeCheck
|
5
|
+
module LanguageServer
|
6
|
+
module PositionHelper
|
7
|
+
def from_line_column_to_index(content, row, col)
|
8
|
+
i = 0
|
9
|
+
result = 0
|
10
|
+
lines = content.lines
|
11
|
+
while i < row
|
12
|
+
result += lines[i].size
|
13
|
+
i += 1
|
14
|
+
end
|
15
|
+
result += col
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
def from_index_to_line_column(content, index)
|
20
|
+
lines = content[0..index].lines
|
21
|
+
row = lines.size - 1
|
22
|
+
col = lines.last.size - 1
|
23
|
+
[row, col]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Here we define the Language Server Protocol Constants we're using.
|
3
|
+
# For complete docs, see the following:
|
4
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current
|
5
|
+
module ThemeCheck
|
6
|
+
module LanguageServer
|
7
|
+
module CompletionItemKinds
|
8
|
+
TEXT = 1
|
9
|
+
METHOD = 2
|
10
|
+
FUNCTION = 3
|
11
|
+
CONSTRUCTOR = 4
|
12
|
+
FIELD = 5
|
13
|
+
VARIABLE = 6
|
14
|
+
CLASS = 7
|
15
|
+
INTERFACE = 8
|
16
|
+
MODULE = 9
|
17
|
+
PROPERTY = 10
|
18
|
+
UNIT = 11
|
19
|
+
VALUE = 12
|
20
|
+
ENUM = 13
|
21
|
+
KEYWORD = 14
|
22
|
+
SNIPPET = 15
|
23
|
+
COLOR = 16
|
24
|
+
FILE = 17
|
25
|
+
REFERENCE = 18
|
26
|
+
FOLDER = 19
|
27
|
+
ENUM_MEMBER = 20
|
28
|
+
CONSTANT = 21
|
29
|
+
STRUCT = 22
|
30
|
+
EVENT = 23
|
31
|
+
OPERATOR = 24
|
32
|
+
TYPE_PARAMETER = 25
|
33
|
+
end
|
34
|
+
|
35
|
+
module TextDocumentSyncKind
|
36
|
+
NONE = 0
|
37
|
+
FULL = 1
|
38
|
+
INCREMENTAL = 2
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
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_given?
|
30
|
+
|
31
|
+
chunks = @buffer.split(SPLITTER)
|
32
|
+
chunks.shift if 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
|
+
block.call(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
|
data/lib/theme_check/offense.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ThemeCheck
|
3
|
+
Position = Struct.new(:line, :column)
|
4
|
+
|
3
5
|
class Offense
|
4
6
|
MAX_SOURCE_EXCERPT_SIZE = 120
|
5
7
|
|
@@ -35,6 +37,9 @@ module ThemeCheck
|
|
35
37
|
elsif @node
|
36
38
|
@node.line_number
|
37
39
|
end
|
40
|
+
|
41
|
+
@start_position = nil
|
42
|
+
@end_position = nil
|
38
43
|
end
|
39
44
|
|
40
45
|
def source_excerpt
|
@@ -50,29 +55,19 @@ module ThemeCheck
|
|
50
55
|
end
|
51
56
|
|
52
57
|
def start_line
|
53
|
-
|
54
|
-
line_number - 1
|
58
|
+
start_position.line
|
55
59
|
end
|
56
60
|
|
57
|
-
def
|
58
|
-
|
59
|
-
start_line + markup.count("\n") - 1
|
60
|
-
elsif markup
|
61
|
-
start_line + markup.count("\n")
|
62
|
-
else
|
63
|
-
start_line
|
64
|
-
end
|
61
|
+
def start_column
|
62
|
+
start_position.column
|
65
63
|
end
|
66
64
|
|
67
|
-
def
|
68
|
-
|
69
|
-
template.full_line(start_line + 1).index(markup.split("\n", 2).first)
|
65
|
+
def end_line
|
66
|
+
end_position.line
|
70
67
|
end
|
71
68
|
|
72
69
|
def end_column
|
73
|
-
|
74
|
-
markup_end = markup.split("\n").last
|
75
|
-
template.full_line(end_line + 1).index(markup_end) + markup_end.size
|
70
|
+
end_position.column
|
76
71
|
end
|
77
72
|
|
78
73
|
def code_name
|
@@ -118,5 +113,45 @@ module ThemeCheck
|
|
118
113
|
message
|
119
114
|
end
|
120
115
|
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def full_line(line)
|
120
|
+
# Liquid::Template is 1-indexed.
|
121
|
+
template.full_line(line + 1)
|
122
|
+
end
|
123
|
+
|
124
|
+
def lines_of_content
|
125
|
+
@lines ||= markup.lines.map { |x| x.sub(/\n$/, '') }
|
126
|
+
end
|
127
|
+
|
128
|
+
# 0-indexed, inclusive
|
129
|
+
def start_position
|
130
|
+
return @start_position if @start_position
|
131
|
+
return @start_position = Position.new(0, 0) unless line_number && markup
|
132
|
+
|
133
|
+
position = Position.new
|
134
|
+
position.line = line_number - 1
|
135
|
+
position.column = full_line(position.line).index(lines_of_content.first) || 0
|
136
|
+
|
137
|
+
@start_position = position
|
138
|
+
end
|
139
|
+
|
140
|
+
# 0-indexed, exclusive. It's the line + col that are exclusive.
|
141
|
+
# This is why it doesn't make sense to calculate them separately.
|
142
|
+
def end_position
|
143
|
+
return @end_position if @end_position
|
144
|
+
return @end_position = Position.new(0, 0) unless line_number && markup
|
145
|
+
|
146
|
+
position = Position.new
|
147
|
+
position.line = start_line + lines_of_content.size - 1
|
148
|
+
position.column = if start_line == position.line
|
149
|
+
start_column + markup.size
|
150
|
+
else
|
151
|
+
lines_of_content.last.size
|
152
|
+
end
|
153
|
+
|
154
|
+
@end_position = position
|
155
|
+
end
|
121
156
|
end
|
122
157
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module ThemeCheck
|
5
|
+
module ShopifyLiquid
|
6
|
+
module Tag
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def labels
|
10
|
+
@tags ||= begin
|
11
|
+
YAML.load(File.read("#{__dir__}/../../../data/shopify_liquid/tags.yml"))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/theme_check/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: theme-check
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc-André Cournoyer
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02-
|
11
|
+
date: 2021-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: liquid
|
@@ -80,6 +80,7 @@ files:
|
|
80
80
|
- data/shopify_liquid/filters.yml
|
81
81
|
- data/shopify_liquid/objects.yml
|
82
82
|
- data/shopify_liquid/plus_objects.yml
|
83
|
+
- data/shopify_liquid/tags.yml
|
83
84
|
- dev.yml
|
84
85
|
- docs/preview.png
|
85
86
|
- exe/theme-check
|
@@ -123,8 +124,17 @@ files:
|
|
123
124
|
- lib/theme_check/json_file.rb
|
124
125
|
- lib/theme_check/json_helpers.rb
|
125
126
|
- lib/theme_check/language_server.rb
|
127
|
+
- lib/theme_check/language_server/completion_engine.rb
|
128
|
+
- lib/theme_check/language_server/completion_helper.rb
|
129
|
+
- lib/theme_check/language_server/completion_provider.rb
|
130
|
+
- lib/theme_check/language_server/completion_providers/filter_completion_provider.rb
|
131
|
+
- lib/theme_check/language_server/completion_providers/object_completion_provider.rb
|
132
|
+
- lib/theme_check/language_server/completion_providers/tag_completion_provider.rb
|
126
133
|
- lib/theme_check/language_server/handler.rb
|
134
|
+
- lib/theme_check/language_server/position_helper.rb
|
135
|
+
- lib/theme_check/language_server/protocol.rb
|
127
136
|
- lib/theme_check/language_server/server.rb
|
137
|
+
- lib/theme_check/language_server/tokens.rb
|
128
138
|
- lib/theme_check/liquid_check.rb
|
129
139
|
- lib/theme_check/locale_diff.rb
|
130
140
|
- lib/theme_check/node.rb
|
@@ -136,6 +146,7 @@ files:
|
|
136
146
|
- lib/theme_check/shopify_liquid/deprecated_filter.rb
|
137
147
|
- lib/theme_check/shopify_liquid/filter.rb
|
138
148
|
- lib/theme_check/shopify_liquid/object.rb
|
149
|
+
- lib/theme_check/shopify_liquid/tag.rb
|
139
150
|
- lib/theme_check/storage.rb
|
140
151
|
- lib/theme_check/tags.rb
|
141
152
|
- lib/theme_check/template.rb
|