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.
- 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
|