theme-check 0.8.0 → 0.8.1
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 +10 -0
- data/data/shopify_translation_keys.yml +1 -0
- data/docs/checks/space_inside_braces.md +6 -0
- data/lib/theme_check.rb +4 -0
- data/lib/theme_check/analyzer.rb +19 -9
- data/lib/theme_check/check.rb +5 -1
- data/lib/theme_check/checks.rb +2 -8
- data/lib/theme_check/checks/missing_enable_comment.rb +4 -4
- data/lib/theme_check/checks/space_inside_braces.rb +8 -2
- data/lib/theme_check/disabled_check.rb +39 -0
- data/lib/theme_check/disabled_checks.rb +20 -32
- data/lib/theme_check/exceptions.rb +32 -0
- data/lib/theme_check/json_file.rb +5 -1
- data/lib/theme_check/language_server.rb +0 -1
- data/lib/theme_check/language_server/completion_engine.rb +1 -1
- data/lib/theme_check/language_server/constants.rb +5 -1
- data/lib/theme_check/language_server/document_link_engine.rb +2 -2
- data/lib/theme_check/language_server/handler.rb +32 -24
- data/lib/theme_check/node.rb +12 -0
- data/lib/theme_check/offense.rb +14 -48
- data/lib/theme_check/position.rb +77 -0
- data/lib/theme_check/position_helper.rb +37 -0
- data/lib/theme_check/remote_asset_file.rb +3 -0
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check/visitor.rb +9 -10
- metadata +6 -3
- data/lib/theme_check/language_server/position_helper.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b841f4d2f809e07dec57140fd76f373e216f721903baa7ac1dce747b5ce77c82
|
4
|
+
data.tar.gz: b513333c296966cedb4d00345cb7ab1a3d4dcbb305b92b3eb96edc598867bba5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d75dc49ae97db15d4622d6a59b7a12a0eab0de6a6bcf19e6e8bca3b7f6eb6f94c40c11cfcdb3ac911bbc4d93e6d08cfbb83e85de763d00cb60b6fab10bfc7bb
|
7
|
+
data.tar.gz: eb7c5fdb74551461de7fd643071734dc8b2442db4a5a80492b982bc392c67a100614e4a9fa3b4580e28c5f1b8321564adc8f9580618f3bcdbc36dfd4e4ab13e3
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,14 @@
|
|
1
1
|
|
2
|
+
0.8.1 / 2021-04-22
|
3
|
+
==================
|
4
|
+
|
5
|
+
* Add consistent spacing around the pipe character (`|`) in variable expressions to the `SpaceInsideBrace` check ([#73](https://github.com/shopify/theme-check/issues/73))
|
6
|
+
* Add ReCaptcha system translation ([#265](https://github.com/shopify/theme-check/issues/265))
|
7
|
+
* Fix document links in `{% liquid %}` tags ([#263](https://github.com/shopify/theme-check/issues/263))
|
8
|
+
* Fix theme-check-disable for checks based on regular expressions ([#242](https://github.com/shopify/theme-check/issues/242))
|
9
|
+
* Fix VS Code crash on new window ([#264](https://github.com/shopify/theme-check/issues/264))
|
10
|
+
* Rescue errors thrown by remote_asset_file
|
11
|
+
|
2
12
|
0.8.0 / 2021-04-13
|
3
13
|
==================
|
4
14
|
|
@@ -17,6 +17,10 @@ This check is aimed at eliminating ugly Liquid:
|
|
17
17
|
<!-- After commas and semicolons -->
|
18
18
|
{% form 'type', object, key:value %}
|
19
19
|
{% endform %}
|
20
|
+
|
21
|
+
<!-- Arround filter pipelines -->
|
22
|
+
{{ url | asset_url | img_tag }}
|
23
|
+
{% assign my_upcase_string = "Hello world"| upcase %}
|
20
24
|
```
|
21
25
|
|
22
26
|
:+1: Examples of **correct** code for this check:
|
@@ -33,6 +37,8 @@ This check is aimed at eliminating ugly Liquid:
|
|
33
37
|
media_size: section.settings.product_recommendations_image_ratio,
|
34
38
|
center_align_text: section.settings.center_align_text
|
35
39
|
%}
|
40
|
+
{{ url | asset_url | img_tag }}
|
41
|
+
{% assign my_upcase_string = "Hello world" | upcase %}
|
36
42
|
```
|
37
43
|
|
38
44
|
## Check Options
|
data/lib/theme_check.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "liquid"
|
3
3
|
|
4
|
+
require_relative "theme_check/exceptions"
|
4
5
|
require_relative "theme_check/analyzer"
|
5
6
|
require_relative "theme_check/check"
|
6
7
|
require_relative "theme_check/checks_tracking"
|
7
8
|
require_relative "theme_check/cli"
|
9
|
+
require_relative "theme_check/disabled_check"
|
8
10
|
require_relative "theme_check/disabled_checks"
|
9
11
|
require_relative "theme_check/liquid_check"
|
10
12
|
require_relative "theme_check/locale_diff"
|
@@ -14,6 +16,8 @@ require_relative "theme_check/regex_helpers"
|
|
14
16
|
require_relative "theme_check/json_check"
|
15
17
|
require_relative "theme_check/json_file"
|
16
18
|
require_relative "theme_check/json_helpers"
|
19
|
+
require_relative "theme_check/position_helper"
|
20
|
+
require_relative "theme_check/position"
|
17
21
|
require_relative "theme_check/language_server"
|
18
22
|
require_relative "theme_check/checks"
|
19
23
|
require_relative "theme_check/config"
|
data/lib/theme_check/analyzer.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ThemeCheck
|
3
3
|
class Analyzer
|
4
|
-
attr_reader :offenses
|
5
|
-
|
6
4
|
def initialize(theme, checks = Check.all.map(&:new), auto_correct = false)
|
7
5
|
@theme = theme
|
8
|
-
@offenses = []
|
9
6
|
@auto_correct = auto_correct
|
10
7
|
|
11
8
|
@liquid_checks = Checks.new
|
@@ -13,7 +10,6 @@ module ThemeCheck
|
|
13
10
|
|
14
11
|
checks.each do |check|
|
15
12
|
check.theme = @theme
|
16
|
-
check.offenses = @offenses
|
17
13
|
|
18
14
|
case check
|
19
15
|
when LiquidCheck
|
@@ -26,26 +22,40 @@ module ThemeCheck
|
|
26
22
|
@visitor = Visitor.new(@liquid_checks)
|
27
23
|
end
|
28
24
|
|
25
|
+
def offenses
|
26
|
+
@liquid_checks.flat_map(&:offenses) + @json_checks.flat_map(&:offenses)
|
27
|
+
end
|
28
|
+
|
29
|
+
def offenses_clear!
|
30
|
+
@liquid_checks.each do |check|
|
31
|
+
check.offenses.clear
|
32
|
+
end
|
33
|
+
|
34
|
+
@json_checks.each do |check|
|
35
|
+
check.offenses.clear
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
29
39
|
def analyze_theme
|
30
|
-
|
40
|
+
offenses_clear!
|
31
41
|
@theme.liquid.each { |template| @visitor.visit_template(template) }
|
32
42
|
@theme.json.each { |json_file| @json_checks.call(:on_file, json_file) }
|
33
43
|
@liquid_checks.call(:on_end)
|
34
44
|
@json_checks.call(:on_end)
|
35
|
-
|
45
|
+
offenses
|
36
46
|
end
|
37
47
|
|
38
48
|
def uncorrectable_offenses
|
39
49
|
unless @auto_correct
|
40
|
-
return
|
50
|
+
return offenses
|
41
51
|
end
|
42
52
|
|
43
|
-
|
53
|
+
offenses.select { |offense| !offense.correctable? }
|
44
54
|
end
|
45
55
|
|
46
56
|
def correct_offenses
|
47
57
|
if @auto_correct
|
48
|
-
|
58
|
+
offenses.each(&:correct)
|
49
59
|
@theme.liquid.each(&:write)
|
50
60
|
end
|
51
61
|
end
|
data/lib/theme_check/check.rb
CHANGED
@@ -6,8 +6,8 @@ module ThemeCheck
|
|
6
6
|
include JsonHelpers
|
7
7
|
|
8
8
|
attr_accessor :theme
|
9
|
-
attr_accessor :offenses
|
10
9
|
attr_accessor :options
|
10
|
+
attr_writer :offenses
|
11
11
|
|
12
12
|
SEVERITIES = [
|
13
13
|
:error,
|
@@ -69,6 +69,10 @@ module ThemeCheck
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
+
def offenses
|
73
|
+
@offenses ||= []
|
74
|
+
end
|
75
|
+
|
72
76
|
def severity
|
73
77
|
self.class.severity
|
74
78
|
end
|
data/lib/theme_check/checks.rb
CHANGED
@@ -9,14 +9,8 @@ module ThemeCheck
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
self.class.new(
|
14
|
-
end
|
15
|
-
|
16
|
-
def except_for(disabled_checks)
|
17
|
-
still_enabled = reject { |check| disabled_checks.all.include?(check.code_name) }
|
18
|
-
|
19
|
-
self.class.new((always_enabled + still_enabled).uniq)
|
12
|
+
def disableable
|
13
|
+
self.class.new(select(&:can_disable?))
|
20
14
|
end
|
21
15
|
end
|
22
16
|
end
|
@@ -17,13 +17,13 @@ module ThemeCheck
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def after_document(node)
|
20
|
-
|
21
|
-
return
|
20
|
+
checks_missing_end_index = @disabled_checks.checks_missing_end_index
|
21
|
+
return if checks_missing_end_index.empty?
|
22
22
|
|
23
|
-
message = if
|
23
|
+
message = if checks_missing_end_index.any? { |name| name == :all }
|
24
24
|
"All checks were"
|
25
25
|
else
|
26
|
-
|
26
|
+
checks_missing_end_index.join(', ') + " " + (checks_missing_end_index.size == 1 ? "was" : "were")
|
27
27
|
end
|
28
28
|
|
29
29
|
add_offense("#{message} disabled but not re-enabled with theme-check-enable", node: node)
|
@@ -15,12 +15,18 @@ module ThemeCheck
|
|
15
15
|
return if :assign == node.type_name
|
16
16
|
|
17
17
|
outside_of_strings(node.markup) do |chunk|
|
18
|
-
chunk.scan(/([
|
18
|
+
chunk.scan(/([,:|]) +/) do |_match|
|
19
19
|
add_offense("Too many spaces after '#{Regexp.last_match(1)}'", node: node, markup: Regexp.last_match(0))
|
20
20
|
end
|
21
|
-
chunk.scan(/([
|
21
|
+
chunk.scan(/([,:|])\S/) do |_match|
|
22
22
|
add_offense("Space missing after '#{Regexp.last_match(1)}'", node: node, markup: Regexp.last_match(0))
|
23
23
|
end
|
24
|
+
chunk.scan(/ ([|])+/) do |_match|
|
25
|
+
add_offense("Too many spaces before '#{Regexp.last_match(1)}'", node: node, markup: Regexp.last_match(0))
|
26
|
+
end
|
27
|
+
chunk.scan(/\A(?<pipe>\|)|\S(?<pipe>\|)/) do |_match|
|
28
|
+
add_offense("Space missing before '#{Regexp.last_match(:pipe)}'", node: node, markup: Regexp.last_match(:pipe))
|
29
|
+
end
|
24
30
|
end
|
25
31
|
end
|
26
32
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This class keeps track of checks being turned on and off in ranges.
|
4
|
+
# We'll use the node position to figure out if the test is disabled or not.
|
5
|
+
module ThemeCheck
|
6
|
+
class DisabledCheck
|
7
|
+
attr_reader :name, :ranges
|
8
|
+
attr_accessor :first_line
|
9
|
+
|
10
|
+
def initialize(name)
|
11
|
+
@name = name
|
12
|
+
@ranges = []
|
13
|
+
@first_line = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def start_index=(index)
|
17
|
+
return unless ranges.empty? || !last.end.nil?
|
18
|
+
@ranges << (index..)
|
19
|
+
end
|
20
|
+
|
21
|
+
def end_index=(index)
|
22
|
+
return if ranges.empty? || !last.end.nil?
|
23
|
+
@ranges << (@ranges.pop.begin..index)
|
24
|
+
end
|
25
|
+
|
26
|
+
def disabled?(index)
|
27
|
+
ranges.any? { |range| range.cover?(index) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def last
|
31
|
+
ranges.last
|
32
|
+
end
|
33
|
+
|
34
|
+
def missing_end_index?
|
35
|
+
return false if first_line && ranges.size == 1
|
36
|
+
last.end.nil?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -8,50 +8,36 @@ module ThemeCheck
|
|
8
8
|
|
9
9
|
ACTION_DISABLE_CHECKS = :disable
|
10
10
|
ACTION_ENABLE_CHECKS = :enable
|
11
|
-
ACTION_UNRELATED_COMMENT = :unrelated
|
12
11
|
|
13
12
|
def initialize
|
14
|
-
@
|
15
|
-
@all_disabled = false
|
16
|
-
@full_document_disabled = false
|
13
|
+
@disabled_checks = {}
|
17
14
|
end
|
18
15
|
|
19
16
|
def update(node)
|
20
17
|
text = comment_text(node)
|
21
|
-
|
22
18
|
if start_disabling?(text)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@full_document_disabled = true
|
19
|
+
checks_from_text(text).each do |check_name|
|
20
|
+
@disabled_checks[check_name] ||= DisabledCheck.new(check_name)
|
21
|
+
@disabled_checks[check_name].start_index = node.start_index
|
22
|
+
@disabled_checks[check_name].first_line = true if node.line_number == 1
|
28
23
|
end
|
29
24
|
elsif stop_disabling?(text)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
25
|
+
checks_from_text(text).each do |check_name|
|
26
|
+
next unless @disabled_checks.key?(check_name)
|
27
|
+
@disabled_checks[check_name].end_index = node.end_index
|
28
|
+
end
|
34
29
|
end
|
35
30
|
end
|
36
31
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
# Whether all checks should be disabled
|
43
|
-
def all_disabled?
|
44
|
-
@all_disabled
|
45
|
-
end
|
46
|
-
|
47
|
-
# Get a list of all the individual disabled checks
|
48
|
-
def all
|
49
|
-
@disabled
|
32
|
+
def disabled?(key, index)
|
33
|
+
@disabled_checks[:all]&.disabled?(index) ||
|
34
|
+
@disabled_checks[key]&.disabled?(index)
|
50
35
|
end
|
51
36
|
|
52
|
-
|
53
|
-
|
54
|
-
|
37
|
+
def checks_missing_end_index
|
38
|
+
@disabled_checks.values
|
39
|
+
.select(&:missing_end_index?)
|
40
|
+
.map(&:name)
|
55
41
|
end
|
56
42
|
|
57
43
|
private
|
@@ -69,9 +55,11 @@ module ThemeCheck
|
|
69
55
|
end
|
70
56
|
|
71
57
|
# Return a list of checks from a theme-check-disable comment
|
72
|
-
# Returns [] if all checks are meant to be disabled
|
58
|
+
# Returns [:all] if all checks are meant to be disabled
|
73
59
|
def checks_from_text(text)
|
74
|
-
text.gsub(DISABLE_PREFIX_PATTERN, '').strip.split(',').map(&:strip)
|
60
|
+
checks = text.gsub(DISABLE_PREFIX_PATTERN, '').strip.split(',').map(&:strip)
|
61
|
+
return [:all] if checks.empty?
|
62
|
+
checks
|
75
63
|
end
|
76
64
|
end
|
77
65
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "net/http"
|
3
|
+
|
4
|
+
TIMEOUT_EXCEPTIONS = [
|
5
|
+
Net::ReadTimeout,
|
6
|
+
Net::OpenTimeout,
|
7
|
+
Net::WriteTimeout,
|
8
|
+
Errno::ETIMEDOUT,
|
9
|
+
Timeout::Error,
|
10
|
+
]
|
11
|
+
|
12
|
+
CONNECTION_EXCEPTIONS = [
|
13
|
+
IOError,
|
14
|
+
EOFError,
|
15
|
+
SocketError,
|
16
|
+
Errno::EINVAL,
|
17
|
+
Errno::ECONNRESET,
|
18
|
+
Errno::ECONNABORTED,
|
19
|
+
Errno::EPIPE,
|
20
|
+
Errno::ECONNREFUSED,
|
21
|
+
Errno::EAGAIN,
|
22
|
+
Errno::EHOSTUNREACH,
|
23
|
+
Errno::ENETUNREACH,
|
24
|
+
]
|
25
|
+
|
26
|
+
NET_HTTP_EXCEPTIONS = [
|
27
|
+
Net::HTTPBadResponse,
|
28
|
+
Net::HTTPHeaderSyntaxError,
|
29
|
+
Net::ProtocolError,
|
30
|
+
*TIMEOUT_EXCEPTIONS,
|
31
|
+
*CONNECTION_EXCEPTIONS,
|
32
|
+
]
|
@@ -20,6 +20,10 @@ module ThemeCheck
|
|
20
20
|
@relative_pathname ||= Pathname.new(@relative_path)
|
21
21
|
end
|
22
22
|
|
23
|
+
def source
|
24
|
+
@source ||= @storage.read(@relative_path)
|
25
|
+
end
|
26
|
+
|
23
27
|
def content
|
24
28
|
load!
|
25
29
|
@content
|
@@ -39,7 +43,7 @@ module ThemeCheck
|
|
39
43
|
def load!
|
40
44
|
return if @loaded
|
41
45
|
|
42
|
-
@content = JSON.parse(
|
46
|
+
@content = JSON.parse(source)
|
43
47
|
rescue JSON::ParserError => e
|
44
48
|
@parser_error = e
|
45
49
|
ensure
|
@@ -4,7 +4,6 @@ require_relative "language_server/constants"
|
|
4
4
|
require_relative "language_server/handler"
|
5
5
|
require_relative "language_server/server"
|
6
6
|
require_relative "language_server/tokens"
|
7
|
-
require_relative "language_server/position_helper"
|
8
7
|
require_relative "language_server/completion_helper"
|
9
8
|
require_relative "language_server/completion_provider"
|
10
9
|
require_relative "language_server/completion_engine"
|
@@ -12,7 +12,7 @@ module ThemeCheck
|
|
12
12
|
|
13
13
|
def completions(relative_path, line, col)
|
14
14
|
buffer = @storage.read(relative_path)
|
15
|
-
cursor =
|
15
|
+
cursor = from_row_column_to_index(buffer, line, col)
|
16
16
|
token = find_token(buffer, cursor)
|
17
17
|
return [] if token.nil?
|
18
18
|
|
@@ -4,7 +4,11 @@ module ThemeCheck
|
|
4
4
|
module LanguageServer
|
5
5
|
PARTIAL_RENDER = %r{
|
6
6
|
\{\%-?\s*render\s+'(?<partial>[^']*)'|
|
7
|
-
\{\%-?\s*render\s+"(?<partial>[^"]*)"
|
7
|
+
\{\%-?\s*render\s+"(?<partial>[^"]*)"|
|
8
|
+
|
9
|
+
# in liquid tags the whole line is white space until render
|
10
|
+
^\s*render\s+'(?<partial>[^']*)'|
|
11
|
+
^\s*render\s+"(?<partial>[^"]*)"
|
8
12
|
}mix
|
9
13
|
end
|
10
14
|
end
|
@@ -14,12 +14,12 @@ module ThemeCheck
|
|
14
14
|
buffer = @storage.read(relative_path)
|
15
15
|
return [] unless buffer
|
16
16
|
matches(buffer, PARTIAL_RENDER).map do |match|
|
17
|
-
start_line, start_character =
|
17
|
+
start_line, start_character = from_index_to_row_column(
|
18
18
|
buffer,
|
19
19
|
match.begin(:partial),
|
20
20
|
)
|
21
21
|
|
22
|
-
end_line, end_character =
|
22
|
+
end_line, end_character = from_index_to_row_column(
|
23
23
|
buffer,
|
24
24
|
match.end(:partial)
|
25
25
|
)
|
@@ -23,17 +23,17 @@ module ThemeCheck
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def on_initialize(id, params)
|
26
|
-
@root_path = params["rootPath"]
|
26
|
+
@root_path = path_from_uri(params["rootUri"]) || params["rootPath"]
|
27
|
+
|
28
|
+
# Tell the client we don't support anything if there's no rootPath
|
29
|
+
return send_response(id, { capabilities: {} }) if @root_path.nil?
|
27
30
|
@storage = in_memory_storage(@root_path)
|
28
31
|
@completion_engine = CompletionEngine.new(@storage)
|
29
32
|
@document_link_engine = DocumentLinkEngine.new(@storage)
|
30
33
|
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#responseMessage
|
31
|
-
send_response(
|
32
|
-
|
33
|
-
|
34
|
-
capabilities: CAPABILITIES,
|
35
|
-
}
|
36
|
-
)
|
34
|
+
send_response(id, {
|
35
|
+
capabilities: CAPABILITIES,
|
36
|
+
})
|
37
37
|
end
|
38
38
|
|
39
39
|
def on_exit(_id, _params)
|
@@ -63,20 +63,14 @@ module ThemeCheck
|
|
63
63
|
|
64
64
|
def on_text_document_document_link(id, params)
|
65
65
|
relative_path = relative_path_from_text_document_uri(params)
|
66
|
-
send_response(
|
67
|
-
id: id,
|
68
|
-
result: document_links(relative_path)
|
69
|
-
)
|
66
|
+
send_response(id, document_links(relative_path))
|
70
67
|
end
|
71
68
|
|
72
69
|
def on_text_document_completion(id, params)
|
73
70
|
relative_path = relative_path_from_text_document_uri(params)
|
74
71
|
line = params.dig('position', 'line')
|
75
72
|
col = params.dig('position', 'character')
|
76
|
-
send_response(
|
77
|
-
id: id,
|
78
|
-
result: completions(relative_path, line, col)
|
79
|
-
)
|
73
|
+
send_response(id, completions(relative_path, line, col))
|
80
74
|
end
|
81
75
|
|
82
76
|
private
|
@@ -99,7 +93,11 @@ module ThemeCheck
|
|
99
93
|
end
|
100
94
|
|
101
95
|
def text_document_uri(params)
|
102
|
-
params.dig('textDocument', 'uri')
|
96
|
+
path_from_uri(params.dig('textDocument', 'uri'))
|
97
|
+
end
|
98
|
+
|
99
|
+
def path_from_uri(uri)
|
100
|
+
uri&.sub('file://', '')
|
103
101
|
end
|
104
102
|
|
105
103
|
def relative_path_from_text_document_uri(params)
|
@@ -167,13 +165,10 @@ module ThemeCheck
|
|
167
165
|
|
168
166
|
def send_diagnostic(path, offenses)
|
169
167
|
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
diagnostics: offenses.map { |offense| offense_to_diagnostic(offense) },
|
175
|
-
},
|
176
|
-
)
|
168
|
+
send_notification('textDocument/publishDiagnostics', {
|
169
|
+
uri: "file://#{path}",
|
170
|
+
diagnostics: offenses.map { |offense| offense_to_diagnostic(offense) },
|
171
|
+
})
|
177
172
|
end
|
178
173
|
|
179
174
|
def offense_to_diagnostic(offense)
|
@@ -220,11 +215,24 @@ module ThemeCheck
|
|
220
215
|
}
|
221
216
|
end
|
222
217
|
|
223
|
-
def
|
218
|
+
def send_message(message)
|
224
219
|
message[:jsonrpc] = '2.0'
|
225
220
|
@server.send_response(message)
|
226
221
|
end
|
227
222
|
|
223
|
+
def send_response(id, result = nil, error = nil)
|
224
|
+
message = { id: id }
|
225
|
+
message[:result] = result if result
|
226
|
+
message[:error] = error if error
|
227
|
+
send_message(message)
|
228
|
+
end
|
229
|
+
|
230
|
+
def send_notification(method, params)
|
231
|
+
message = { method: method }
|
232
|
+
message[:params] = params
|
233
|
+
send_message(message)
|
234
|
+
end
|
235
|
+
|
228
236
|
def log(message)
|
229
237
|
@server.log(message)
|
230
238
|
end
|
data/lib/theme_check/node.rb
CHANGED
@@ -125,5 +125,17 @@ module ThemeCheck
|
|
125
125
|
start = template.full_line(line_number).index(markup)
|
126
126
|
[start, start + markup.length - 1]
|
127
127
|
end
|
128
|
+
|
129
|
+
def position
|
130
|
+
@position ||= Position.new(markup, template&.source, line_number)
|
131
|
+
end
|
132
|
+
|
133
|
+
def start_index
|
134
|
+
position.start_index
|
135
|
+
end
|
136
|
+
|
137
|
+
def end_index
|
138
|
+
position.end_index
|
139
|
+
end
|
128
140
|
end
|
129
141
|
end
|
data/lib/theme_check/offense.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ThemeCheck
|
3
|
-
Position = Struct.new(:line, :column)
|
4
|
-
|
5
3
|
class Offense
|
6
4
|
MAX_SOURCE_EXCERPT_SIZE = 120
|
7
5
|
|
@@ -20,6 +18,7 @@ module ThemeCheck
|
|
20
18
|
end
|
21
19
|
|
22
20
|
@node = node
|
21
|
+
@template = nil
|
23
22
|
if node
|
24
23
|
@template = node.template
|
25
24
|
elsif template
|
@@ -40,8 +39,7 @@ module ThemeCheck
|
|
40
39
|
@node.line_number
|
41
40
|
end
|
42
41
|
|
43
|
-
@
|
44
|
-
@end_position = nil
|
42
|
+
@position = Position.new(@markup, @template&.source, @line_number)
|
45
43
|
end
|
46
44
|
|
47
45
|
def source_excerpt
|
@@ -56,20 +54,28 @@ module ThemeCheck
|
|
56
54
|
end
|
57
55
|
end
|
58
56
|
|
57
|
+
def start_index
|
58
|
+
@position.start_index
|
59
|
+
end
|
60
|
+
|
59
61
|
def start_line
|
60
|
-
|
62
|
+
@position.start_row
|
61
63
|
end
|
62
64
|
|
63
65
|
def start_column
|
64
|
-
|
66
|
+
@position.start_column
|
67
|
+
end
|
68
|
+
|
69
|
+
def end_index
|
70
|
+
@position.end_index
|
65
71
|
end
|
66
72
|
|
67
73
|
def end_line
|
68
|
-
|
74
|
+
@position.end_row
|
69
75
|
end
|
70
76
|
|
71
77
|
def end_column
|
72
|
-
|
78
|
+
@position.end_column
|
73
79
|
end
|
74
80
|
|
75
81
|
def code_name
|
@@ -115,45 +121,5 @@ module ThemeCheck
|
|
115
121
|
message
|
116
122
|
end
|
117
123
|
end
|
118
|
-
|
119
|
-
private
|
120
|
-
|
121
|
-
def full_line(line)
|
122
|
-
# Liquid::Template is 1-indexed.
|
123
|
-
template.full_line(line + 1)
|
124
|
-
end
|
125
|
-
|
126
|
-
def lines_of_content
|
127
|
-
@lines ||= markup.lines.map { |x| x.sub(/\n$/, '') }
|
128
|
-
end
|
129
|
-
|
130
|
-
# 0-indexed, inclusive
|
131
|
-
def start_position
|
132
|
-
return @start_position if @start_position
|
133
|
-
return @start_position = Position.new(0, 0) unless line_number && markup
|
134
|
-
|
135
|
-
position = Position.new
|
136
|
-
position.line = line_number - 1
|
137
|
-
position.column = full_line(position.line).index(lines_of_content.first) || 0
|
138
|
-
|
139
|
-
@start_position = position
|
140
|
-
end
|
141
|
-
|
142
|
-
# 0-indexed, exclusive. It's the line + col that are exclusive.
|
143
|
-
# This is why it doesn't make sense to calculate them separately.
|
144
|
-
def end_position
|
145
|
-
return @end_position if @end_position
|
146
|
-
return @end_position = Position.new(0, 0) unless line_number && markup
|
147
|
-
|
148
|
-
position = Position.new
|
149
|
-
position.line = start_line + lines_of_content.size - 1
|
150
|
-
position.column = if start_line == position.line
|
151
|
-
start_column + markup.size
|
152
|
-
else
|
153
|
-
lines_of_content.last.size
|
154
|
-
end
|
155
|
-
|
156
|
-
@end_position = position
|
157
|
-
end
|
158
124
|
end
|
159
125
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
class Position
|
5
|
+
include PositionHelper
|
6
|
+
|
7
|
+
def initialize(needle, contents, line_number_1_indexed)
|
8
|
+
@needle = needle
|
9
|
+
@contents = contents
|
10
|
+
@line_number_1_indexed = line_number_1_indexed
|
11
|
+
@start_row_column = nil
|
12
|
+
@end_row_column = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def start_line_index
|
16
|
+
from_row_column_to_index(contents, line_number, 0)
|
17
|
+
end
|
18
|
+
|
19
|
+
# 0-indexed, inclusive
|
20
|
+
def start_index
|
21
|
+
contents.index(needle, start_line_index) || start_line_index
|
22
|
+
end
|
23
|
+
|
24
|
+
# 0-indexed, exclusive
|
25
|
+
def end_index
|
26
|
+
start_index + needle.size
|
27
|
+
end
|
28
|
+
|
29
|
+
def start_row
|
30
|
+
start_row_column[0]
|
31
|
+
end
|
32
|
+
|
33
|
+
def start_column
|
34
|
+
start_row_column[1]
|
35
|
+
end
|
36
|
+
|
37
|
+
def end_row
|
38
|
+
end_row_column[0]
|
39
|
+
end
|
40
|
+
|
41
|
+
def end_column
|
42
|
+
end_row_column[1]
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def contents
|
48
|
+
return '' unless @contents.is_a?(String) && !@contents.empty?
|
49
|
+
@contents
|
50
|
+
end
|
51
|
+
|
52
|
+
def line_number
|
53
|
+
return 0 if @line_number_1_indexed.nil?
|
54
|
+
bounded(0, @line_number_1_indexed - 1, contents.lines.size - 1)
|
55
|
+
end
|
56
|
+
|
57
|
+
def needle
|
58
|
+
if @needle.nil? && !contents.empty? && !@line_number_1_indexed.nil?
|
59
|
+
contents.lines(chomp: true)[line_number] || ''
|
60
|
+
elsif contents.empty? || @needle.nil?
|
61
|
+
''
|
62
|
+
else
|
63
|
+
@needle
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def start_row_column
|
68
|
+
return @start_row_column unless @start_row_column.nil?
|
69
|
+
@start_row_column = from_index_to_row_column(contents, start_index)
|
70
|
+
end
|
71
|
+
|
72
|
+
def end_row_column
|
73
|
+
return @end_row_column unless @end_row_column.nil?
|
74
|
+
@end_row_column = from_index_to_row_column(contents, end_index)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Note: Everything is 0-indexed here.
|
3
|
+
|
4
|
+
module ThemeCheck
|
5
|
+
module PositionHelper
|
6
|
+
def from_row_column_to_index(content, row, col)
|
7
|
+
return 0 unless content.is_a?(String) && !content.empty?
|
8
|
+
return 0 unless row.is_a?(Integer) && col.is_a?(Integer)
|
9
|
+
i = 0
|
10
|
+
result = 0
|
11
|
+
safe_row = bounded(0, row, content.lines.size - 1)
|
12
|
+
lines = content.lines
|
13
|
+
line_size = lines[i].size
|
14
|
+
while i < safe_row
|
15
|
+
result += line_size
|
16
|
+
i += 1
|
17
|
+
line_size = lines[i].size
|
18
|
+
end
|
19
|
+
result += bounded(0, col, line_size - 1)
|
20
|
+
result
|
21
|
+
end
|
22
|
+
|
23
|
+
def from_index_to_row_column(content, index)
|
24
|
+
return [0, 0] unless content.is_a?(String) && !content.empty?
|
25
|
+
return [0, 0] unless index.is_a?(Integer)
|
26
|
+
safe_index = bounded(0, index, content.size - 1)
|
27
|
+
lines = content[0..safe_index].lines
|
28
|
+
row = lines.size - 1
|
29
|
+
col = lines.last.size - 1
|
30
|
+
[row, col]
|
31
|
+
end
|
32
|
+
|
33
|
+
def bounded(a, x, b)
|
34
|
+
[a, [x, b].min].max
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/theme_check/version.rb
CHANGED
data/lib/theme_check/visitor.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ThemeCheck
|
3
3
|
class Visitor
|
4
|
+
attr_reader :checks
|
5
|
+
|
4
6
|
def initialize(checks)
|
5
7
|
@checks = checks
|
6
8
|
end
|
@@ -8,6 +10,7 @@ module ThemeCheck
|
|
8
10
|
def visit_template(template)
|
9
11
|
@disabled_checks = DisabledChecks.new
|
10
12
|
visit(Node.new(template.root, nil, template))
|
13
|
+
remove_disabled_offenses
|
11
14
|
rescue Liquid::Error => exception
|
12
15
|
exception.template_name = template.name
|
13
16
|
call_checks(:on_error, exception)
|
@@ -29,20 +32,16 @@ module ThemeCheck
|
|
29
32
|
@disabled_checks.update(node) if node.comment?
|
30
33
|
end
|
31
34
|
|
32
|
-
def visit_children(node)
|
33
|
-
node.children.each { |child| visit(child) }
|
34
|
-
end
|
35
|
-
|
36
35
|
def call_checks(method, *args)
|
37
36
|
checks.call(method, *args)
|
38
37
|
end
|
39
38
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
39
|
+
def remove_disabled_offenses
|
40
|
+
checks.disableable.each do |check|
|
41
|
+
check.offenses.reject! do |offense|
|
42
|
+
@disabled_checks.disabled?(offense.code_name, offense.start_index)
|
43
|
+
end
|
44
|
+
end
|
46
45
|
end
|
47
46
|
end
|
48
47
|
end
|
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.8.
|
4
|
+
version: 0.8.1
|
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-04-
|
11
|
+
date: 2021-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: liquid
|
@@ -138,7 +138,9 @@ files:
|
|
138
138
|
- lib/theme_check/cli.rb
|
139
139
|
- lib/theme_check/config.rb
|
140
140
|
- lib/theme_check/corrector.rb
|
141
|
+
- lib/theme_check/disabled_check.rb
|
141
142
|
- lib/theme_check/disabled_checks.rb
|
143
|
+
- lib/theme_check/exceptions.rb
|
142
144
|
- lib/theme_check/file_system_storage.rb
|
143
145
|
- lib/theme_check/in_memory_storage.rb
|
144
146
|
- lib/theme_check/json_check.rb
|
@@ -155,7 +157,6 @@ files:
|
|
155
157
|
- lib/theme_check/language_server/constants.rb
|
156
158
|
- lib/theme_check/language_server/document_link_engine.rb
|
157
159
|
- lib/theme_check/language_server/handler.rb
|
158
|
-
- lib/theme_check/language_server/position_helper.rb
|
159
160
|
- lib/theme_check/language_server/protocol.rb
|
160
161
|
- lib/theme_check/language_server/server.rb
|
161
162
|
- lib/theme_check/language_server/tokens.rb
|
@@ -165,6 +166,8 @@ files:
|
|
165
166
|
- lib/theme_check/offense.rb
|
166
167
|
- lib/theme_check/packager.rb
|
167
168
|
- lib/theme_check/parsing_helpers.rb
|
169
|
+
- lib/theme_check/position.rb
|
170
|
+
- lib/theme_check/position_helper.rb
|
168
171
|
- lib/theme_check/printer.rb
|
169
172
|
- lib/theme_check/regex_helpers.rb
|
170
173
|
- lib/theme_check/releaser.rb
|
@@ -1,27 +0,0 @@
|
|
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
|