platformos-check 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +2 -0
  3. data/.gitignore +22 -0
  4. data/.rubocop.yml +555 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/CONTRIBUTING.md +209 -0
  8. data/Gemfile +33 -0
  9. data/Guardfile +7 -0
  10. data/LICENSE.md +10 -0
  11. data/Makefile +18 -0
  12. data/README.md +189 -0
  13. data/RELEASING.md +35 -0
  14. data/Rakefile +83 -0
  15. data/TROUBLESHOOTING.md +35 -0
  16. data/bin/platformos-check +29 -0
  17. data/bin/platformos-check-language-server +29 -0
  18. data/config/default.yml +98 -0
  19. data/config/nothing.yml +11 -0
  20. data/data/platformos_liquid/built_in_liquid_objects.json +66 -0
  21. data/data/platformos_liquid/deprecated_filters.json +22 -0
  22. data/data/platformos_liquid/documentation/filters.json +6 -0
  23. data/data/platformos_liquid/documentation/latest.json +2 -0
  24. data/data/platformos_liquid/documentation/objects.json +6 -0
  25. data/data/platformos_liquid/documentation/tags.json +6 -0
  26. data/docker/check.Dockerfile +3 -0
  27. data/docker/lsp.Dockerfile +21 -0
  28. data/docs/api/check.md +15 -0
  29. data/docs/api/html_check.md +46 -0
  30. data/docs/api/liquid_check.md +115 -0
  31. data/docs/api/yaml_check.md +19 -0
  32. data/docs/checks/TEMPLATE.md.erb +52 -0
  33. data/docs/checks/convert_include_to_render.md +48 -0
  34. data/docs/checks/deprecated_filter.md +30 -0
  35. data/docs/checks/html_parsing_error.md +50 -0
  36. data/docs/checks/img_lazy_loading.md +63 -0
  37. data/docs/checks/img_width_and_height.md +79 -0
  38. data/docs/checks/invalid_args.md +56 -0
  39. data/docs/checks/liquid_tag.md +65 -0
  40. data/docs/checks/missing_enable_comment.md +50 -0
  41. data/docs/checks/missing_template.md +65 -0
  42. data/docs/checks/parse_json_format.md +76 -0
  43. data/docs/checks/parser_blocking_javascript.md +97 -0
  44. data/docs/checks/required_layout_object.md +28 -0
  45. data/docs/checks/space_inside_braces.md +89 -0
  46. data/docs/checks/syntax_error.md +49 -0
  47. data/docs/checks/template_length.md +45 -0
  48. data/docs/checks/undefined_object.md +71 -0
  49. data/docs/checks/unknown_filter.md +46 -0
  50. data/docs/checks/unused_assign.md +63 -0
  51. data/docs/checks/unused_partial.md +32 -0
  52. data/docs/checks/valid_yaml.md +48 -0
  53. data/docs/flamegraph.svg +18488 -0
  54. data/docs/language_server/code-action-command-palette.png +0 -0
  55. data/docs/language_server/code-action-flow.png +0 -0
  56. data/docs/language_server/code-action-keyboard.png +0 -0
  57. data/docs/language_server/code-action-light-bulb.png +0 -0
  58. data/docs/language_server/code-action-problem.png +0 -0
  59. data/docs/language_server/code-action-quickfix.png +0 -0
  60. data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
  61. data/docs/preview.png +0 -0
  62. data/exe/platformos-check +6 -0
  63. data/exe/platformos-check-language-server +7 -0
  64. data/lib/platformos_check/analyzer.rb +178 -0
  65. data/lib/platformos_check/api_call_file.rb +9 -0
  66. data/lib/platformos_check/app.rb +138 -0
  67. data/lib/platformos_check/app_file.rb +89 -0
  68. data/lib/platformos_check/app_file_rewriter.rb +79 -0
  69. data/lib/platformos_check/asset_file.rb +34 -0
  70. data/lib/platformos_check/bug.rb +23 -0
  71. data/lib/platformos_check/check.rb +163 -0
  72. data/lib/platformos_check/checks/TEMPLATE.rb.erb +11 -0
  73. data/lib/platformos_check/checks/convert_include_to_render.rb +17 -0
  74. data/lib/platformos_check/checks/deprecated_filter.rb +123 -0
  75. data/lib/platformos_check/checks/html_parsing_error.rb +13 -0
  76. data/lib/platformos_check/checks/img_lazy_loading.rb +18 -0
  77. data/lib/platformos_check/checks/img_width_and_height.rb +46 -0
  78. data/lib/platformos_check/checks/invalid_args.rb +81 -0
  79. data/lib/platformos_check/checks/liquid_tag.rb +47 -0
  80. data/lib/platformos_check/checks/missing_enable_comment.rb +37 -0
  81. data/lib/platformos_check/checks/missing_template.rb +107 -0
  82. data/lib/platformos_check/checks/parse_json_format.rb +31 -0
  83. data/lib/platformos_check/checks/parser_blocking_javascript.rb +17 -0
  84. data/lib/platformos_check/checks/required_layout_object.rb +41 -0
  85. data/lib/platformos_check/checks/space_inside_braces.rb +150 -0
  86. data/lib/platformos_check/checks/syntax_error.rb +31 -0
  87. data/lib/platformos_check/checks/template_length.rb +20 -0
  88. data/lib/platformos_check/checks/undefined_object.rb +206 -0
  89. data/lib/platformos_check/checks/unknown_filter.rb +27 -0
  90. data/lib/platformos_check/checks/unused_assign.rb +101 -0
  91. data/lib/platformos_check/checks/unused_partial.rb +93 -0
  92. data/lib/platformos_check/checks/valid_yaml.rb +16 -0
  93. data/lib/platformos_check/checks.rb +73 -0
  94. data/lib/platformos_check/checks_tracking.rb +9 -0
  95. data/lib/platformos_check/cli.rb +239 -0
  96. data/lib/platformos_check/config.rb +219 -0
  97. data/lib/platformos_check/config_file.rb +6 -0
  98. data/lib/platformos_check/corrector.rb +68 -0
  99. data/lib/platformos_check/disabled_check.rb +44 -0
  100. data/lib/platformos_check/disabled_checks.rb +96 -0
  101. data/lib/platformos_check/email_file.rb +9 -0
  102. data/lib/platformos_check/exceptions.rb +36 -0
  103. data/lib/platformos_check/file_system_storage.rb +93 -0
  104. data/lib/platformos_check/graphql_file.rb +68 -0
  105. data/lib/platformos_check/html_check.rb +8 -0
  106. data/lib/platformos_check/html_node.rb +210 -0
  107. data/lib/platformos_check/html_visitor.rb +36 -0
  108. data/lib/platformos_check/in_memory_storage.rb +68 -0
  109. data/lib/platformos_check/json_file.rb +57 -0
  110. data/lib/platformos_check/json_helper.rb +73 -0
  111. data/lib/platformos_check/json_helpers.rb +24 -0
  112. data/lib/platformos_check/json_printer.rb +32 -0
  113. data/lib/platformos_check/language_server/bridge.rb +167 -0
  114. data/lib/platformos_check/language_server/channel.rb +69 -0
  115. data/lib/platformos_check/language_server/client_capabilities.rb +27 -0
  116. data/lib/platformos_check/language_server/code_action_engine.rb +32 -0
  117. data/lib/platformos_check/language_server/code_action_provider.rb +41 -0
  118. data/lib/platformos_check/language_server/code_action_providers/quickfix_code_action_provider.rb +85 -0
  119. data/lib/platformos_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +41 -0
  120. data/lib/platformos_check/language_server/completion_context.rb +52 -0
  121. data/lib/platformos_check/language_server/completion_engine.rb +32 -0
  122. data/lib/platformos_check/language_server/completion_helper.rb +26 -0
  123. data/lib/platformos_check/language_server/completion_provider.rb +53 -0
  124. data/lib/platformos_check/language_server/completion_providers/assignments_completion_provider.rb +40 -0
  125. data/lib/platformos_check/language_server/completion_providers/filter_completion_provider.rb +102 -0
  126. data/lib/platformos_check/language_server/completion_providers/object_attribute_completion_provider.rb +48 -0
  127. data/lib/platformos_check/language_server/completion_providers/object_completion_provider.rb +38 -0
  128. data/lib/platformos_check/language_server/completion_providers/render_snippet_completion_provider.rb +50 -0
  129. data/lib/platformos_check/language_server/completion_providers/tag_completion_provider.rb +41 -0
  130. data/lib/platformos_check/language_server/configuration.rb +89 -0
  131. data/lib/platformos_check/language_server/constants.rb +29 -0
  132. data/lib/platformos_check/language_server/diagnostic.rb +129 -0
  133. data/lib/platformos_check/language_server/diagnostics_engine.rb +131 -0
  134. data/lib/platformos_check/language_server/diagnostics_manager.rb +184 -0
  135. data/lib/platformos_check/language_server/document_change_corrector.rb +271 -0
  136. data/lib/platformos_check/language_server/document_link_engine.rb +21 -0
  137. data/lib/platformos_check/language_server/document_link_provider.rb +71 -0
  138. data/lib/platformos_check/language_server/document_link_providers/asset_document_link_provider.rb +11 -0
  139. data/lib/platformos_check/language_server/document_link_providers/include_document_link_provider.rb +11 -0
  140. data/lib/platformos_check/language_server/document_link_providers/render_document_link_provider.rb +11 -0
  141. data/lib/platformos_check/language_server/document_link_providers/section_document_link_provider.rb +11 -0
  142. data/lib/platformos_check/language_server/execute_command_engine.rb +19 -0
  143. data/lib/platformos_check/language_server/execute_command_provider.rb +30 -0
  144. data/lib/platformos_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
  145. data/lib/platformos_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +28 -0
  146. data/lib/platformos_check/language_server/handler.rb +310 -0
  147. data/lib/platformos_check/language_server/hover_engine.rb +32 -0
  148. data/lib/platformos_check/language_server/hover_provider.rb +53 -0
  149. data/lib/platformos_check/language_server/hover_providers/filter_hover_provider.rb +113 -0
  150. data/lib/platformos_check/language_server/io_messenger.rb +109 -0
  151. data/lib/platformos_check/language_server/messenger.rb +27 -0
  152. data/lib/platformos_check/language_server/protocol.rb +55 -0
  153. data/lib/platformos_check/language_server/server.rb +188 -0
  154. data/lib/platformos_check/language_server/tokens.rb +55 -0
  155. data/lib/platformos_check/language_server/type_helper.rb +22 -0
  156. data/lib/platformos_check/language_server/uri_helper.rb +39 -0
  157. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/node_handler.rb +87 -0
  158. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/scope.rb +60 -0
  159. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder/scope_visitor.rb +44 -0
  160. data/lib/platformos_check/language_server/variable_lookup_finder/assignments_finder.rb +76 -0
  161. data/lib/platformos_check/language_server/variable_lookup_finder/constants.rb +44 -0
  162. data/lib/platformos_check/language_server/variable_lookup_finder/liquid_fixer.rb +103 -0
  163. data/lib/platformos_check/language_server/variable_lookup_finder/potential_lookup.rb +10 -0
  164. data/lib/platformos_check/language_server/variable_lookup_finder/tolerant_parser.rb +94 -0
  165. data/lib/platformos_check/language_server/variable_lookup_finder.rb +262 -0
  166. data/lib/platformos_check/language_server/variable_lookup_traverser.rb +70 -0
  167. data/lib/platformos_check/language_server/versioned_in_memory_storage.rb +84 -0
  168. data/lib/platformos_check/language_server.rb +71 -0
  169. data/lib/platformos_check/layout_file.rb +15 -0
  170. data/lib/platformos_check/liquid_check.rb +10 -0
  171. data/lib/platformos_check/liquid_file.rb +102 -0
  172. data/lib/platformos_check/liquid_node.rb +570 -0
  173. data/lib/platformos_check/liquid_visitor.rb +39 -0
  174. data/lib/platformos_check/migration_file.rb +9 -0
  175. data/lib/platformos_check/node.rb +53 -0
  176. data/lib/platformos_check/offense.rb +228 -0
  177. data/lib/platformos_check/page_file.rb +9 -0
  178. data/lib/platformos_check/parsing_helpers.rb +21 -0
  179. data/lib/platformos_check/partial_file.rb +15 -0
  180. data/lib/platformos_check/platformos_liquid/deprecated_filter.rb +31 -0
  181. data/lib/platformos_check/platformos_liquid/documentation/markdown_template.rb +51 -0
  182. data/lib/platformos_check/platformos_liquid/documentation.rb +45 -0
  183. data/lib/platformos_check/platformos_liquid/filter.rb +19 -0
  184. data/lib/platformos_check/platformos_liquid/object.rb +15 -0
  185. data/lib/platformos_check/platformos_liquid/source_index/base_entry.rb +66 -0
  186. data/lib/platformos_check/platformos_liquid/source_index/base_state.rb +23 -0
  187. data/lib/platformos_check/platformos_liquid/source_index/filter_entry.rb +26 -0
  188. data/lib/platformos_check/platformos_liquid/source_index/filter_state.rb +11 -0
  189. data/lib/platformos_check/platformos_liquid/source_index/object_entry.rb +20 -0
  190. data/lib/platformos_check/platformos_liquid/source_index/object_state.rb +11 -0
  191. data/lib/platformos_check/platformos_liquid/source_index/parameter_entry.rb +25 -0
  192. data/lib/platformos_check/platformos_liquid/source_index/property_entry.rb +21 -0
  193. data/lib/platformos_check/platformos_liquid/source_index/return_type_entry.rb +41 -0
  194. data/lib/platformos_check/platformos_liquid/source_index/tag_entry.rb +24 -0
  195. data/lib/platformos_check/platformos_liquid/source_index/tag_state.rb +11 -0
  196. data/lib/platformos_check/platformos_liquid/source_index.rb +79 -0
  197. data/lib/platformos_check/platformos_liquid/source_manager.rb +116 -0
  198. data/lib/platformos_check/platformos_liquid/tag.rb +59 -0
  199. data/lib/platformos_check/platformos_liquid.rb +21 -0
  200. data/lib/platformos_check/position.rb +180 -0
  201. data/lib/platformos_check/position_helper.rb +57 -0
  202. data/lib/platformos_check/printer.rb +87 -0
  203. data/lib/platformos_check/regex_helpers.rb +21 -0
  204. data/lib/platformos_check/releaser.rb +43 -0
  205. data/lib/platformos_check/schema_file.rb +6 -0
  206. data/lib/platformos_check/sms_file.rb +9 -0
  207. data/lib/platformos_check/storage.rb +29 -0
  208. data/lib/platformos_check/string_helpers.rb +48 -0
  209. data/lib/platformos_check/tags/background.rb +67 -0
  210. data/lib/platformos_check/tags/base.rb +14 -0
  211. data/lib/platformos_check/tags/base_block.rb +14 -0
  212. data/lib/platformos_check/tags/base_tag_methods.rb +59 -0
  213. data/lib/platformos_check/tags/cache.rb +13 -0
  214. data/lib/platformos_check/tags/export.rb +30 -0
  215. data/lib/platformos_check/tags/form.rb +19 -0
  216. data/lib/platformos_check/tags/function.rb +58 -0
  217. data/lib/platformos_check/tags/graphql.rb +70 -0
  218. data/lib/platformos_check/tags/hash_assign.rb +75 -0
  219. data/lib/platformos_check/tags/log.rb +15 -0
  220. data/lib/platformos_check/tags/parse_json.rb +24 -0
  221. data/lib/platformos_check/tags/print.rb +20 -0
  222. data/lib/platformos_check/tags/redirect_to.rb +15 -0
  223. data/lib/platformos_check/tags/render.rb +60 -0
  224. data/lib/platformos_check/tags/response_headers.rb +20 -0
  225. data/lib/platformos_check/tags/response_status.rb +20 -0
  226. data/lib/platformos_check/tags/return.rb +20 -0
  227. data/lib/platformos_check/tags/session.rb +27 -0
  228. data/lib/platformos_check/tags/sign_in.rb +27 -0
  229. data/lib/platformos_check/tags/spam_protection.rb +15 -0
  230. data/lib/platformos_check/tags/theme_render.rb +58 -0
  231. data/lib/platformos_check/tags/try.rb +59 -0
  232. data/lib/platformos_check/tags.rb +65 -0
  233. data/lib/platformos_check/translation_file.rb +6 -0
  234. data/lib/platformos_check/user_schema_file.rb +6 -0
  235. data/lib/platformos_check/version.rb +5 -0
  236. data/lib/platformos_check/yaml_check.rb +11 -0
  237. data/lib/platformos_check/yaml_file.rb +57 -0
  238. data/lib/platformos_check.rb +106 -0
  239. data/platformos-check.gemspec +34 -0
  240. metadata +329 -0
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ class RenderSnippetCompletionProvider < CompletionProvider
6
+ def completions(context)
7
+ content = context.content
8
+ cursor = context.cursor
9
+
10
+ return [] if content.nil?
11
+ return [] unless cursor_on_quoted_argument?(content, cursor)
12
+
13
+ partial = snippet(content) || ''
14
+ snippets
15
+ .select { |x| x.start_with?(partial) }
16
+ .map { |x| snippet_to_completion(x) }
17
+ end
18
+
19
+ private
20
+
21
+ def cursor_on_quoted_argument?(content, cursor)
22
+ match = content.match(PARTIAL_RENDER)
23
+ return false if match.nil?
24
+
25
+ match.begin(:partial) <= cursor && cursor <= match.end(:partial)
26
+ end
27
+
28
+ def snippet(content)
29
+ match = content.match(PARTIAL_RENDER)
30
+ return if match.nil?
31
+
32
+ match[:partial]
33
+ end
34
+
35
+ def snippets
36
+ @storage
37
+ .files
38
+ .select { |x| x.include?('snippets/') }
39
+ end
40
+
41
+ def snippet_to_completion(file)
42
+ {
43
+ label: File.basename(file, '.liquid'),
44
+ kind: CompletionItemKinds::SNIPPET,
45
+ detail: file
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ class TagCompletionProvider < CompletionProvider
6
+ def completions(context)
7
+ content = context.content
8
+ cursor = context.cursor
9
+
10
+ return [] if content.nil?
11
+ return [] unless can_complete?(content, cursor)
12
+
13
+ partial = first_word(content) || ''
14
+ labels = PlatformosLiquid::Tag.labels
15
+ labels += PlatformosLiquid::Tag.end_labels
16
+ labels
17
+ .select { |w| w.start_with?(partial) }
18
+ .map { |tag| tag_to_completion(tag) }
19
+ end
20
+
21
+ def can_complete?(content, cursor)
22
+ content.start_with?(Liquid::TagStart) && (
23
+ cursor_on_first_word?(content, cursor) ||
24
+ cursor_on_start_content?(content, cursor, Liquid::TagStart)
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ def tag_to_completion(tag)
31
+ content = PlatformosLiquid::Documentation.tag_doc(tag)
32
+
33
+ {
34
+ label: tag,
35
+ kind: CompletionItemKinds::KEYWORD,
36
+ **doc_hash(content)
37
+ }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ class Configuration
6
+ CHECK_ON_OPEN = :'platformosCheck.checkOnOpen'
7
+ CHECK_ON_SAVE = :'platformosCheck.checkOnSave'
8
+ CHECK_ON_CHANGE = :'platformosCheck.checkOnChange'
9
+ ONLY_SINGLE_FILE = :'platformosCheck.onlySingleFileChecks'
10
+
11
+ def initialize(bridge, capabilities)
12
+ @bridge = bridge
13
+ @capabilities = capabilities
14
+ @mutex = Mutex.new
15
+ @initialized = false
16
+ @config = {
17
+ CHECK_ON_OPEN => null_coalesce(@capabilities.initialization_option(CHECK_ON_OPEN), true),
18
+ CHECK_ON_SAVE => null_coalesce(@capabilities.initialization_option(CHECK_ON_SAVE), true),
19
+ CHECK_ON_CHANGE => null_coalesce(@capabilities.initialization_option(CHECK_ON_CHANGE), true),
20
+ ONLY_SINGLE_FILE => null_coalesce(@capabilities.initialization_option(ONLY_SINGLE_FILE), false)
21
+ }
22
+ end
23
+
24
+ def fetch(force: nil)
25
+ @mutex.synchronize do
26
+ return unless @capabilities.supports_workspace_configuration?
27
+ return if initialized? && !force
28
+
29
+ keys = [
30
+ CHECK_ON_OPEN,
31
+ CHECK_ON_SAVE,
32
+ CHECK_ON_CHANGE,
33
+ ONLY_SINGLE_FILE
34
+ ]
35
+
36
+ configs = @bridge.send_request(
37
+ "workspace/configuration",
38
+ items: keys.map do |key|
39
+ { section: key }
40
+ end
41
+ )
42
+
43
+ keys.each.with_index do |key, i|
44
+ @config[key] = configs[i] unless configs[i].nil?
45
+ end
46
+
47
+ @initialized = true
48
+ end
49
+ end
50
+
51
+ def register_did_change_capability
52
+ return unless @capabilities.supports_workspace_did_change_configuration_dynamic_registration?
53
+
54
+ @bridge.send_request('client/registerCapability', registrations: [{
55
+ id: "workspace/didChangeConfiguration",
56
+ method: "workspace/didChangeConfiguration"
57
+ }])
58
+ end
59
+
60
+ def initialized?
61
+ @initialized
62
+ end
63
+
64
+ def check_on_open?
65
+ fetch # making sure we have an initialized value
66
+ @config[CHECK_ON_OPEN]
67
+ end
68
+
69
+ def check_on_save?
70
+ fetch # making sure we have for an initialized value
71
+ @config[CHECK_ON_SAVE]
72
+ end
73
+
74
+ def check_on_change?
75
+ fetch # making sure we have for an initialized value
76
+ @config[CHECK_ON_CHANGE]
77
+ end
78
+
79
+ def only_single_file?
80
+ fetch # making sure we have for an initialized value
81
+ @config[ONLY_SINGLE_FILE]
82
+ end
83
+
84
+ def null_coalesce(value, default)
85
+ value.nil? ? default : value
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ def self.partial_tag(tag)
6
+ /
7
+ \{%-?\s*#{tag}\s+'(?<partial>[^']*)'|
8
+ \{%-?\s*#{tag}\s+"(?<partial>[^"]*)"|
9
+
10
+ # in liquid tags the whole line is white space until the tag
11
+ ^\s*#{tag}\s+'(?<partial>[^']*)'|
12
+ ^\s*#{tag}\s+"(?<partial>[^"]*)"
13
+ /mix
14
+ end
15
+
16
+ PARTIAL_RENDER = partial_tag('render')
17
+ PARTIAL_INCLUDE = partial_tag('include')
18
+ PARTIAL_SECTION = partial_tag('section')
19
+
20
+ ASSET_INCLUDE = /
21
+ \{\{-?\s*'(?<partial>[^']*)'\s*\|\s*asset_url|
22
+ \{\{-?\s*"(?<partial>[^"]*)"\s*\|\s*asset_url|
23
+
24
+ # in liquid tags the whole line is white space until the asset partial
25
+ ^\s*(?:echo|assign[^=]*=)\s*'(?<partial>[^']*)'\s*\|\s*asset_url|
26
+ ^\s*(?:echo|assign[^=]*=)\s*"(?<partial>[^"]*)"\s*\|\s*asset_url
27
+ /mix
28
+ end
29
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ class Diagnostic
6
+ include URIHelper
7
+
8
+ attr_reader :offense
9
+
10
+ def initialize(offense)
11
+ @offense = offense
12
+ @diagnostic = nil
13
+ end
14
+
15
+ def ==(other)
16
+ case other
17
+ when Hash, Diagnostic
18
+ to_h == other.to_h
19
+ else
20
+ raise ArgumentError
21
+ end
22
+ end
23
+
24
+ def to_h
25
+ return @diagnostic unless @diagnostic.nil?
26
+
27
+ @diagnostic = {
28
+ source: "platformos-check",
29
+ code:,
30
+ message:,
31
+ range:,
32
+ severity:,
33
+ data:
34
+ }
35
+ @diagnostic[:codeDescription] = code_description unless offense.doc.nil?
36
+ @diagnostic
37
+ end
38
+
39
+ def to_s
40
+ to_h.to_s
41
+ end
42
+
43
+ def single_file?
44
+ offense.single_file?
45
+ end
46
+
47
+ def whole_platformos_app?
48
+ offense.whole_platformos_app?
49
+ end
50
+
51
+ def correctable?
52
+ offense.correctable?
53
+ end
54
+
55
+ def code
56
+ offense.code_name
57
+ end
58
+
59
+ def message
60
+ offense.message
61
+ end
62
+
63
+ def code_description
64
+ {
65
+ href: offense.doc
66
+ }
67
+ end
68
+
69
+ def severity
70
+ case offense.severity
71
+ when :error
72
+ 1
73
+ when :suggestion
74
+ 2
75
+ when :style
76
+ 3
77
+ else
78
+ 4
79
+ end
80
+ end
81
+
82
+ def range
83
+ {
84
+ start: {
85
+ line: offense.start_row,
86
+ character: offense.start_column
87
+ },
88
+ end: {
89
+ line: offense.end_row,
90
+ character: offense.end_column
91
+ }
92
+ }
93
+ end
94
+
95
+ def start_index
96
+ offense.start_index
97
+ end
98
+
99
+ def end_index
100
+ offense.end_index
101
+ end
102
+
103
+ def absolute_path
104
+ @absolute_path ||= offense&.app_file&.path
105
+ end
106
+
107
+ def relative_path
108
+ @relative_path ||= offense&.app_file&.relative_path
109
+ end
110
+
111
+ def uri
112
+ @uri ||= absolute_path && file_uri(absolute_path)
113
+ end
114
+
115
+ def file_version
116
+ @version ||= offense&.version
117
+ end
118
+
119
+ def data
120
+ {
121
+ absolute_path: absolute_path.to_s,
122
+ relative_path: relative_path.to_s,
123
+ uri:,
124
+ version: file_version
125
+ }
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module LanguageServer
5
+ class DiagnosticsEngine
6
+ include URIHelper
7
+
8
+ attr_reader :storage
9
+
10
+ def initialize(storage, bridge, diagnostics_manager = DiagnosticsManager.new)
11
+ @diagnostics_lock = Mutex.new
12
+ @diagnostics_manager = diagnostics_manager
13
+ @storage = storage
14
+ @bridge = bridge
15
+ end
16
+
17
+ def first_run?
18
+ @diagnostics_manager.first_run?
19
+ end
20
+
21
+ def analyze_and_send_offenses(absolute_path_or_paths, config, force: false, only_single_file: false)
22
+ return unless @diagnostics_lock.try_lock
23
+
24
+ platformos_app = PlatformosCheck::App.new(storage)
25
+ analyzer = PlatformosCheck::Analyzer.new(platformos_app, config.enabled_checks)
26
+
27
+ if !only_single_file && (@diagnostics_manager.first_run? || force)
28
+ run_full_platformos_check(analyzer)
29
+ else
30
+ run_partial_platformos_check(absolute_path_or_paths, platformos_app, analyzer, only_single_file)
31
+ end
32
+
33
+ @diagnostics_lock.unlock
34
+ end
35
+
36
+ def clear_diagnostics(relative_paths)
37
+ return unless @diagnostics_lock.try_lock
38
+
39
+ as_array(relative_paths).each do |relative_path|
40
+ send_clearing_diagnostics(relative_path)
41
+ end
42
+
43
+ @diagnostics_lock.unlock
44
+ end
45
+
46
+ private
47
+
48
+ def run_full_platformos_check(analyzer)
49
+ raise 'Unsafe operation' unless @diagnostics_lock.owned?
50
+
51
+ token = @bridge.send_create_work_done_progress_request
52
+ @bridge.send_work_done_progress_begin(token, "Full platformos_app check")
53
+ @bridge.log("Checking #{storage.root}")
54
+ offenses = nil
55
+ time = Benchmark.measure do
56
+ offenses = analyzer.analyze_platformos_app do |path, i, total|
57
+ @bridge.send_work_done_progress_report(token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
58
+ end
59
+ end
60
+ end_message = "Found #{offenses.size} offenses in #{format("%0.2f", time.real)}s"
61
+ @bridge.send_work_done_progress_end(token, end_message)
62
+ @bridge.log(end_message)
63
+ send_diagnostics(offenses)
64
+ end
65
+
66
+ def run_partial_platformos_check(absolute_path_or_paths, platformos_app, analyzer, only_single_file)
67
+ raise 'Unsafe operation' unless @diagnostics_lock.owned?
68
+
69
+ relative_paths = as_array(absolute_path_or_paths).map do |absolute_path|
70
+ Pathname.new(storage.relative_path(absolute_path))
71
+ end
72
+
73
+ app_files = relative_paths
74
+ .map { |relative_path| platformos_app[relative_path] }
75
+ .reject(&:nil?)
76
+
77
+ deleted_relative_paths = relative_paths - app_files.map(&:relative_path)
78
+ deleted_relative_paths
79
+ .each { |p| send_clearing_diagnostics(p) }
80
+
81
+ token = @bridge.send_create_work_done_progress_request
82
+ @bridge.send_work_done_progress_begin(token, "Partial platformos_app check")
83
+ offenses = nil
84
+ time = Benchmark.measure do
85
+ offenses = analyzer.analyze_files(app_files, only_single_file:) do |path, i, total|
86
+ @bridge.send_work_done_progress_report(token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
87
+ end
88
+ end
89
+ end_message = "Found #{offenses.size} new offenses in #{format("%0.2f", time.real)}s"
90
+ @bridge.send_work_done_progress_end(token, end_message)
91
+ @bridge.log(end_message)
92
+ send_diagnostics(offenses, app_files.map(&:relative_path), only_single_file:)
93
+ end
94
+
95
+ def send_clearing_diagnostics(relative_path)
96
+ raise 'Unsafe operation' unless @diagnostics_lock.owned?
97
+
98
+ relative_path = Pathname.new(relative_path) unless relative_path.is_a?(Pathname)
99
+ @diagnostics_manager.clear_diagnostics(relative_path)
100
+ send_diagnostic(relative_path, DiagnosticsManager::NO_DIAGNOSTICS)
101
+ end
102
+
103
+ def as_array(maybe_array)
104
+ case maybe_array
105
+ when Array
106
+ maybe_array
107
+ else
108
+ [maybe_array]
109
+ end
110
+ end
111
+
112
+ def send_diagnostics(offenses, analyzed_files = nil, only_single_file: false)
113
+ @diagnostics_manager.build_diagnostics(
114
+ offenses,
115
+ analyzed_files:,
116
+ only_single_file:
117
+ ).each do |relative_path, diagnostics|
118
+ send_diagnostic(relative_path, diagnostics)
119
+ end
120
+ end
121
+
122
+ def send_diagnostic(relative_path, diagnostics)
123
+ # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
124
+ @bridge.send_notification('textDocument/publishDiagnostics', {
125
+ uri: file_uri(storage.path(relative_path)),
126
+ diagnostics: diagnostics.map(&:to_h)
127
+ })
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module PlatformosCheck
6
+ module LanguageServer
7
+ class DiagnosticsManager
8
+ # The empty array is used in the protocol to mean that no
9
+ # diagnostics exist for this file. It's not always evident when
10
+ # reading code.
11
+ NO_DIAGNOSTICS = [].freeze
12
+
13
+ # This class exists to facilitate LanguageServer diagnostics tracking.
14
+ #
15
+ # Motivations:
16
+ # 1. The first time we lint, we want all the errors from all the files.
17
+ # 2. If we fix all the errors in a file, we have to send an empty array for that file.
18
+ # 3. If we do a partial check, we should consider the whole platformos_app diagnostics as valid, and return cached results
19
+ # 4. We should be able to create WorkspaceEdits from diagnostics, so that the ExecuteCommandEngine can do its job
20
+ # 5. We should clean up diagnostics that were applied by the client
21
+ def initialize
22
+ @latest_diagnostics = {} # { [Pathname(relative_path)] => Diagnostic[] }
23
+ @mutex = Mutex.new
24
+ @first_run = true
25
+ end
26
+
27
+ def first_run?
28
+ @first_run
29
+ end
30
+
31
+ def diagnostics(relative_path)
32
+ relative_path = Pathname.new(relative_path) if relative_path.is_a?(String)
33
+ @mutex.synchronize { @latest_diagnostics[relative_path] || [] }
34
+ end
35
+
36
+ def build_diagnostics(offenses, analyzed_files: nil, only_single_file: false)
37
+ @mutex.synchronize do
38
+ full_check = analyzed_files.nil?
39
+ analyzed_paths = analyzed_files.map { |path| Pathname.new(path) } unless full_check
40
+
41
+ # When analyzed_files is nil, contains all offenses.
42
+ # When analyzed_files is !nil, contains all whole platformos_app offenses and single file offenses of the analyzed_files.
43
+ current_diagnostics = offenses
44
+ .select(&:app_file)
45
+ .group_by(&:app_file)
46
+ .transform_keys { |app_file| Pathname.new(app_file.relative_path) }
47
+ .transform_values do |app_file_offenses|
48
+ app_file_offenses.map { |o| Diagnostic.new(o) }
49
+ end
50
+
51
+ previous_paths = paths(@latest_diagnostics)
52
+ current_paths = paths(current_diagnostics)
53
+
54
+ diagnostics_update = (current_paths + previous_paths).map do |path|
55
+ # When doing single file checks, we keep the whole platformos_app old
56
+ # ones and accept the new single ones
57
+ if only_single_file && analyzed_paths.include?(path)
58
+ single_file_diagnostics = current_diagnostics[path] || NO_DIAGNOSTICS
59
+ whole_platformos_app_diagnostics = whole_platformos_app_diagnostics(path) || NO_DIAGNOSTICS
60
+ [path, single_file_diagnostics + whole_platformos_app_diagnostics]
61
+
62
+ # If doing single file checks that are not in the
63
+ # analyzed_paths array then we just keep the old
64
+ # diagnostics
65
+ elsif only_single_file
66
+ [path, previous_diagnostics(path) || NO_DIAGNOSTICS]
67
+
68
+ # When doing a full_check, we either send the current
69
+ # diagnostics or an empty array to clear the diagnostics
70
+ # for that file.
71
+ elsif full_check
72
+ [path, current_diagnostics[path] || NO_DIAGNOSTICS]
73
+
74
+ # When doing a partial check, the single file diagnostics
75
+ # from the previous runs should be sent. Otherwise the
76
+ # latest results are the good ones.
77
+ else
78
+ new_diagnostics = current_diagnostics[path] || NO_DIAGNOSTICS
79
+ should_use_cached_results = !analyzed_paths.include?(path)
80
+ old_diagnostics = should_use_cached_results ? single_file_diagnostics(path) : []
81
+ [path, new_diagnostics + old_diagnostics]
82
+ end
83
+ end.to_h
84
+
85
+ @latest_diagnostics = diagnostics_update
86
+ .reject { |_, v| v.empty? }
87
+
88
+ @first_run = false
89
+
90
+ # Only send updates for the current file when running with only_single_file
91
+ diagnostics_update
92
+ .reject { |p, _| only_single_file && !analyzed_paths.include?(p) }
93
+ end
94
+ end
95
+
96
+ def workspace_edit(diagnostics)
97
+ diagnostics = sanitize(diagnostics)
98
+ .select(&:correctable?)
99
+
100
+ {
101
+ documentChanges: document_changes(diagnostics)
102
+ }
103
+ end
104
+
105
+ def delete_applied(diagnostics)
106
+ diagnostics = sanitize(diagnostics)
107
+ .select(&:correctable?)
108
+
109
+ previous_paths = paths(@latest_diagnostics)
110
+
111
+ diagnostics.each do |diagnostic|
112
+ delete(diagnostic.relative_path, diagnostic)
113
+ end
114
+
115
+ current_paths = paths(@latest_diagnostics)
116
+
117
+ (current_paths + previous_paths).map do |path|
118
+ [path, @latest_diagnostics[path] || []]
119
+ end.to_h
120
+ end
121
+
122
+ # For when you know there shouldn't be anything on that file
123
+ # anymore. (e.g. file delete or file rename)
124
+ def clear_diagnostics(relative_path)
125
+ relative_path = sanitize_path(relative_path)
126
+ @latest_diagnostics.delete(relative_path)
127
+ end
128
+
129
+ private
130
+
131
+ def sanitize(diagnostics)
132
+ diagnostics = diagnostics.map { |hash| find(hash) }.reject(&:nil?) if diagnostics[0].is_a?(Hash)
133
+ diagnostics
134
+ end
135
+
136
+ def sanitize_path(relative_path)
137
+ case relative_path
138
+ when String
139
+ Pathname.new(relative_path)
140
+ else
141
+ relative_path
142
+ end
143
+ end
144
+
145
+ def delete(relative_path, diagnostic)
146
+ relative_path = sanitize_path(relative_path)
147
+ @mutex.synchronize do
148
+ @latest_diagnostics[relative_path]&.delete(diagnostic)
149
+ @latest_diagnostics.delete(relative_path) if @latest_diagnostics[relative_path] && @latest_diagnostics[relative_path].empty?
150
+ end
151
+ end
152
+
153
+ def find(diagnostic_hash)
154
+ diagnostics(diagnostic_hash.dig(:data, :relative_path))
155
+ .find { |d| d == diagnostic_hash }
156
+ end
157
+
158
+ def document_changes(diagnostics)
159
+ corrector = DocumentChangeCorrector.new
160
+ diagnostics.each do |diagnostic|
161
+ offense = diagnostic.offense
162
+ offense.correct(corrector)
163
+ end
164
+ corrector.document_changes
165
+ end
166
+
167
+ def paths(diagnostics)
168
+ (diagnostics || {}).keys.map { |path| Pathname.new(path) }.to_set
169
+ end
170
+
171
+ def single_file_diagnostics(relative_path)
172
+ @latest_diagnostics[relative_path]&.select(&:single_file?) || []
173
+ end
174
+
175
+ def whole_platformos_app_diagnostics(relative_path)
176
+ @latest_diagnostics[relative_path]&.select(&:whole_platformos_app?) || []
177
+ end
178
+
179
+ def previous_diagnostics(relative_path)
180
+ @latest_diagnostics[relative_path]
181
+ end
182
+ end
183
+ end
184
+ end