theme-check 0.3.2 → 0.7.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 +10 -3
- data/.rubocop.yml +12 -3
- data/CHANGELOG.md +42 -0
- data/CONTRIBUTING.md +5 -2
- data/Gemfile +5 -3
- data/LICENSE.md +2 -0
- data/README.md +12 -4
- data/RELEASING.md +10 -3
- data/Rakefile +6 -0
- data/config/default.yml +16 -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/remote_asset.md +82 -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 +4 -0
- data/lib/theme_check/asset_file.rb +34 -0
- data/lib/theme_check/check.rb +20 -10
- 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 +2 -1
- 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 +8 -15
- data/lib/theme_check/checks/remote_asset.rb +98 -0
- 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 +14 -1
- data/lib/theme_check/checks/undefined_object.rb +16 -7
- 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 +2 -1
- 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 +49 -13
- data/lib/theme_check/config.rb +5 -2
- data/lib/theme_check/disabled_checks.rb +2 -2
- data/lib/theme_check/in_memory_storage.rb +13 -8
- 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 +48 -0
- data/lib/theme_check/language_server/handler.rb +105 -10
- 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 +9 -4
- data/lib/theme_check/language_server/tokens.rb +55 -0
- data/lib/theme_check/liquid_check.rb +11 -0
- data/lib/theme_check/node.rb +1 -2
- data/lib/theme_check/offense.rb +52 -15
- data/lib/theme_check/regex_helpers.rb +15 -0
- data/lib/theme_check/releaser.rb +39 -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 +10 -8
- data/lib/theme_check/shopify_liquid/filter.rb +3 -5
- data/lib/theme_check/shopify_liquid/object.rb +2 -6
- data/lib/theme_check/shopify_liquid/tag.rb +14 -0
- data/lib/theme_check/storage.rb +3 -3
- data/lib/theme_check/string_helpers.rb +47 -0
- data/lib/theme_check/tags.rb +1 -2
- data/lib/theme_check/theme.rb +7 -1
- data/lib/theme_check/version.rb +1 -1
- data/theme-check.gemspec +1 -2
- metadata +57 -18
|
@@ -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.start_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.start_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.start_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.start_with?(partial) }
|
|
11
|
+
.map { |tag| tag_to_completion(tag) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def can_complete?(content, cursor)
|
|
15
|
+
content.start_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
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
class DocumentLinkEngine
|
|
6
|
+
include PositionHelper
|
|
7
|
+
include RegexHelpers
|
|
8
|
+
|
|
9
|
+
def initialize(storage)
|
|
10
|
+
@storage = storage
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def document_links(relative_path)
|
|
14
|
+
buffer = @storage.read(relative_path)
|
|
15
|
+
return [] unless buffer
|
|
16
|
+
matches(buffer, PARTIAL_RENDER).map do |match|
|
|
17
|
+
start_line, start_character = from_index_to_line_column(
|
|
18
|
+
buffer,
|
|
19
|
+
match.begin(:partial),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
end_line, end_character = from_index_to_line_column(
|
|
23
|
+
buffer,
|
|
24
|
+
match.end(:partial)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
target: 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
|
+
}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def link(partial)
|
|
44
|
+
"file://#{@storage.path('snippets/' + partial + '.liquid')}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -4,9 +4,14 @@ module ThemeCheck
|
|
|
4
4
|
module LanguageServer
|
|
5
5
|
class Handler
|
|
6
6
|
CAPABILITIES = {
|
|
7
|
+
completionProvider: {
|
|
8
|
+
triggerCharacters: ['.', '{{ ', '{% '],
|
|
9
|
+
context: true,
|
|
10
|
+
},
|
|
11
|
+
documentLinkProvider: true,
|
|
7
12
|
textDocumentSync: {
|
|
8
13
|
openClose: true,
|
|
9
|
-
change:
|
|
14
|
+
change: TextDocumentSyncKind::FULL,
|
|
10
15
|
willSave: false,
|
|
11
16
|
save: true,
|
|
12
17
|
},
|
|
@@ -19,6 +24,9 @@ module ThemeCheck
|
|
|
19
24
|
|
|
20
25
|
def on_initialize(id, params)
|
|
21
26
|
@root_path = params["rootPath"]
|
|
27
|
+
@storage = in_memory_storage(@root_path)
|
|
28
|
+
@completion_engine = CompletionEngine.new(@storage)
|
|
29
|
+
@document_link_engine = DocumentLinkEngine.new(@storage)
|
|
22
30
|
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#responseMessage
|
|
23
31
|
send_response(
|
|
24
32
|
id: id,
|
|
@@ -31,17 +39,88 @@ module ThemeCheck
|
|
|
31
39
|
def on_exit(_id, _params)
|
|
32
40
|
close!
|
|
33
41
|
end
|
|
42
|
+
alias_method :on_shutdown, :on_exit
|
|
43
|
+
|
|
44
|
+
def on_text_document_did_change(_id, params)
|
|
45
|
+
relative_path = relative_path_from_text_document_uri(params)
|
|
46
|
+
@storage.write(relative_path, content_changes_text(params))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def on_text_document_did_close(_id, params)
|
|
50
|
+
relative_path = relative_path_from_text_document_uri(params)
|
|
51
|
+
@storage.write(relative_path, "")
|
|
52
|
+
end
|
|
34
53
|
|
|
35
54
|
def on_text_document_did_open(_id, params)
|
|
36
|
-
|
|
55
|
+
relative_path = relative_path_from_text_document_uri(params)
|
|
56
|
+
@storage.write(relative_path, text_document_text(params))
|
|
57
|
+
analyze_and_send_offenses(text_document_uri(params))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def on_text_document_did_save(_id, params)
|
|
61
|
+
analyze_and_send_offenses(text_document_uri(params))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def on_text_document_document_link(id, params)
|
|
65
|
+
relative_path = relative_path_from_text_document_uri(params)
|
|
66
|
+
send_response(
|
|
67
|
+
id: id,
|
|
68
|
+
result: document_links(relative_path)
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def on_text_document_completion(id, params)
|
|
73
|
+
relative_path = relative_path_from_text_document_uri(params)
|
|
74
|
+
line = params.dig('position', 'line')
|
|
75
|
+
col = params.dig('position', 'character')
|
|
76
|
+
send_response(
|
|
77
|
+
id: id,
|
|
78
|
+
result: completions(relative_path, line, col)
|
|
79
|
+
)
|
|
37
80
|
end
|
|
38
|
-
alias_method :on_text_document_did_save, :on_text_document_did_open
|
|
39
81
|
|
|
40
82
|
private
|
|
41
83
|
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
|
|
84
|
+
def in_memory_storage(root)
|
|
85
|
+
config = config_for_path(root)
|
|
86
|
+
|
|
87
|
+
# Make a real FS to get the files from the snippets folder
|
|
88
|
+
fs = ThemeCheck::FileSystemStorage.new(
|
|
89
|
+
config.root,
|
|
90
|
+
ignored_patterns: config.ignored_patterns
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Turn that into a hash of empty buffers
|
|
94
|
+
files = fs.files
|
|
95
|
+
.map { |fn| [fn, ""] }
|
|
96
|
+
.to_h
|
|
97
|
+
|
|
98
|
+
InMemoryStorage.new(files, config.root)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def text_document_uri(params)
|
|
102
|
+
params.dig('textDocument', 'uri').sub('file://', '')
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def relative_path_from_text_document_uri(params)
|
|
106
|
+
@storage.relative_path(text_document_uri(params))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def text_document_text(params)
|
|
110
|
+
params.dig('textDocument', 'text')
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def content_changes_text(params)
|
|
114
|
+
params.dig('contentChanges', 0, 'text')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def config_for_path(path)
|
|
118
|
+
root = ThemeCheck::Config.find(path) || @root_path
|
|
119
|
+
ThemeCheck::Config.from_path(root)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def analyze_and_send_offenses(absolute_path)
|
|
123
|
+
config = config_for_path(absolute_path)
|
|
45
124
|
storage = ThemeCheck::FileSystemStorage.new(
|
|
46
125
|
config.root,
|
|
47
126
|
ignored_patterns: config.ignored_patterns
|
|
@@ -60,6 +139,14 @@ module ThemeCheck
|
|
|
60
139
|
analyzer.offenses
|
|
61
140
|
end
|
|
62
141
|
|
|
142
|
+
def completions(relative_path, line, col)
|
|
143
|
+
@completion_engine.completions(relative_path, line, col)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def document_links(relative_path)
|
|
147
|
+
@document_link_engine.document_links(relative_path)
|
|
148
|
+
end
|
|
149
|
+
|
|
63
150
|
def send_diagnostics(offenses)
|
|
64
151
|
reported_files = Set.new
|
|
65
152
|
|
|
@@ -83,19 +170,27 @@ module ThemeCheck
|
|
|
83
170
|
send_response(
|
|
84
171
|
method: 'textDocument/publishDiagnostics',
|
|
85
172
|
params: {
|
|
86
|
-
uri: "file
|
|
173
|
+
uri: "file://#{path}",
|
|
87
174
|
diagnostics: offenses.map { |offense| offense_to_diagnostic(offense) },
|
|
88
175
|
},
|
|
89
176
|
)
|
|
90
177
|
end
|
|
91
178
|
|
|
92
179
|
def offense_to_diagnostic(offense)
|
|
93
|
-
{
|
|
180
|
+
diagnostic = {
|
|
181
|
+
code: offense.code_name,
|
|
182
|
+
message: offense.message,
|
|
94
183
|
range: range(offense),
|
|
95
184
|
severity: severity(offense),
|
|
96
|
-
code: offense.code_name,
|
|
97
185
|
source: "theme-check",
|
|
98
|
-
|
|
186
|
+
}
|
|
187
|
+
diagnostic["codeDescription"] = code_description(offense) unless offense.doc.nil?
|
|
188
|
+
diagnostic
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def code_description(offense)
|
|
192
|
+
{
|
|
193
|
+
href: offense.doc,
|
|
99
194
|
}
|
|
100
195
|
end
|
|
101
196
|
|
|
@@ -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
|