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,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module PlatformosCheck
|
6
|
+
class HtmlNode < Node
|
7
|
+
extend Forwardable
|
8
|
+
include RegexHelpers
|
9
|
+
include PositionHelper
|
10
|
+
attr_reader :app_file, :parent
|
11
|
+
|
12
|
+
class << self
|
13
|
+
include RegexHelpers
|
14
|
+
|
15
|
+
def parse(liquid_file)
|
16
|
+
placeholder_values = []
|
17
|
+
parseable_source = +liquid_file.source.clone
|
18
|
+
|
19
|
+
# Replace all non-empty liquid tags with ≬{i}######≬ to prevent the HTML
|
20
|
+
# parser from freaking out. We transparently replace those placeholders in
|
21
|
+
# HtmlNode.
|
22
|
+
#
|
23
|
+
# We're using base36 to prevent index bleeding on 36^3 tags.
|
24
|
+
# `{{x}}` -> `≬#{i}≬` would properly be transformed for 46656 tags in a single file.
|
25
|
+
# Should be enough.
|
26
|
+
#
|
27
|
+
# The base10 alternative would have overflowed at 1000 (`{{x}}` -> `≬1000≬`) which seemed more likely.
|
28
|
+
#
|
29
|
+
# Didn't go with base64 because of the `=` character that would have messed with HTML parsing.
|
30
|
+
#
|
31
|
+
# (Note, we're also maintaining newline characters in there so
|
32
|
+
# that line numbers match the source...)
|
33
|
+
matches(parseable_source, LIQUID_TAG_OR_VARIABLE).each do |m|
|
34
|
+
value = m[0]
|
35
|
+
next unless value.size > 4 # skip empty tags/variables {%%} and {{}}
|
36
|
+
|
37
|
+
placeholder_values.push(value)
|
38
|
+
key = (placeholder_values.size - 1).to_s(36)
|
39
|
+
|
40
|
+
# Doing shenanigans so that line numbers match... Ugh.
|
41
|
+
keyed_placeholder = parseable_source[m.begin(0)...m.end(0)]
|
42
|
+
|
43
|
+
# First and last chars are ≬
|
44
|
+
keyed_placeholder[0] = "≬"
|
45
|
+
keyed_placeholder[-1] = "≬"
|
46
|
+
|
47
|
+
# Non newline characters are #
|
48
|
+
keyed_placeholder.gsub!(/[^\n≬]/, '#')
|
49
|
+
|
50
|
+
# First few # are replaced by the base10 ID of the tag
|
51
|
+
i = -1
|
52
|
+
keyed_placeholder.gsub!('#') do
|
53
|
+
i += 1
|
54
|
+
if i > key.size - 1
|
55
|
+
'#'
|
56
|
+
else
|
57
|
+
key[i]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Replace source by placeholder
|
62
|
+
parseable_source[m.begin(0)...m.end(0)] = keyed_placeholder
|
63
|
+
end
|
64
|
+
|
65
|
+
new(
|
66
|
+
Nokogiri::HTML5.fragment(parseable_source, max_tree_depth: 400, max_attributes: 400),
|
67
|
+
liquid_file,
|
68
|
+
placeholder_values,
|
69
|
+
parseable_source
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize(value, app_file, placeholder_values, parseable_source, parent = nil)
|
75
|
+
@value = value
|
76
|
+
@app_file = app_file
|
77
|
+
@placeholder_values = placeholder_values
|
78
|
+
@parseable_source = parseable_source
|
79
|
+
@parent = parent
|
80
|
+
end
|
81
|
+
|
82
|
+
# @value is not forwarded because we _need_ to replace the
|
83
|
+
# placeholders for the HtmlNode to make sense.
|
84
|
+
def value
|
85
|
+
if literal?
|
86
|
+
content
|
87
|
+
else
|
88
|
+
markup
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def children
|
93
|
+
@children ||= @value
|
94
|
+
.children
|
95
|
+
.map { |child| HtmlNode.new(child, app_file, @placeholder_values, @parseable_source, self) }
|
96
|
+
end
|
97
|
+
|
98
|
+
def markup
|
99
|
+
@markup ||= replace_placeholders(parseable_markup)
|
100
|
+
end
|
101
|
+
|
102
|
+
def line_number
|
103
|
+
@value.line
|
104
|
+
end
|
105
|
+
|
106
|
+
def start_index
|
107
|
+
position.start_index
|
108
|
+
end
|
109
|
+
|
110
|
+
def end_index
|
111
|
+
position.end_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_row
|
123
|
+
position.end_row
|
124
|
+
end
|
125
|
+
|
126
|
+
def end_column
|
127
|
+
position.end_column
|
128
|
+
end
|
129
|
+
|
130
|
+
def literal?
|
131
|
+
@value.name == "text"
|
132
|
+
end
|
133
|
+
|
134
|
+
def element?
|
135
|
+
@value.element?
|
136
|
+
end
|
137
|
+
|
138
|
+
def attributes
|
139
|
+
@attributes ||= @value.attributes
|
140
|
+
.map { |k, v| [replace_placeholders(k), replace_placeholders(v.value)] }
|
141
|
+
.to_h
|
142
|
+
end
|
143
|
+
|
144
|
+
def parseable_markup
|
145
|
+
return @parseable_source if @value.name == "#document-fragment"
|
146
|
+
return @value.to_str if @value.comment?
|
147
|
+
return @value.content if literal?
|
148
|
+
|
149
|
+
start_index = from_row_column_to_index(@parseable_source, line_number - 1, 0)
|
150
|
+
@parseable_source
|
151
|
+
.match(/<\s*#{name}[^>]*>/im, start_index)[0]
|
152
|
+
rescue NoMethodError
|
153
|
+
# Don't know what's up with the following issue. Don't think
|
154
|
+
# null check is correct approach. This should give us more info.
|
155
|
+
# https://github.com/Platform-OS/platformos-lsp/issues/528
|
156
|
+
PlatformosCheck.bug(<<~MSG)
|
157
|
+
Can't find a parseable tag of name #{name} inside the parseable HTML.
|
158
|
+
|
159
|
+
Tag name:
|
160
|
+
#{@value.name.inspect}
|
161
|
+
|
162
|
+
File:
|
163
|
+
#{@app_file.relative_path}
|
164
|
+
|
165
|
+
Line number:
|
166
|
+
#{line_number}
|
167
|
+
|
168
|
+
Excerpt:
|
169
|
+
```
|
170
|
+
#{@app_file.source.lines[line_number - 1...line_number + 5].join("")}
|
171
|
+
```
|
172
|
+
|
173
|
+
Parseable Excerpt:
|
174
|
+
```
|
175
|
+
#{@parseable_source.lines[line_number - 1...line_number + 5].join("")}
|
176
|
+
```
|
177
|
+
MSG
|
178
|
+
end
|
179
|
+
|
180
|
+
def content
|
181
|
+
@content ||= replace_placeholders(@value.content)
|
182
|
+
end
|
183
|
+
|
184
|
+
def name
|
185
|
+
if @value.name == "#document-fragment"
|
186
|
+
"document"
|
187
|
+
else
|
188
|
+
@value.name
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def position
|
195
|
+
@position ||= Position.new(
|
196
|
+
markup,
|
197
|
+
app_file.source,
|
198
|
+
line_number_1_indexed: line_number
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
202
|
+
def replace_placeholders(string)
|
203
|
+
# Replace all ≬{i}####≬ with the actual content.
|
204
|
+
string.gsub(HTML_LIQUID_PLACEHOLDER) do |match|
|
205
|
+
key = /[0-9a-z]+/.match(match.delete("\n"))[0]
|
206
|
+
@placeholder_values[key.to_i(36)]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
module PlatformosCheck
|
7
|
+
class HtmlVisitor
|
8
|
+
attr_reader :checks
|
9
|
+
|
10
|
+
def initialize(checks)
|
11
|
+
@checks = checks
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_liquid_file(liquid_file)
|
15
|
+
visit(HtmlNode.parse(liquid_file))
|
16
|
+
rescue ArgumentError => e
|
17
|
+
call_checks(:on_parse_error, e, liquid_file)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def visit(node)
|
23
|
+
call_checks(:on_element, node) if node.element?
|
24
|
+
call_checks(:"on_#{node.name}", node)
|
25
|
+
node.children.each { |child| visit(child) }
|
26
|
+
return if node.literal?
|
27
|
+
|
28
|
+
call_checks(:"after_#{node.name}", node)
|
29
|
+
call_checks(:after_element, node) if node.element?
|
30
|
+
end
|
31
|
+
|
32
|
+
def call_checks(method, *)
|
33
|
+
checks.call(method, *)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# An in-memory storage is not written to disk. The reasons why you'd
|
4
|
+
# want to do that are your own. The idea is to not write to disk
|
5
|
+
# something that doesn't need to be there. If you have your platformos_app
|
6
|
+
# as a big hash already, leave it like that and save yourself some IO.
|
7
|
+
module PlatformosCheck
|
8
|
+
class InMemoryStorage < Storage
|
9
|
+
attr_reader :root
|
10
|
+
|
11
|
+
def initialize(files = {}, root = "/dev/null")
|
12
|
+
@files = files # Hash<String, String>
|
13
|
+
@root = Pathname.new(root)
|
14
|
+
end
|
15
|
+
|
16
|
+
def path(relative_path)
|
17
|
+
@root.join(relative_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def read(relative_path)
|
21
|
+
@files[relative_path]
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(relative_path, content)
|
25
|
+
@files[relative_path] = content
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove(relative_path)
|
29
|
+
@files.delete(relative_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def mkdir(relative_path)
|
33
|
+
@files[relative_path] = nil
|
34
|
+
reset_memoizers
|
35
|
+
end
|
36
|
+
|
37
|
+
# TODO: Fix corrector
|
38
|
+
# def rename(old_path, new_path)
|
39
|
+
# old_path += '/' if old_path[-1] != '/'
|
40
|
+
# new_path += '/' if new_path[-1] != '/'
|
41
|
+
# @files.transform_keys! { |k| k.sub(/\A#{old_path}/, new_path) }
|
42
|
+
#
|
43
|
+
# reset_memoizers
|
44
|
+
# end
|
45
|
+
|
46
|
+
def files
|
47
|
+
@files.keys
|
48
|
+
end
|
49
|
+
|
50
|
+
def directories
|
51
|
+
@directories ||= @files
|
52
|
+
.keys
|
53
|
+
.flat_map { |relative_path| Pathname.new(relative_path).ascend.to_a }
|
54
|
+
.map(&:to_s)
|
55
|
+
.uniq
|
56
|
+
end
|
57
|
+
|
58
|
+
def relative_path(absolute_path)
|
59
|
+
Pathname.new(absolute_path).relative_path_from(@root).to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def reset_memoizers
|
65
|
+
@directories = nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module PlatformosCheck
|
6
|
+
class JsonFile < AppFile
|
7
|
+
def initialize(relative_path, storage)
|
8
|
+
super
|
9
|
+
@loaded = false
|
10
|
+
@content = nil
|
11
|
+
@parser_error = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def content
|
15
|
+
load!
|
16
|
+
@content
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_error
|
20
|
+
load!
|
21
|
+
@parser_error
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_contents(new_content = {})
|
25
|
+
raise ArgumentError if new_content.is_a?(String)
|
26
|
+
|
27
|
+
@content = new_content
|
28
|
+
end
|
29
|
+
|
30
|
+
def write
|
31
|
+
pretty = JSON.pretty_generate(@content)
|
32
|
+
return unless source.rstrip != pretty.rstrip
|
33
|
+
|
34
|
+
# Most editors add a trailing \n at the end of files. Here we
|
35
|
+
# try to maintain the convention.
|
36
|
+
eof = source.end_with?("\n") ? "\n" : ""
|
37
|
+
@storage.write(@relative_path, pretty.gsub("\n", @eol) + eof)
|
38
|
+
@source = pretty
|
39
|
+
end
|
40
|
+
|
41
|
+
def json?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def load!
|
48
|
+
return if @loaded
|
49
|
+
|
50
|
+
@content = JSON.parse(source)
|
51
|
+
rescue JSON::ParserError => e
|
52
|
+
@parser_error = e
|
53
|
+
ensure
|
54
|
+
@loaded = true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlatformosCheck
|
4
|
+
class JsonHelper
|
5
|
+
# Deeply sets a value in a hash. Accepts both arrays and strings for path.
|
6
|
+
def self.set(hash, path, value)
|
7
|
+
path = path.split('.') if path.is_a?(String)
|
8
|
+
path.each_with_index.reduce(hash) do |pointer, (token, index)|
|
9
|
+
if index == path.size - 1
|
10
|
+
pointer[token] = value
|
11
|
+
elsif !pointer.key?(token) || !pointer[token].is_a?(Hash)
|
12
|
+
pointer[token] = {}
|
13
|
+
end
|
14
|
+
pointer[token]
|
15
|
+
end
|
16
|
+
hash
|
17
|
+
end
|
18
|
+
|
19
|
+
# Deeply delete a key from a hash
|
20
|
+
def self.delete(hash, path)
|
21
|
+
path = path.split('.') if path.is_a?(String)
|
22
|
+
path.each_with_index.reduce(hash) do |pointer, (token, index)|
|
23
|
+
break pointer.delete(token) if index == path.size - 1
|
24
|
+
|
25
|
+
pointer[token]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Deeply add key/values inside a hash.
|
30
|
+
#
|
31
|
+
# Handles arrays by adding the key/value to all hashes inside the array.
|
32
|
+
#
|
33
|
+
# Specially handles objects that have the "id" key like this:
|
34
|
+
#
|
35
|
+
# e.g.
|
36
|
+
#
|
37
|
+
# json = {
|
38
|
+
# "deep" => [
|
39
|
+
# { "id" => "hi" },
|
40
|
+
# { "id" => "oh" },
|
41
|
+
# ],
|
42
|
+
# }
|
43
|
+
# assert_equal(
|
44
|
+
# {
|
45
|
+
# "deep" => [
|
46
|
+
# { "id" => "hi", "ho" => "ho" },
|
47
|
+
# { "id" => "oh" },
|
48
|
+
# ],
|
49
|
+
# },
|
50
|
+
# JsonHelper.json_corrector(json, "deep.hi.ho", "ho")
|
51
|
+
# )
|
52
|
+
def self.json_corrector(json, path, value)
|
53
|
+
return json unless json.is_a?(Hash)
|
54
|
+
|
55
|
+
path = path.split('.') if path.is_a?(String)
|
56
|
+
path.each_with_index.reduce(json) do |pointer, (token, index)|
|
57
|
+
case pointer
|
58
|
+
when Array
|
59
|
+
pointer.each do |item|
|
60
|
+
json_corrector(item, path.drop(1), value)
|
61
|
+
end
|
62
|
+
|
63
|
+
when Hash
|
64
|
+
break pointer[token] = value if index == path.size - 1
|
65
|
+
|
66
|
+
pointer[token] = {} unless pointer.key?(token) || pointer.key?("id")
|
67
|
+
pointer[token].nil? && pointer["id"] == token ? pointer : pointer[token]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
json
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlatformosCheck
|
4
|
+
module JsonHelpers
|
5
|
+
def format_json_parse_error(error)
|
6
|
+
message = error.message[/\d+: (.+)$/, 1] || 'Invalid syntax'
|
7
|
+
"#{message} in JSON"
|
8
|
+
end
|
9
|
+
|
10
|
+
def pretty_json(hash, start_level: 1, indent: " ")
|
11
|
+
start_indent = indent * start_level
|
12
|
+
|
13
|
+
<<~JSON
|
14
|
+
|
15
|
+
#{start_indent}#{JSON.pretty_generate(
|
16
|
+
hash,
|
17
|
+
indent:,
|
18
|
+
array_nl: "\n#{start_indent}",
|
19
|
+
object_nl: "\n#{start_indent}"
|
20
|
+
)}
|
21
|
+
JSON
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module PlatformosCheck
|
6
|
+
class JsonPrinter
|
7
|
+
def initialize(out_stream = STDOUT)
|
8
|
+
@out = out_stream
|
9
|
+
end
|
10
|
+
|
11
|
+
def print(offenses)
|
12
|
+
json = offenses_by_path(offenses)
|
13
|
+
@out.puts JSON.dump(json)
|
14
|
+
end
|
15
|
+
|
16
|
+
def offenses_by_path(offenses)
|
17
|
+
offenses
|
18
|
+
.map(&:to_h)
|
19
|
+
.group_by { |offense| offense[:path] }
|
20
|
+
.map do |(path, path_offenses)|
|
21
|
+
{
|
22
|
+
path:,
|
23
|
+
offenses: path_offenses.map { |offense| offense.filter { |k, _v| k != :path } },
|
24
|
+
errorCount: path_offenses.count { |offense| offense[:severity] == Check::SEVERITY_VALUES[:error] },
|
25
|
+
suggestionCount: path_offenses.count { |offense| offense[:severity] == Check::SEVERITY_VALUES[:suggestion] },
|
26
|
+
styleCount: path_offenses.count { |offense| offense[:severity] == Check::SEVERITY_VALUES[:style] }
|
27
|
+
}
|
28
|
+
end
|
29
|
+
.sort_by { |o| o[:path] || Pathname.new('') }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "timeout"
|
4
|
+
|
5
|
+
# This class exists as a bridge (or boundary) between our handlers and the outside world.
|
6
|
+
#
|
7
|
+
# It is concerned with all the Language Server Protocol constructs. i.e.
|
8
|
+
#
|
9
|
+
# - sending Hash messages as JSON
|
10
|
+
# - reading JSON messages as Hashes
|
11
|
+
# - preparing, sending and resolving requests
|
12
|
+
# - preparing and sending responses
|
13
|
+
# - preparing and sending notifications
|
14
|
+
# - preparing and sending progress notifications
|
15
|
+
#
|
16
|
+
# But it _not_ concerned by _how_ those messages are sent to the
|
17
|
+
# outside world. That's the job of the messenger.
|
18
|
+
#
|
19
|
+
# This enables us to have all the language server protocol logic
|
20
|
+
# in here living independently of how we communicate with the
|
21
|
+
# client (STDIO or websocket)
|
22
|
+
module PlatformosCheck
|
23
|
+
module LanguageServer
|
24
|
+
class Bridge
|
25
|
+
attr_writer :supports_work_done_progress
|
26
|
+
|
27
|
+
def initialize(messenger)
|
28
|
+
# The messenger is responsible for IO.
|
29
|
+
# Could be STDIO or WebSockets or Mock.
|
30
|
+
@messenger = messenger
|
31
|
+
|
32
|
+
# Whether the client supports work done progress notifications
|
33
|
+
@supports_work_done_progress = false
|
34
|
+
|
35
|
+
@work_done_progress_mutex = Mutex.new
|
36
|
+
@work_done_progress_token = 1
|
37
|
+
end
|
38
|
+
|
39
|
+
def log(message)
|
40
|
+
@messenger.log(message)
|
41
|
+
end
|
42
|
+
|
43
|
+
def read_message
|
44
|
+
message_body = @messenger.read_message
|
45
|
+
message_json = JSON.parse(message_body, symbolize_names: true)
|
46
|
+
@messenger.log(JSON.pretty_generate(message_json)) if PlatformosCheck.debug?
|
47
|
+
message_json
|
48
|
+
end
|
49
|
+
|
50
|
+
def send_message(message_hash)
|
51
|
+
message_hash[:jsonrpc] = '2.0'
|
52
|
+
message_body = JSON.dump(message_hash)
|
53
|
+
@messenger.log(JSON.pretty_generate(message_hash)) if PlatformosCheck.debug?
|
54
|
+
@messenger.send_message(message_body)
|
55
|
+
end
|
56
|
+
|
57
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#requestMessage
|
58
|
+
def send_request(method, params = nil)
|
59
|
+
channel = Channel.create
|
60
|
+
message = { id: channel.id }
|
61
|
+
message[:method] = method
|
62
|
+
message[:params] = params if params
|
63
|
+
send_message(message)
|
64
|
+
channel.pop
|
65
|
+
ensure
|
66
|
+
channel.close
|
67
|
+
end
|
68
|
+
|
69
|
+
def receive_response(id, result)
|
70
|
+
Channel.by_id(id) << result
|
71
|
+
end
|
72
|
+
|
73
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#responseMessage
|
74
|
+
def send_response(id, result = nil, error = nil)
|
75
|
+
message = { id: }
|
76
|
+
if error
|
77
|
+
message[:error] = error
|
78
|
+
else
|
79
|
+
message[:result] = result
|
80
|
+
end
|
81
|
+
send_message(message)
|
82
|
+
end
|
83
|
+
|
84
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#responseError
|
85
|
+
def send_internal_error(id, e)
|
86
|
+
# For a reason I can't comprehend, sometimes
|
87
|
+
# e.full_message _hangs_ and brings your CPU to 100%.
|
88
|
+
# It's wrapped in here because it prints anyway...
|
89
|
+
# This shit is weird, yo.
|
90
|
+
Timeout.timeout(1) do
|
91
|
+
warn e.full_message
|
92
|
+
end
|
93
|
+
ensure
|
94
|
+
send_response(id, nil, {
|
95
|
+
code: ErrorCodes::INTERNAL_ERROR,
|
96
|
+
message: "A platformos-check-language-server has occured, inspect OUTPUT logs for details."
|
97
|
+
})
|
98
|
+
end
|
99
|
+
|
100
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
|
101
|
+
def send_notification(method, params)
|
102
|
+
message = { method: }
|
103
|
+
message[:params] = params
|
104
|
+
send_message(message)
|
105
|
+
end
|
106
|
+
|
107
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
|
108
|
+
def send_progress(token, value)
|
109
|
+
send_notification("$/progress", token:, value:)
|
110
|
+
end
|
111
|
+
|
112
|
+
def supports_work_done_progress?
|
113
|
+
@supports_work_done_progress
|
114
|
+
end
|
115
|
+
|
116
|
+
def send_create_work_done_progress_request
|
117
|
+
# This isn't necessary, but it kind of is to make it obvious
|
118
|
+
# that this variable is not thread safe. Don't try to refactor
|
119
|
+
# this with @work_done_progress_token because you're going to
|
120
|
+
# have a hard time.
|
121
|
+
token = @work_done_progress_mutex.synchronize do
|
122
|
+
@work_done_progress_token += 1
|
123
|
+
end
|
124
|
+
|
125
|
+
return token unless supports_work_done_progress?
|
126
|
+
|
127
|
+
# We're going to wait for a response here...
|
128
|
+
send_request("window/workDoneProgress/create", {
|
129
|
+
token:
|
130
|
+
})
|
131
|
+
|
132
|
+
token
|
133
|
+
end
|
134
|
+
|
135
|
+
def send_work_done_progress_begin(token, title)
|
136
|
+
return unless supports_work_done_progress?
|
137
|
+
|
138
|
+
send_progress(token, {
|
139
|
+
kind: 'begin',
|
140
|
+
title:,
|
141
|
+
cancellable: false,
|
142
|
+
percentage: 0
|
143
|
+
})
|
144
|
+
end
|
145
|
+
|
146
|
+
def send_work_done_progress_report(token, message, percentage)
|
147
|
+
return unless supports_work_done_progress?
|
148
|
+
|
149
|
+
send_progress(token, {
|
150
|
+
kind: 'report',
|
151
|
+
message:,
|
152
|
+
cancellable: false,
|
153
|
+
percentage:
|
154
|
+
})
|
155
|
+
end
|
156
|
+
|
157
|
+
def send_work_done_progress_end(token, message)
|
158
|
+
return unless supports_work_done_progress?
|
159
|
+
|
160
|
+
send_progress(token, {
|
161
|
+
kind: 'end',
|
162
|
+
message:
|
163
|
+
})
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|