theme-check 1.1.0 → 1.5.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/.github/workflows/theme-check.yml +5 -9
- data/.gitignore +1 -0
- data/CHANGELOG.md +50 -0
- data/CONTRIBUTING.md +1 -1
- data/RELEASING.md +34 -2
- data/bin/theme-check +29 -0
- data/bin/theme-check-language-server +29 -0
- data/config/default.yml +15 -1
- data/config/theme_app_extension.yml +15 -0
- data/data/shopify_liquid/objects.yml +1 -0
- data/docs/checks/app_block_valid_tags.md +40 -0
- data/docs/checks/asset_size_app_block_css.md +1 -1
- data/docs/checks/deprecate_lazysizes.md +0 -3
- data/docs/checks/deprecated_global_app_block_type.md +65 -0
- data/docs/checks/missing_template.md +25 -0
- data/docs/checks/pagination_size.md +44 -0
- data/docs/checks/template_length.md +1 -1
- data/docs/checks/undefined_object.md +5 -0
- data/lib/theme_check/analyzer.rb +1 -0
- data/lib/theme_check/check.rb +3 -3
- data/lib/theme_check/checks/app_block_valid_tags.rb +36 -0
- data/lib/theme_check/checks/asset_size_css.rb +3 -3
- data/lib/theme_check/checks/asset_size_javascript.rb +2 -2
- data/lib/theme_check/checks/convert_include_to_render.rb +3 -1
- data/lib/theme_check/checks/default_locale.rb +3 -1
- data/lib/theme_check/checks/deprecate_bgsizes.rb +1 -1
- data/lib/theme_check/checks/deprecate_lazysizes.rb +7 -4
- data/lib/theme_check/checks/deprecated_global_app_block_type.rb +57 -0
- data/lib/theme_check/checks/img_lazy_loading.rb +1 -1
- data/lib/theme_check/checks/img_width_and_height.rb +3 -3
- data/lib/theme_check/checks/missing_template.rb +21 -5
- data/lib/theme_check/checks/pagination_size.rb +64 -0
- data/lib/theme_check/checks/parser_blocking_javascript.rb +1 -1
- data/lib/theme_check/checks/remote_asset.rb +3 -3
- data/lib/theme_check/checks/space_inside_braces.rb +27 -7
- data/lib/theme_check/checks/template_length.rb +1 -1
- data/lib/theme_check/checks/undefined_object.rb +1 -1
- data/lib/theme_check/checks/valid_html_translation.rb +1 -1
- data/lib/theme_check/checks.rb +11 -1
- data/lib/theme_check/cli.rb +18 -2
- data/lib/theme_check/corrector.rb +9 -0
- data/lib/theme_check/file_system_storage.rb +12 -0
- data/lib/theme_check/html_check.rb +0 -1
- data/lib/theme_check/html_node.rb +37 -16
- data/lib/theme_check/html_visitor.rb +17 -3
- data/lib/theme_check/json_check.rb +2 -2
- data/lib/theme_check/json_file.rb +11 -0
- data/lib/theme_check/json_printer.rb +27 -0
- data/lib/theme_check/language_server/constants.rb +18 -11
- data/lib/theme_check/language_server/document_link_engine.rb +3 -67
- data/lib/theme_check/language_server/document_link_provider.rb +71 -0
- data/lib/theme_check/language_server/document_link_providers/asset_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/include_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/render_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/section_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/handler.rb +17 -9
- data/lib/theme_check/language_server/server.rb +9 -0
- data/lib/theme_check/language_server/uri_helper.rb +37 -0
- data/lib/theme_check/language_server.rb +6 -0
- data/lib/theme_check/node.rb +6 -4
- data/lib/theme_check/offense.rb +56 -3
- data/lib/theme_check/parsing_helpers.rb +4 -3
- data/lib/theme_check/position.rb +98 -14
- data/lib/theme_check/regex_helpers.rb +5 -2
- data/lib/theme_check/theme.rb +3 -0
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +1 -0
- data/theme-check.gemspec +1 -1
- metadata +20 -6
- data/bin/liquid-server +0 -4
@@ -3,81 +3,17 @@
|
|
3
3
|
module ThemeCheck
|
4
4
|
module LanguageServer
|
5
5
|
class DocumentLinkEngine
|
6
|
-
include PositionHelper
|
7
|
-
include RegexHelpers
|
8
|
-
|
9
6
|
def initialize(storage)
|
10
7
|
@storage = storage
|
8
|
+
@providers = DocumentLinkProvider.all.map { |x| x.new(storage) }
|
11
9
|
end
|
12
10
|
|
13
11
|
def document_links(relative_path)
|
14
12
|
buffer = @storage.read(relative_path)
|
15
13
|
return [] unless buffer
|
16
|
-
|
17
|
-
|
18
|
-
buffer,
|
19
|
-
match.begin(:partial),
|
20
|
-
)
|
21
|
-
|
22
|
-
end_line, end_character = from_index_to_row_column(
|
23
|
-
buffer,
|
24
|
-
match.end(:partial)
|
25
|
-
)
|
26
|
-
|
27
|
-
{
|
28
|
-
target: snippet_link(match[:partial]),
|
29
|
-
range: {
|
30
|
-
start: {
|
31
|
-
line: start_line,
|
32
|
-
character: start_character,
|
33
|
-
},
|
34
|
-
end: {
|
35
|
-
line: end_line,
|
36
|
-
character: end_character,
|
37
|
-
},
|
38
|
-
},
|
39
|
-
}
|
14
|
+
@providers.flat_map do |p|
|
15
|
+
p.document_links(buffer)
|
40
16
|
end
|
41
|
-
asset_matches = matches(buffer, ASSET_INCLUDE).map do |match|
|
42
|
-
start_line, start_character = from_index_to_row_column(
|
43
|
-
buffer,
|
44
|
-
match.begin(:partial),
|
45
|
-
)
|
46
|
-
|
47
|
-
end_line, end_character = from_index_to_row_column(
|
48
|
-
buffer,
|
49
|
-
match.end(:partial)
|
50
|
-
)
|
51
|
-
|
52
|
-
{
|
53
|
-
target: asset_link(match[:partial]),
|
54
|
-
range: {
|
55
|
-
start: {
|
56
|
-
line: start_line,
|
57
|
-
character: start_character,
|
58
|
-
},
|
59
|
-
end: {
|
60
|
-
line: end_line,
|
61
|
-
character: end_character,
|
62
|
-
},
|
63
|
-
},
|
64
|
-
}
|
65
|
-
end
|
66
|
-
snippet_matches + asset_matches
|
67
|
-
end
|
68
|
-
|
69
|
-
def snippet_link(partial)
|
70
|
-
file_link('snippets', partial, '.liquid')
|
71
|
-
end
|
72
|
-
|
73
|
-
def asset_link(partial)
|
74
|
-
file_link('assets', partial, '')
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
|
79
|
-
def file_link(directory, partial, extension)
|
80
|
-
"file://#{@storage.path(directory + '/' + partial + extension)}"
|
81
17
|
end
|
82
18
|
end
|
83
19
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module LanguageServer
|
5
|
+
class DocumentLinkProvider
|
6
|
+
include RegexHelpers
|
7
|
+
include PositionHelper
|
8
|
+
include URIHelper
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :partial_regexp, :destination_directory, :destination_postfix
|
12
|
+
|
13
|
+
def all
|
14
|
+
@all ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
def inherited(subclass)
|
18
|
+
all << subclass
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(storage = InMemoryStorage.new)
|
23
|
+
@storage = storage
|
24
|
+
end
|
25
|
+
|
26
|
+
def partial_regexp
|
27
|
+
self.class.partial_regexp
|
28
|
+
end
|
29
|
+
|
30
|
+
def destination_directory
|
31
|
+
self.class.destination_directory
|
32
|
+
end
|
33
|
+
|
34
|
+
def destination_postfix
|
35
|
+
self.class.destination_postfix
|
36
|
+
end
|
37
|
+
|
38
|
+
def document_links(buffer)
|
39
|
+
matches(buffer, partial_regexp).map do |match|
|
40
|
+
start_line, start_character = from_index_to_row_column(
|
41
|
+
buffer,
|
42
|
+
match.begin(:partial),
|
43
|
+
)
|
44
|
+
|
45
|
+
end_line, end_character = from_index_to_row_column(
|
46
|
+
buffer,
|
47
|
+
match.end(:partial)
|
48
|
+
)
|
49
|
+
|
50
|
+
{
|
51
|
+
target: file_link(match[:partial]),
|
52
|
+
range: {
|
53
|
+
start: {
|
54
|
+
line: start_line,
|
55
|
+
character: start_character,
|
56
|
+
},
|
57
|
+
end: {
|
58
|
+
line: end_line,
|
59
|
+
character: end_character,
|
60
|
+
},
|
61
|
+
},
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def file_link(partial)
|
67
|
+
file_uri(@storage.path(destination_directory + '/' + partial + destination_postfix))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/theme_check/language_server/document_link_providers/include_document_link_provider.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module LanguageServer
|
5
|
+
class IncludeDocumentLinkProvider < DocumentLinkProvider
|
6
|
+
@partial_regexp = PARTIAL_INCLUDE
|
7
|
+
@destination_directory = "snippets"
|
8
|
+
@destination_postfix = ".liquid"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module LanguageServer
|
5
|
+
class RenderDocumentLinkProvider < DocumentLinkProvider
|
6
|
+
@partial_regexp = PARTIAL_RENDER
|
7
|
+
@destination_directory = "snippets"
|
8
|
+
@destination_postfix = ".liquid"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/theme_check/language_server/document_link_providers/section_document_link_provider.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module LanguageServer
|
5
|
+
class SectionDocumentLinkProvider < DocumentLinkProvider
|
6
|
+
@partial_regexp = PARTIAL_SECTION
|
7
|
+
@destination_directory = "sections"
|
8
|
+
@destination_postfix = ".liquid"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -1,9 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "benchmark"
|
3
4
|
|
4
5
|
module ThemeCheck
|
5
6
|
module LanguageServer
|
6
7
|
class Handler
|
8
|
+
include URIHelper
|
9
|
+
|
7
10
|
CAPABILITIES = {
|
8
11
|
completionProvider: {
|
9
12
|
triggerCharacters: ['.', '{{ ', '{% '],
|
@@ -24,7 +27,7 @@ module ThemeCheck
|
|
24
27
|
end
|
25
28
|
|
26
29
|
def on_initialize(id, params)
|
27
|
-
@root_path =
|
30
|
+
@root_path = root_path_from_params(params)
|
28
31
|
|
29
32
|
# Tell the client we don't support anything if there's no rootPath
|
30
33
|
return send_response(id, { capabilities: {} }) if @root_path.nil?
|
@@ -53,10 +56,9 @@ module ThemeCheck
|
|
53
56
|
end
|
54
57
|
|
55
58
|
def on_text_document_did_open(_id, params)
|
56
|
-
return unless @diagnostics_tracker.first_run?
|
57
59
|
relative_path = relative_path_from_text_document_uri(params)
|
58
60
|
@storage.write(relative_path, text_document_text(params))
|
59
|
-
analyze_and_send_offenses(text_document_uri(params))
|
61
|
+
analyze_and_send_offenses(text_document_uri(params)) if @diagnostics_tracker.first_run?
|
60
62
|
end
|
61
63
|
|
62
64
|
def on_text_document_did_save(_id, params)
|
@@ -95,17 +97,23 @@ module ThemeCheck
|
|
95
97
|
end
|
96
98
|
|
97
99
|
def text_document_uri(params)
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
def path_from_uri(uri)
|
102
|
-
uri&.sub('file://', '')
|
100
|
+
file_path(params.dig('textDocument', 'uri'))
|
103
101
|
end
|
104
102
|
|
105
103
|
def relative_path_from_text_document_uri(params)
|
106
104
|
@storage.relative_path(text_document_uri(params))
|
107
105
|
end
|
108
106
|
|
107
|
+
def root_path_from_params(params)
|
108
|
+
root_uri = params["rootUri"]
|
109
|
+
root_path = params["rootPath"]
|
110
|
+
if root_uri
|
111
|
+
file_path(root_uri)
|
112
|
+
elsif root_path
|
113
|
+
root_path
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
109
117
|
def text_document_text(params)
|
110
118
|
params.dig('textDocument', 'text')
|
111
119
|
end
|
@@ -171,7 +179,7 @@ module ThemeCheck
|
|
171
179
|
def send_diagnostic(path, offenses)
|
172
180
|
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
|
173
181
|
send_notification('textDocument/publishDiagnostics', {
|
174
|
-
uri:
|
182
|
+
uri: file_uri(path),
|
175
183
|
diagnostics: offenses.map { |offense| offense_to_diagnostic(offense) },
|
176
184
|
})
|
177
185
|
end
|
@@ -25,6 +25,15 @@ module ThemeCheck
|
|
25
25
|
@out = out_stream
|
26
26
|
@err = err_stream
|
27
27
|
|
28
|
+
# Because programming is fun,
|
29
|
+
#
|
30
|
+
# Ruby on Windows turns \n into \r\n. Which means that \r\n
|
31
|
+
# gets turned into \r\r\n. Which means that the protocol
|
32
|
+
# breaks on windows unless we turn STDOUT into binary mode.
|
33
|
+
#
|
34
|
+
# Hours wasted: 9.
|
35
|
+
@out.binmode
|
36
|
+
|
28
37
|
@out.sync = true # do not buffer
|
29
38
|
@err.sync = true # do not buffer
|
30
39
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "benchmark"
|
4
|
+
require "uri"
|
5
|
+
require "cgi"
|
6
|
+
|
7
|
+
module ThemeCheck
|
8
|
+
module LanguageServer
|
9
|
+
module URIHelper
|
10
|
+
# Will URI.encode a string the same way VS Code would. There are two things
|
11
|
+
# to watch out for:
|
12
|
+
#
|
13
|
+
# 1. VS Code still uses the outdated '%20' for spaces
|
14
|
+
# 2. VS Code prefixes Windows paths with / (so /C:/Users/... is expected)
|
15
|
+
#
|
16
|
+
# Exists because of https://github.com/Shopify/theme-check/issues/360
|
17
|
+
def file_uri(absolute_path)
|
18
|
+
"file://" + absolute_path
|
19
|
+
.to_s
|
20
|
+
.split('/')
|
21
|
+
.map { |x| CGI.escape(x).gsub('+', '%20') }
|
22
|
+
.join('/')
|
23
|
+
.sub(%r{^/?}, '/') # Windows paths should be prefixed by /c:
|
24
|
+
end
|
25
|
+
|
26
|
+
def file_path(uri_string)
|
27
|
+
return if uri_string.nil?
|
28
|
+
uri = URI(uri_string)
|
29
|
+
path = CGI.unescape(uri.path)
|
30
|
+
# On Windows, VS Code sends the URLs as file:///C:/...
|
31
|
+
# /C:/1234 is not a valid path in ruby. So we strip the slash.
|
32
|
+
path = path.sub(%r{^/([a-z]:/)}i, '\1')
|
33
|
+
path
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require_relative "language_server/protocol"
|
3
3
|
require_relative "language_server/constants"
|
4
|
+
require_relative "language_server/uri_helper"
|
4
5
|
require_relative "language_server/handler"
|
5
6
|
require_relative "language_server/server"
|
6
7
|
require_relative "language_server/tokens"
|
@@ -8,6 +9,7 @@ require_relative "language_server/variable_lookup_finder"
|
|
8
9
|
require_relative "language_server/completion_helper"
|
9
10
|
require_relative "language_server/completion_provider"
|
10
11
|
require_relative "language_server/completion_engine"
|
12
|
+
require_relative "language_server/document_link_provider"
|
11
13
|
require_relative "language_server/document_link_engine"
|
12
14
|
require_relative "language_server/diagnostics_tracker"
|
13
15
|
|
@@ -15,6 +17,10 @@ Dir[__dir__ + "/language_server/completion_providers/*.rb"].each do |file|
|
|
15
17
|
require file
|
16
18
|
end
|
17
19
|
|
20
|
+
Dir[__dir__ + "/language_server/document_link_providers/*.rb"].each do |file|
|
21
|
+
require file
|
22
|
+
end
|
23
|
+
|
18
24
|
module ThemeCheck
|
19
25
|
module LanguageServer
|
20
26
|
def self.start
|
data/lib/theme_check/node.rb
CHANGED
@@ -22,9 +22,7 @@ module ThemeCheck
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def markup=(markup)
|
25
|
-
if
|
26
|
-
@value.raw = markup
|
27
|
-
elsif @value.instance_variable_defined?(:@markup)
|
25
|
+
if @value.instance_variable_defined?(:@markup)
|
28
26
|
@value.instance_variable_set(:@markup, markup)
|
29
27
|
end
|
30
28
|
end
|
@@ -127,7 +125,11 @@ module ThemeCheck
|
|
127
125
|
end
|
128
126
|
|
129
127
|
def position
|
130
|
-
@position ||= Position.new(
|
128
|
+
@position ||= Position.new(
|
129
|
+
markup,
|
130
|
+
template&.source,
|
131
|
+
line_number_1_indexed: line_number
|
132
|
+
)
|
131
133
|
end
|
132
134
|
|
133
135
|
def start_index
|
data/lib/theme_check/offense.rb
CHANGED
@@ -1,11 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ThemeCheck
|
3
3
|
class Offense
|
4
|
+
include PositionHelper
|
5
|
+
|
4
6
|
MAX_SOURCE_EXCERPT_SIZE = 120
|
5
7
|
|
6
8
|
attr_reader :check, :message, :template, :node, :markup, :line_number, :correction
|
7
9
|
|
8
|
-
def initialize(
|
10
|
+
def initialize(
|
11
|
+
check:, # instance of a ThemeCheck::Check
|
12
|
+
message: nil, # error message for the offense
|
13
|
+
template: nil, # Template
|
14
|
+
node: nil, # Node or HtmlNode
|
15
|
+
markup: nil, # string
|
16
|
+
line_number: nil, # line number of the error (1-indexed)
|
17
|
+
# node_markup_offset is the index inside node.markup to start
|
18
|
+
# looking for markup :mindblow:.
|
19
|
+
# This is so we can accurately highlight node substrings.
|
20
|
+
# e.g. if we have the following scenario in which we
|
21
|
+
# want to highlight the middle comma
|
22
|
+
# * node.markup == "replace ',',', '"
|
23
|
+
# * markup == ","
|
24
|
+
# Then we need some way of telling our Position class to start
|
25
|
+
# looking for the second comma. This is done with node_markup_offset.
|
26
|
+
# More context can be found in #376.
|
27
|
+
node_markup_offset: 0,
|
28
|
+
correction: nil # block
|
29
|
+
)
|
9
30
|
@check = check
|
10
31
|
@correction = correction
|
11
32
|
|
@@ -39,7 +60,13 @@ module ThemeCheck
|
|
39
60
|
@node.line_number
|
40
61
|
end
|
41
62
|
|
42
|
-
@position = Position.new(
|
63
|
+
@position = Position.new(
|
64
|
+
@markup,
|
65
|
+
@template&.source,
|
66
|
+
line_number_1_indexed: @line_number,
|
67
|
+
node_markup_offset: node_markup_offset,
|
68
|
+
node_markup: node&.markup
|
69
|
+
)
|
43
70
|
end
|
44
71
|
|
45
72
|
def source_excerpt
|
@@ -103,8 +130,13 @@ module ThemeCheck
|
|
103
130
|
tokens.join(":") if tokens.any?
|
104
131
|
end
|
105
132
|
|
133
|
+
def location_range
|
134
|
+
tokens = [template&.relative_path, start_index, end_index].compact
|
135
|
+
tokens.join(":") if tokens.any?
|
136
|
+
end
|
137
|
+
|
106
138
|
def correctable?
|
107
|
-
|
139
|
+
!!correction
|
108
140
|
end
|
109
141
|
|
110
142
|
def correct
|
@@ -139,5 +171,26 @@ module ThemeCheck
|
|
139
171
|
message
|
140
172
|
end
|
141
173
|
end
|
174
|
+
|
175
|
+
def to_s_range
|
176
|
+
if template
|
177
|
+
"#{message} at #{location_range}"
|
178
|
+
else
|
179
|
+
message
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def to_h
|
184
|
+
{
|
185
|
+
check: check.code_name,
|
186
|
+
path: template&.relative_path,
|
187
|
+
severity: check.severity_value,
|
188
|
+
start_line: start_line,
|
189
|
+
start_column: start_column,
|
190
|
+
end_line: end_line,
|
191
|
+
end_column: end_column,
|
192
|
+
message: message,
|
193
|
+
}
|
194
|
+
end
|
142
195
|
end
|
143
196
|
end
|