platformos-check 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.dockerignore +2 -0
- data/.gitignore +22 -0
- data/.rubocop.yml +555 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +209 -0
- data/Gemfile +33 -0
- data/Guardfile +7 -0
- data/LICENSE.md +10 -0
- data/Makefile +18 -0
- data/README.md +189 -0
- data/RELEASING.md +35 -0
- data/Rakefile +83 -0
- data/TROUBLESHOOTING.md +35 -0
- data/bin/platformos-check +29 -0
- data/bin/platformos-check-language-server +29 -0
- data/config/default.yml +98 -0
- data/config/nothing.yml +11 -0
- data/data/platformos_liquid/built_in_liquid_objects.json +66 -0
- data/data/platformos_liquid/deprecated_filters.json +22 -0
- data/data/platformos_liquid/documentation/filters.json +6 -0
- data/data/platformos_liquid/documentation/latest.json +2 -0
- data/data/platformos_liquid/documentation/objects.json +6 -0
- data/data/platformos_liquid/documentation/tags.json +6 -0
- data/docker/check.Dockerfile +3 -0
- data/docker/lsp.Dockerfile +21 -0
- data/docs/api/check.md +15 -0
- data/docs/api/html_check.md +46 -0
- data/docs/api/liquid_check.md +115 -0
- data/docs/api/yaml_check.md +19 -0
- data/docs/checks/TEMPLATE.md.erb +52 -0
- data/docs/checks/convert_include_to_render.md +48 -0
- data/docs/checks/deprecated_filter.md +30 -0
- data/docs/checks/html_parsing_error.md +50 -0
- data/docs/checks/img_lazy_loading.md +63 -0
- data/docs/checks/img_width_and_height.md +79 -0
- data/docs/checks/invalid_args.md +56 -0
- data/docs/checks/liquid_tag.md +65 -0
- data/docs/checks/missing_enable_comment.md +50 -0
- data/docs/checks/missing_template.md +65 -0
- data/docs/checks/parse_json_format.md +76 -0
- data/docs/checks/parser_blocking_javascript.md +97 -0
- data/docs/checks/required_layout_object.md +28 -0
- data/docs/checks/space_inside_braces.md +89 -0
- data/docs/checks/syntax_error.md +49 -0
- data/docs/checks/template_length.md +45 -0
- data/docs/checks/undefined_object.md +71 -0
- data/docs/checks/unknown_filter.md +46 -0
- data/docs/checks/unused_assign.md +63 -0
- data/docs/checks/unused_partial.md +32 -0
- data/docs/checks/valid_yaml.md +48 -0
- data/docs/flamegraph.svg +18488 -0
- data/docs/language_server/code-action-command-palette.png +0 -0
- data/docs/language_server/code-action-flow.png +0 -0
- data/docs/language_server/code-action-keyboard.png +0 -0
- data/docs/language_server/code-action-light-bulb.png +0 -0
- data/docs/language_server/code-action-problem.png +0 -0
- data/docs/language_server/code-action-quickfix.png +0 -0
- data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
- data/docs/preview.png +0 -0
- data/exe/platformos-check +6 -0
- data/exe/platformos-check-language-server +7 -0
- data/lib/platformos_check/analyzer.rb +178 -0
- data/lib/platformos_check/api_call_file.rb +9 -0
- data/lib/platformos_check/app.rb +138 -0
- data/lib/platformos_check/app_file.rb +89 -0
- data/lib/platformos_check/app_file_rewriter.rb +79 -0
- data/lib/platformos_check/asset_file.rb +34 -0
- data/lib/platformos_check/bug.rb +23 -0
- data/lib/platformos_check/check.rb +163 -0
- data/lib/platformos_check/checks/TEMPLATE.rb.erb +11 -0
- data/lib/platformos_check/checks/convert_include_to_render.rb +17 -0
- data/lib/platformos_check/checks/deprecated_filter.rb +123 -0
- data/lib/platformos_check/checks/html_parsing_error.rb +13 -0
- data/lib/platformos_check/checks/img_lazy_loading.rb +18 -0
- data/lib/platformos_check/checks/img_width_and_height.rb +46 -0
- data/lib/platformos_check/checks/invalid_args.rb +81 -0
- data/lib/platformos_check/checks/liquid_tag.rb +47 -0
- data/lib/platformos_check/checks/missing_enable_comment.rb +37 -0
- data/lib/platformos_check/checks/missing_template.rb +107 -0
- data/lib/platformos_check/checks/parse_json_format.rb +31 -0
- data/lib/platformos_check/checks/parser_blocking_javascript.rb +17 -0
- data/lib/platformos_check/checks/required_layout_object.rb +41 -0
- data/lib/platformos_check/checks/space_inside_braces.rb +150 -0
- data/lib/platformos_check/checks/syntax_error.rb +31 -0
- data/lib/platformos_check/checks/template_length.rb +20 -0
- data/lib/platformos_check/checks/undefined_object.rb +206 -0
- data/lib/platformos_check/checks/unknown_filter.rb +27 -0
- data/lib/platformos_check/checks/unused_assign.rb +101 -0
- data/lib/platformos_check/checks/unused_partial.rb +93 -0
- data/lib/platformos_check/checks/valid_yaml.rb +16 -0
- data/lib/platformos_check/checks.rb +73 -0
- data/lib/platformos_check/checks_tracking.rb +9 -0
- data/lib/platformos_check/cli.rb +239 -0
- data/lib/platformos_check/config.rb +219 -0
- data/lib/platformos_check/config_file.rb +6 -0
- data/lib/platformos_check/corrector.rb +68 -0
- data/lib/platformos_check/disabled_check.rb +44 -0
- data/lib/platformos_check/disabled_checks.rb +96 -0
- data/lib/platformos_check/email_file.rb +9 -0
- data/lib/platformos_check/exceptions.rb +36 -0
- data/lib/platformos_check/file_system_storage.rb +93 -0
- data/lib/platformos_check/graphql_file.rb +68 -0
- data/lib/platformos_check/html_check.rb +8 -0
- data/lib/platformos_check/html_node.rb +210 -0
- data/lib/platformos_check/html_visitor.rb +36 -0
- data/lib/platformos_check/in_memory_storage.rb +68 -0
- data/lib/platformos_check/json_file.rb +57 -0
- data/lib/platformos_check/json_helper.rb +73 -0
- data/lib/platformos_check/json_helpers.rb +24 -0
- data/lib/platformos_check/json_printer.rb +32 -0
- data/lib/platformos_check/language_server/bridge.rb +167 -0
- data/lib/platformos_check/language_server/channel.rb +69 -0
- data/lib/platformos_check/language_server/client_capabilities.rb +27 -0
- data/lib/platformos_check/language_server/code_action_engine.rb +32 -0
- data/lib/platformos_check/language_server/code_action_provider.rb +41 -0
- data/lib/platformos_check/language_server/code_action_providers/quickfix_code_action_provider.rb +85 -0
- data/lib/platformos_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +41 -0
- data/lib/platformos_check/language_server/completion_context.rb +52 -0
- data/lib/platformos_check/language_server/completion_engine.rb +32 -0
- data/lib/platformos_check/language_server/completion_helper.rb +26 -0
- data/lib/platformos_check/language_server/completion_provider.rb +53 -0
- data/lib/platformos_check/language_server/completion_providers/assignments_completion_provider.rb +40 -0
- data/lib/platformos_check/language_server/completion_providers/filter_completion_provider.rb +102 -0
- data/lib/platformos_check/language_server/completion_providers/object_attribute_completion_provider.rb +48 -0
- data/lib/platformos_check/language_server/completion_providers/object_completion_provider.rb +38 -0
- data/lib/platformos_check/language_server/completion_providers/render_snippet_completion_provider.rb +50 -0
- data/lib/platformos_check/language_server/completion_providers/tag_completion_provider.rb +41 -0
- data/lib/platformos_check/language_server/configuration.rb +89 -0
- data/lib/platformos_check/language_server/constants.rb +29 -0
- data/lib/platformos_check/language_server/diagnostic.rb +129 -0
- data/lib/platformos_check/language_server/diagnostics_engine.rb +131 -0
- data/lib/platformos_check/language_server/diagnostics_manager.rb +184 -0
- data/lib/platformos_check/language_server/document_change_corrector.rb +271 -0
- data/lib/platformos_check/language_server/document_link_engine.rb +21 -0
- data/lib/platformos_check/language_server/document_link_provider.rb +71 -0
- data/lib/platformos_check/language_server/document_link_providers/asset_document_link_provider.rb +11 -0
- data/lib/platformos_check/language_server/document_link_providers/include_document_link_provider.rb +11 -0
- data/lib/platformos_check/language_server/document_link_providers/render_document_link_provider.rb +11 -0
- data/lib/platformos_check/language_server/document_link_providers/section_document_link_provider.rb +11 -0
- data/lib/platformos_check/language_server/execute_command_engine.rb +19 -0
- data/lib/platformos_check/language_server/execute_command_provider.rb +30 -0
- data/lib/platformos_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
- data/lib/platformos_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +28 -0
- data/lib/platformos_check/language_server/handler.rb +310 -0
- data/lib/platformos_check/language_server/hover_engine.rb +32 -0
- data/lib/platformos_check/language_server/hover_provider.rb +53 -0
- data/lib/platformos_check/language_server/hover_providers/filter_hover_provider.rb +113 -0
- data/lib/platformos_check/language_server/io_messenger.rb +109 -0
- data/lib/platformos_check/language_server/messenger.rb +27 -0
- data/lib/platformos_check/language_server/protocol.rb +55 -0
- data/lib/platformos_check/language_server/server.rb +188 -0
- data/lib/platformos_check/language_server/tokens.rb +55 -0
- data/lib/platformos_check/language_server/type_helper.rb +22 -0
- data/lib/platformos_check/language_server/uri_helper.rb +39 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/node_handler.rb +87 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/scope.rb +60 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/scope_visitor.rb +44 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder.rb +76 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/constants.rb +44 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/liquid_fixer.rb +103 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/potential_lookup.rb +10 -0
- data/lib/platformos_check/language_server/variable_lookup_finder/tolerant_parser.rb +94 -0
- data/lib/platformos_check/language_server/variable_lookup_finder.rb +262 -0
- data/lib/platformos_check/language_server/variable_lookup_traverser.rb +70 -0
- data/lib/platformos_check/language_server/versioned_in_memory_storage.rb +84 -0
- data/lib/platformos_check/language_server.rb +71 -0
- data/lib/platformos_check/layout_file.rb +15 -0
- data/lib/platformos_check/liquid_check.rb +10 -0
- data/lib/platformos_check/liquid_file.rb +102 -0
- data/lib/platformos_check/liquid_node.rb +570 -0
- data/lib/platformos_check/liquid_visitor.rb +39 -0
- data/lib/platformos_check/migration_file.rb +9 -0
- data/lib/platformos_check/node.rb +53 -0
- data/lib/platformos_check/offense.rb +228 -0
- data/lib/platformos_check/page_file.rb +9 -0
- data/lib/platformos_check/parsing_helpers.rb +21 -0
- data/lib/platformos_check/partial_file.rb +15 -0
- data/lib/platformos_check/platformos_liquid/deprecated_filter.rb +31 -0
- data/lib/platformos_check/platformos_liquid/documentation/markdown_template.rb +51 -0
- data/lib/platformos_check/platformos_liquid/documentation.rb +45 -0
- data/lib/platformos_check/platformos_liquid/filter.rb +19 -0
- data/lib/platformos_check/platformos_liquid/object.rb +15 -0
- data/lib/platformos_check/platformos_liquid/source_index/base_entry.rb +66 -0
- data/lib/platformos_check/platformos_liquid/source_index/base_state.rb +23 -0
- data/lib/platformos_check/platformos_liquid/source_index/filter_entry.rb +26 -0
- data/lib/platformos_check/platformos_liquid/source_index/filter_state.rb +11 -0
- data/lib/platformos_check/platformos_liquid/source_index/object_entry.rb +20 -0
- data/lib/platformos_check/platformos_liquid/source_index/object_state.rb +11 -0
- data/lib/platformos_check/platformos_liquid/source_index/parameter_entry.rb +25 -0
- data/lib/platformos_check/platformos_liquid/source_index/property_entry.rb +21 -0
- data/lib/platformos_check/platformos_liquid/source_index/return_type_entry.rb +41 -0
- data/lib/platformos_check/platformos_liquid/source_index/tag_entry.rb +24 -0
- data/lib/platformos_check/platformos_liquid/source_index/tag_state.rb +11 -0
- data/lib/platformos_check/platformos_liquid/source_index.rb +79 -0
- data/lib/platformos_check/platformos_liquid/source_manager.rb +116 -0
- data/lib/platformos_check/platformos_liquid/tag.rb +59 -0
- data/lib/platformos_check/platformos_liquid.rb +21 -0
- data/lib/platformos_check/position.rb +180 -0
- data/lib/platformos_check/position_helper.rb +57 -0
- data/lib/platformos_check/printer.rb +87 -0
- data/lib/platformos_check/regex_helpers.rb +21 -0
- data/lib/platformos_check/releaser.rb +43 -0
- data/lib/platformos_check/schema_file.rb +6 -0
- data/lib/platformos_check/sms_file.rb +9 -0
- data/lib/platformos_check/storage.rb +29 -0
- data/lib/platformos_check/string_helpers.rb +48 -0
- data/lib/platformos_check/tags/background.rb +67 -0
- data/lib/platformos_check/tags/base.rb +14 -0
- data/lib/platformos_check/tags/base_block.rb +14 -0
- data/lib/platformos_check/tags/base_tag_methods.rb +59 -0
- data/lib/platformos_check/tags/cache.rb +13 -0
- data/lib/platformos_check/tags/export.rb +30 -0
- data/lib/platformos_check/tags/form.rb +19 -0
- data/lib/platformos_check/tags/function.rb +58 -0
- data/lib/platformos_check/tags/graphql.rb +70 -0
- data/lib/platformos_check/tags/hash_assign.rb +75 -0
- data/lib/platformos_check/tags/log.rb +15 -0
- data/lib/platformos_check/tags/parse_json.rb +24 -0
- data/lib/platformos_check/tags/print.rb +20 -0
- data/lib/platformos_check/tags/redirect_to.rb +15 -0
- data/lib/platformos_check/tags/render.rb +60 -0
- data/lib/platformos_check/tags/response_headers.rb +20 -0
- data/lib/platformos_check/tags/response_status.rb +20 -0
- data/lib/platformos_check/tags/return.rb +20 -0
- data/lib/platformos_check/tags/session.rb +27 -0
- data/lib/platformos_check/tags/sign_in.rb +27 -0
- data/lib/platformos_check/tags/spam_protection.rb +15 -0
- data/lib/platformos_check/tags/theme_render.rb +58 -0
- data/lib/platformos_check/tags/try.rb +59 -0
- data/lib/platformos_check/tags.rb +65 -0
- data/lib/platformos_check/translation_file.rb +6 -0
- data/lib/platformos_check/user_schema_file.rb +6 -0
- data/lib/platformos_check/version.rb +5 -0
- data/lib/platformos_check/yaml_check.rb +11 -0
- data/lib/platformos_check/yaml_file.rb +57 -0
- data/lib/platformos_check.rb +106 -0
- data/platformos-check.gemspec +34 -0
- metadata +329 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlatformosCheck
|
4
|
+
class LayoutFile < LiquidFile
|
5
|
+
DIR_PREFIX = %r{\A/?((marketplace_builder|app)/(views/layouts)/|modules/((\w|-)*)/(private|public)/(views/layouts)/)}
|
6
|
+
|
7
|
+
def layout?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def dir_prefix
|
12
|
+
DIR_PREFIX
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlatformosCheck
|
4
|
+
class LiquidFile < AppFile
|
5
|
+
def write
|
6
|
+
content = rewriter.to_s
|
7
|
+
return unless source != content
|
8
|
+
|
9
|
+
@storage.write(@relative_path, content.gsub("\n", @eol))
|
10
|
+
@source = content
|
11
|
+
@rewriter = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def liquid?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def template?
|
19
|
+
name.start_with?('template')
|
20
|
+
end
|
21
|
+
|
22
|
+
def notification?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def migration?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def page?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def partial?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
def layout?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def section?
|
43
|
+
name.start_with?('sections')
|
44
|
+
end
|
45
|
+
|
46
|
+
def snippet?
|
47
|
+
name.start_with?('snippet')
|
48
|
+
end
|
49
|
+
|
50
|
+
def rewriter
|
51
|
+
@rewriter ||= AppFileRewriter.new(@relative_path, source)
|
52
|
+
end
|
53
|
+
|
54
|
+
def source_excerpt(line)
|
55
|
+
original_lines = source.split("\n")
|
56
|
+
original_lines[bounded(0, line - 1, original_lines.size - 1)].strip
|
57
|
+
rescue StandardError => e
|
58
|
+
PlatformosCheck.bug(<<~EOS)
|
59
|
+
Exception while running `source_excerpt(#{line})`:
|
60
|
+
```
|
61
|
+
#{e.class}: #{e.message}
|
62
|
+
#{e.backtrace.join("\n ")}
|
63
|
+
```
|
64
|
+
|
65
|
+
path: #{path}
|
66
|
+
|
67
|
+
source:
|
68
|
+
```
|
69
|
+
#{source}
|
70
|
+
```
|
71
|
+
EOS
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse
|
75
|
+
@ast ||= self.class.parse(source)
|
76
|
+
end
|
77
|
+
|
78
|
+
def warnings
|
79
|
+
@ast.warnings
|
80
|
+
end
|
81
|
+
|
82
|
+
def root
|
83
|
+
parse.root
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.parse(source)
|
87
|
+
Tags.register_tags!
|
88
|
+
Liquid::Template.parse(
|
89
|
+
source,
|
90
|
+
line_numbers: true,
|
91
|
+
error_mode: :warn,
|
92
|
+
disable_liquid_c_nodes: true
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def bounded(lower, x, upper)
|
99
|
+
[lower, [x, upper].min].max
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,570 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlatformosCheck
|
4
|
+
# A node from the Liquid AST, the result of parsing a liquid file.
|
5
|
+
class LiquidNode < Node
|
6
|
+
attr_reader :value, :parent, :app_file
|
7
|
+
|
8
|
+
def initialize(value, parent, app_file)
|
9
|
+
raise ArgumentError, "Expected a Liquid AST Node" if value.is_a?(LiquidNode)
|
10
|
+
|
11
|
+
@value = value
|
12
|
+
@parent = parent
|
13
|
+
@app_file = app_file
|
14
|
+
@tag_markup = nil
|
15
|
+
@line_number_offset = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
# Array of children nodes.
|
19
|
+
def children
|
20
|
+
@children ||= begin
|
21
|
+
nodes =
|
22
|
+
if comment?
|
23
|
+
[]
|
24
|
+
elsif defined?(@value.class::ParseTreeVisitor)
|
25
|
+
@value.class::ParseTreeVisitor.new(@value, {}).children
|
26
|
+
elsif @value.respond_to?(:nodelist)
|
27
|
+
Array(@value.nodelist)
|
28
|
+
else
|
29
|
+
[]
|
30
|
+
end
|
31
|
+
# Work around a bug in Liquid::Variable::ParseTreeVisitor that doesn't return
|
32
|
+
# the args in a hash as children nodes.
|
33
|
+
nodes = nodes.flat_map do |node|
|
34
|
+
case node
|
35
|
+
when Hash
|
36
|
+
node.values
|
37
|
+
else
|
38
|
+
node
|
39
|
+
end
|
40
|
+
end
|
41
|
+
nodes
|
42
|
+
.reject(&:nil?) # We don't want nil nodes, and they can happen
|
43
|
+
.map { |node| LiquidNode.new(node, self, @app_file) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# The original source code of the node. Doesn't contain wrapping braces.
|
48
|
+
def markup
|
49
|
+
if tag?
|
50
|
+
tag_markup
|
51
|
+
elsif literal?
|
52
|
+
value.to_s
|
53
|
+
elsif @value.instance_variable_defined?(:@markup)
|
54
|
+
@value.instance_variable_get(:@markup)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# The original source code of the node. Does contain wrapping braces.
|
59
|
+
def outer_markup
|
60
|
+
if literal?
|
61
|
+
markup
|
62
|
+
elsif variable_lookup?
|
63
|
+
''
|
64
|
+
elsif variable?
|
65
|
+
start_token + markup + end_token
|
66
|
+
elsif tag? && block?
|
67
|
+
start_index = block_start_start_index
|
68
|
+
end_index = block_start_end_index
|
69
|
+
end_index += inner_markup.size
|
70
|
+
end_index = find_block_delimiter(end_index)&.end(0)
|
71
|
+
source[start_index...end_index]
|
72
|
+
elsif tag?
|
73
|
+
source[block_start_start_index...block_start_end_index]
|
74
|
+
else
|
75
|
+
inner_markup
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def inner_markup
|
80
|
+
return '' unless block?
|
81
|
+
|
82
|
+
@inner_markup ||= source[block_start_end_index...block_end_start_index]
|
83
|
+
end
|
84
|
+
|
85
|
+
def inner_json
|
86
|
+
return nil unless parse_json?
|
87
|
+
|
88
|
+
@inner_json ||= JSON.parse(inner_markup)
|
89
|
+
rescue JSON::ParserError
|
90
|
+
# Handled by ValidSchema
|
91
|
+
@inner_json = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def markup=(markup)
|
95
|
+
return unless @value.instance_variable_defined?(:@markup)
|
96
|
+
|
97
|
+
@value.instance_variable_set(:@markup, markup)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Most nodes have a line number, but it's not guaranteed.
|
101
|
+
def line_number
|
102
|
+
if tag? && @value.respond_to?(:line_number)
|
103
|
+
markup # initialize the line_number_offset
|
104
|
+
@value.line_number - @line_number_offset
|
105
|
+
elsif @value.respond_to?(:line_number)
|
106
|
+
@value.line_number
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def start_index
|
111
|
+
position.start_index
|
112
|
+
end
|
113
|
+
|
114
|
+
def start_row
|
115
|
+
position.start_row
|
116
|
+
end
|
117
|
+
|
118
|
+
def start_column
|
119
|
+
position.start_column
|
120
|
+
end
|
121
|
+
|
122
|
+
def end_index
|
123
|
+
position.end_index
|
124
|
+
end
|
125
|
+
|
126
|
+
def end_row
|
127
|
+
position.end_row
|
128
|
+
end
|
129
|
+
|
130
|
+
def end_column
|
131
|
+
position.end_column
|
132
|
+
end
|
133
|
+
|
134
|
+
# Literals are hard-coded values in the liquid file.
|
135
|
+
def literal?
|
136
|
+
@value.is_a?(String) || @value.is_a?(Integer)
|
137
|
+
end
|
138
|
+
|
139
|
+
# A {% tag %} node?
|
140
|
+
def tag?
|
141
|
+
@value.is_a?(Liquid::Tag)
|
142
|
+
end
|
143
|
+
|
144
|
+
def variable?
|
145
|
+
@value.is_a?(Liquid::Variable)
|
146
|
+
end
|
147
|
+
|
148
|
+
def assigned_or_echoed_variable?
|
149
|
+
variable? && start_token == ""
|
150
|
+
end
|
151
|
+
|
152
|
+
def variable_lookup?
|
153
|
+
@value.is_a?(Liquid::VariableLookup)
|
154
|
+
end
|
155
|
+
|
156
|
+
# A {% comment %} block node?
|
157
|
+
def comment?
|
158
|
+
@value.is_a?(Liquid::Comment)
|
159
|
+
end
|
160
|
+
|
161
|
+
# {% # comment %}
|
162
|
+
def inline_comment?
|
163
|
+
@value.is_a?(Liquid::InlineComment)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Top level node of every liquid_file.
|
167
|
+
def document?
|
168
|
+
@value.is_a?(Liquid::Document)
|
169
|
+
end
|
170
|
+
alias root? document?
|
171
|
+
|
172
|
+
# A {% tag %}...{% endtag %} node?
|
173
|
+
def block_tag?
|
174
|
+
@value.is_a?(Liquid::Block)
|
175
|
+
end
|
176
|
+
|
177
|
+
# The body of blocks
|
178
|
+
def block_body?
|
179
|
+
@value.is_a?(Liquid::BlockBody)
|
180
|
+
end
|
181
|
+
|
182
|
+
# A block of type of node?
|
183
|
+
def block?
|
184
|
+
block_tag? || block_body? || document?
|
185
|
+
end
|
186
|
+
|
187
|
+
def parse_json?
|
188
|
+
@value.is_a?(PlatformosCheck::Tags::ParseJson)
|
189
|
+
end
|
190
|
+
|
191
|
+
def function?
|
192
|
+
@value.is_a?(PlatformosCheck::Tags::Function)
|
193
|
+
end
|
194
|
+
|
195
|
+
# The `:under_score_name` of this type of node. Used to dispatch to the `on_<type_name>`
|
196
|
+
# and `after_<type_name>` check methods.
|
197
|
+
def type_name
|
198
|
+
@type_name ||= StringHelpers.underscore(StringHelpers.demodulize(@value.class.name)).to_sym
|
199
|
+
end
|
200
|
+
|
201
|
+
def filters
|
202
|
+
raise TypeError, "Attempting to lookup filters of #{type_name}. Only variables have filters." unless variable?
|
203
|
+
|
204
|
+
@value.filters
|
205
|
+
end
|
206
|
+
|
207
|
+
def source
|
208
|
+
app_file&.source
|
209
|
+
end
|
210
|
+
|
211
|
+
# For debugging purposes, this might be easier for the eyes.
|
212
|
+
def to_h
|
213
|
+
if literal?
|
214
|
+
return @value
|
215
|
+
elsif variable_lookup?
|
216
|
+
return {
|
217
|
+
type_name:,
|
218
|
+
name: value.name.to_s,
|
219
|
+
lookups: children.map(&:to_h)
|
220
|
+
}
|
221
|
+
end
|
222
|
+
|
223
|
+
{
|
224
|
+
type_name:,
|
225
|
+
markup: outer_markup,
|
226
|
+
children: children.map(&:to_h)
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
def block_start_markup
|
231
|
+
source[block_start_start_index...block_start_end_index]
|
232
|
+
end
|
233
|
+
|
234
|
+
def block_start_start_index
|
235
|
+
@block_start_start_index ||= if inside_liquid_tag?
|
236
|
+
backtrack_on_whitespace(source, start_index, /[ \t]/)
|
237
|
+
elsif tag?
|
238
|
+
backtrack_on_whitespace(source, start_index) - start_token.length
|
239
|
+
else
|
240
|
+
position.start_index - start_token.length
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def block_start_end_index
|
245
|
+
@block_start_end_index ||= position.end_index + end_token.size
|
246
|
+
end
|
247
|
+
|
248
|
+
def block_end_markup
|
249
|
+
source[block_end_start_index...block_end_end_index]
|
250
|
+
end
|
251
|
+
|
252
|
+
def block_end_start_index
|
253
|
+
return block_start_end_index unless tag? && block?
|
254
|
+
|
255
|
+
@block_end_start_index ||= block_end_match&.begin(0) || block_start_end_index
|
256
|
+
end
|
257
|
+
|
258
|
+
def block_end_end_index
|
259
|
+
return block_end_start_index unless tag? && block?
|
260
|
+
|
261
|
+
@block_end_end_index ||= block_end_match&.end(0) || block_start_end_index
|
262
|
+
end
|
263
|
+
|
264
|
+
def outer_markup_start_index
|
265
|
+
outer_markup_position.start_index
|
266
|
+
end
|
267
|
+
|
268
|
+
def outer_markup_end_index
|
269
|
+
outer_markup_position.end_index
|
270
|
+
end
|
271
|
+
|
272
|
+
def outer_markup_start_row
|
273
|
+
outer_markup_position.start_row
|
274
|
+
end
|
275
|
+
|
276
|
+
def outer_markup_start_column
|
277
|
+
outer_markup_position.start_column
|
278
|
+
end
|
279
|
+
|
280
|
+
def outer_markup_end_row
|
281
|
+
outer_markup_position.end_row
|
282
|
+
end
|
283
|
+
|
284
|
+
def outer_markup_end_column
|
285
|
+
outer_markup_position.end_column
|
286
|
+
end
|
287
|
+
|
288
|
+
def inner_markup_start_index
|
289
|
+
inner_markup_position.start_index
|
290
|
+
end
|
291
|
+
|
292
|
+
def inner_markup_end_index
|
293
|
+
inner_markup_position.end_index
|
294
|
+
end
|
295
|
+
|
296
|
+
def inner_markup_start_row
|
297
|
+
inner_markup_position.start_row
|
298
|
+
end
|
299
|
+
|
300
|
+
def inner_markup_start_column
|
301
|
+
inner_markup_position.start_column
|
302
|
+
end
|
303
|
+
|
304
|
+
def inner_markup_end_row
|
305
|
+
inner_markup_position.end_row
|
306
|
+
end
|
307
|
+
|
308
|
+
def inner_markup_end_column
|
309
|
+
inner_markup_position.end_column
|
310
|
+
end
|
311
|
+
|
312
|
+
WHITESPACE = /\s/
|
313
|
+
|
314
|
+
# Is this node inside a `{% liquid ... %}` block?
|
315
|
+
def inside_liquid_tag?
|
316
|
+
# What we're doing here is starting at the start of the tag and
|
317
|
+
# backtrack on all the whitespace until we land on something. If
|
318
|
+
# that something is {% or %-, then we can safely assume that
|
319
|
+
# we're inside a full tag and not a liquid tag.
|
320
|
+
@inside_liquid_tag ||= if tag? && start_index && source
|
321
|
+
i = 1
|
322
|
+
i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
|
323
|
+
first_two_backtracked_characters = source[(start_index - i - 1)..(start_index - i)]
|
324
|
+
first_two_backtracked_characters != "{%" && first_two_backtracked_characters != "%-"
|
325
|
+
else
|
326
|
+
false
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# Is this node inside a tag or variable that starts by removing whitespace. i.e. {%- or {{-
|
331
|
+
def whitespace_trimmed_start?
|
332
|
+
@whitespace_trimmed_start ||= if start_index && source && !inside_liquid_tag?
|
333
|
+
i = 1
|
334
|
+
i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
|
335
|
+
source[start_index - i] == "-"
|
336
|
+
else
|
337
|
+
false
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
# Is this node inside a tag or variable ends starts by removing whitespace. i.e. -%} or -}}
|
342
|
+
def whitespace_trimmed_end?
|
343
|
+
@whitespace_trimmed_end ||= if end_index && source && !inside_liquid_tag?
|
344
|
+
i = 0
|
345
|
+
i += 1 while source[end_index + i] =~ WHITESPACE && i < source.size
|
346
|
+
source[end_index + i] == "-"
|
347
|
+
else
|
348
|
+
false
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def start_token
|
353
|
+
if inside_liquid_tag?
|
354
|
+
""
|
355
|
+
elsif variable? && source[start_index - 3..start_index - 1] == "{{-"
|
356
|
+
"{{-"
|
357
|
+
elsif variable? && source[start_index - 2..start_index - 1] == "{{"
|
358
|
+
"{{"
|
359
|
+
elsif tag? && whitespace_trimmed_start?
|
360
|
+
"{%-"
|
361
|
+
elsif tag?
|
362
|
+
"{%"
|
363
|
+
else
|
364
|
+
""
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
def end_token
|
369
|
+
if inside_liquid_tag? && source[end_index] == "\n"
|
370
|
+
"\n"
|
371
|
+
elsif inside_liquid_tag?
|
372
|
+
""
|
373
|
+
elsif variable? && source[end_index...end_index + 3] == "-}}"
|
374
|
+
"-}}"
|
375
|
+
elsif variable? && source[end_index...end_index + 2] == "}}"
|
376
|
+
"}}"
|
377
|
+
elsif tag? && whitespace_trimmed_end?
|
378
|
+
"-%}"
|
379
|
+
elsif tag?
|
380
|
+
"%}"
|
381
|
+
else # this could happen because we're in an assign statement (variable)
|
382
|
+
""
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
private
|
387
|
+
|
388
|
+
def position
|
389
|
+
@position ||= Position.new(
|
390
|
+
markup,
|
391
|
+
app_file&.source,
|
392
|
+
line_number_1_indexed: line_number
|
393
|
+
)
|
394
|
+
end
|
395
|
+
|
396
|
+
def outer_markup_position
|
397
|
+
@outer_markup_position ||= StrictPosition.new(
|
398
|
+
outer_markup,
|
399
|
+
source,
|
400
|
+
block_start_start_index
|
401
|
+
)
|
402
|
+
end
|
403
|
+
|
404
|
+
def inner_markup_position
|
405
|
+
@inner_markup_position ||= StrictPosition.new(
|
406
|
+
inner_markup,
|
407
|
+
source,
|
408
|
+
block_start_end_index
|
409
|
+
)
|
410
|
+
end
|
411
|
+
|
412
|
+
# Here we're hacking around a glorious bug in Liquid that makes it so the
|
413
|
+
# line_number and markup of a tag is wrong if there's whitespace
|
414
|
+
# between the tag_name and the markup of the tag.
|
415
|
+
#
|
416
|
+
# {%
|
417
|
+
# render
|
418
|
+
# 'foo'
|
419
|
+
# %}
|
420
|
+
#
|
421
|
+
# Returns a raw value of "render 'foo'\n".
|
422
|
+
# The "\n " between render and 'foo' got replaced by a single space.
|
423
|
+
#
|
424
|
+
# And the line number is the one of 'foo'\n%}. Yay!
|
425
|
+
#
|
426
|
+
# This breaks any kind of position logic we have since that string
|
427
|
+
# does not exist in the app_file.
|
428
|
+
def tag_markup
|
429
|
+
return @tag_markup if @tag_markup
|
430
|
+
|
431
|
+
l = 1
|
432
|
+
scanner = StringScanner.new(source)
|
433
|
+
scanner.scan_until(/\n/) while l < @value.line_number && (l += 1)
|
434
|
+
start = scanner.charpos
|
435
|
+
|
436
|
+
tag_name = @value.tag_name
|
437
|
+
tag_markup = @value.instance_variable_get(:@markup)
|
438
|
+
|
439
|
+
# This is tricky, if the tag_markup is empty, then the tag could
|
440
|
+
# either start on a previous line, or the tag could start on the
|
441
|
+
# same line.
|
442
|
+
#
|
443
|
+
# Consider this:
|
444
|
+
# 1 {%
|
445
|
+
# 2 comment
|
446
|
+
# 3 %}{% endcomment %}{%comment%}
|
447
|
+
#
|
448
|
+
# Both comments would markup == "" AND line_number == 3
|
449
|
+
#
|
450
|
+
# There's no way to determine which one is the correct one, but
|
451
|
+
# we'll try our best to at least give you one.
|
452
|
+
#
|
453
|
+
# To screw with you even more, the name of the tag could be
|
454
|
+
# outside of a tag on the same line :) But I won't do anything
|
455
|
+
# about that (yet?).
|
456
|
+
#
|
457
|
+
# {% comment
|
458
|
+
# %}comment{% endcomment %}
|
459
|
+
if tag_markup.empty?
|
460
|
+
eol = source.index("\n", start) || source.size
|
461
|
+
|
462
|
+
# OK here I'm trying one of two things. Either tag_start is on
|
463
|
+
# the same line OR tag_start is on a previous line. The line
|
464
|
+
# number would be at the end of the whitespace after tag_name.
|
465
|
+
unless (tag_start = source.index(tag_name, start)) && tag_start < eol
|
466
|
+
tag_start = start
|
467
|
+
tag_start -= 1 while source[tag_start - 1] =~ WHITESPACE
|
468
|
+
tag_start -= @value.tag_name.size
|
469
|
+
|
470
|
+
# keep track of the error in line_number
|
471
|
+
@line_number_offset = source[tag_start...start].count("\n")
|
472
|
+
end
|
473
|
+
tag_end = tag_start + tag_name.size
|
474
|
+
tag_end += 1 while source[tag_end] =~ WHITESPACE
|
475
|
+
|
476
|
+
# return the real raw content
|
477
|
+
@tag_markup = source[tag_start...tag_end]
|
478
|
+
return @tag_markup
|
479
|
+
|
480
|
+
# because line_numbers are not enough to accurately
|
481
|
+
# determine the position of the raw markup and because that
|
482
|
+
# markup could be present on the same line outside of a Tag. e.g.
|
483
|
+
#
|
484
|
+
# uhoh {% if uhoh %}
|
485
|
+
elsif (match = /#{tag_name} +#{Regexp.escape(tag_markup)}/.match(source, start))
|
486
|
+
return @tag_markup = match[0]
|
487
|
+
end
|
488
|
+
|
489
|
+
# find the markup
|
490
|
+
markup_start = source.index(tag_markup, start)
|
491
|
+
markup_end = markup_start + tag_markup.size
|
492
|
+
|
493
|
+
# go back until you find the tag_name
|
494
|
+
tag_start = markup_start
|
495
|
+
tag_start -= 1 while source[tag_start - 1] =~ WHITESPACE
|
496
|
+
tag_start -= tag_name.size
|
497
|
+
|
498
|
+
# keep track of the error in line_number
|
499
|
+
@line_number_offset = source[tag_start...markup_start].count("\n")
|
500
|
+
|
501
|
+
# return the real raw content
|
502
|
+
@tag_markup = source[tag_start...markup_end]
|
503
|
+
end
|
504
|
+
|
505
|
+
# Returns the index of the leftmost consecutive whitespace
|
506
|
+
# starting from start going backwards.
|
507
|
+
#
|
508
|
+
# e.g. backtrack_on_whitespace("01 45", 4) would return 2.
|
509
|
+
# e.g. backtrack_on_whitespace("{% render %}", 5) would return 2.
|
510
|
+
def backtrack_on_whitespace(string, start, whitespace = WHITESPACE)
|
511
|
+
i = start
|
512
|
+
i -= 1 while string[i - 1] =~ whitespace && i > 0
|
513
|
+
i
|
514
|
+
end
|
515
|
+
|
516
|
+
def find_block_delimiter(start_index)
|
517
|
+
return nil unless tag? && block?
|
518
|
+
|
519
|
+
tag_start, tag_end = if inside_liquid_tag?
|
520
|
+
[
|
521
|
+
/^\s*#{@value.tag_name}\s*/,
|
522
|
+
/^\s*end#{@value.tag_name}\s*/
|
523
|
+
]
|
524
|
+
else
|
525
|
+
[
|
526
|
+
/#{Liquid::TagStart}-?\s*#{@value.tag_name}/mi,
|
527
|
+
/#{Liquid::TagStart}-?\s*end#{@value.tag_name}\s*-?#{Liquid::TagEnd}/mi
|
528
|
+
]
|
529
|
+
end
|
530
|
+
|
531
|
+
# This little algorithm below find the _correct_ block delimiter
|
532
|
+
# (endif, endcase, endcomment) for the current tag. What do I
|
533
|
+
# mean by correct? It means the one you'd expect. Making sure
|
534
|
+
# that we don't do the naive regex find. Since you can have
|
535
|
+
# nested ifs, fors, etc.
|
536
|
+
#
|
537
|
+
# It works by having a stack, pushing onto the stack when we
|
538
|
+
# open a tag of our type_name. And popping when we find a
|
539
|
+
# closing tag of our type_name.
|
540
|
+
#
|
541
|
+
# When the stack is empty, we return the end tag match.
|
542
|
+
index = start_index
|
543
|
+
stack = []
|
544
|
+
stack.push("open")
|
545
|
+
loop do
|
546
|
+
tag_start_match = tag_start.match(source, index)
|
547
|
+
tag_end_match = tag_end.match(source, index)
|
548
|
+
|
549
|
+
return nil unless tag_end_match
|
550
|
+
|
551
|
+
# We have found a tag_start and it appeared _before_ the
|
552
|
+
# tag_end that we found, thus we push it onto the stack.
|
553
|
+
stack.push("open") if tag_start_match && tag_start_match.end(0) < tag_end_match.end(0)
|
554
|
+
|
555
|
+
# We have found a tag_end, therefore we pop
|
556
|
+
stack.pop
|
557
|
+
|
558
|
+
# Nothing left on the stack, we're done.
|
559
|
+
break tag_end_match if stack.empty?
|
560
|
+
|
561
|
+
# We keep looking from the end of the end tag we just found.
|
562
|
+
index = tag_end_match.end(0)
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
def block_end_match
|
567
|
+
@block_end_match ||= find_block_delimiter(block_start_end_index)
|
568
|
+
end
|
569
|
+
end
|
570
|
+
end
|