theme-check 1.9.1 → 1.10.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -1
- data/README.md +1 -0
- data/RELEASING.md +4 -4
- data/data/shopify_liquid/filters.yml +68 -51
- data/lib/theme_check/analyzer.rb +31 -19
- data/lib/theme_check/checks/asset_size_css_stylesheet_tag.rb +1 -1
- data/lib/theme_check/checks/asset_url_filters.rb +2 -2
- data/lib/theme_check/checks/content_for_header_modification.rb +1 -1
- data/lib/theme_check/checks/deprecated_filter.rb +2 -2
- data/lib/theme_check/checks/parser_blocking_script_tag.rb +1 -1
- data/lib/theme_check/checks/remote_asset.rb +27 -4
- data/lib/theme_check/checks/translation_key_exists.rb +1 -1
- data/lib/theme_check/checks/undefined_object.rb +2 -0
- data/lib/theme_check/checks/unknown_filter.rb +1 -1
- data/lib/theme_check/checks.rb +1 -2
- data/lib/theme_check/cli.rb +5 -6
- data/lib/theme_check/config.rb +37 -37
- data/lib/theme_check/disabled_checks.rb +6 -0
- data/lib/theme_check/html_node.rb +31 -1
- data/lib/theme_check/language_server/configuration.rb +31 -12
- data/lib/theme_check/language_server/diagnostic.rb +4 -0
- data/lib/theme_check/language_server/diagnostics_engine.rb +11 -6
- data/lib/theme_check/language_server/diagnostics_manager.rb +29 -3
- data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +6 -1
- data/lib/theme_check/language_server/handler.rb +5 -3
- data/lib/theme_check/liquid_node.rb +5 -0
- data/lib/theme_check/position.rb +10 -7
- data/lib/theme_check/position_helper.rb +18 -0
- data/lib/theme_check/theme_file.rb +3 -1
- data/lib/theme_check/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 131d90a928b4130827c6e41ea99ccd852e650417e88baceb66910ff083cf3aaa
|
4
|
+
data.tar.gz: a754d8b221da85eea053ce6f7547c9cad5a3a1e5f7a41562d61922a1278abe5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e2bc230eee811d2d0d8b62a09d11b3b88f2e54939f0c1aeda843d3f8d8455e34d0379aa89a9f388401d4ebb593a00abdfbb57357717fb9167bf7a73fb89943d
|
7
|
+
data.tar.gz: f901b9cd064ee47fa39a6b661524e2d4eafc52b6cda54aceabd4ff371c1d562cfb43967c666642b1ef4d1c1b72cb793ab3cf202bfaee2a5f80aedb1840335155
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,37 @@
|
|
1
1
|
|
2
|
+
v1.10.1 / 2022-02-24
|
3
|
+
====================
|
4
|
+
|
5
|
+
* Revert "Prevent bad render tags from passing theme-check ([#551](https://github.com/shopify/theme-check/issues/551))"
|
6
|
+
|
7
|
+
v1.10.0 / 2022-02-24
|
8
|
+
====================
|
9
|
+
|
10
|
+
## Features
|
11
|
+
|
12
|
+
* Performance Improvements ([#556](https://github.com/shopify/theme-check/issues/556))
|
13
|
+
- Boasts ~125x faster `checkOnChange` checks.
|
14
|
+
|
15
|
+
* New language server configuration: `"themeCheck.onlySingleFileChecks"` ([#556](https://github.com/shopify/theme-check/issues/556))
|
16
|
+
- When `true`, disables whole theme checks in the editor and makes it so only the open files are checked.
|
17
|
+
- When `false` (default), behaves as before.
|
18
|
+
|
19
|
+
## Fixes
|
20
|
+
|
21
|
+
* Do not complain about variables passed to the default filter ([#532](https://github.com/shopify/theme-check/issues/532))
|
22
|
+
* `extend:` should accept absolute and relative paths ([#555](https://github.com/shopify/theme-check/issues/555))
|
23
|
+
* Gracefully handle initialized without config. ([#552](https://github.com/shopify/theme-check/issues/552))
|
24
|
+
* Add missing metafield filters ([#550](https://github.com/shopify/theme-check/issues/550))
|
25
|
+
* Handle media.sources edge case in RemoteAsset ([#549](https://github.com/shopify/theme-check/issues/549))
|
26
|
+
* Disable HTML checks inside Liquid comments ([#548](https://github.com/shopify/theme-check/issues/548))
|
27
|
+
* Prevent bad render tags from passing theme-check ([#551](https://github.com/shopify/theme-check/issues/551))
|
28
|
+
* Handle rogue carriage returns ([#547](https://github.com/shopify/theme-check/issues/547))
|
29
|
+
|
30
|
+
v1.9.2 / 2021-12-13
|
31
|
+
===================
|
32
|
+
|
33
|
+
* Improve HTML parsing errors
|
34
|
+
|
2
35
|
v1.9.1 / 2021-12-09
|
3
36
|
===================
|
4
37
|
|
@@ -9,7 +42,7 @@ v1.9.0 / 2021-12-01
|
|
9
42
|
|
10
43
|
## Features
|
11
44
|
|
12
|
-
* Add Corrections as Code Actions in Language Server (quickfix + source.fixAll) (#471)
|
45
|
+
* Add Corrections as Code Actions in Language Server (quickfix + source.fixAll) ([#471](https://github.com/shopify/theme-check/issues/471))
|
13
46
|
* Add SchemaJsonFormat check ([#512](https://github.com/shopify/theme-check/issues/512))
|
14
47
|
* Add checkOn{Open,Change,Save} Language Server Configurations ([#511](https://github.com/shopify/theme-check/issues/511))
|
15
48
|
* Add support for new filters `image_tag` + `image_url`
|
data/README.md
CHANGED
@@ -186,6 +186,7 @@ DeprecateLazysizes:
|
|
186
186
|
- `themeCheck.checkOnOpen` (default: `true`) makes it so theme check runs on file open.
|
187
187
|
- `themeCheck.checkOnChange` (default: `true`) makes it so theme check runs on file change.
|
188
188
|
- `themeCheck.checkOnSave` (default: `true`) makes it so theme check runs on file save.
|
189
|
+
- `themeCheck.onlySingleFileChecks` (default: `false`) makes it so we only check the opened files and disable "whole theme" checks (e.g. UnusedSnippet, TranslationKeyExists)
|
189
190
|
|
190
191
|
⚠️ **Note:** Quickfixes only work on a freshly checked file. If any of those configurations are turned off, you will need to rerun theme-check in order to apply quickfixes.
|
191
192
|
|
data/RELEASING.md
CHANGED
@@ -73,11 +73,11 @@
|
|
73
73
|
|
74
74
|
1. Release `theme-check` on RubyGems by following the steps in the previous section.
|
75
75
|
|
76
|
-
2. Update the `theme-check` version in [`shopify-cli`](https://github.com/shopify/shopify-cli)'s `
|
76
|
+
2. Update the `theme-check` version in [`shopify-cli`](https://github.com/shopify/shopify-cli)'s `shopify-cli.gemspec` file.
|
77
77
|
|
78
|
-
|
78
|
+
3. Run `bundle update theme-check` and get an updated `Gemfile.lock`
|
79
79
|
|
80
|
-
|
80
|
+
4. Create a branch + a commit on the [`shopify-cli`](https://github.com/Shopify/shopify-cli) repository.
|
81
81
|
|
82
82
|
```bash
|
83
83
|
VERSION=X.X.X
|
@@ -87,7 +87,7 @@
|
|
87
87
|
git commit -m "Bump theme-check version to $VERSION"
|
88
88
|
```
|
89
89
|
|
90
|
-
|
90
|
+
5. Create a pull-request for those changes on the [`shopify-cli`](https://github.com/Shopify/shopify-cli) repository.
|
91
91
|
|
92
92
|
```bash
|
93
93
|
# shortcut if you have `hub` installed
|
@@ -1,80 +1,91 @@
|
|
1
1
|
---
|
2
2
|
Liquid::StandardFilters:
|
3
|
-
-
|
4
|
-
-
|
3
|
+
- times
|
4
|
+
- h
|
5
5
|
- uniq
|
6
|
+
- compact
|
7
|
+
- url_encode
|
6
8
|
- escape
|
9
|
+
- escape_once
|
10
|
+
- url_decode
|
11
|
+
- base64_encode
|
12
|
+
- base64_decode
|
13
|
+
- base64_url_safe_encode
|
14
|
+
- base64_url_safe_decode
|
15
|
+
- truncatewords
|
16
|
+
- strip_html
|
17
|
+
- strip_newlines
|
7
18
|
- replace
|
8
19
|
- remove
|
9
|
-
-
|
20
|
+
- sort_natural
|
21
|
+
- replace_first
|
22
|
+
- remove_first
|
23
|
+
- newline_to_br
|
10
24
|
- upcase
|
11
25
|
- downcase
|
12
26
|
- capitalize
|
13
|
-
-
|
27
|
+
- divided_by
|
28
|
+
- minus
|
29
|
+
- at_most
|
30
|
+
- at_least
|
31
|
+
- size
|
14
32
|
- last
|
15
33
|
- split
|
16
|
-
- size
|
17
34
|
- append
|
18
35
|
- reverse
|
19
|
-
- join
|
20
36
|
- concat
|
21
37
|
- prepend
|
22
|
-
-
|
23
|
-
- url_decode
|
24
|
-
- truncatewords
|
25
|
-
- strip_html
|
38
|
+
- join
|
26
39
|
- strip
|
27
40
|
- lstrip
|
28
41
|
- rstrip
|
29
|
-
- strip_newlines
|
30
|
-
- plus
|
31
|
-
- sort_natural
|
32
|
-
- compact
|
33
|
-
- replace_first
|
34
|
-
- remove_first
|
35
|
-
- newline_to_br
|
36
|
-
- minus
|
37
|
-
- divided_by
|
38
|
-
- at_least
|
39
|
-
- at_most
|
40
42
|
- sort
|
43
|
+
- where
|
44
|
+
- map
|
41
45
|
- default
|
42
|
-
-
|
46
|
+
- first
|
43
47
|
- slice
|
48
|
+
- modulo
|
44
49
|
- abs
|
45
|
-
-
|
46
|
-
- floor
|
50
|
+
- date
|
47
51
|
- ceil
|
48
52
|
- round
|
49
53
|
- truncate
|
50
|
-
-
|
51
|
-
-
|
54
|
+
- plus
|
55
|
+
- floor
|
52
56
|
FormFilter:
|
53
|
-
- payment_button
|
54
57
|
- payment_terms
|
55
58
|
- installments_pricing
|
56
59
|
- default_errors
|
60
|
+
- payment_button
|
57
61
|
DateFilter:
|
58
62
|
- date
|
59
63
|
I18nFilter:
|
60
64
|
- translate
|
65
|
+
- time_tag
|
66
|
+
- sentence
|
61
67
|
- t
|
62
68
|
- date
|
63
|
-
-
|
64
|
-
-
|
69
|
+
- app_block_path_for
|
70
|
+
- dev_shop?
|
71
|
+
- app_extension_path_for
|
72
|
+
- global_block_type?
|
73
|
+
- app_block_path?
|
74
|
+
- app_extension_path?
|
75
|
+
- app_snippet_path?
|
76
|
+
- registration_uuid_from
|
77
|
+
- handle_from
|
65
78
|
UrlFilter:
|
66
79
|
- stylesheet_tag
|
67
80
|
- script_tag
|
68
81
|
- img_tag
|
69
|
-
-
|
70
|
-
- image_url
|
82
|
+
- link_to
|
71
83
|
- shopify_asset_url
|
72
84
|
- payment_type_img_url
|
73
85
|
- payment_type_svg_tag
|
74
86
|
- placeholder_svg_tag
|
75
|
-
- link_to
|
76
|
-
- asset_url
|
77
87
|
- img_url
|
88
|
+
- asset_url
|
78
89
|
- asset_img_url
|
79
90
|
- global_asset_url
|
80
91
|
- file_url
|
@@ -82,10 +93,12 @@ UrlFilter:
|
|
82
93
|
- product_img_url
|
83
94
|
- collection_img_url
|
84
95
|
- article_img_url
|
96
|
+
- image_url
|
85
97
|
- preload_tag
|
86
98
|
JsonFilter:
|
87
99
|
- json
|
88
100
|
ColorFilter:
|
101
|
+
- color_mix
|
89
102
|
- color_contrast
|
90
103
|
- color_difference
|
91
104
|
- brightness_difference
|
@@ -100,40 +113,39 @@ ColorFilter:
|
|
100
113
|
- color_darken
|
101
114
|
- color_saturate
|
102
115
|
- color_desaturate
|
103
|
-
- color_mix
|
104
116
|
MoneyFilter:
|
105
|
-
- money_without_currency
|
106
|
-
- money_without_trailing_zeros
|
107
117
|
- money
|
108
118
|
- money_with_currency
|
119
|
+
- money_without_currency
|
120
|
+
- money_without_trailing_zeros
|
109
121
|
StringFilter:
|
122
|
+
- md5
|
123
|
+
- camelcase
|
124
|
+
- format_code
|
125
|
+
- handle
|
126
|
+
- camelize
|
110
127
|
- handleize
|
111
128
|
- url_param_escape
|
112
129
|
- url_escape
|
113
|
-
- camelcase
|
114
|
-
- camelize
|
115
130
|
- encode_url_component
|
116
|
-
- format_code
|
117
|
-
- md5
|
118
|
-
- handle
|
119
131
|
CollectionFilter:
|
132
|
+
- sort_by
|
120
133
|
- within
|
121
134
|
- link_to_vendor
|
122
|
-
- url_for_vendor
|
123
135
|
- link_to_type
|
124
136
|
- url_for_type
|
125
|
-
-
|
137
|
+
- url_for_vendor
|
126
138
|
TagFilter:
|
127
|
-
- link_to_add_tag
|
128
|
-
- link_to_remove_tag
|
129
139
|
- link_to_tag
|
130
140
|
- highlight_active_tag
|
141
|
+
- link_to_add_tag
|
142
|
+
- link_to_remove_tag
|
131
143
|
CryptoFilter:
|
132
144
|
- hmac_sha1
|
133
|
-
- sha256
|
134
145
|
- hmac_sha256
|
135
146
|
- md5
|
136
147
|
- sha1
|
148
|
+
- sha256
|
137
149
|
CustomerAccountFilter:
|
138
150
|
- customer_login_link
|
139
151
|
- customer_logout_link
|
@@ -147,16 +159,16 @@ CurrencyFormFilter:
|
|
147
159
|
PaginationFilter:
|
148
160
|
- default_pagination
|
149
161
|
WeightFilter:
|
162
|
+
- unit
|
150
163
|
- weight
|
151
164
|
- weight_with_unit
|
152
|
-
- unit
|
153
165
|
TextFilter:
|
154
|
-
-
|
155
|
-
- paragraphize
|
166
|
+
- pluralize
|
156
167
|
- highlight
|
157
168
|
- format_address
|
169
|
+
- paragraphize
|
158
170
|
- excerpt
|
159
|
-
-
|
171
|
+
- pad_spaces
|
160
172
|
FontFilter:
|
161
173
|
- font_face
|
162
174
|
- font_url
|
@@ -164,13 +176,18 @@ FontFilter:
|
|
164
176
|
DistanceFilter:
|
165
177
|
- distance_from
|
166
178
|
MediaFilter:
|
167
|
-
- external_video_url
|
168
179
|
- external_video_tag
|
169
180
|
- video_tag
|
170
181
|
- model_viewer_tag
|
171
182
|
- media_tag
|
183
|
+
- image_tag
|
184
|
+
- external_video_url
|
172
185
|
ThemeFilter:
|
173
186
|
- theme_url
|
174
187
|
- link_to_theme
|
188
|
+
- _online_store_editor_live_setting
|
189
|
+
MetafieldFilter:
|
190
|
+
- metafield_tag
|
191
|
+
- metafield_text
|
175
192
|
DebugFilter:
|
176
193
|
- debug
|
data/lib/theme_check/analyzer.rb
CHANGED
@@ -63,30 +63,36 @@ module ThemeCheck
|
|
63
63
|
finish
|
64
64
|
end
|
65
65
|
|
66
|
-
def analyze_files(files)
|
66
|
+
def analyze_files(files, only_single_file: false)
|
67
67
|
reset
|
68
68
|
|
69
69
|
ThemeCheck.with_liquid_c_disabled do
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
70
|
+
total = files.size
|
71
|
+
offset = 0
|
72
|
+
|
73
|
+
unless only_single_file
|
74
|
+
# Call all checks that run on the whole theme
|
75
|
+
liquid_visitor = LiquidVisitor.new(@liquid_checks.whole_theme, @disabled_checks)
|
76
|
+
html_visitor = HtmlVisitor.new(@html_checks.whole_theme)
|
77
|
+
total += total_file_count
|
78
|
+
offset = total_file_count
|
79
|
+
@theme.liquid.each_with_index do |liquid_file, i|
|
80
|
+
yield(liquid_file.relative_path.to_s, i, total) if block_given?
|
81
|
+
liquid_visitor.visit_liquid_file(liquid_file)
|
82
|
+
html_visitor.visit_liquid_file(liquid_file)
|
83
|
+
end
|
79
84
|
|
80
|
-
|
81
|
-
|
82
|
-
|
85
|
+
@theme.json.each_with_index do |json_file, i|
|
86
|
+
yield(json_file.relative_path.to_s, liquid_file_count + i, total) if block_given?
|
87
|
+
@json_checks.whole_theme.call(:on_file, json_file)
|
88
|
+
end
|
83
89
|
end
|
84
90
|
|
85
91
|
# Call checks that run on a single files, only on specified file
|
86
92
|
liquid_visitor = LiquidVisitor.new(@liquid_checks.single_file, @disabled_checks)
|
87
93
|
html_visitor = HtmlVisitor.new(@html_checks.single_file)
|
88
94
|
files.each_with_index do |theme_file, i|
|
89
|
-
yield(theme_file.relative_path.to_s,
|
95
|
+
yield(theme_file.relative_path.to_s, offset + i, total) if block_given?
|
90
96
|
if theme_file.liquid?
|
91
97
|
liquid_visitor.visit_liquid_file(theme_file)
|
92
98
|
html_visitor.visit_liquid_file(theme_file)
|
@@ -96,7 +102,7 @@ module ThemeCheck
|
|
96
102
|
end
|
97
103
|
end
|
98
104
|
|
99
|
-
finish
|
105
|
+
finish(only_single_file)
|
100
106
|
end
|
101
107
|
|
102
108
|
def uncorrectable_offenses
|
@@ -138,10 +144,16 @@ module ThemeCheck
|
|
138
144
|
end
|
139
145
|
end
|
140
146
|
|
141
|
-
def finish
|
142
|
-
|
143
|
-
|
144
|
-
|
147
|
+
def finish(only_single_file = false)
|
148
|
+
if only_single_file
|
149
|
+
@liquid_checks.single_file.call(:on_end)
|
150
|
+
@html_checks.single_file.call(:on_end)
|
151
|
+
@json_checks.single_file.call(:on_end)
|
152
|
+
else
|
153
|
+
@liquid_checks.call(:on_end)
|
154
|
+
@html_checks.call(:on_end)
|
155
|
+
@json_checks.call(:on_end)
|
156
|
+
end
|
145
157
|
|
146
158
|
@disabled_checks.remove_disabled_offenses(@liquid_checks)
|
147
159
|
@disabled_checks.remove_disabled_offenses(@json_checks)
|
@@ -11,7 +11,7 @@ module ThemeCheck
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def on_variable(node)
|
14
|
-
used_filters = node.
|
14
|
+
used_filters = node.filters.map { |name, *_rest| name }
|
15
15
|
return unless used_filters.include?("stylesheet_tag")
|
16
16
|
file_size = stylesheet_tag_pipeline_to_file_size(node.markup)
|
17
17
|
return if file_size.nil?
|
@@ -36,12 +36,12 @@ module ThemeCheck
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def html_resource_drop?(variable_node)
|
39
|
-
variable_node.
|
39
|
+
variable_node.filters
|
40
40
|
.any? { |(filter_name, *_filter_args)| HTML_FILTERS.include?(filter_name) }
|
41
41
|
end
|
42
42
|
|
43
43
|
def variable_hosted_by_shopify?(variable_node)
|
44
|
-
variable_node.
|
44
|
+
variable_node.filters
|
45
45
|
.any? { |(filter_name, *_filter_args)| ASSET_URL_FILTERS.include?(filter_name) }
|
46
46
|
end
|
47
47
|
end
|
@@ -14,7 +14,7 @@ module ThemeCheck
|
|
14
14
|
return unless node.value.name.is_a?(Liquid::VariableLookup)
|
15
15
|
return unless node.value.name.name == "content_for_header"
|
16
16
|
|
17
|
-
if @in_assign || @in_capture || node.
|
17
|
+
if @in_assign || @in_capture || node.filters.any?
|
18
18
|
add_offense(
|
19
19
|
"Do not rely on the content of `content_for_header`",
|
20
20
|
node: node,
|
@@ -10,7 +10,7 @@ module ThemeCheck
|
|
10
10
|
MAX_SIZE = 5760
|
11
11
|
|
12
12
|
def on_variable(node)
|
13
|
-
used_filters = node.
|
13
|
+
used_filters = node.filters.map { |name, *_rest| name }
|
14
14
|
used_filters.each do |filter|
|
15
15
|
alternatives = ShopifyLiquid::DeprecatedFilter.alternatives(filter)
|
16
16
|
next unless alternatives
|
@@ -33,7 +33,7 @@ module ThemeCheck
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def add_img_url_offense(node)
|
36
|
-
img_url_filter = node.
|
36
|
+
img_url_filter = node.filters.find { |filter| filter[0] == "img_url" }
|
37
37
|
_name, img_url_filter_size, img_url_filter_props = img_url_filter
|
38
38
|
size_spec = img_url_filter_size&.dig(0)
|
39
39
|
scale = img_url_filter_props&.delete("scale")
|
@@ -7,7 +7,7 @@ module ThemeCheck
|
|
7
7
|
doc docs_url(__FILE__)
|
8
8
|
|
9
9
|
def on_variable(node)
|
10
|
-
used_filters = node.
|
10
|
+
used_filters = node.filters.map { |name, *_rest| name }
|
11
11
|
if used_filters.include?("script_tag")
|
12
12
|
add_offense(
|
13
13
|
"The script_tag filter is parser-blocking. Use a script tag with the async or defer " \
|
@@ -22,7 +22,6 @@ module ThemeCheck
|
|
22
22
|
return if resource_url =~ ABSOLUTE_PATH
|
23
23
|
return if resource_url =~ RELATIVE_PATH
|
24
24
|
return if url_hosted_by_shopify?(resource_url)
|
25
|
-
return if url_is_setting_variable?(resource_url)
|
26
25
|
|
27
26
|
# Ignore non-stylesheet link tags
|
28
27
|
rel = node.attributes["rel"]
|
@@ -37,12 +36,36 @@ module ThemeCheck
|
|
37
36
|
private
|
38
37
|
|
39
38
|
def url_hosted_by_shopify?(url)
|
40
|
-
url
|
41
|
-
|
39
|
+
asset_url?(url) || looks_like_hosted_by_shopify?(url) || url_is_setting_variable?(url)
|
40
|
+
end
|
41
|
+
|
42
|
+
# There are some cases where it's kind of hard to tell if it's
|
43
|
+
# hosted by Shopify or not.
|
44
|
+
#
|
45
|
+
# e.g. {{ image }} is hosted on primary domain (not CDN)
|
46
|
+
#
|
47
|
+
# e.g. media.sources are on the CDN
|
48
|
+
# {% for source in media.sources %}
|
49
|
+
# {{ source.url }}
|
50
|
+
# {% endfor %}
|
51
|
+
#
|
52
|
+
# So I'll go 80/20 here and assume that people name their variable
|
53
|
+
# source in `for source in media.sources`.
|
54
|
+
def looks_like_hosted_by_shopify?(url)
|
55
|
+
liquid_variable?(url) && url =~ /source\.url/
|
42
56
|
end
|
43
57
|
|
44
58
|
def url_is_setting_variable?(url)
|
45
|
-
|
59
|
+
liquid_variable?(url) && url =~ /settings\./
|
60
|
+
end
|
61
|
+
|
62
|
+
def asset_url?(url)
|
63
|
+
liquid_variable?(url) &&
|
64
|
+
AssetUrlFilters::ASSET_URL_FILTERS.any? { |filter| url.include?(filter) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def liquid_variable?(url)
|
68
|
+
url.start_with?(Liquid::VariableStart)
|
46
69
|
end
|
47
70
|
end
|
48
71
|
end
|
@@ -16,7 +16,7 @@ module ThemeCheck
|
|
16
16
|
|
17
17
|
def on_variable(node)
|
18
18
|
return unless @theme.default_locale_json&.content&.is_a?(Hash)
|
19
|
-
return unless node.
|
19
|
+
return unless node.filters.any? { |name, _| name == "t" || name == "translate" }
|
20
20
|
|
21
21
|
@nodes[node.theme_file.name] << node
|
22
22
|
end
|
@@ -167,6 +167,8 @@ module ThemeCheck
|
|
167
167
|
node = node.parent
|
168
168
|
node = node.parent if %i(condition variable_lookup).include?(node.type_name)
|
169
169
|
|
170
|
+
next if node.variable? && node.filters.any? { |(filter_name)| filter_name == "default" }
|
171
|
+
|
170
172
|
if render_node
|
171
173
|
add_offense("Missing argument `#{name}`", node: render_node)
|
172
174
|
else
|
@@ -15,7 +15,7 @@ module ThemeCheck
|
|
15
15
|
doc docs_url(__FILE__)
|
16
16
|
|
17
17
|
def on_variable(node)
|
18
|
-
used_filters = node.
|
18
|
+
used_filters = node.filters.map { |name, *_rest| name }
|
19
19
|
undefined_filters = used_filters - ShopifyLiquid::Filter.labels
|
20
20
|
|
21
21
|
undefined_filters.each do |undefined_filter|
|
data/lib/theme_check/checks.rb
CHANGED
data/lib/theme_check/cli.rb
CHANGED
@@ -160,12 +160,11 @@ module ThemeCheck
|
|
160
160
|
def init
|
161
161
|
dotfile_path = ThemeCheck::Config.find(@path)
|
162
162
|
if dotfile_path.nil?
|
163
|
-
config_name =
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
File.write(File.join(@path, ThemeCheck::Config::DOTFILE), File.read(ThemeCheck::Config.bundled_config_path(config_name)))
|
163
|
+
config_name = @config_path || "default"
|
164
|
+
File.write(
|
165
|
+
File.join(@path, ThemeCheck::Config::DOTFILE),
|
166
|
+
File.read(ThemeCheck::Config.bundled_config_path(config_name))
|
167
|
+
)
|
169
168
|
|
170
169
|
puts "Writing new #{ThemeCheck::Config::DOTFILE} to #{@path}"
|
171
170
|
else
|
data/lib/theme_check/config.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module ThemeCheck
|
4
4
|
class Config
|
5
5
|
DOTFILE = '.theme-check.yml'
|
6
|
-
BUNDLED_CONFIGS_DIR = "#{__dir__}/../../config"
|
6
|
+
BUNDLED_CONFIGS_DIR = Pathname.new("#{__dir__}/../../config").realpath
|
7
7
|
BOOLEAN = [true, false]
|
8
8
|
|
9
9
|
attr_reader :root
|
@@ -14,7 +14,7 @@ module ThemeCheck
|
|
14
14
|
|
15
15
|
def from_path(path)
|
16
16
|
if (filename = find(path))
|
17
|
-
new(root: filename.dirname, configuration:
|
17
|
+
new(root: filename.dirname, configuration: load_config(filename))
|
18
18
|
else
|
19
19
|
# No configuration file
|
20
20
|
new(root: path)
|
@@ -39,49 +39,57 @@ module ThemeCheck
|
|
39
39
|
|
40
40
|
def load_file(absolute_path)
|
41
41
|
@last_loaded_config = absolute_path
|
42
|
-
|
42
|
+
# An empty file returns false, so we || {}.
|
43
|
+
YAML.load_file(absolute_path) || {}
|
43
44
|
end
|
44
45
|
|
45
46
|
def bundled_config_path(name)
|
46
|
-
"#{BUNDLED_CONFIGS_DIR}/#{name}"
|
47
|
+
"#{BUNDLED_CONFIGS_DIR}/#{name.to_s.sub(/^:/, '')}.yml"
|
48
|
+
end
|
49
|
+
|
50
|
+
def bundled_config?(name)
|
51
|
+
name.is_a?(Symbol) || (name.is_a?(String) && name[0] == ":")
|
47
52
|
end
|
48
53
|
|
49
54
|
def load_bundled_config(name)
|
50
55
|
load_file(bundled_config_path(name))
|
51
56
|
end
|
52
57
|
|
53
|
-
def load_config(
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
58
|
+
def load_config(name, pwd = Pathname.pwd)
|
59
|
+
return load_bundled_config(name) if bundled_config?(name)
|
60
|
+
path = name.is_a?(Pathname) ? name : Pathname.new(name)
|
61
|
+
path = pwd.join(path) if path.relative?
|
62
|
+
return {} unless path.exist?
|
63
|
+
config = load_file(path)
|
64
|
+
extends = config["extends"] || :default
|
65
|
+
merge_configurations!(load_config(extends, path.realpath.dirname), config)
|
66
|
+
end
|
67
|
+
|
68
|
+
def merge_configurations!(config, other)
|
69
|
+
config.merge(other) do |_key, old_value, new_value|
|
70
|
+
case old_value
|
71
|
+
when Hash
|
72
|
+
merge_configurations!(old_value, new_value)
|
73
|
+
else
|
74
|
+
new_value
|
75
|
+
end
|
60
76
|
end
|
61
77
|
end
|
62
78
|
|
63
79
|
def default
|
64
|
-
|
80
|
+
load_config(":default")
|
65
81
|
end
|
66
82
|
end
|
67
83
|
|
68
84
|
def initialize(root: nil, configuration: nil, should_resolve_requires: true)
|
69
85
|
@configuration = if configuration
|
70
|
-
# TODO: Do we need to handle extends here? What base configuration
|
71
|
-
# should we validate against once Theme App Extensions has its own
|
72
|
-
# checks? :all?
|
73
86
|
validate_configuration(configuration)
|
74
87
|
else
|
75
|
-
|
88
|
+
self.class.default
|
76
89
|
end
|
77
90
|
|
78
|
-
|
79
|
-
|
80
|
-
while extends
|
81
|
-
extended_configuration = self.class.load_config(extends)
|
82
|
-
extends = extended_configuration["extends"]
|
83
|
-
@configuration = merge_configurations!(@configuration, extended_configuration)
|
84
|
-
end
|
91
|
+
extends = @configuration["extends"] || :default
|
92
|
+
@configuration = self.class.merge_configurations!(self.class.load_config(extends), @configuration)
|
85
93
|
|
86
94
|
@root = if root && @configuration.key?("root")
|
87
95
|
Pathname.new(root).join(@configuration["root"])
|
@@ -177,6 +185,12 @@ module ThemeCheck
|
|
177
185
|
else
|
178
186
|
warn("bad configuration type for #{name}: expected a Hash, got #{value.inspect}")
|
179
187
|
end
|
188
|
+
elsif key == "extends"
|
189
|
+
if value.is_a?(Symbol) || value.is_a?(String)
|
190
|
+
valid_configuration[key] = value
|
191
|
+
else
|
192
|
+
warn("bad configuration type for extends: expected a Symbol or a String, got #{value.inspect}")
|
193
|
+
end
|
180
194
|
elsif key == "severity"
|
181
195
|
valid_configuration[key] = value
|
182
196
|
elsif default.nil?
|
@@ -193,20 +207,6 @@ module ThemeCheck
|
|
193
207
|
valid_configuration
|
194
208
|
end
|
195
209
|
|
196
|
-
def merge_configurations!(configuration, extended_configuration)
|
197
|
-
extended_configuration.each do |key, default|
|
198
|
-
value = configuration[key]
|
199
|
-
|
200
|
-
case value
|
201
|
-
when Hash
|
202
|
-
merge_configurations!(value, default)
|
203
|
-
when nil
|
204
|
-
configuration[key] = default
|
205
|
-
end
|
206
|
-
end
|
207
|
-
configuration
|
208
|
-
end
|
209
|
-
|
210
210
|
def resolve_requires
|
211
211
|
self["require"]&.each do |path|
|
212
212
|
require(File.join(@root, path))
|
@@ -30,6 +30,12 @@ module ThemeCheck
|
|
30
30
|
next unless disabled
|
31
31
|
disabled.end_index = node.end_index
|
32
32
|
end
|
33
|
+
else
|
34
|
+
# We want to disable checks inside comments
|
35
|
+
# (e.g. html checks inside {% comment %})
|
36
|
+
disabled = @disabled_checks[[node.theme_file, :all]]
|
37
|
+
disabled.start_index = node.inner_markup_start_index
|
38
|
+
disabled.end_index = node.inner_markup_end_index
|
33
39
|
end
|
34
40
|
end
|
35
41
|
|
@@ -140,9 +140,39 @@ module ThemeCheck
|
|
140
140
|
end
|
141
141
|
|
142
142
|
def parseable_markup
|
143
|
+
return @parseable_source if @value.name == "#document-fragment"
|
144
|
+
return @value.to_str if @value.comment?
|
145
|
+
return @value.content if literal?
|
146
|
+
|
143
147
|
start_index = from_row_column_to_index(@parseable_source, line_number - 1, 0)
|
144
148
|
@parseable_source
|
145
|
-
.match(/<\s*#{
|
149
|
+
.match(/<\s*#{name}[^>]*>/im, start_index)[0]
|
150
|
+
rescue NoMethodError
|
151
|
+
# Don't know what's up with the following issue. Don't think
|
152
|
+
# null check is correct approach. This should give us more info.
|
153
|
+
# https://github.com/Shopify/theme-check/issues/528
|
154
|
+
ThemeCheck.bug(<<~MSG)
|
155
|
+
Can't find a parseable tag of name #{name} inside the parseable HTML.
|
156
|
+
|
157
|
+
Tag name:
|
158
|
+
#{@value.name.inspect}
|
159
|
+
|
160
|
+
File:
|
161
|
+
#{@theme_file.relative_path}
|
162
|
+
|
163
|
+
Line number:
|
164
|
+
#{line_number}
|
165
|
+
|
166
|
+
Excerpt:
|
167
|
+
```
|
168
|
+
#{@theme_file.source.lines[line_number - 1...line_number + 5].join("")}
|
169
|
+
```
|
170
|
+
|
171
|
+
Parseable Excerpt:
|
172
|
+
```
|
173
|
+
#{@parseable_source.lines[line_number - 1...line_number + 5].join("")}
|
174
|
+
```
|
175
|
+
MSG
|
146
176
|
end
|
147
177
|
|
148
178
|
def content
|
@@ -6,6 +6,7 @@ module ThemeCheck
|
|
6
6
|
CHECK_ON_OPEN = :"themeCheck.checkOnOpen"
|
7
7
|
CHECK_ON_SAVE = :"themeCheck.checkOnSave"
|
8
8
|
CHECK_ON_CHANGE = :"themeCheck.checkOnChange"
|
9
|
+
ONLY_SINGLE_FILE = :"themeCheck.onlySingleFileChecks"
|
9
10
|
|
10
11
|
def initialize(bridge, capabilities)
|
11
12
|
@bridge = bridge
|
@@ -13,9 +14,10 @@ module ThemeCheck
|
|
13
14
|
@mutex = Mutex.new
|
14
15
|
@initialized = false
|
15
16
|
@config = {
|
16
|
-
CHECK_ON_OPEN => @capabilities.initialization_option(CHECK_ON_OPEN)
|
17
|
-
CHECK_ON_SAVE => @capabilities.initialization_option(CHECK_ON_SAVE)
|
18
|
-
CHECK_ON_CHANGE => @capabilities.initialization_option(CHECK_ON_CHANGE)
|
17
|
+
CHECK_ON_OPEN => null_coalesce(@capabilities.initialization_option(CHECK_ON_OPEN), true),
|
18
|
+
CHECK_ON_SAVE => null_coalesce(@capabilities.initialization_option(CHECK_ON_SAVE), true),
|
19
|
+
CHECK_ON_CHANGE => null_coalesce(@capabilities.initialization_option(CHECK_ON_CHANGE), true),
|
20
|
+
ONLY_SINGLE_FILE => null_coalesce(@capabilities.initialization_option(ONLY_SINGLE_FILE), false),
|
19
21
|
}
|
20
22
|
end
|
21
23
|
|
@@ -23,17 +25,25 @@ module ThemeCheck
|
|
23
25
|
@mutex.synchronize do
|
24
26
|
return unless @capabilities.supports_workspace_configuration?
|
25
27
|
return if initialized? && !force
|
26
|
-
|
28
|
+
|
29
|
+
keys = [
|
30
|
+
CHECK_ON_OPEN,
|
31
|
+
CHECK_ON_SAVE,
|
32
|
+
CHECK_ON_CHANGE,
|
33
|
+
ONLY_SINGLE_FILE,
|
34
|
+
]
|
35
|
+
|
36
|
+
configs = @bridge.send_request(
|
27
37
|
"workspace/configuration",
|
28
|
-
items:
|
29
|
-
{ section:
|
30
|
-
|
31
|
-
{ section: CHECK_ON_CHANGE },
|
32
|
-
],
|
38
|
+
items: keys.map do |key|
|
39
|
+
{ section: key }
|
40
|
+
end
|
33
41
|
)
|
34
|
-
|
35
|
-
|
36
|
-
|
42
|
+
|
43
|
+
keys.each.with_index do |key, i|
|
44
|
+
@config[key] = configs[i] unless configs[i].nil?
|
45
|
+
end
|
46
|
+
|
37
47
|
@initialized = true
|
38
48
|
end
|
39
49
|
end
|
@@ -64,6 +74,15 @@ module ThemeCheck
|
|
64
74
|
fetch # making sure we have for an initialized value
|
65
75
|
@config[CHECK_ON_CHANGE]
|
66
76
|
end
|
77
|
+
|
78
|
+
def only_single_file?
|
79
|
+
fetch # making sure we have for an initialized value
|
80
|
+
@config[ONLY_SINGLE_FILE]
|
81
|
+
end
|
82
|
+
|
83
|
+
def null_coalesce(value, default)
|
84
|
+
value.nil? ? default : value
|
85
|
+
end
|
67
86
|
end
|
68
87
|
end
|
69
88
|
end
|
@@ -19,14 +19,14 @@ module ThemeCheck
|
|
19
19
|
@diagnostics_manager.first_run?
|
20
20
|
end
|
21
21
|
|
22
|
-
def analyze_and_send_offenses(absolute_path, config, force: false)
|
22
|
+
def analyze_and_send_offenses(absolute_path, config, force: false, only_single_file: false)
|
23
23
|
return unless @diagnostics_lock.try_lock
|
24
24
|
@token += 1
|
25
25
|
@bridge.send_create_work_done_progress_request(@token)
|
26
26
|
theme = ThemeCheck::Theme.new(storage)
|
27
27
|
analyzer = ThemeCheck::Analyzer.new(theme, config.enabled_checks)
|
28
28
|
|
29
|
-
if @diagnostics_manager.first_run? || force
|
29
|
+
if (!only_single_file && @diagnostics_manager.first_run?) || force
|
30
30
|
@bridge.send_work_done_progress_begin(@token, "Full theme check")
|
31
31
|
@bridge.log("Checking #{storage.root}")
|
32
32
|
offenses = nil
|
@@ -37,6 +37,7 @@ module ThemeCheck
|
|
37
37
|
end
|
38
38
|
end_message = "Found #{offenses.size} offenses in #{format("%0.2f", time.real)}s"
|
39
39
|
@bridge.send_work_done_progress_end(@token, end_message)
|
40
|
+
@bridge.log(end_message)
|
40
41
|
send_diagnostics(offenses)
|
41
42
|
else
|
42
43
|
# Analyze selected files
|
@@ -47,14 +48,14 @@ module ThemeCheck
|
|
47
48
|
@bridge.send_work_done_progress_begin(@token, "Partial theme check")
|
48
49
|
offenses = nil
|
49
50
|
time = Benchmark.measure do
|
50
|
-
offenses = analyzer.analyze_files([file]) do |path, i, total|
|
51
|
+
offenses = analyzer.analyze_files([file], only_single_file: only_single_file) do |path, i, total|
|
51
52
|
@bridge.send_work_done_progress_report(@token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
|
52
53
|
end
|
53
54
|
end
|
54
55
|
end_message = "Found #{offenses.size} new offenses in #{format("%0.2f", time.real)}s"
|
55
56
|
@bridge.send_work_done_progress_end(@token, end_message)
|
56
57
|
@bridge.log(end_message)
|
57
|
-
send_diagnostics(offenses, [relative_path])
|
58
|
+
send_diagnostics(offenses, [relative_path], only_single_file: only_single_file)
|
58
59
|
end
|
59
60
|
end
|
60
61
|
@diagnostics_lock.unlock
|
@@ -62,8 +63,12 @@ module ThemeCheck
|
|
62
63
|
|
63
64
|
private
|
64
65
|
|
65
|
-
def send_diagnostics(offenses, analyzed_files = nil)
|
66
|
-
@diagnostics_manager.build_diagnostics(
|
66
|
+
def send_diagnostics(offenses, analyzed_files = nil, only_single_file: false)
|
67
|
+
@diagnostics_manager.build_diagnostics(
|
68
|
+
offenses,
|
69
|
+
analyzed_files: analyzed_files,
|
70
|
+
only_single_file: only_single_file
|
71
|
+
).each do |relative_path, diagnostics|
|
67
72
|
send_diagnostic(relative_path, diagnostics)
|
68
73
|
end
|
69
74
|
end
|
@@ -27,7 +27,7 @@ module ThemeCheck
|
|
27
27
|
@mutex.synchronize { @latest_diagnostics[relative_path] || [] }
|
28
28
|
end
|
29
29
|
|
30
|
-
def build_diagnostics(offenses, analyzed_files: nil)
|
30
|
+
def build_diagnostics(offenses, analyzed_files: nil, only_single_file: false)
|
31
31
|
@mutex.synchronize do
|
32
32
|
full_check = analyzed_files.nil?
|
33
33
|
analyzed_paths = analyzed_files.map { |path| Pathname.new(path) } unless full_check
|
@@ -46,10 +46,23 @@ module ThemeCheck
|
|
46
46
|
current_paths = paths(current_diagnostics)
|
47
47
|
|
48
48
|
diagnostics_update = (current_paths + previous_paths).map do |path|
|
49
|
+
# When doing single file checks, we keep the whole theme old
|
50
|
+
# ones and accept the new single ones
|
51
|
+
if only_single_file && analyzed_paths.include?(path)
|
52
|
+
single_file_diagnostics = current_diagnostics[path] || []
|
53
|
+
whole_theme_diagnostics = whole_theme_diagnostics(path) || []
|
54
|
+
[path, single_file_diagnostics + whole_theme_diagnostics]
|
55
|
+
|
56
|
+
# If doing single file checks that are not in the
|
57
|
+
# analyzed_paths array then we just keep the old
|
58
|
+
# diagnostics
|
59
|
+
elsif only_single_file
|
60
|
+
[path, previous_diagnostics(path) || []]
|
61
|
+
|
49
62
|
# When doing a full_check, we either send the current
|
50
63
|
# diagnostics or an empty array to clear the diagnostics
|
51
64
|
# for that file.
|
52
|
-
|
65
|
+
elsif full_check
|
53
66
|
[path, current_diagnostics[path] || []]
|
54
67
|
|
55
68
|
# When doing a partial check, the single file diagnostics
|
@@ -63,9 +76,14 @@ module ThemeCheck
|
|
63
76
|
end
|
64
77
|
end.to_h
|
65
78
|
|
66
|
-
@latest_diagnostics = diagnostics_update
|
79
|
+
@latest_diagnostics = diagnostics_update
|
80
|
+
.reject { |_, v| v.empty? }
|
81
|
+
|
67
82
|
@first_run = false
|
83
|
+
|
84
|
+
# Only send updates for the current file when running with only_single_file
|
68
85
|
diagnostics_update
|
86
|
+
.reject { |p, _| only_single_file && !analyzed_paths.include?(p) }
|
69
87
|
end
|
70
88
|
end
|
71
89
|
|
@@ -131,6 +149,14 @@ module ThemeCheck
|
|
131
149
|
def single_file_diagnostics(relative_path)
|
132
150
|
@latest_diagnostics[relative_path]&.select(&:single_file?) || []
|
133
151
|
end
|
152
|
+
|
153
|
+
def whole_theme_diagnostics(relative_path)
|
154
|
+
@latest_diagnostics[relative_path]&.select(&:whole_theme?) || []
|
155
|
+
end
|
156
|
+
|
157
|
+
def previous_diagnostics(relative_path)
|
158
|
+
@latest_diagnostics[relative_path]
|
159
|
+
end
|
134
160
|
end
|
135
161
|
end
|
136
162
|
end
|
@@ -14,7 +14,12 @@ module ThemeCheck
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def execute(_args)
|
17
|
-
@diagnostics_engine.analyze_and_send_offenses(
|
17
|
+
@diagnostics_engine.analyze_and_send_offenses(
|
18
|
+
@root_path,
|
19
|
+
@root_config,
|
20
|
+
only_single_file: false,
|
21
|
+
force: true
|
22
|
+
)
|
18
23
|
nil
|
19
24
|
end
|
20
25
|
end
|
@@ -64,6 +64,7 @@ module ThemeCheck
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def on_initialized(_id, _params)
|
67
|
+
return unless @configuration
|
67
68
|
@configuration.fetch
|
68
69
|
@configuration.register_did_change_capability
|
69
70
|
end
|
@@ -85,7 +86,7 @@ module ThemeCheck
|
|
85
86
|
def on_text_document_did_change(_id, params)
|
86
87
|
relative_path = relative_path_from_text_document_uri(params)
|
87
88
|
@storage.write(relative_path, content_changes_text(params), text_document_version(params))
|
88
|
-
analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_change?
|
89
|
+
analyze_and_send_offenses(text_document_uri(params), only_single_file: true) if @configuration.check_on_change?
|
89
90
|
end
|
90
91
|
|
91
92
|
def on_text_document_did_close(_id, params)
|
@@ -189,10 +190,11 @@ module ThemeCheck
|
|
189
190
|
ThemeCheck::Config.from_path(root)
|
190
191
|
end
|
191
192
|
|
192
|
-
def analyze_and_send_offenses(absolute_path)
|
193
|
+
def analyze_and_send_offenses(absolute_path, only_single_file: nil)
|
193
194
|
@diagnostics_engine.analyze_and_send_offenses(
|
194
195
|
absolute_path,
|
195
|
-
config_for_path(absolute_path)
|
196
|
+
config_for_path(absolute_path),
|
197
|
+
only_single_file: only_single_file.nil? ? @configuration.only_single_file? : only_single_file
|
196
198
|
)
|
197
199
|
end
|
198
200
|
|
@@ -184,6 +184,11 @@ module ThemeCheck
|
|
184
184
|
@type_name ||= StringHelpers.underscore(StringHelpers.demodulize(@value.class.name)).to_sym
|
185
185
|
end
|
186
186
|
|
187
|
+
def filters
|
188
|
+
raise TypeError, "Attempting to lookup filters of #{type_name}. Only variables have filters." unless variable?
|
189
|
+
@value.filters
|
190
|
+
end
|
191
|
+
|
187
192
|
def source
|
188
193
|
theme_file&.source
|
189
194
|
end
|
data/lib/theme_check/position.rb
CHANGED
@@ -4,6 +4,8 @@ module ThemeCheck
|
|
4
4
|
class Position
|
5
5
|
include PositionHelper
|
6
6
|
|
7
|
+
attr_reader :contents
|
8
|
+
|
7
9
|
def initialize(
|
8
10
|
needle_arg,
|
9
11
|
contents_arg,
|
@@ -12,7 +14,13 @@ module ThemeCheck
|
|
12
14
|
node_markup_offset: 0 # the index of markup inside the node_markup
|
13
15
|
)
|
14
16
|
@needle = needle_arg
|
15
|
-
|
17
|
+
|
18
|
+
@contents = if contents_arg&.is_a?(String) && !contents_arg.empty?
|
19
|
+
contents_arg
|
20
|
+
else
|
21
|
+
''
|
22
|
+
end
|
23
|
+
|
16
24
|
@line_number_1_indexed = line_number_1_indexed
|
17
25
|
@node_markup_offset = node_markup_offset
|
18
26
|
@node_markup = node_markup
|
@@ -77,18 +85,13 @@ module ThemeCheck
|
|
77
85
|
node_markup_start + @node_markup_offset
|
78
86
|
end
|
79
87
|
|
80
|
-
def contents
|
81
|
-
return '' unless @contents.is_a?(String) && !@contents.empty?
|
82
|
-
@contents
|
83
|
-
end
|
84
|
-
|
85
88
|
def line_number
|
86
89
|
return 0 if @line_number_1_indexed.nil?
|
87
90
|
bounded(0, @line_number_1_indexed - 1, content_line_count)
|
88
91
|
end
|
89
92
|
|
90
93
|
def needle
|
91
|
-
if has_content_and_line_number_but_no_needle?
|
94
|
+
@cached_needle ||= if has_content_and_line_number_but_no_needle?
|
92
95
|
entire_line_needle
|
93
96
|
elsif contents.empty? || @needle.nil?
|
94
97
|
''
|
@@ -3,6 +3,11 @@
|
|
3
3
|
|
4
4
|
module ThemeCheck
|
5
5
|
module PositionHelper
|
6
|
+
# Apparently this old implementation is 2x slower (with benchmark/ips),
|
7
|
+
# so dropping with the following one... It's ugly af but
|
8
|
+
# this shit runs 100K+ times in one theme-check so it gotta go
|
9
|
+
# fast!
|
10
|
+
#
|
6
11
|
def from_row_column_to_index(content, row, col)
|
7
12
|
return 0 unless content.is_a?(String) && !content.empty?
|
8
13
|
return 0 unless row.is_a?(Integer) && col.is_a?(Integer)
|
@@ -15,6 +20,19 @@ module ThemeCheck
|
|
15
20
|
bounded(result, result + col, scanner.pre_match.size)
|
16
21
|
end
|
17
22
|
|
23
|
+
# def from_row_column_to_index(content, row, col)
|
24
|
+
# return 0 unless content.is_a?(String) && !content.empty?
|
25
|
+
# return 0 unless row.is_a?(Integer) && col.is_a?(Integer)
|
26
|
+
# i = 0
|
27
|
+
# safe_row = bounded(0, row, content.count("\n"))
|
28
|
+
# charpos = -1
|
29
|
+
# charpos = content.index("\n", charpos + 1) while i < safe_row && (i += 1) && charpos
|
30
|
+
# result = charpos ? charpos + 1 : 0
|
31
|
+
# next_line = content.index("\n", result)
|
32
|
+
# upper_bound = next_line ? next_line : content.size - 1
|
33
|
+
# bounded(result, result + col, upper_bound)
|
34
|
+
# end
|
35
|
+
|
18
36
|
def from_index_to_row_column(content, index)
|
19
37
|
return [0, 0] unless content.is_a?(String) && !content.empty?
|
20
38
|
return [0, 0] unless index.is_a?(Integer)
|
@@ -45,7 +45,9 @@ module ThemeCheck
|
|
45
45
|
@source = @storage.read(@relative_path)
|
46
46
|
end
|
47
47
|
@eol = @source.include?("\r\n") ? "\r\n" : "\n"
|
48
|
-
@source = @source
|
48
|
+
@source = @source
|
49
|
+
.gsub(/\r(?!\n)/, "\r\n") # fix rogue \r without followup \n with \r\n
|
50
|
+
.gsub("\r\n", "\n")
|
49
51
|
end
|
50
52
|
|
51
53
|
def json?
|
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: 1.
|
4
|
+
version: 1.10.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:
|
11
|
+
date: 2022-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: liquid
|