theme-check 1.8.0 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +21 -0
  4. data/README.md +10 -0
  5. data/RELEASING.md +13 -0
  6. data/config/default.yml +5 -0
  7. data/data/shopify_liquid/deprecated_filters.yml +4 -0
  8. data/data/shopify_liquid/filters.yml +2 -1
  9. data/docs/checks/schema_json_format.md +76 -0
  10. data/docs/language_server/code-action-command-palette.png +0 -0
  11. data/docs/language_server/code-action-flow.png +0 -0
  12. data/docs/language_server/code-action-keyboard.png +0 -0
  13. data/docs/language_server/code-action-light-bulb.png +0 -0
  14. data/docs/language_server/code-action-problem.png +0 -0
  15. data/docs/language_server/code-action-quickfix.png +0 -0
  16. data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
  17. data/lib/theme_check/checks/asset_size_app_block_css.rb +2 -3
  18. data/lib/theme_check/checks/asset_size_app_block_javascript.rb +2 -3
  19. data/lib/theme_check/checks/asset_url_filters.rb +2 -0
  20. data/lib/theme_check/checks/default_locale.rb +1 -1
  21. data/lib/theme_check/checks/deprecated_filter.rb +79 -4
  22. data/lib/theme_check/checks/deprecated_global_app_block_type.rb +2 -3
  23. data/lib/theme_check/checks/matching_schema_translations.rb +4 -6
  24. data/lib/theme_check/checks/matching_translations.rb +1 -0
  25. data/lib/theme_check/checks/missing_required_template_files.rb +3 -3
  26. data/lib/theme_check/checks/missing_template.rb +1 -1
  27. data/lib/theme_check/checks/pagination_size.rb +2 -3
  28. data/lib/theme_check/checks/remote_asset.rb +5 -0
  29. data/lib/theme_check/checks/required_directories.rb +1 -1
  30. data/lib/theme_check/checks/schema_json_format.rb +29 -0
  31. data/lib/theme_check/checks/space_inside_braces.rb +132 -87
  32. data/lib/theme_check/checks/translation_key_exists.rb +33 -13
  33. data/lib/theme_check/checks/unused_snippet.rb +1 -1
  34. data/lib/theme_check/checks/valid_html_translation.rb +1 -1
  35. data/lib/theme_check/checks/valid_schema.rb +2 -2
  36. data/lib/theme_check/corrector.rb +28 -54
  37. data/lib/theme_check/file_system_storage.rb +4 -3
  38. data/lib/theme_check/html_node.rb +99 -6
  39. data/lib/theme_check/html_visitor.rb +1 -32
  40. data/lib/theme_check/in_memory_storage.rb +9 -0
  41. data/lib/theme_check/json_helpers.rb +14 -0
  42. data/lib/theme_check/language_server/bridge.rb +1 -1
  43. data/lib/theme_check/language_server/client_capabilities.rb +27 -0
  44. data/lib/theme_check/language_server/code_action_engine.rb +32 -0
  45. data/lib/theme_check/language_server/code_action_provider.rb +42 -0
  46. data/lib/theme_check/language_server/code_action_providers/quickfix_code_action_provider.rb +83 -0
  47. data/lib/theme_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +40 -0
  48. data/lib/theme_check/language_server/configuration.rb +69 -0
  49. data/lib/theme_check/language_server/diagnostic.rb +124 -0
  50. data/lib/theme_check/language_server/diagnostics_engine.rb +15 -60
  51. data/lib/theme_check/language_server/diagnostics_manager.rb +136 -0
  52. data/lib/theme_check/language_server/document_change_corrector.rb +267 -0
  53. data/lib/theme_check/language_server/document_link_provider.rb +6 -6
  54. data/lib/theme_check/language_server/execute_command_engine.rb +19 -0
  55. data/lib/theme_check/language_server/execute_command_provider.rb +30 -0
  56. data/lib/theme_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
  57. data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +22 -0
  58. data/lib/theme_check/language_server/handler.rb +79 -28
  59. data/lib/theme_check/language_server/io_messenger.rb +9 -1
  60. data/lib/theme_check/language_server/server.rb +8 -7
  61. data/lib/theme_check/language_server/uri_helper.rb +1 -0
  62. data/lib/theme_check/language_server/versioned_in_memory_storage.rb +69 -0
  63. data/lib/theme_check/language_server.rb +23 -5
  64. data/lib/theme_check/liquid_node.rb +249 -39
  65. data/lib/theme_check/locale_diff.rb +16 -4
  66. data/lib/theme_check/node.rb +16 -0
  67. data/lib/theme_check/offense.rb +27 -23
  68. data/lib/theme_check/regex_helpers.rb +1 -1
  69. data/lib/theme_check/schema_helper.rb +70 -0
  70. data/lib/theme_check/storage.rb +4 -0
  71. data/lib/theme_check/theme.rb +1 -1
  72. data/lib/theme_check/theme_file.rb +8 -1
  73. data/lib/theme_check/theme_file_rewriter.rb +18 -9
  74. data/lib/theme_check/version.rb +1 -1
  75. data/lib/theme_check.rb +7 -2
  76. metadata +26 -3
  77. data/lib/theme_check/language_server/diagnostics_tracker.rb +0 -66
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e8f59cffd194662dc9f66474d70310caeec0a713a3029c92efaefbf605ee69bc
4
- data.tar.gz: 7bc65dd34a4b387c13cb0395d3d1cec4d60095a7ba2c3e6fa8fc5fa50bea2df8
3
+ metadata.gz: e8c77dfef7eb85a2d0f29abb2b133af2988b5ecfde756a8a976c704844b522fb
4
+ data.tar.gz: 4acf2c7d7ec6e36282fee4e5fac6d22b28067566579e82bb14bf32bfc6bdb0d3
5
5
  SHA512:
