platformos-check 0.0.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.
Files changed (240) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +2 -0
  3. data/.gitignore +22 -0
  4. data/.rubocop.yml +555 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/CONTRIBUTING.md +209 -0
  8. data/Gemfile +33 -0
  9. data/Guardfile +7 -0
  10. data/LICENSE.md +10 -0
  11. data/Makefile +18 -0
  12. data/README.md +189 -0
  13. data/RELEASING.md +35 -0
  14. data/Rakefile +83 -0
  15. data/TROUBLESHOOTING.md +35 -0
  16. data/bin/platformos-check +29 -0
  17. data/bin/platformos-check-language-server +29 -0
  18. data/config/default.yml +98 -0
  19. data/config/nothing.yml +11 -0
  20. data/data/platformos_liquid/built_in_liquid_objects.json +66 -0
  21. data/data/platformos_liquid/deprecated_filters.json +22 -0
  22. data/data/platformos_liquid/documentation/filters.json +6 -0
  23. data/data/platformos_liquid/documentation/latest.json +2 -0
  24. data/data/platformos_liquid/documentation/objects.json +6 -0
  25. data/data/platformos_liquid/documentation/tags.json +6 -0
  26. data/docker/check.Dockerfile +3 -0
  27. data/docker/lsp.Dockerfile +21 -0
  28. data/docs/api/check.md +15 -0
  29. data/docs/api/html_check.md +46 -0
  30. data/docs/api/liquid_check.md +115 -0
  31. data/docs/api/yaml_check.md +19 -0
  32. data/docs/checks/TEMPLATE.md.erb +52 -0
  33. data/docs/checks/convert_include_to_render.md +48 -0
  34. data/docs/checks/deprecated_filter.md +30 -0
  35. data/docs/checks/html_parsing_error.md +50 -0
  36. data/docs/checks/img_lazy_loading.md +63 -0
  37. data/docs/checks/img_width_and_height.md +79 -0
  38. data/docs/checks/invalid_args.md +56 -0
  39. data/docs/checks/liquid_tag.md +65 -0
  40. data/docs/checks/missing_enable_comment.md +50 -0
  41. data/docs/checks/missing_template.md +65 -0
  42. data/docs/checks/parse_json_format.md +76 -0
  43. data/docs/checks/parser_blocking_javascript.md +97 -0
  44. data/docs/checks/required_layout_object.md +28 -0
  45. data/docs/checks/space_inside_braces.md +89 -0
  46. data/docs/checks/syntax_error.md +49 -0
  47. data/docs/checks/template_length.md +45 -0
  48. data/docs/checks/undefined_object.md +71 -0
  49. data/docs/checks/unknown_filter.md +46 -0
  50. data/docs/checks/unused_assign.md +63 -0
  51. data/docs/checks/unused_partial.md +32 -0
  52. data/docs/checks/valid_yaml.md +48 -0
  53. data/docs/flamegraph.svg +18488 -0
  54. data/docs/language_server/code-action-command-palette.png +0 -0
  55. data/docs/language_server/code-action-flow.png +0 -0
  56. data/docs/language_server/code-action-keyboard.png +0 -0
  57. data/docs/language_server/code-action-light-bulb.png +0 -0
  58. data/docs/language_server/code-action-problem.png +0 -0
  59. data/docs/language_server/code-action-quickfix.png +0 -0
  60. data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
  61. data/docs/preview.png +0 -0
  62. data/exe/platformos-check +6 -0
  63. data/exe/platformos-check-language-server +7 -0
  64. data/lib/platformos_check/analyzer.rb +178 -0
  65. data/lib/platformos_check/api_call_file.rb +9 -0
  66. data/lib/platformos_check/app.rb +138 -0
  67. data/lib/platformos_check/app_file.rb +89 -0
  68. data/lib/platformos_check/app_file_rewriter.rb +79 -0
  69. data/lib/platformos_check/asset_file.rb +34 -0
  70. data/lib/platformos_check/bug.rb +23 -0
  71. data/lib/platformos_check/check.rb +163 -0
  72. data/lib/platformos_check/checks/TEMPLATE.rb.erb +11 -0
  73. data/lib/platformos_check/checks/convert_include_to_render.rb +17 -0
  74. data/lib/platformos_check/checks/deprecated_filter.rb +123 -0
  75. data/lib/platformos_check/checks/html_parsing_error.rb +13 -0
  76. data/lib/platformos_check/checks/img_lazy_loading.rb +18 -0
  77. data/lib/platformos_check/checks/img_width_and_height.rb +46 -0
  78. data/lib/platformos_check/checks/invalid_args.rb +81 -0
  79. data/lib/platformos_check/checks/liquid_tag.rb +47 -0
  80. data/lib/platformos_check/checks/missing_enable_comment.rb +37 -0
  81. data/lib/platformos_check/checks/missing_template.rb +107 -0
  82. data/lib/platformos_check/checks/parse_json_format.rb +31 -0
  83. data/lib/platformos_check/checks/parser_blocking_javascript.rb +17 -0
  84. data/lib/platformos_check/checks/required_layout_object.rb +41 -0
  85. data/lib/platformos_check/checks/space_inside_braces.rb +150 -0
  86. data/lib/platformos_check/checks/syntax_error.rb +31 -0
  87. data/lib/platformos_check/checks/template_length.rb +20 -0
  88. data/lib/platformos_check/checks/undefined_object.rb +206 -0
  89. data/lib/platformos_check/checks/unknown_filter.rb +27 -0
  90. data/lib/platformos_check/checks/unused_assign.rb +101 -0
  91. data/lib/platformos_check/checks/unused_partial.rb +93 -0
  92. data/lib/platformos_check/checks/valid_yaml.rb +16 -0
  93. data/lib/platformos_check/checks.rb +73 -0
  94. data/lib/platformos_check/checks_tracking.rb +9 -0
  95. data/lib/platformos_check/cli.rb +239 -0
  96. data/lib/platformos_check/config.rb +219 -0
  97. data/lib/platformos_check/config_file.rb +6 -0
  98. data/lib/platformos_check/corrector.rb +68 -0
  99. data/lib/platformos_check/disabled_check.rb +44 -0
  100. data/lib/platformos_check/disabled_checks.rb +96 -0
  101. data/lib/platformos_check/email_file.rb +9 -0
  102. data/lib/platformos_check/exceptions.rb +36 -0
  103. data/lib/platformos_check/file_system_storage.rb +93 -0
  104. data/lib/platformos_check/graphql_file.rb +68 -0
  105. data/lib/platformos_check/html_check.rb +8 -0
  106. data/lib/platformos_check/html_node.rb +210 -0
  107. data/lib/platformos_check/html_visitor.rb +36 -0
  108. data/lib/platformos_check/in_memory_storage.rb +68 -0
  109. data/lib/platformos_check/json_file.rb +57 -0
  110. data/lib/platformos_check/json_helper.rb +73 -0
  111. data/lib/platformos_check/json_helpers.rb +24 -0
  112. data/lib/platformos_check/json_printer.rb +32 -0
  113. data/lib/platformos_check/language_server/bridge.rb +167 -0
  114. data/lib/platformos_check/language_server/channel.rb +69 -0
  115. data/lib/platformos_check/language_server/client_capabilities.rb +27 -0
  116. data/lib/platformos_check/language_server/code_action_engine.rb +32 -0
  117. data/lib/platformos_check/language_server/code_action_provider.rb +41 -0
  118. data/lib/platformos_check/language_server/code_action_providers/quickfix_code_action_provider.rb +85 -0
  119. data/lib/platformos_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +41 -0
  120. data/lib/platformos_check/language_server/completion_context.rb +52 -0
  121. data/lib/platformos_check/language_server/completion_engine.rb +32 -0
  122. data/lib/platformos_check/language_server/completion_helper.rb +26 -0
  123. data/lib/platformos_check/language_server/completion_provider.rb +53 -0
  124. data/lib/platformos_check/language_server/completion_providers/assignments_completion_provider.rb +40 -0
  125. data/lib/platformos_check/language_server/completion_providers/filter_completion_provider.rb +102 -0
  126. data/lib/platformos_check/language_server/completion_providers/object_attribute_completion_provider.rb +48 -0
  127. data/lib/platformos_check/language_server/completion_providers/object_completion_provider.rb +38 -0
  128. data/lib/platformos_check/language_server/completion_providers/render_snippet_completion_provider.rb +50 -0
  129. data/lib/platformos_check/language_server/completion_providers/tag_completion_provider.rb +41 -0
  130. data/lib/platformos_check/language_server/configuration.rb +89 -0
  131. data/lib/platformos_check/language_server/constants.rb +29 -0
  132. data/lib/platformos_check/language_server/diagnostic.rb +129 -0
  133. data/lib/platformos_check/language_server/diagnostics_engine.rb +131 -0
  134. data/lib/platformos_check/language_server/diagnostics_manager.rb +184 -0
  135. data/lib/platformos_check/language_server/document_change_corrector.rb +271 -0
  136. data/lib/platformos_check/language_server/document_link_engine.rb +21 -0
  137. data/lib/platformos_check/language_server/document_link_provider.rb +71 -0
  138. data/lib/platformos_check/language_server/document_link_providers/asset_document_link_provider.rb +11 -0
  139. data/lib/platformos_check/language_server/document_link_providers/include_document_link_provider.rb +11 -0
  140. data/lib/platformos_check/language_server/document_link_providers/render_document_link_provider.rb +11 -0
  141. data/lib/platformos_check/language_server/document_link_providers/section_document_link_provider.rb +11 -0
  142. data/lib/platformos_check/language_server/execute_command_engine.rb +19 -0
  143. data/lib/platformos_check/language_server/execute_command_provider.rb +30 -0
  144. data/lib/platformos_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
  145. data/lib/platformos_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +28 -0
  146. data/lib/platformos_check/language_server/handler.rb +310 -0
  147. data/lib/platformos_check/language_server/hover_engine.rb +32 -0
  148. data/lib/platformos_check/language_server/hover_provider.rb +53 -0
  149. data/lib/platformos_check/language_server/hover_providers/filter_hover_provider.rb +113 -0
  150. data/lib/platformos_check/language_server/io_messenger.rb +109 -0
  151. data/lib/platformos_check/language_server/messenger.rb +27 -0
  152. data/lib/platformos_check/language_server/protocol.rb +55 -0
  153. data/lib/platformos_check/language_server/server.rb +188 -0
  154. data/lib/platformos_check/language_server/tokens.rb +55 -0
  155. data/lib/platformos_check/language_server/type_helper.rb +22 -0
  156. data/lib/platformos_check/language_server/uri_helper.rb +39 -0
  157. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/node_handler.rb +87 -0
  158. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/scope.rb +60 -0
  159. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/scope_visitor.rb +44 -0
  160. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder.rb +76 -0
  161. data/lib/platformos_check/language_server/variable_lookup_finder/constants.rb +44 -0
  162. data/lib/platformos_check/language_server/variable_lookup_finder/liquid_fixer.rb +103 -0
  163. data/lib/platformos_check/language_server/variable_lookup_finder/potential_lookup.rb +10 -0
  164. data/lib/platformos_check/language_server/variable_lookup_finder/tolerant_parser.rb +94 -0
  165. data/lib/platformos_check/language_server/variable_lookup_finder.rb +262 -0
  166. data/lib/platformos_check/language_server/variable_lookup_traverser.rb +70 -0
  167. data/lib/platformos_check/language_server/versioned_in_memory_storage.rb +84 -0
  168. data/lib/platformos_check/language_server.rb +71 -0
  169. data/lib/platformos_check/layout_file.rb +15 -0
  170. data/lib/platformos_check/liquid_check.rb +10 -0
  171. data/lib/platformos_check/liquid_file.rb +102 -0
  172. data/lib/platformos_check/liquid_node.rb +570 -0
  173. data/lib/platformos_check/liquid_visitor.rb +39 -0
  174. data/lib/platformos_check/migration_file.rb +9 -0
  175. data/lib/platformos_check/node.rb +53 -0
  176. data/lib/platformos_check/offense.rb +228 -0
  177. data/lib/platformos_check/page_file.rb +9 -0
  178. data/lib/platformos_check/parsing_helpers.rb +21 -0
  179. data/lib/platformos_check/partial_file.rb +15 -0
  180. data/lib/platformos_check/platformos_liquid/deprecated_filter.rb +31 -0
  181. data/lib/platformos_check/platformos_liquid/documentation/markdown_template.rb +51 -0
  182. data/lib/platformos_check/platformos_liquid/documentation.rb +45 -0
  183. data/lib/platformos_check/platformos_liquid/filter.rb +19 -0
  184. data/lib/platformos_check/platformos_liquid/object.rb +15 -0
  185. data/lib/platformos_check/platformos_liquid/source_index/base_entry.rb +66 -0
  186. data/lib/platformos_check/platformos_liquid/source_index/base_state.rb +23 -0
  187. data/lib/platformos_check/platformos_liquid/source_index/filter_entry.rb +26 -0
  188. data/lib/platformos_check/platformos_liquid/source_index/filter_state.rb +11 -0
  189. data/lib/platformos_check/platformos_liquid/source_index/object_entry.rb +20 -0
  190. data/lib/platformos_check/platformos_liquid/source_index/object_state.rb +11 -0
  191. data/lib/platformos_check/platformos_liquid/source_index/parameter_entry.rb +25 -0
  192. data/lib/platformos_check/platformos_liquid/source_index/property_entry.rb +21 -0
  193. data/lib/platformos_check/platformos_liquid/source_index/return_type_entry.rb +41 -0
  194. data/lib/platformos_check/platformos_liquid/source_index/tag_entry.rb +24 -0
  195. data/lib/platformos_check/platformos_liquid/source_index/tag_state.rb +11 -0
  196. data/lib/platformos_check/platformos_liquid/source_index.rb +79 -0
  197. data/lib/platformos_check/platformos_liquid/source_manager.rb +116 -0
  198. data/lib/platformos_check/platformos_liquid/tag.rb +59 -0
  199. data/lib/platformos_check/platformos_liquid.rb +21 -0
  200. data/lib/platformos_check/position.rb +180 -0
  201. data/lib/platformos_check/position_helper.rb +57 -0
  202. data/lib/platformos_check/printer.rb +87 -0
  203. data/lib/platformos_check/regex_helpers.rb +21 -0
  204. data/lib/platformos_check/releaser.rb +43 -0
  205. data/lib/platformos_check/schema_file.rb +6 -0
  206. data/lib/platformos_check/sms_file.rb +9 -0
  207. data/lib/platformos_check/storage.rb +29 -0
  208. data/lib/platformos_check/string_helpers.rb +48 -0
  209. data/lib/platformos_check/tags/background.rb +67 -0
  210. data/lib/platformos_check/tags/base.rb +14 -0
  211. data/lib/platformos_check/tags/base_block.rb +14 -0
  212. data/lib/platformos_check/tags/base_tag_methods.rb +59 -0
  213. data/lib/platformos_check/tags/cache.rb +13 -0
  214. data/lib/platformos_check/tags/export.rb +30 -0
  215. data/lib/platformos_check/tags/form.rb +19 -0
  216. data/lib/platformos_check/tags/function.rb +58 -0
  217. data/lib/platformos_check/tags/graphql.rb +70 -0
  218. data/lib/platformos_check/tags/hash_assign.rb +75 -0
  219. data/lib/platformos_check/tags/log.rb +15 -0
  220. data/lib/platformos_check/tags/parse_json.rb +24 -0
  221. data/lib/platformos_check/tags/print.rb +20 -0
  222. data/lib/platformos_check/tags/redirect_to.rb +15 -0
  223. data/lib/platformos_check/tags/render.rb +60 -0
  224. data/lib/platformos_check/tags/response_headers.rb +20 -0
  225. data/lib/platformos_check/tags/response_status.rb +20 -0
  226. data/lib/platformos_check/tags/return.rb +20 -0
  227. data/lib/platformos_check/tags/session.rb +27 -0
  228. data/lib/platformos_check/tags/sign_in.rb +27 -0
  229. data/lib/platformos_check/tags/spam_protection.rb +15 -0
  230. data/lib/platformos_check/tags/theme_render.rb +58 -0
  231. data/lib/platformos_check/tags/try.rb +59 -0
  232. data/lib/platformos_check/tags.rb +65 -0
  233. data/lib/platformos_check/translation_file.rb +6 -0
  234. data/lib/platformos_check/user_schema_file.rb +6 -0
  235. data/lib/platformos_check/version.rb +5 -0
  236. data/lib/platformos_check/yaml_check.rb +11 -0
  237. data/lib/platformos_check/yaml_file.rb +57 -0
  238. data/lib/platformos_check.rb +106 -0
  239. data/platformos-check.gemspec +34 -0
  240. metadata +329 -0
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ # Reports missing include/render/section liquid file
5
+ class MissingTemplate < LiquidCheck
6
+ class MissingFileCorrection
7
+ def initialize(path:, directory:, extension:)
8
+ @path = path
9
+ @directory = directory
10
+ @extension = extension
11
+ end
12
+
13
+ def full_relative_path
14
+ @full_relative_path ||= module? ? module_path : app_path
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :path, :directory, :extension
20
+
21
+ def app_path
22
+ ['app', directory, "#{path}#{extension}"].join(File::SEPARATOR)
23
+ end
24
+
25
+ def module_path
26
+ path.split(File::SEPARATOR).insert(2, "public#{File::SEPARATOR}#{directory}").join(File::SEPARATOR) + extension
27
+ end
28
+
29
+ def module?
30
+ path.start_with?('modules/')
31
+ end
32
+ end
33
+
34
+ severity :suggestion
35
+ category :liquid
36
+ doc docs_url(__FILE__)
37
+ single_file false
38
+
39
+ def initialize(ignore_missing: [])
40
+ @ignore_missing = ignore_missing
41
+ end
42
+
43
+ def on_include(node)
44
+ partial = node.value.template_name_expr
45
+ return unless partial.is_a?(String)
46
+
47
+ add_missing_partial_offense(partial, node:)
48
+ end
49
+
50
+ def on_render(node)
51
+ partial = node.value.template_name_expr
52
+ return unless partial.is_a?(String)
53
+
54
+ add_missing_partial_offense(partial, node:)
55
+ end
56
+
57
+ def on_function(node)
58
+ partial = node.value.from
59
+ return unless partial.is_a?(String)
60
+
61
+ add_missing_function_offense(partial, node:)
62
+ end
63
+
64
+ def on_graphql(node)
65
+ return if node.value.inline_query
66
+
67
+ graphql_partial = node.value.partial_name
68
+ return unless graphql_partial.is_a?(String)
69
+
70
+ add_missing_graphql_offense(graphql_partial, node:)
71
+ end
72
+
73
+ private
74
+
75
+ def ignore?(path)
76
+ all_ignored_patterns.any? { |pattern| File.fnmatch?(pattern, path) }
77
+ end
78
+
79
+ def all_ignored_patterns
80
+ @all_ignored_patterns ||= @ignore_missing + ignored_patterns
81
+ end
82
+
83
+ def add_missing_partial_offense(path, node:)
84
+ return if ignore?(path) || platformos_app.grouped_files[PartialFile][path]
85
+
86
+ add_offense("'#{path}' is not found", node:) # do |corrector|
87
+ # corrector.create_file(@platformos_app.storage, MissingFileCorrection.new(path:, directory: 'views/partials', extension: '.liquid').full_relative_path, "")
88
+ # end
89
+ end
90
+
91
+ def add_missing_function_offense(path, node:)
92
+ return if ignore?(path) || platformos_app.grouped_files[PartialFile][path]
93
+
94
+ add_offense("'#{path}' is not found", node:) # do |corrector|
95
+ # corrector.create_file(@platformos_app.storage, MissingFileCorrection.new(path:, directory: 'lib', extension: '.liquid').full_relative_path, "")
96
+ # end
97
+ end
98
+
99
+ def add_missing_graphql_offense(path, node:)
100
+ return if ignore?(path) || platformos_app.grouped_files[GraphqlFile][path]
101
+
102
+ add_offense("'#{path}' is not found", node:) # do |corrector|
103
+ # corrector.create_file(@platformos_app.storage, MissingFileCorrection.new(path:, directory: 'graphql', extension: '.graphql').full_relative_path, "")
104
+ # end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ class ParseJsonFormat < LiquidCheck
5
+ severity :style
6
+ category :liquid
7
+ doc docs_url(__FILE__)
8
+
9
+ def initialize(start_level: 0, indent: ' ')
10
+ @pretty_json_opts = {
11
+ indent:,
12
+ start_level:
13
+ }
14
+ end
15
+
16
+ def on_parse_json(node)
17
+ parse_json = node.inner_json
18
+ return if parse_json.nil?
19
+
20
+ pretty_parse_json = pretty_json(parse_json, **@pretty_json_opts)
21
+ return unless pretty_parse_json != node.inner_markup
22
+
23
+ add_offense(
24
+ "JSON formatting could be improved",
25
+ node:
26
+ ) do |corrector|
27
+ corrector.replace_inner_json(node, parse_json, **@pretty_json_opts)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ # Reports errors when trying to use parser-blocking script tags
5
+ class ParserBlockingJavaScript < HtmlCheck
6
+ severity :error
7
+ categories :html, :performance
8
+ doc docs_url(__FILE__)
9
+
10
+ def on_script(node)
11
+ return unless node.attributes["src"]
12
+ return if node.attributes["defer"] || node.attributes["async"] || node.attributes["type"] == "module"
13
+
14
+ add_offense("Missing async or defer attribute on script tag", node:)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ # Reports missing content_for_layout in layouts
5
+ class RequiredLayoutObject < LiquidCheck
6
+ severity :error
7
+ category :liquid
8
+ doc docs_url(__FILE__)
9
+
10
+ def initialize
11
+ @content_for_layout_found = false
12
+ end
13
+
14
+ def on_document(node)
15
+ @layout_platformos_app_node = node if node.app_file.layout?
16
+ end
17
+
18
+ def on_variable(node)
19
+ return unless node.value.name.is_a?(Liquid::VariableLookup)
20
+
21
+ @content_for_layout_found ||= node.value.name.name == "content_for_layout"
22
+ end
23
+
24
+ def after_document(node)
25
+ return unless node.app_file.layout?
26
+
27
+ add_missing_object_offense("content_for_layout", "</body>") unless @content_for_layout_found
28
+ end
29
+
30
+ private
31
+
32
+ def add_missing_object_offense(name, tag)
33
+ add_offense("layout must include {{#{name}}}", node: @layout_platformos_app_node) do
34
+ if @layout_platformos_app_node.source.index(tag)
35
+ @layout_platformos_app_node.source.insert(@layout_platformos_app_node.source.index(tag), " {{ #{name} }}\n ")
36
+ @layout_platformos_app_node.markup = @layout_platformos_app_node.source
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ # Ensure {% ... %} & {{ ... }} have consistent spaces.
5
+ class SpaceInsideBraces < LiquidCheck
6
+ severity :style
7
+ category :liquid
8
+ doc docs_url(__FILE__)
9
+
10
+ def on_node(node)
11
+ return unless node.markup
12
+ return if node.literal?
13
+ return if node.assigned_or_echoed_variable?
14
+
15
+ outside_of_strings(node.markup) do |chunk, chunk_start|
16
+ chunk.scan(/(?<token>[,:|]|==|<>|<=|>=|<|>|!=)(?<offense> +)/) do |_match|
17
+ add_too_many_spaces_after_offense(Regexp.last_match, node, chunk_start)
18
+ end
19
+ chunk.scan(/(?<offense>(?<token>[,:|]|==|<>|<=|>=|<\b|>\b|!=)(\S|\z))/) do |_match|
20
+ add_space_missing_after_offense(Regexp.last_match, node, chunk_start)
21
+ end
22
+ chunk.scan(/(?<offense>\s{2,})(?<token>\||==|<>|<=|>=|<|>|!=)+/) do |_match|
23
+ add_too_many_spaces_before_offense(Regexp.last_match, node, chunk_start) unless Regexp.last_match(:offense).include?("\n")
24
+ end
25
+ chunk.scan(/(\A|\S)(?<offense>(?<token>\||==|<>|<=|>=|<|\b>|!=))/) do |_match|
26
+ add_space_missing_before_offense(Regexp.last_match, node, chunk_start)
27
+ end
28
+ end
29
+ end
30
+
31
+ BlockMarkup = Struct.new(:markup, :node_markup_offset)
32
+
33
+ def on_tag(node)
34
+ return if node.inside_liquid_tag?
35
+
36
+ # Both the start and end tags
37
+ blocks = [
38
+ BlockMarkup.new(node.block_start_markup, node.block_start_start_index - node.start_index),
39
+ BlockMarkup.new(node.block_end_markup, node.block_end_start_index - node.start_index)
40
+ ]
41
+
42
+ blocks.each do |block|
43
+ # Looking at spaces after the start token
44
+ add_space_missing_after_offense(Regexp.last_match, node, block.node_markup_offset) if block.markup =~ /^(?<token>{%-?)(?<offense>[^ \n\t-])/
45
+
46
+ add_too_many_spaces_after_offense(Regexp.last_match, node, block.node_markup_offset) if block.markup =~ /^(?<token>{%-?)(?<offense> {2,})\S/
47
+
48
+ # Looking at spaces before the end token
49
+ add_space_missing_before_offense(Regexp.last_match, node, block.node_markup_offset) if block.markup =~ /(?<offense>[^ \n\t-])(?<token>-?%})$/
50
+
51
+ add_too_many_spaces_before_offense(Regexp.last_match, node, block.node_markup_offset) if block.markup =~ /\S(?<offense> {2,})(?<token>-?%})$/
52
+
53
+ next
54
+ end
55
+ end
56
+
57
+ def on_variable(node)
58
+ return if node.markup.empty?
59
+ return if node.assigned_or_echoed_variable?
60
+
61
+ block_start_offset = node.block_start_start_index - node.start_index
62
+
63
+ # Looking at spaces after the start token
64
+ add_space_missing_after_offense(Regexp.last_match, node, block_start_offset) if node.block_start_markup =~ /^(?<token>{{-?)(?<offense>[^ \n\t-])/
65
+
66
+ add_too_many_spaces_after_offense(Regexp.last_match, node, block_start_offset) if node.block_start_markup =~ /^(?<token>{{-?)(?<offense> {2,})\S/
67
+
68
+ # Looking at spaces before the end token
69
+ add_space_missing_before_offense(Regexp.last_match, node, block_start_offset) if node.block_start_markup =~ /(?<offense>[^ \n\t-])(?<token>-?}})$/
70
+
71
+ return unless node.block_start_markup =~ /\S(?<offense> {2,})(?<token>-?}})$/
72
+
73
+ add_too_many_spaces_before_offense(Regexp.last_match, node, block_start_offset)
74
+ end
75
+
76
+ def add_space_missing_after_offense(match, node, source_offset)
77
+ add_offense_for_match(
78
+ "Space missing after '#{match[:token]}'",
79
+ match,
80
+ node,
81
+ source_offset
82
+ ) do |corrector|
83
+ corrector.insert_after(
84
+ node,
85
+ ' ',
86
+ (node.start_index + source_offset + match.begin(:token))...
87
+ (node.start_index + source_offset + match.end(:token))
88
+ )
89
+ end
90
+ end
91
+
92
+ def add_too_many_spaces_after_offense(match, node, source_offset)
93
+ add_offense_for_match(
94
+ "Too many spaces after '#{match[:token]}'",
95
+ match,
96
+ node,
97
+ source_offset
98
+ ) do |corrector|
99
+ corrector.replace(
100
+ node,
101
+ ' ',
102
+ (node.start_index + source_offset + match.begin(:offense))...
103
+ (node.start_index + source_offset + match.end(:offense))
104
+ )
105
+ end
106
+ end
107
+
108
+ def add_space_missing_before_offense(match, node, source_offset)
109
+ add_offense_for_match(
110
+ "Space missing before '#{match[:token]}'",
111
+ match,
112
+ node,
113
+ source_offset
114
+ ) do |corrector|
115
+ corrector.insert_before(
116
+ node,
117
+ ' ',
118
+ (node.start_index + source_offset + match.begin(:token))...
119
+ (node.start_index + source_offset + match.end(:token))
120
+ )
121
+ end
122
+ end
123
+
124
+ def add_too_many_spaces_before_offense(match, node, source_offset)
125
+ add_offense_for_match(
126
+ "Too many spaces before '#{match[:token]}'",
127
+ match,
128
+ node,
129
+ source_offset
130
+ ) do |corrector|
131
+ corrector.replace(
132
+ node,
133
+ ' ',
134
+ (node.start_index + source_offset + match.begin(:offense))...
135
+ (node.start_index + source_offset + match.end(:offense))
136
+ )
137
+ end
138
+ end
139
+
140
+ def add_offense_for_match(message, match, node, source_offset, &)
141
+ add_offense(
142
+ message,
143
+ node:,
144
+ markup: match[:offense],
145
+ node_markup_offset: source_offset + match.begin(:offense),
146
+ &
147
+ )
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ # Report Liquid syntax errors
5
+ class SyntaxError < LiquidCheck
6
+ severity :error
7
+ category :liquid
8
+ doc docs_url(__FILE__)
9
+
10
+ def on_document(node)
11
+ node.app_file.warnings.each do |warning|
12
+ add_exception_as_offense(warning, app_file: node.app_file)
13
+ end
14
+ end
15
+
16
+ def on_error(exception)
17
+ add_exception_as_offense(exception, app_file: platformos_app[exception.template_name])
18
+ end
19
+
20
+ private
21
+
22
+ def add_exception_as_offense(exception, app_file:)
23
+ add_offense(
24
+ exception.to_s(false).sub(/ in ".*"$/, ''),
25
+ line_number: exception.line_number,
26
+ markup: exception.markup_context&.sub(/^in "(.*)"$/, '\1'),
27
+ app_file:
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ class TemplateLength < LiquidCheck
5
+ severity :suggestion
6
+ category :liquid
7
+ doc docs_url(__FILE__)
8
+
9
+ def initialize(max_length: 600)
10
+ @max_length = max_length
11
+ end
12
+
13
+ def after_document(node)
14
+ lines = node.app_file.source.count("\n")
15
+ return unless lines > @max_length
16
+
17
+ add_offense("Template has too many lines [#{lines}/#{@max_length}]", app_file: node.app_file)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ class UndefinedObject < LiquidCheck
5
+ category :liquid
6
+ doc docs_url(__FILE__)
7
+ severity :error
8
+
9
+ class TemplateInfo
10
+ def initialize(app_file: nil)
11
+ @all_variable_lookups = {}
12
+ @all_assigns = {}
13
+ @all_captures = {}
14
+ @all_forloops = {}
15
+ @all_renders = {}
16
+ @app_file = app_file
17
+ end
18
+
19
+ attr_reader :all_assigns, :all_captures, :all_forloops, :app_file
20
+
21
+ def add_render(name:, node:)
22
+ @all_renders[name] = node
23
+ end
24
+
25
+ def add_variable_lookup(name:, node:)
26
+ parent = node
27
+ line_number = nil
28
+ loop do
29
+ line_number = parent.line_number
30
+ parent = parent.parent
31
+ break unless line_number.nil? && parent
32
+ end
33
+ key = [name, line_number]
34
+ @all_variable_lookups[key] = node
35
+ end
36
+
37
+ def all_variables
38
+ all_assigns.keys + all_captures.keys + all_forloops.keys
39
+ end
40
+
41
+ def each_partial
42
+ @all_renders.each do |(name, info)|
43
+ yield [name, info]
44
+ end
45
+ end
46
+
47
+ def each_variable_lookup(unique_keys = false)
48
+ seen = Set.new
49
+ @all_variable_lookups.each do |(key, info)|
50
+ name, _line_number = key
51
+
52
+ next if unique_keys && seen.include?(name)
53
+
54
+ seen << name
55
+
56
+ yield [key, info]
57
+ end
58
+ end
59
+ end
60
+
61
+ def initialize(config_type: :default, exclude_partials: true)
62
+ @config_type = config_type
63
+ @exclude_partials = exclude_partials
64
+ @files = {}
65
+ end
66
+
67
+ def on_document(node)
68
+ return if ignore?(node)
69
+
70
+ @files[node.app_file.name] = TemplateInfo.new(app_file: node.app_file)
71
+ end
72
+
73
+ def on_assign(node)
74
+ return if ignore?(node)
75
+
76
+ @files[node.app_file.name].all_assigns[node.value.to] = node
77
+ end
78
+
79
+ def on_capture(node)
80
+ return if ignore?(node)
81
+
82
+ @files[node.app_file.name].all_captures[node.value.instance_variable_get(:@to)] = node
83
+ end
84
+
85
+ def on_parse_json(node)
86
+ return if ignore?(node)
87
+
88
+ @files[node.app_file.name].all_captures[node.value.to] = node
89
+ end
90
+
91
+ def on_for(node)
92
+ return if ignore?(node)
93
+
94
+ @files[node.app_file.name].all_forloops[node.value.variable_name] = node
95
+ end
96
+
97
+ def on_include(_node)
98
+ # NOOP: we purposely do nothing on `include` since it is deprecated
99
+ end
100
+
101
+ def on_render(node)
102
+ return if ignore?(node)
103
+ return unless node.value.template_name_expr.is_a?(String)
104
+
105
+ partial_name = node.value.template_name_expr
106
+ @files[node.app_file.name].add_render(
107
+ name: partial_name,
108
+ node:
109
+ )
110
+ end
111
+
112
+ def on_function(node)
113
+ return if ignore?(node)
114
+
115
+ name = node.value.from.is_a?(String) ? node.value.from : node.value.from.name
116
+ @files[node.app_file.name].add_render(
117
+ name:,
118
+ node:
119
+ )
120
+
121
+ @files[node.app_file.name].all_assigns[node.value.to] = node
122
+ end
123
+
124
+ def on_graphql(node)
125
+ return if ignore?(node)
126
+
127
+ @files[node.app_file.name].all_assigns[node.value.to] = node
128
+ end
129
+
130
+ def on_variable_lookup(node)
131
+ return if ignore?(node)
132
+
133
+ @files[node.app_file.name].add_variable_lookup(
134
+ name: node.value.name,
135
+ node:
136
+ )
137
+ end
138
+
139
+ def on_end
140
+ all_global_objects = PlatformosCheck::PlatformosLiquid::Object.labels
141
+ all_global_objects.freeze
142
+
143
+ each_template do |(_name, info)|
144
+ if info.app_file.notification?
145
+ # NOTE: `data` comes from graphql for notifications
146
+ check_object(info, all_global_objects + %w[data response form])
147
+ else
148
+ check_object(info, all_global_objects)
149
+ end
150
+ end
151
+ end
152
+
153
+ private
154
+
155
+ attr_reader :config_type
156
+
157
+ def ignore?(node)
158
+ @exclude_partials && node.app_file.partial?
159
+ end
160
+
161
+ def each_template
162
+ @files.each do |(name, info)|
163
+ next if info.app_file.partial?
164
+
165
+ yield [name, info]
166
+ end
167
+ end
168
+
169
+ def check_object(info, all_global_objects, render_node = nil, visited_partials = Set.new)
170
+ check_undefined(info, all_global_objects, render_node)
171
+
172
+ info.each_partial do |(partial_name, node)|
173
+ partial_info = @files[partial_name]
174
+ next unless partial_info # NOTE: undefined partial
175
+
176
+ partial_variables = node.value.attributes.keys +
177
+ [node.value.instance_variable_get(:@alias_name)]
178
+ unless visited_partials.include?(partial_name)
179
+ visited_partials << partial_name
180
+ check_object(partial_info, all_global_objects + partial_variables, node, visited_partials)
181
+ end
182
+ end
183
+ end
184
+
185
+ def check_undefined(info, all_global_objects, render_node)
186
+ all_variables = info.all_variables
187
+
188
+ info.each_variable_lookup(!!render_node) do |(key, node)|
189
+ name, line_number = key
190
+ next if all_variables.include?(name)
191
+ next if all_global_objects.include?(name)
192
+
193
+ node = node.parent
194
+ node = node.parent if %i[condition variable_lookup].include?(node.type_name)
195
+
196
+ next if node.variable? && node.filters.any? { |(filter_name)| filter_name == "default" }
197
+
198
+ if render_node
199
+ add_offense("Missing argument `#{name}`", node: render_node)
200
+ else
201
+ add_offense("Undefined object `#{name}`", node:, line_number:)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ #
5
+ # Unwanted:
6
+ #
7
+ # {{ x | some_unknown_filter }}
8
+ #
9
+ # Wanted:
10
+ #
11
+ # {{ x | upcase }}
12
+ #
13
+ class UnknownFilter < LiquidCheck
14
+ severity :error
15
+ category :liquid
16
+ doc docs_url(__FILE__)
17
+
18
+ def on_variable(node)
19
+ used_filters = node.filters.map { |name, *_rest| name }
20
+ undefined_filters = used_filters - PlatformosLiquid::Filter.labels - PlatformosLiquid::Filter.aliases
21
+
22
+ undefined_filters.each do |undefined_filter|
23
+ add_offense("Undefined filter `#{undefined_filter}`", node:)
24
+ end
25
+ end
26
+ end
27
+ end