platformos-check 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,197 @@
1
+ # How to correct code with Code Actions, Commands and Workspace Edits
2
+
3
+ The [Language Server Protocol (LSP)][lsp] empowers Language Server developers to offer refactorings, quick fixes and the execution of commands to their users. In PlatformOS Check, we take advantage of this to offer quick fixes and autocorrection to users.
4
+
5
+ This document exists to give you an overview of how that works.
6
+
7
+ ## Overview
8
+
9
+ It goes like this:
10
+
11
+ 1. The Client (VS Code, vim, etc.) asks the Server for [Code Actions](#code-actions) at a character position, and the server responds with available `quickfix` and `source.fixAll` code actions.
12
+ 2. The User _may_ select one of the code actions by interacting with the UI (see [visual examples](#code-actions)). The selection triggers the Client to asks the Server to execute a `correction` [Command](#Commands) for certain [Diagnostics](#diagnostic) on behalf of the user.
13
+ 3. The Server asks the Client to apply a [Workspace Edit](#workspace-edits) to fix the diagnostics.
14
+
15
+ ## Sequence Diagram
16
+
17
+ <img src="code-action-flow.png" width="700">
18
+
19
+ <details>
20
+ <summary>Sequence Diagram Explanation</summary>
21
+
22
+ 1. The Client asks the Server for [code actions](#code-actions) for the current file and character range.
23
+ 2. The Server responds with a list of code actions that can be applied for this location and character range. (`quickfix` and `source.fixAll`)
24
+ 3. ...Wait for user input...
25
+ 4. The User selects one of the code actions
26
+ 5. The Client sends a `workspace/executeCommand` request to the Server for this code action.
27
+ 6. The Server figures out the [workspace edit](#workspace-edits) for the command and arguments.
28
+ 7. The Server sends a `workspace/applyEdit` request for the file modifications that would fix the diagnostics.
29
+ 8. The Client responds with the status of the `applyEdit`.
30
+ 9. The Server cleans up its internal representation of the diagnostics and updates the client with the latest diagnostics.
31
+ 11. The Server responds to the `workspace/executeCommand` request.
32
+
33
+ </details>
34
+
35
+ ## What it's like in the code
36
+
37
+ We handle two Client->Server LSP requests:
38
+
39
+ 1. On `textDocument/codeAction`, our `CodeActionEngine` returns the results obtained from all `CodeActionProvider`.
40
+ 2. On `workspace/executeCommand`, our `ExecuteCommandEngine` dispatches the command and arguments to appropriate `ExecuteCommandProvider`.
41
+
42
+ We define providers:
43
+
44
+ - Two `CodeActionProvider`:
45
+ 1. [`QuickFixCodeActionProvider`](/lib/platformos_check/language_server/code_action_providers/quickfix_code_action_provider.rb) - This one provides code actions that fix _one_ diagnostic.
46
+ 2. [`SourceFixAllCodeActionProvider`](/lib/platformos_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb) - This one provides code actions that fix _all diagnostics in the current file_.
47
+ - One `ExecuteCommandProvider`:
48
+ 1. [`CorrectionExecuteCommandProvider`](/lib/platformos_check/language_server/execute_command_providers/correction_execute_command_provider.rb) - This one takes a list of diagnostics as arguments, turns them into a [WorkspaceEdit](#workspace-edit) and tries to apply them with the server->client `workspace/applyEdit` request.
49
+
50
+ We define a [`DocumentChangeCorrector`](/lib/platformos_check/language_server/document_change_corrector.rb) (an LSP analog to our [`Corrector`](/lib/platformos_check/corrector.rb) class). This class turns corrector calls into document changes supported by the LSP. For more details, see the [LSP reference on resource changes][lspresourcechange].
51
+
52
+ ## Definitions
53
+
54
+ ### Code Actions
55
+
56
+ A [CodeAction][lspcodeaction] is the Language Server Protocol construct for "stuff you might want to do on the code."
57
+
58
+ Think refactoring, running tests, fixing lint errors, etc.
59
+
60
+ The client figures out which one it can run by executing the client->server `textDocument/codeAction` request.
61
+
62
+ <details>
63
+ <summary>Visual Examples</summary>
64
+
65
+ In VS Code, code actions of different kinds have special meanings and are mapped to multiple places in the UI.
66
+
67
+ * `Quick Fix...` button on diagnostic hover
68
+
69
+ <img src="code-action-quickfix.png" width=500>
70
+
71
+ * Diagnostic right click in the problems tab
72
+
73
+ <img src="code-action-problem.png" width=500>
74
+
75
+ * Command palette `Quick Fix...` and `Fix All`
76
+
77
+ <img src="code-action-command-palette.png" width=500>
78
+
79
+ * Keyboard shortcuts
80
+
81
+ <img src="code-action-keyboard.png" width=700>
82
+ </details>
83
+
84
+ <details>
85
+ <summary>TypeScript Interface</summary>
86
+
87
+ ```ts
88
+ interface CodeAction {
89
+ title: string; // UI string, human readable for the action
90
+ kind?: CodeActionKind; // OPTIONAL, for filtering
91
+ diagnostics?: Diagnostic[]; // The diagnostics that the action SOLVES.
92
+ isPreferred?: boolean; // Are used by auto fix and can be targetted by keybindings.
93
+ // Shown as faded out in the code action menu when the user request a more specific type of code action
94
+ disabled?: {
95
+ reason: string;
96
+ },
97
+
98
+ // if both edit and command are present, edit is run first then command.
99
+ // I think edit is used so the client performs the change, wheras the command
100
+ // would be done by the server
101
+ edit?: WorkspaceEdit; // what this action does ??!!
102
+ command?: Command; // the command that it executes
103
+ data?: any; // sent from the CodeAction to the codeAction/resolve.
104
+ }
105
+
106
+ interface Command {
107
+ title: string; // Title of the command, like `save`
108
+ command: string; // id
109
+ arguments?: any[]
110
+ }
111
+ ```
112
+ </details>
113
+
114
+ ---
115
+
116
+ ### Commands
117
+
118
+ A [Command][lspcommand] is the Language Server Protocol representation of a command that can be executed by the Server.
119
+
120
+ Think of them as data representation of function calls that can be made by the Client.
121
+
122
+ Typically, a command is associated with a Code Action. If the Client wants to perform this Code Action, it will make a Client->Server `workspace/executeCommand` request.
123
+
124
+ <details>
125
+ <summary>TypeScript Interface</summary>
126
+
127
+ ```ts
128
+ interface Command {
129
+ title: string; // Title of the command, like `save`
130
+ command: string; // id
131
+ arguments?: any[]
132
+ }
133
+ ```
134
+ </details>
135
+
136
+ ---
137
+
138
+ ### Workspace Edits
139
+
140
+ A [WorkspaceEdit][lspworkspaceedit] is the Language Server Protocol construct that abstracts code changes as data.
141
+
142
+ Think edit file, create file, delete file but as data.
143
+
144
+ <details>
145
+ <summary>TypeScript Interface</summary>
146
+
147
+ ```ts
148
+ interface WorkspaceEdit {
149
+ changes?: { uri: TextEdit[] ];
150
+ documentChanges: (TextDocumentEdit | CreateFile | RenameFile | DeleteFile|)[];
151
+ changeAnnotations?: { [id: string]: ChangeAnnotation }
152
+ }
153
+
154
+ // see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#resourceChanges
155
+ interface CreateFile {
156
+ kind: 'create';
157
+ uri: DocumentUri;
158
+ options?: CreateFileOptions;
159
+ annotationId?: ChangeAnnotationIdentifier;
160
+ }
161
+
162
+ interface TextEdit {
163
+ range: Range;
164
+ newText: string; // can be empty to delete
165
+ }
166
+
167
+ interface TextDocumentEdit {
168
+ textDocument: OptionalVersionedTextDocumentIdentifier;
169
+ edits: (TextEdit | AnnotatedTextEdit)[]
170
+ }
171
+
172
+ interface OptionalVersionedTextDocumentIdentifier {
173
+ uri: TextDocumentURI;
174
+ // null is for when the file wasn't open from the client
175
+ // integer is for when you know what it was.
176
+ version: integer | null;
177
+ }
178
+ ```
179
+ </details>
180
+
181
+ ---
182
+
183
+ ### Diagnostic
184
+
185
+ A [Diagnostic][lspdiagnostic] is the Language Server Protocol construct for errors, warnings and information bubbles.
186
+
187
+ In our case, diagnostics are PlatformOS Check offenses.
188
+
189
+ They appear in the Problems tab and in the gutter of the editor.
190
+
191
+ [lsp]: https://microsoft.github.io/language-server-protocol/specification
192
+ [lspcodeaction]: https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction
193
+ [lspcommand]: https://microsoft.github.io/language-server-protocol/specification#command
194
+ [lspexecutecommand]: https://microsoft.github.io/language-server-protocol/specification#workspace_executeCommand
195
+ [lspworkspaceedit]: https://microsoft.github.io/language-server-protocol/specification#workspaceEdit
196
+ [lspdiagnostic]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#diagnostic
197
+ [lspresourcechange]: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#resourceChanges
data/docs/preview.png ADDED
Binary file
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "platformos_check"
5
+
6
+ PlatformosCheck::Cli.parse_and_run(ARGV)
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'platformos_check'
5
+
6
+ status_code = PlatformosCheck::LanguageServer.start
7
+ exit! status_code
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ class Analyzer
5
+ def initialize(platformos_app, checks = Check.all.map(&:new), auto_correct = false)
6
+ @platformos_app = platformos_app
7
+ @auto_correct = auto_correct
8
+
9
+ @liquid_checks = Checks.new
10
+ @yaml_checks = Checks.new
11
+ @html_checks = Checks.new
12
+
13
+ checks.each do |check|
14
+ check.platformos_app = @platformos_app
15
+
16
+ case check
17
+ when LiquidCheck
18
+ @liquid_checks << check
19
+ when YamlCheck
20
+ @yaml_checks << check
21
+ when HtmlCheck
22
+ @html_checks << check
23
+ end
24
+ end
25
+ end
26
+
27
+ def offenses
28
+ @liquid_checks.flat_map(&:offenses) +
29
+ @yaml_checks.flat_map(&:offenses) +
30
+ @html_checks.flat_map(&:offenses)
31
+ end
32
+
33
+ def yaml_file_count
34
+ @yaml_file_count ||= @platformos_app.yaml.size
35
+ end
36
+
37
+ def liquid_file_count
38
+ @liquid_file_count ||= @platformos_app.liquid.size
39
+ end
40
+
41
+ def total_file_count
42
+ yaml_file_count + liquid_file_count
43
+ end
44
+
45
+ # Returns all offenses for all files in platformos_app
46
+ def analyze_platformos_app
47
+ reset
48
+
49
+ liquid_visitor = LiquidVisitor.new(@liquid_checks, @disabled_checks)
50
+ html_visitor = HtmlVisitor.new(@html_checks)
51
+
52
+ PlatformosCheck.with_liquid_c_disabled do
53
+ @platformos_app.liquid.each_with_index do |liquid_file, i|
54
+ yield(liquid_file.relative_path.to_s, i, total_file_count) if block_given?
55
+ liquid_visitor.visit_liquid_file(liquid_file)
56
+ html_visitor.visit_liquid_file(liquid_file)
57
+ end
58
+ end
59
+
60
+ @platformos_app.yaml.each_with_index do |yaml_file, i|
61
+ yield(yaml_file.relative_path.to_s, liquid_file_count + i, total_file_count) if block_given?
62
+ @yaml_checks.call(:on_file, yaml_file)
63
+ end
64
+
65
+ finish(false)
66
+
67
+ offenses
68
+ end
69
+
70
+ # When only_single_file is false:
71
+ # Runs single file checks for each file in `files`
72
+ # Runs whole platformos_app checks
73
+ # Returns single file checks offenses for file in `files` + whole platformos_app checks
74
+ # When only_single_file is true:
75
+ # Runs single file checks for each file in `files`
76
+ # Does not run whole platformos_app checks
77
+ # Returns single file checks offenses for file in `files`
78
+ # When files is empty and only_single_file is false:
79
+ # Only returns whole platformos_app checks
80
+ # When files is empty and only_single_file is true:
81
+ # Returns empty array
82
+ def analyze_files(files, only_single_file: false)
83
+ reset
84
+
85
+ PlatformosCheck.with_liquid_c_disabled do
86
+ total = files.size
87
+ offset = 0
88
+
89
+ unless only_single_file
90
+ # Call all checks that run on the whole platformos_app
91
+ liquid_visitor = LiquidVisitor.new(@liquid_checks.whole_platformos_app, @disabled_checks)
92
+ html_visitor = HtmlVisitor.new(@html_checks.whole_platformos_app)
93
+ total += total_file_count
94
+ offset = total_file_count
95
+ @platformos_app.liquid.each_with_index do |liquid_file, i|
96
+ yield(liquid_file.relative_path.to_s, i, total) if block_given?
97
+ liquid_visitor.visit_liquid_file(liquid_file)
98
+ html_visitor.visit_liquid_file(liquid_file)
99
+ end
100
+
101
+ @platformos_app.yaml.each_with_index do |yaml_file, i|
102
+ yield(yaml_file.relative_path.to_s, liquid_file_count + i, total) if block_given?
103
+ @yaml_checks.whole_platformos_app.call(:on_file, yaml_file)
104
+ end
105
+ end
106
+
107
+ # Call checks that run on a single files, only on specified file
108
+ liquid_visitor = LiquidVisitor.new(@liquid_checks.single_file, @disabled_checks)
109
+ html_visitor = HtmlVisitor.new(@html_checks.single_file)
110
+ files.each_with_index do |app_file, i|
111
+ yield(app_file.relative_path.to_s, offset + i, total) if block_given?
112
+ if app_file.liquid?
113
+ liquid_visitor.visit_liquid_file(app_file)
114
+ html_visitor.visit_liquid_file(app_file)
115
+ elsif app_file.yaml?
116
+ @yaml_checks.single_file.call(:on_file, app_file)
117
+ end
118
+ end
119
+ end
120
+
121
+ finish(only_single_file)
122
+
123
+ offenses
124
+ end
125
+
126
+ def uncorrectable_offenses
127
+ return offenses unless @auto_correct
128
+
129
+ offenses.select { |offense| !offense.correctable? }
130
+ end
131
+
132
+ def correct_offenses
133
+ return unless @auto_correct
134
+
135
+ offenses.each(&:correct)
136
+ end
137
+
138
+ def write_corrections
139
+ return unless @auto_correct
140
+
141
+ @platformos_app.liquid.each(&:write)
142
+ end
143
+
144
+ private
145
+
146
+ def reset
147
+ @disabled_checks = DisabledChecks.new
148
+
149
+ @liquid_checks.each do |check|
150
+ check.offenses.clear
151
+ end
152
+
153
+ @html_checks.each do |check|
154
+ check.offenses.clear
155
+ end
156
+
157
+ @yaml_checks.each do |check|
158
+ check.offenses.clear
159
+ end
160
+ end
161
+
162
+ def finish(only_single_file = false)
163
+ if only_single_file
164
+ @liquid_checks.single_file.call(:on_end)
165
+ @html_checks.single_file.call(:on_end)
166
+ @yaml_checks.single_file.call(:on_end)
167
+ else
168
+ @liquid_checks.call(:on_end)
169
+ @html_checks.call(:on_end)
170
+ @yaml_checks.call(:on_end)
171
+ end
172
+
173
+ @disabled_checks.remove_disabled_offenses(@liquid_checks)
174
+ @disabled_checks.remove_disabled_offenses(@yaml_checks)
175
+ @disabled_checks.remove_disabled_offenses(@html_checks)
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ class ApiCallFile < LiquidFile
5
+ def notification?
6
+ true
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ # frozen_string_lite/al: true
4
+
5
+ require "pathname"
6
+
7
+ module PlatformosCheck
8
+ class App
9
+ API_CALLS_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/|modules/(.+)(private|public|marketplace_builder|app)/)?)(notifications/api_call_notifications|api_calls)/(.+)\.liquid\z}
10
+ ASSETS_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/|modules/(.+)(private|public|marketplace_builder|app)/)?)assets/}
11
+ EMAILS_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/|modules/(.+)(private|public|marketplace_builder|app)/)?)(notifications/email_notifications|emails)/(.+)\.liquid\z}
12
+ GRAPHQL_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/|modules/(.+)(private|public|marketplace_builder|app)/)?)(graph_queries|graphql)s?/(.+)\.graphql\z}
13
+
14
+ MIGRATIONS_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/|modules/(.+)(private|public|marketplace_builder|app)/)?)migrations/(.+)\.liquid\z}
15
+ PAGES_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/|modules/(.+)(private|public|marketplace_builder|app)/)?)(pages|views/pages)/(.+)}
16
+ PARTIALS_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/|modules/(.+)(private|public|marketplace_builder|app)/)?)(views/partials|lib)/(.+)}
17
+ LAYOUTS_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/|modules/(.+)(private|public|marketplace_builder|app)/)?)(views/layouts)/(.+)}
18
+ SCHEMA_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/|modules/(.+)(private|public|marketplace_builder|app)/)?)(custom_model_types|model_schemas|schema)/(.+)\.yml\z}
19
+ SMSES_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/|modules/(.+)(private|public|marketplace_builder|app)/)?)(notifications/sms_notifications|smses)/(.+)\.liquid\z}
20
+ USER_SCHEMA_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/)?)user.yml}
21
+ TRANSLATIONS_REGEX = %r{\A(?-mix:^/?((marketplace_builder|app)/|modules/(.+)(private|public|marketplace_builder|app)/)?)translations.+.yml}
22
+ CONFIG_REGEX = %r{(?-mix:^\\/?((marketplace_builder|app)\\/)?)config.yml}
23
+
24
+ REGEXP_MAP = {
25
+ API_CALLS_REGEX => ApiCallFile,
26
+ ASSETS_REGEX => AssetFile,
27
+ EMAILS_REGEX => EmailFile,
28
+ GRAPHQL_REGEX => GraphqlFile,
29
+ MIGRATIONS_REGEX => MigrationFile,
30
+ PAGES_REGEX => PageFile,
31
+ PARTIALS_REGEX => PartialFile,
32
+ LAYOUTS_REGEX => LayoutFile,
33
+ SCHEMA_REGEX => SchemaFile,
34
+ SMSES_REGEX => SmsFile,
35
+ USER_SCHEMA_REGEX => UserSchemaFile,
36
+ TRANSLATIONS_REGEX => TranslationFile,
37
+ CONFIG_REGEX => ConfigFile
38
+ }
39
+
40
+ attr_reader :storage
41
+
42
+ def initialize(storage)
43
+ @storage = storage
44
+ end
45
+
46
+ def grouped_files
47
+ @grouped_files ||= begin
48
+ hash = {}
49
+ REGEXP_MAP.each_value { |v| hash[v] = {} }
50
+ storage.files.each do |path|
51
+ regexp, klass = REGEXP_MAP.detect { |k, _v| k.match?(path) }
52
+ if regexp
53
+ f = klass.new(path, storage)
54
+ hash[klass][f.name] = f
55
+ elsif /\.liquid$/i.match?(path)
56
+ hash[LiquidFile] ||= {}
57
+ f = LiquidFile.new(path, storage)
58
+ hash[LiquidFile][f.name] = f
59
+ end
60
+ end
61
+ hash
62
+ end
63
+ end
64
+
65
+ def assets
66
+ grouped_files[AssetFile]&.values
67
+ end
68
+
69
+ def liquid
70
+ layouts + partials + pages + notifications
71
+ end
72
+
73
+ def yaml
74
+ schema + translations
75
+ end
76
+
77
+ def schema
78
+ grouped_files[SchemaFile]&.values || []
79
+ end
80
+
81
+ def translations
82
+ grouped_files[TranslationFile]&.values || []
83
+ end
84
+
85
+ def partials
86
+ grouped_files[PartialFile]&.values || []
87
+ end
88
+
89
+ def layouts
90
+ grouped_files[LayoutFile]&.values || []
91
+ end
92
+
93
+ def notifications
94
+ emails + smses + api_calls
95
+ end
96
+
97
+ def emails
98
+ grouped_files[EmailFile]&.values || []
99
+ end
100
+
101
+ def smses
102
+ grouped_files[SmsFile]&.values || []
103
+ end
104
+
105
+ def api_calls
106
+ grouped_files[ApiCallFile]&.values || []
107
+ end
108
+
109
+ def pages
110
+ grouped_files[PageFile]&.values || []
111
+ end
112
+
113
+ def directories
114
+ storage.directories
115
+ end
116
+
117
+ def all
118
+ @all ||= grouped_files.values.map(&:values).flatten
119
+ end
120
+
121
+ def [](name_or_relative_path)
122
+ case name_or_relative_path
123
+ when Pathname
124
+ all.find { |t| t.relative_path == name_or_relative_path }
125
+ else
126
+ all.find { |t| t.name == name_or_relative_path }
127
+ end
128
+ end
129
+
130
+ def sections
131
+ liquid.select(&:section?)
132
+ end
133
+
134
+ def snippets
135
+ liquid.select(&:snippet?)
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module PlatformosCheck
6
+ class AppFile
7
+ attr_reader :version, :storage
8
+
9
+ def initialize(relative_path, storage)
10
+ @relative_path = relative_path
11
+ @storage = storage
12
+ @source = nil
13
+ @version = nil
14
+ @eol = "\n"
15
+ end
16
+
17
+ def path
18
+ @storage.path(@relative_path)
19
+ end
20
+
21
+ def relative_path
22
+ @relative_pathname ||= Pathname.new(@relative_path)
23
+ end
24
+
25
+ def name
26
+ @name ||= dir_prefix.nil? ? relative_path.sub_ext('').to_s : build_name
27
+ end
28
+
29
+ def build_name
30
+ n = relative_path.sub(dir_prefix, '').sub_ext('').to_s
31
+ return n if module_name.nil?
32
+
33
+ prefix = "modules#{File::SEPARATOR}#{module_name}#{File::SEPARATOR}"
34
+ return n if n.start_with?(prefix)
35
+
36
+ "#{prefix}#{n}"
37
+ end
38
+
39
+ def dir_prefix
40
+ nil
41
+ end
42
+
43
+ def module_name
44
+ @module_name ||= begin
45
+ dir_names = @relative_path.split(File::SEPARATOR).reject(&:empty?)
46
+ dir_names.first == 'modules' ? dir_names[1] : nil
47
+ end
48
+ end
49
+
50
+ # For the corrector to work properly, we should have a
51
+ # simple mental model of the internal representation of eol
52
+ # characters (Windows uses \r\n, Linux uses \n).
53
+ #
54
+ # Parser::Source::Buffer strips the \r from the source file, so if
55
+ # you are autocorrecting the file you might lose that info and
56
+ # cause a git diff. It also makes the node.start_index/end_index
57
+ # calculation break. That's not cool.
58
+ #
59
+ # So in here we track whether the source file has \r\n in it and
60
+ # we'll make sure that the file we write has the same eol as the
61
+ # source file.
62
+ def source
63
+ return @source if @source
64
+
65
+ if @storage.versioned?
66
+ @source, @version = @storage.read_version(@relative_path)
67
+ else
68
+ @source = @storage.read(@relative_path)
69
+ end
70
+ @eol = @source.include?("\r\n") ? "\r\n" : "\n"
71
+ @source = @source
72
+ .gsub(/\r(?!\n)/, "\r\n") # fix rogue \r without followup \n with \r\n
73
+ .gsub("\r\n", "\n")
74
+ end
75
+
76
+ def yaml?
77
+ false
78
+ end
79
+
80
+ def liquid?
81
+ false
82
+ end
83
+
84
+ def ==(other)
85
+ other.is_a?(self.class) && relative_path == other.relative_path
86
+ end
87
+ alias eql? ==
88
+ end
89
+ end