6
- metadata.gz: 2ed214b8d5abb83dd3b9ede347fc52f6ed4cce4ddc42b2e0879c24e54b21c0ff30055b5ac8b3e45c3fa5da9efc5da5343c168c6cd818af863d5a73e4841b796b
7
- data.tar.gz: 8777e953d57ae33ce11c8bd527e061567ddc179dad28c612d1c7e6f80dd8a5d2534e786b86d5f55d944695c10c08f34b3aeb26057700b50b7b6642fbf8007290
6
+ metadata.gz: 78f577b16e6759aaa0f9ce1165c7eda732f15b1a4c2d67c4fb383773020614f9062badd8d71c946925152026f73b5655b1060c1460910ec91faeaac18f6c0c51
7
+ data.tar.gz: 110e8010b5a34cc44365b6c4b231afdb0951c49bf6b22b97b28ac12b51c84eef9b1ce0fb84bb69cf75a49aa4c3fe6a40ad00fd9fdf0ee0403e38db1fe1af2045
data/.gitignore CHANGED
@@ -13,3 +13,4 @@ Gemfile.lock
13
13
  .rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml
14
14
  packaging/builds
15
15
  .vscode
16
+ *.DS_Store
data/CHANGELOG.md CHANGED
@@ -1,4 +1,25 @@
1
1
 
