theme-check 1.9.1 → 1.10.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 +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
|