theme-check 1.3.0 → 1.5.2
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 +3 -3
- data/.gitignore +1 -0
- data/CHANGELOG.md +38 -0
- data/CONTRIBUTING.md +58 -0
- data/Gemfile +3 -0
- data/config/default.yml +4 -1
- data/data/shopify_liquid/objects.yml +1 -0
- data/docs/checks/deprecate_lazysizes.md +0 -3
- data/docs/checks/deprecated_global_app_block_type.md +65 -0
- data/docs/checks/template_length.md +1 -1
- data/docs/flamegraph.svg +18488 -0
- data/lib/theme_check/analyzer.rb +1 -0
- data/lib/theme_check/checks/default_locale.rb +3 -1
- data/lib/theme_check/checks/deprecate_lazysizes.rb +6 -3
- data/lib/theme_check/checks/deprecated_global_app_block_type.rb +57 -0
- data/lib/theme_check/checks/liquid_tag.rb +1 -1
- data/lib/theme_check/checks/pagination_size.rb +33 -14
- data/lib/theme_check/checks/remote_asset.rb +2 -2
- data/lib/theme_check/checks/required_directories.rb +3 -1
- data/lib/theme_check/checks/space_inside_braces.rb +47 -24
- data/lib/theme_check/checks/template_length.rb +1 -1
- data/lib/theme_check/cli.rb +28 -5
- data/lib/theme_check/corrector.rb +9 -0
- data/lib/theme_check/file_system_storage.rb +6 -0
- data/lib/theme_check/in_memory_storage.rb +4 -0
- data/lib/theme_check/json_file.rb +11 -0
- data/lib/theme_check/json_printer.rb +6 -1
- 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 -13
- data/lib/theme_check/language_server/server.rb +11 -13
- 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 +120 -8
- data/lib/theme_check/position.rb +27 -16
- data/lib/theme_check/position_helper.rb +13 -15
- data/lib/theme_check/printer.rb +9 -5
- data/lib/theme_check/remote_asset_file.rb +4 -0
- data/lib/theme_check/theme.rb +2 -1
- data/lib/theme_check/version.rb +1 -1
- metadata +11 -2
@@ -3,9 +3,13 @@ require 'json'
|
|
3
3
|
|
4
4
|
module ThemeCheck
|
5
5
|
class JsonPrinter
|
6
|
+
def initialize(out_stream = STDOUT)
|
7
|
+
@out = out_stream
|
8
|
+
end
|
9
|
+
|
6
10
|
def print(offenses)
|
7
11
|
json = offenses_by_path(offenses)
|
8
|
-
puts JSON.dump(json)
|
12
|
+
@out.puts JSON.dump(json)
|
9
13
|
end
|
10
14
|
|
11
15
|
def offenses_by_path(offenses)
|
@@ -21,6 +25,7 @@ module ThemeCheck
|
|
21
25
|
styleCount: path_offenses.count { |offense| offense[:severity] == Check::SEVERITY_VALUES[:style] },
|
22
26
|
}
|
23
27
|
end
|
28
|
+
.sort_by { |o| o[:path] }
|
24
29
|
end
|
25
30
|
end
|
26
31
|
end
|
@@ -2,21 +2,28 @@
|
|
2
2
|
|
3
3
|
module ThemeCheck
|
4
4
|
module LanguageServer
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
def self.partial_tag(tag)
|
6
|
+
%r{
|
7
|
+
\{\%-?\s*#{tag}\s+'(?<partial>[^']*)'|
|
8
|
+
\{\%-?\s*#{tag}\s+"(?<partial>[^"]*)"|
|
9
|
+
|
10
|
+
# in liquid tags the whole line is white space until the tag
|
11
|
+
^\s*#{tag}\s+'(?<partial>[^']*)'|
|
12
|
+
^\s*#{tag}\s+"(?<partial>[^"]*)"
|
13
|
+
}mix
|
14
|
+
end
|
15
|
+
|
16
|
+
PARTIAL_RENDER = partial_tag('render')
|
17
|
+
PARTIAL_INCLUDE = partial_tag('include')
|
18
|
+
PARTIAL_SECTION = partial_tag('section')
|
8
19
|
|
9
|
-
# in liquid tags the whole line is white space until render
|
10
|
-
^\s*render\s+'(?<partial>[^']*)'|
|
11
|
-
^\s*render\s+"(?<partial>[^"]*)"
|
12
|
-
}mix
|
13
20
|
ASSET_INCLUDE = %r{
|
14
|
-
\{
|
15
|
-
\{
|
21
|
+
\{\{-?\s*'(?<partial>[^']*)'\s*\|\s*asset_url|
|
22
|
+
\{\{-?\s*"(?<partial>[^"]*)"\s*\|\s*asset_url|
|
16
23
|
|
17
24
|
# in liquid tags the whole line is white space until the asset partial
|
18
|
-
^\s*'(?<partial>[^']*)'\s*\|\s*asset_url|
|
19
|
-
^\s*"(?<partial>[^"]*)"\s*\|\s*asset_url
|
25
|
+
^\s*(?:echo|assign[^=]*\=)\s*'(?<partial>[^']*)'\s*\|\s*asset_url|
|
26
|
+
^\s*(?:echo|assign[^=]*\=)\s*"(?<partial>[^"]*)"\s*\|\s*asset_url
|
20
27
|
}mix
|
21
28
|
end
|
22
29
|
end
|
@@ -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,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "benchmark"
|
3
|
-
require "uri"
|
4
|
-
require "cgi"
|
5
4
|
|
6
5
|
module ThemeCheck
|
7
6
|
module LanguageServer
|
8
7
|
class Handler
|
8
|
+
include URIHelper
|
9
|
+
|
9
10
|
CAPABILITIES = {
|
10
11
|
completionProvider: {
|
11
12
|
triggerCharacters: ['.', '{{ ', '{% '],
|
@@ -26,7 +27,7 @@ module ThemeCheck
|
|
26
27
|
end
|
27
28
|
|
28
29
|
def on_initialize(id, params)
|
29
|
-
@root_path =
|
30
|
+
@root_path = root_path_from_params(params)
|
30
31
|
|
31
32
|
# Tell the client we don't support anything if there's no rootPath
|
32
33
|
return send_response(id, { capabilities: {} }) if @root_path.nil?
|
@@ -55,10 +56,9 @@ module ThemeCheck
|
|
55
56
|
end
|
56
57
|
|
57
58
|
def on_text_document_did_open(_id, params)
|
58
|
-
return unless @diagnostics_tracker.first_run?
|
59
59
|
relative_path = relative_path_from_text_document_uri(params)
|
60
60
|
@storage.write(relative_path, text_document_text(params))
|
61
|
-
analyze_and_send_offenses(text_document_uri(params))
|
61
|
+
analyze_and_send_offenses(text_document_uri(params)) if @diagnostics_tracker.first_run?
|
62
62
|
end
|
63
63
|
|
64
64
|
def on_text_document_did_save(_id, params)
|
@@ -97,19 +97,23 @@ module ThemeCheck
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def text_document_uri(params)
|
100
|
-
|
101
|
-
end
|
102
|
-
|
103
|
-
def path_from_uri(uri_string)
|
104
|
-
return if uri_string.nil?
|
105
|
-
uri = URI(uri_string)
|
106
|
-
CGI.unescape(uri.path)
|
100
|
+
file_path(params.dig('textDocument', 'uri'))
|
107
101
|
end
|
108
102
|
|
109
103
|
def relative_path_from_text_document_uri(params)
|
110
104
|
@storage.relative_path(text_document_uri(params))
|
111
105
|
end
|
112
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
|
+
|
113
117
|
def text_document_text(params)
|
114
118
|
params.dig('textDocument', 'text')
|
115
119
|
end
|
@@ -175,7 +179,7 @@ module ThemeCheck
|
|
175
179
|
def send_diagnostic(path, offenses)
|
176
180
|
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
|
177
181
|
send_notification('textDocument/publishDiagnostics', {
|
178
|
-
uri:
|
182
|
+
uri: file_uri(path),
|
179
183
|
diagnostics: offenses.map { |offense| offense_to_diagnostic(offense) },
|
180
184
|
})
|
181
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
|
|
@@ -52,19 +61,8 @@ module ThemeCheck
|
|
52
61
|
response_body = JSON.dump(response)
|
53
62
|
log(JSON.pretty_generate(response)) if $DEBUG
|
54
63
|
|
55
|
-
#
|
56
|
-
|
57
|
-
# Ruby on Windows turns \n into \r\n. Which means that \r\n
|
58
|
-
# gets turned into \r\r\n. Which means that the protocol
|
59
|
-
# breaks on windows unless we turn STDOUT into binary mode and
|
60
|
-
# set the encoding manually (yuk!) or we do this little hack
|
61
|
-
# here and put \n which gets transformed into \r\n on windows
|
62
|
-
# only...
|
63
|
-
#
|
64
|
-
# Hours wasted: 8.
|
65
|
-
eol = Gem.win_platform? ? "\n" : "\r\n"
|
66
|
-
@out.write("Content-Length: #{response_body.bytesize}#{eol}")
|
67
|
-
@out.write(eol)
|
64
|
+
@out.write("Content-Length: #{response_body.bytesize}\r\n")
|
65
|
+
@out.write("\r\n")
|
68
66
|
@out.write(response_body)
|
69
67
|
@out.flush
|
70
68
|
end
|
@@ -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
|