2
+ v1.9.0 / 2021-12-01
3
+ ===================
4
+
5
+ ## Features
6
+
7
+ * Add Corrections as Code Actions in Language Server (quickfix + source.fixAll) (#471)
8
+ * Add SchemaJsonFormat check ([#512](https://github.com/shopify/theme-check/issues/512))
9
+ * Add checkOn{Open,Change,Save} Language Server Configurations ([#511](https://github.com/shopify/theme-check/issues/511))
10
+ * Add support for new filters `image_tag` + `image_url`
11
+ * Add autocorrection to `img_url` to ease migration
12
+ * Deprecate `img_tag`, `img_url`
13
+
14
+ ## Fixes
15
+
16
+ * Fix missing null check in ValidHTMLTranslation ([#517](https://github.com/shopify/theme-check/issues/517))
17
+ * Fix MatchingTranslations check + corrections ([#515](https://github.com/shopify/theme-check/issues/515))
18
+ * Fix RemoteAsset false positive from settings variables ([#516](https://github.com/shopify/theme-check/issues/516))
19
+ * Make SpaceInsideBraces work for missing cases ([#509](https://github.com/shopify/theme-check/issues/509))
20
+ * Fix Liquid in HTML parsing
21
+ * Make TranslationKeyExists also check section translations
22
+
2
23
  v1.8.0 / 2021-11-09
3
24
  ===================
4
25
 
data/README.md CHANGED
@@ -180,3 +180,13 @@ DeprecateLazysizes:
180
180
  enabled: true
181
181
  severity: error
182
182
  ```
183
+
184
+ ## Language Server Configurations
185
+
186
+ - `themeCheck.checkOnOpen` (default: `true`) makes it so theme check runs on file open.
187
+ - `themeCheck.checkOnChange` (default: `true`) makes it so theme check runs on file change.
188
+ - `themeCheck.checkOnSave` (default: `true`) makes it so theme check runs on file save.
189
+
190
+ ⚠️ **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
+ In VS Code, these can be set directly in your `settings.json`.
data/RELEASING.md CHANGED
@@ -24,6 +24,19 @@
24
24
 
25
25
  6. On [Shipit](https://shipit.shopify.io/shopify/theme-check/rubygems), deploy your commit.
26
26
 
27
+ 7. [Create a GitHub release](https://github.com/Shopify/theme-check/releases/new) for the change.
28
+
29
+ ```
30
+ VERSION=v1.X.Y
31
+ git fetch origin
32
+ git fetch origin --tags
33
+ git reset origin $VERSION
34
+ gh release create -t $VERSION
35
+ ```
36
+
37
+ (It's a good idea to copy parts of the CHANGELOG in there)
38
+
39
+
27
40
  ## Homebrew Release Process
28
41
 
29
42
  1. Release `theme-check` on RubyGems by following the steps in the previous section.
data/config/default.yml CHANGED
@@ -191,3 +191,8 @@ PaginationSize:
191
191
 
192
192
  DeprecatedGlobalAppBlockType:
193
193
  enabled: true
194
+
195
+ SchemaJsonFormat:
196
+ enabled: true
197
+ start_level: 0
198
+ indent: ' '
@@ -4,6 +4,10 @@ Liquid::ColorFilter:
4
4
  - color_to_rgb
5
5
  - color_modify
6
6
  Liquid::UrlFilter:
7
+ img_url:
8
+ - image_url
9
+ img_tag:
10
+ - image_tag
7
11
  collection_img_url:
8
12
  - img_url
9
13
  product_img_url:
@@ -63,10 +63,11 @@ I18nFilter:
63
63
  - sentence
64
64
  - time_tag
65
65
  UrlFilter:
66
- - image_url
67
66
  - stylesheet_tag
68
67
  - script_tag
69
68
  - img_tag
69
+ - image_tag
70
+ - image_url
70
71
  - shopify_asset_url
71
72
  - payment_type_img_url
72
73
  - payment_type_svg_tag
@@ -0,0 +1,76 @@
1
+ # Prevent unformatted schema tags (`SchemaJsonFormat`)
2
+
3
+ _Version 1.9.0+_
4
+
5
+ This check exists to ensure the JSON in your schemas is pretty.
6
+
7
+ It exists as a facilitator for its auto-correction. This way you can right-click fix the problem.
8
+
9
+ ## Examples
10
+
11
+ The following examples contain code snippets that either fail or pass this check.
12
+
13
+ ### ✗ Fail
14
+
15
+ ```liquid
16
+ {% schema %}
17
+ {
18
+ "locales": {
19
+ "en": {
20
+ "title": "Welcome", "product": "Product"
21
+ },
22
+ "fr": { "title": "Bienvenue", "produit": "Produit" }
23
+ }
24
+ }
25
+ {% endschema %}
26
+ ```
27
+
28
+ ### ✓ Pass
29
+
30
+ ```liquid
31
+ {% schema %}
32
+ {
33
+ "locales": {
34
+ "en": {
35
+ "title": "Welcome",
36
+ "missing": "Product"
37
+ },
38
+ "fr": {
39
+ "title": "Bienvenue",
40
+ "missing": "TODO"
41
+ }
42
+ }
43
+ }
44
+ {% endschema %}
45
+ ```
46
+
47
+ ## Options
48
+
49
+ The following example contains the default configuration for this check:
50
+
51
+ ```yaml
52
+ SchemaJsonFormat:
53
+ enabled: true
54
+ severity: style
55
+ start_level: 0
56
+ indent: ' '
57
+ ```
58
+
59
+ | Parameter | Description |
60
+ | --- | --- |
61
+ | enabled | Whether the check is enabled. |
62
+ | severity | The [severity](https://shopify.developers/themes/tools/theme-check/configuration#check-severity) of the check. |
63
+ | start_level | The indentation level. If you prefer an indented schema, set this to 1. |
64
+ | indent | The character(s) used for indentation levels. |
65
+
66
+ ## Disabling this check
67
+
68
+ This check is safe to disable. You might want to disable this check if you do not care about the visual appearance of your schema tags.
69
+
70
+ ## Resources
71
+
72
+ - [Rule source][codesource]
73
+ - [Documentation source][docsource]
74
+
75
+ [codesource]: /lib/theme_check/checks/schema_json_format.rb
76
+ [docsource]: /docs/checks/schema_json_format.md
@@ -0,0 +1,197 @@
1
+ # How to correct code with Code Actions, Commands and Workspace Edits
2
+
3
+ The [Language Server Protocol (LSP)][lsp] empowers Language Server developers to offer refactorings, quick fixes and the execution of commands to their users. In Theme Check, we take advantage of this to offer quick fixes and autocorrection to users.
4
+
5
+ This document exists to give you an overview of how that works.
6
+
7
+ ## Overview
8
+
9
+ It goes like this:
10
+
11
+ 1. The Client (VS Code, vim, etc.) asks the Server for [Code Actions](#code-actions) at a character position, and the server responds with available `quickfix` and `source.fixAll` code actions.
12
+ 2. The User _may_ select one of the code actions by interacting with the UI (see [visual examples](#code-actions)). The selection triggers the Client to asks the Server to execute a `correction` [Command](#Commands) for certain [Diagnostics](#diagnostic) on behalf of the user.
13
+ 3. The Server asks the Client to apply a [Workspace Edit](#workspace-edits) to fix the diagnostics.
14
+
15
+ ## Sequence Diagram
16
+
17
+ <img src="code-action-flow.png" width="700">
18
+
19
+ <details>
20
+ <summary>Sequence Diagram Explanation</summary>
21
+
22
+ 1. The Client asks the Server for [code actions](#code-actions) for the current file and character range.
23
+ 2. The Server responds with a list of code actions that can be applied for this location and character range. (`quickfix` and `source.fixAll`)
24
+ 3. ...Wait for user input...
25
+ 4. The User selects one of the code actions
26
+ 5. The Client sends a `workspace/executeCommand` request to the Server for this code action.
27
+ 6. The Server figures out the [workspace edit](#workspace-edits) for the command and arguments.
28
+ 7. The Server sends a `workspace/applyEdit` request for the file modifications that would fix the diagnostics.
29
+ 8. The Client responds with the status of the `applyEdit`.
30
+ 9. The Server cleans up its internal representation of the diagnostics and updates the client with the latest diagnostics.
31
+ 11. The Server responds to the `workspace/executeCommand` request.
32
+
33
+ </details>
34
+
35
+ ## What it's like in the code
36
+
37
+ We handle two Client->Server LSP requests:
38
+
39
+ 1. On `textDocument/codeAction`, our `CodeActionEngine` returns the results obtained from all `CodeActionProvider`.
40
+ 2. On `workspace/executeCommand`, our `ExecuteCommandEngine` dispatches the command and arguments to appropriate `ExecuteCommandProvider`.
41
+
42
+ We define providers:
43
+
44
+ - Two `CodeActionProvider`:
45
+ 1. [`QuickFixCodeActionProvider`](/lib/theme_check/language_server/code_action_providers/quickfix_code_action_provider.rb) - This one provides code actions that fix _one_ diagnostic.
46
+ 2. [`SourceFixAllCodeActionProvider`](/lib/theme_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb) - This one provides code actions that fix _all diagnostics in the current file_.
47
+ - One `ExecuteCommandProvider`:
48
+ 1. [`CorrectionExecuteCommandProvider`](/lib/theme_check/language_server/execute_command_providers/correction_execute_command_provider.rb) - This one takes a list of diagnostics as arguments, turns them into a [WorkspaceEdit](#workspace-edit) and tries to apply them with the server->client `workspace/applyEdit` request.
49
+
50
+ We define a [`DocumentChangeCorrector`](/lib/theme_check/language_server/document_change_corrector.rb) (an LSP analog to our [`Corrector`](/lib/theme_check/corrector.rb) class). This class turns corrector calls into document changes supported by the LSP. For more details, see the [LSP reference on resource changes][lspresourcechange].
51
+
52
+ ## Definitions
53
+
54
+ ### Code Actions
55
+
56
+ A [CodeAction][lspcodeaction] is the Language Server Protocol construct for "stuff you might want to do on the code."
57
+
58
+ Think refactoring, running tests, fixing lint errors, etc.
59
+
60
+ The client figures out which one it can run by executing the client->server `textDocument/codeAction` request.
61
+
62
+ <details>
63
+ <summary>Visual Examples</summary>
64
+
65
+ In VS Code, code actions of different kinds have special meanings and are mapped to multiple places in the UI.
66
+
67
+ * `Quick Fix...` button on diagnostic hover
68
+
69
+ <img src="code-action-quickfix.png" width=500>
70
+
71
+ * Diagnostic right click in the problems tab
72
+
73
+ <img src="code-action-problem.png" width=500>
74
+
75
+ * Command palette `Quick Fix...` and `Fix All`
76
+
77
+ <img src="code-action-command-palette.png" width=500>
78
+
79
+ * Keyboard shortcuts
80
+
81
+ <img src="code-action-keyboard.png" width=700>
82
+ </details>
83
+
84
+ <details>
85
+ <summary>TypeScript Interface</summary>
86
+
87
+ ```ts
88
+ interface CodeAction {
89
+ title: string; // UI string, human readable for the action
90
+ kind?: CodeActionKind; // OPTIONAL, for filtering
91
+ diagnostics?: Diagnostic[]; // The diagnostics that the action SOLVES.
92
+ isPreferred?: boolean; // Are used by auto fix and can be targetted by keybindings.
93
+ // Shown as faded out in the code action menu when the user request a more specific type of code action
94
+ disabled?: {
95
+ reason: string;
96
+ },
97
+
98
+ // if both edit and command are present, edit is run first then command.
99
+ // I think edit is used so the client performs the change, wheras the command
100
+ // would be done by the server
101
+ edit?: WorkspaceEdit; // what this action does ??!!
102
+ command?: Command; // the command that it executes
103
+ data?: any; // sent from the CodeAction to the codeAction/resolve.
104
+ }
105
+
106
+ interface Command {
107
+ title: string; // Title of the command, like `save`
108
+ command: string; // id
109
+ arguments?: any[]
110
+ }
111
+ ```
112
+ </details>
113
+
114
+ ---
115
+
116
+ ### Commands
117
+
118
+ A [Command][lspcommand] is the Language Server Protocol representation of a command that can be executed by the Server.
119
+
120
+ Think of them as data representation of function calls that can be made by the Client.
121
+
122
+ Typically, a command is associated with a Code Action. If the Client wants to perform this Code Action, it will make a Client->Server `workspace/executeCommand` request.
123
+
124
+ <details>
125
+ <summary>TypeScript Interface</summary>
126
+
127
+ ```ts
128
+ interface Command {
129
+ title: string; // Title of the command, like `save`
130
+ command: string; // id
131
+ arguments?: any[]
132
+ }
133
+ ```
134
+ </details>
135
+
136
+ ---
137
+
138
+ ### Workspace Edits
139
+
140
+ A [WorkspaceEdit][lspworkspaceedit] is the Language Server Protocol construct that abstracts code changes as data.
141
+
142
+ Think edit file, create file, delete file but as data.
143
+
144
+ <details>
145
+ <summary>TypeScript Interface</summary>
146
+
147
+ ```ts
148
+ interface WorkspaceEdit {
149
+ changes?: { uri: TextEdit[] ];
150
+ documentChanges: (TextDocumentEdit | CreateFile | RenameFile | DeleteFile|)[];
151
+ changeAnnotations?: { [id: string]: ChangeAnnotation }
152
+ }
153
+
154
+ // see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#resourceChanges
155
+ interface CreateFile {
156
+ kind: 'create';
157
+ uri: DocumentUri;
158
+ options?: CreateFileOptions;
159
+ annotationId?: ChangeAnnotationIdentifier;
160
+ }
161
+
162
+ interface TextEdit {
163
+ range: Range;
164
+ newText: string; // can be empty to delete
165
+ }
166
+
167
+ interface TextDocumentEdit {
168
+ textDocument: OptionalVersionedTextDocumentIdentifier;
169
+ edits: (TextEdit | AnnotatedTextEdit)[]
170
+ }
171
+
172
+ interface OptionalVersionedTextDocumentIdentifier {
173
+ uri: TextDocumentURI;
174
+ // null is for when the file wasn't open from the client
175
+ // integer is for when you know what it was.
176
+ version: integer | null;
177
+ }
178
+ ```
179
+ </details>
180
+
181
+ ---
182
+
183
+ ### Diagnostic
184
+
185
+ A [Diagnostic][lspdiagnostic] is the Language Server Protocol construct for errors, warnings and information bubbles.
186
+
187
+ In our case, diagnostics are Theme Check offenses.
188
+
189
+ They appear in the Problems tab and in the gutter of the editor.
190
+
191
+ [lsp]: https://microsoft.github.io/language-server-protocol/specification
192
+ [lspcodeaction]: https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction
193
+ [lspcommand]: https://microsoft.github.io/language-server-protocol/specification#command
194
+ [lspexecutecommand]: https://microsoft.github.io/language-server-protocol/specification#workspace_executeCommand
195
+ [lspworkspaceedit]: https://microsoft.github.io/language-server-protocol/specification#workspaceEdit
196
+ [lspdiagnostic]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#diagnostic
197
+ [lspresourcechange]: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#resourceChanges
@@ -18,7 +18,8 @@ module ThemeCheck
18
18
  end
19
19
 
20
20
  def on_schema(node)
21
- schema = JSON.parse(node.value.nodelist.join)
21
+ schema = node.inner_json
22
+ return if schema.nil?
22
23
 
23
24
  if (stylesheet = schema["stylesheet"])
24
25
  size = asset_size(stylesheet)
@@ -29,8 +30,6 @@ module ThemeCheck
29
30
  )
30
31
  end
31
32
  end
32
- rescue JSON::ParserError
33
- # Ignored, handled in ValidSchema.
34
33
  end
35
34
 
36
35
  private
@@ -18,7 +18,8 @@ module ThemeCheck
18
18
  end
19
19
 
20
20
  def on_schema(node)
21
- schema = JSON.parse(node.value.nodelist.join)
21
+ schema = node.inner_json
22
+ return if schema.nil?
22
23
 
23
24
  if (javascript = schema["javascript"])
24
25
  size = asset_size(javascript)
@@ -29,8 +30,6 @@ module ThemeCheck
29
30
  )
30
31
  end
31
32
  end
32
- rescue JSON::ParserError
33
- # Ignored, handled in ValidSchema.
34
33
  end
35
34
 
36
35
  private
@@ -8,6 +8,7 @@ module ThemeCheck
8
8
  HTML_FILTERS = [
9
9
  'stylesheet_tag',
10
10
  'script_tag',
11
+ 'image_tag',
11
12
  'img_tag',
12
13
  ]
13
14
  ASSET_URL_FILTERS = [
@@ -16,6 +17,7 @@ module ThemeCheck
16
17
  'file_img_url',
17
18
  'file_url',
18
19
  'global_asset_url',
20
+ 'image_url',
19
21
  'img_url',
20
22
  'payment_type_img_url',
21
23
  'shopify_asset_url',
@@ -8,7 +8,7 @@ module ThemeCheck
8
8
  def on_end
9
9
  return if @theme.default_locale_json
10
10
  add_offense("Default translation file not found (for example locales/en.default.json)") do |corrector|
11
- corrector.create_default_locale_json(@theme)
11
+ corrector.create_file(@theme.storage, "locales/#{theme.default_locale}.default.json", "{}")
12
12
  end
13
13
  end
14
14
  end
@@ -5,18 +5,93 @@ module ThemeCheck
5
5
  category :liquid
6
6
  severity :suggestion
7
7
 
8
+ # The image_url filter does not accept width or height values
9
+ # greater than this numbr.
10
+ MAX_SIZE = 5760
11
+
8
12
  def on_variable(node)
9
13
  used_filters = node.value.filters.map { |name, *_rest| name }
10
14
  used_filters.each do |filter|
11
15
  alternatives = ShopifyLiquid::DeprecatedFilter.alternatives(filter)
12
16
  next unless alternatives
13
17
 
14
- alternatives = alternatives.map { |alt| "`#{alt}`" }
15
- add_offense(
16
- "Deprecated filter `#{filter}`, consider using an alternative: #{alternatives.join(', ')}",
17
- node: node,
18
+ case filter
19
+ when 'img_url'
20
+ add_img_url_offense(node)
21
+ else
22
+ add_default_offense(node, filter, alternatives)
23
+ end
24
+ end
25
+ end
26
+
27
+ def add_default_offense(node, filter, alternatives)
28
+ alternatives = alternatives.map { |alt| "`#{alt}`" }
29
+ add_offense(
30
+ "Deprecated filter `#{filter}`, consider using an alternative: #{alternatives.join(', ')}",
31
+ node: node,
32
+ )
33
+ end
34
+
35
+ def add_img_url_offense(node)
36
+ img_url_filter = node.value.filters.find { |filter| filter[0] == "img_url" }
37
+ _name, img_url_filter_size, img_url_filter_props = img_url_filter
38
+ size_spec = img_url_filter_size&.dig(0)
39
+ scale = img_url_filter_props&.delete("scale")
40
+
41
+ # Can't correct those.
42
+ return add_default_offense(node, 'img_url', ['image_url']) unless
43
+ (size_spec.nil? || size_spec.is_a?(String)) &&
44
+ (scale.nil? || scale.is_a?(Numeric)) &&
45
+ size_spec != 'small'
46
+
47
+ node_source = node.markup
48
+ node_start_index = node.start_index
49
+ match = node_source.match(/img_url[^|]*/)
50
+ img_url_character_range =
51
+ (node_start_index + match.begin(0))...(node_start_index + match.end(0))
52
+
53
+ scale = (scale || 1).to_i
54
+ width, height = (size_spec&.split('x') || [100, 100])
55
+ .map { |v| v.to_i * scale }
56
+
57
+ image_url_filter_params = [
58
+ width && width > 0 ? "width: #{[width, MAX_SIZE].min}" : nil,
59
+ height && height > 0 ? "height: #{[height, MAX_SIZE].min}" : nil,
60
+ ]
61
+ image_url_filter_params += (img_url_filter_props || {})
62
+ .map do |k, v|
63
+ case v
64
+ when Liquid::VariableLookup
65
+ "#{k}: #{v.name}"
66
+ else
67
+ "#{k}: #{v}"
68
+ end
69
+ end
70
+ image_url_filter_params = image_url_filter_params
71
+ .reject(&:nil?)
72
+ .join(", ")
73
+
74
+ trailing_whitespace = match[0].match(/\s*\Z/)[0]
75
+
76
+ image_url_filter = "image_url"
77
+ image_url_filter += ": " + image_url_filter_params unless image_url_filter_params.empty?
78
+ image_url_filter += trailing_whitespace
79
+
80
+ add_offense(
81
+ "Deprecated filter `img_url`, consider using `image_url`",
82
+ node: node,
83
+ markup: match[0]
84
+ ) do |corrector|
85
+ corrector.replace(
86
+ node,
87
+ image_url_filter,
88
+ img_url_character_range,
18
89
  )
19
90
  end
91
+
92
+ # If anything goes wrong, fail gracefully by returning the default offense.
93
+ rescue
94
+ add_default_offense(node, 'img_url', ['image_url'])
20
95
  end
21
96
  end
22
97
  end
@@ -9,7 +9,8 @@ module ThemeCheck
9
9
  VALID_GLOBAL_APP_BLOCK_TYPE = "@app"
10
10
 
11
11
  def on_schema(node)
12
- schema = JSON.parse(node.value.nodelist.join)
12
+ schema = node.inner_json
13
+ return if schema.nil?
13
14
 
14
15
  if block_types_from(schema).include?(INVALID_GLOBAL_APP_BLOCK_TYPE)
15
16
  add_offense(
@@ -17,8 +18,6 @@ module ThemeCheck
17
18
  node: node
18
19
  )
19
20
  end
20
- rescue JSON::ParserError
21
- # Ignored, handled in ValidSchema.
22
21
  end
23
22
 
24
23
  def on_case(node)
@@ -6,7 +6,8 @@ module ThemeCheck
6
6
  doc docs_url(__FILE__)
7
7
 
8
8
  def on_schema(node)
9
- schema = JSON.parse(node.value.nodelist.join)
9
+ schema = node.inner_json
10
+ return if schema.nil?
10
11
  # Get all locales used in the schema
11
12
  used_locales = Set.new([theme.default_locale])
12
13
  visit_object(schema) do |_, locales|
@@ -21,17 +22,14 @@ module ThemeCheck
21
22
  add_offense("#{key} missing translations for #{missing.join(', ')}", node: node) do |corrector|
22
23
  key = key.split(".")
23
24
  missing.each do |language|
24
- corrector.schema_corrector(schema, key + [language], "TODO")
25
+ SchemaHelper.schema_corrector(schema, key + [language], "TODO")
25
26
  end
26
- corrector.replace_block_body(node, schema)
27
+ corrector.replace_inner_json(node, schema)
27
28
  end
28
29
  end
29
30
  end
30
31
 
31
32
  check_locales(schema, node: node)
32
-
33
- rescue JSON::ParserError
34
- # Ignored, handled in ValidSchema.
35
33
  end
36
34
 
37
35
  private
@@ -13,6 +13,7 @@ module ThemeCheck
13
13
  def on_file(file)
14
14
  return unless file.name.start_with?("locales/")
15
15
  return unless file.content.is_a?(Hash)
16
+ return if file.name =~ /\.schema$/
16
17
  return if file.name == @theme.default_locale_json&.name
17
18
 
18
19
  @files << file
@@ -25,15 +25,15 @@ module ThemeCheck
25
25
  def on_end
26
26
  (REQUIRED_LIQUID_FILES - theme.liquid.map(&:name)).each do |file|
27
27
  add_offense("'#{file}.liquid' is missing") do |corrector|
28
- corrector.create(@theme, "#{file}.liquid", "")
28
+ corrector.create_file(@theme.storage, "#{file}.liquid", "")
29
29
  end
30
30
  end
31
31
  (REQUIRED_TEMPLATE_FILES - (theme.liquid + theme.json).map(&:name)).each do |file|
32
32
  add_offense("'#{file}.liquid' or '#{file}.json' is missing") do |corrector|
33
33
  if REQUIRED_LIQUID_TEMPLATE_FILES.include?(file)
34
- corrector.create(@theme, "#{file}.liquid", "")
34
+ corrector.create_file(@theme.storage, "#{file}.liquid", "")
35
35
  else
36
- corrector.create(@theme, "#{file}.json", "")
36
+ corrector.create_file(@theme.storage, "#{file}.json", "")
37
37
  end
38
38
  end
39
39
  end
@@ -35,7 +35,7 @@ module ThemeCheck
35
35
  path = "#{name}.liquid"
36
36
  unless ignore?(path) || theme[name]
37
37
  add_offense("'#{path}' is not found", node: node) do |corrector|
38
- corrector.create(@theme, "#{name}.liquid", "")
38
+ corrector.create_file(@theme.storage, "#{name}.liquid", "")
39
39
  end
40
40
  end
41
41
  end
@@ -27,13 +27,12 @@ module ThemeCheck
27
27
  end
28
28
 
29
29
  def on_schema(node)
30
- schema = JSON.parse(node.value.nodelist.join)
30
+ schema = node.inner_json
31
+ return if schema.nil?
31
32
 
32
33
  if (settings = schema["settings"])
33
34
  @schema_settings = settings
34
35
  end
35
- rescue JSON::ParserError
36
- # Ignored, handled in ValidSchema.
37
36
  end
38
37
 
39
38
  ##