theme-check 1.7.2 → 1.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +47 -0
  4. data/README.md +10 -0
  5. data/RELEASING.md +13 -0
  6. data/config/default.yml +5 -0
  7. data/data/shopify_liquid/deprecated_filters.yml +4 -0
  8. data/data/shopify_liquid/filters.yml +3 -1
  9. data/docs/checks/TEMPLATE.md.erb +24 -19
  10. data/docs/checks/schema_json_format.md +76 -0
  11. data/docs/language_server/code-action-command-palette.png +0 -0
  12. data/docs/language_server/code-action-flow.png +0 -0
  13. data/docs/language_server/code-action-keyboard.png +0 -0
  14. data/docs/language_server/code-action-light-bulb.png +0 -0
  15. data/docs/language_server/code-action-problem.png +0 -0
  16. data/docs/language_server/code-action-quickfix.png +0 -0
  17. data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
  18. data/exe/theme-check-language-server +0 -4
  19. data/lib/theme_check/checks/asset_size_app_block_css.rb +2 -3
  20. data/lib/theme_check/checks/asset_size_app_block_javascript.rb +2 -3
  21. data/lib/theme_check/checks/asset_url_filters.rb +2 -0
  22. data/lib/theme_check/checks/default_locale.rb +1 -1
  23. data/lib/theme_check/checks/deprecated_filter.rb +81 -4
  24. data/lib/theme_check/checks/deprecated_global_app_block_type.rb +2 -3
  25. data/lib/theme_check/checks/matching_schema_translations.rb +14 -9
  26. data/lib/theme_check/checks/matching_translations.rb +1 -0
  27. data/lib/theme_check/checks/missing_required_template_files.rb +3 -3
  28. data/lib/theme_check/checks/missing_template.rb +1 -1
  29. data/lib/theme_check/checks/pagination_size.rb +2 -3
  30. data/lib/theme_check/checks/remote_asset.rb +5 -0
  31. data/lib/theme_check/checks/required_directories.rb +1 -1
  32. data/lib/theme_check/checks/required_layout_theme_object.rb +9 -4
  33. data/lib/theme_check/checks/schema_json_format.rb +29 -0
  34. data/lib/theme_check/checks/space_inside_braces.rb +132 -87
  35. data/lib/theme_check/checks/translation_key_exists.rb +33 -13
  36. data/lib/theme_check/checks/unused_assign.rb +3 -2
  37. data/lib/theme_check/checks/unused_snippet.rb +1 -1
  38. data/lib/theme_check/checks/valid_html_translation.rb +1 -1
  39. data/lib/theme_check/checks/valid_schema.rb +2 -2
  40. data/lib/theme_check/corrector.rb +34 -23
  41. data/lib/theme_check/file_system_storage.rb +4 -3
  42. data/lib/theme_check/html_node.rb +122 -6
  43. data/lib/theme_check/html_visitor.rb +1 -32
  44. data/lib/theme_check/in_memory_storage.rb +9 -0
  45. data/lib/theme_check/json_helpers.rb +14 -0
  46. data/lib/theme_check/language_server/bridge.rb +19 -5
  47. data/lib/theme_check/language_server/client_capabilities.rb +27 -0
  48. data/lib/theme_check/language_server/code_action_engine.rb +32 -0
  49. data/lib/theme_check/language_server/code_action_provider.rb +42 -0
  50. data/lib/theme_check/language_server/code_action_providers/quickfix_code_action_provider.rb +83 -0
  51. data/lib/theme_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +40 -0
  52. data/lib/theme_check/language_server/configuration.rb +69 -0
  53. data/lib/theme_check/language_server/diagnostic.rb +124 -0
  54. data/lib/theme_check/language_server/diagnostics_engine.rb +15 -60
  55. data/lib/theme_check/language_server/diagnostics_manager.rb +136 -0
  56. data/lib/theme_check/language_server/document_change_corrector.rb +267 -0
  57. data/lib/theme_check/language_server/document_link_provider.rb +6 -6
  58. data/lib/theme_check/language_server/execute_command_engine.rb +19 -0
  59. data/lib/theme_check/language_server/execute_command_provider.rb +30 -0
  60. data/lib/theme_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
  61. data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +22 -0
  62. data/lib/theme_check/language_server/handler.rb +83 -29
  63. data/lib/theme_check/language_server/io_messenger.rb +11 -1
  64. data/lib/theme_check/language_server/protocol.rb +4 -0
  65. data/lib/theme_check/language_server/server.rb +29 -11
  66. data/lib/theme_check/language_server/uri_helper.rb +1 -0
  67. data/lib/theme_check/language_server/versioned_in_memory_storage.rb +69 -0
  68. data/lib/theme_check/language_server.rb +23 -5
  69. data/lib/theme_check/liquid_node.rb +255 -12
  70. data/lib/theme_check/locale_diff.rb +39 -8
  71. data/lib/theme_check/node.rb +16 -0
  72. data/lib/theme_check/offense.rb +27 -23
  73. data/lib/theme_check/position.rb +4 -4
  74. data/lib/theme_check/regex_helpers.rb +1 -1
  75. data/lib/theme_check/schema_helper.rb +70 -0
  76. data/lib/theme_check/storage.rb +4 -0
  77. data/lib/theme_check/tags.rb +0 -1
  78. data/lib/theme_check/theme.rb +1 -1
  79. data/lib/theme_check/theme_file.rb +8 -1
  80. data/lib/theme_check/theme_file_rewriter.rb +28 -6
  81. data/lib/theme_check/version.rb +1 -1
  82. data/lib/theme_check.rb +11 -2
  83. metadata +26 -3
  84. data/lib/theme_check/language_server/diagnostics_tracker.rb +0 -66
@@ -37,12 +37,12 @@ module ThemeCheck
37
37
 
38
38
  def document_links(buffer)
39
39
  matches(buffer, partial_regexp).map do |match|
40
- start_line, start_character = from_index_to_row_column(
40
+ start_row, start_column = from_index_to_row_column(
41
41
  buffer,
42
42
  match.begin(:partial),
43
43
  )
44
44
 
45
- end_line, end_character = from_index_to_row_column(
45
+ end_row, end_column = from_index_to_row_column(
46
46
  buffer,
47
47
  match.end(:partial)
48
48
  )
@@ -51,12 +51,12 @@ module ThemeCheck
51
51
  target: file_link(match[:partial]),
52
52
  range: {
53
53
  start: {
54
- line: start_line,
55
- character: start_character,
54
+ line: start_row,
55
+ character: start_column,
56
56
  },
57
57
  end: {
58
- line: end_line,
59
- character: end_character,
58
+ line: end_row,
59
+ character: end_column,
60
60
  },
61
61
  },
62
62
  }
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ module LanguageServer
5
+ class ExecuteCommandEngine
6
+ def initialize
7
+ @providers = {}
8
+ end
9
+
10
+ def <<(provider)
11
+ @providers[provider.command] = provider
12
+ end
13
+
14
+ def execute(command, arguments)
15
+ @providers[command].execute(arguments)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ module LanguageServer
5
+ class ExecuteCommandProvider
6
+ class << self
7
+ def all
8
+ @all ||= []
9
+ end
10
+
11
+ def inherited(subclass)
12
+ all << subclass
13
+ end
14
+
15
+ def command(cmd = nil)
16
+ @command = cmd unless cmd.nil?
17
+ @command
18
+ end
19
+ end
20
+
21
+ def execute(arguments)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def command
26
+ self.class.command
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ module LanguageServer
5
+ class CorrectionExecuteCommandProvider < ExecuteCommandProvider
6
+ include URIHelper
7
+
8
+ command "correction"
9
+
10
+ attr_reader :storage, :bridge, :diagnostics_manager
11
+
12
+ def initialize(storage, bridge, diagnostics_manager)
13
+ @storage = storage
14
+ @bridge = bridge
15
+ @diagnostics_manager = diagnostics_manager
16
+ end
17
+
18
+ # The arguments passed to this method are the ones forwarded
19
+ # from the selected CodeAction by the client.
20
+ #
21
+ # @param diagnostic_hashes [Array] - of diagnostics
22
+ def execute(diagnostic_hashes)
23
+ # attempt to apply the document changes
24
+ workspace_edit = diagnostics_manager.workspace_edit(diagnostic_hashes)
25
+ result = bridge.send_request('workspace/applyEdit', {
26
+ label: 'Theme Check correction',
27
+ edit: workspace_edit,
28
+ })
29
+
30
+ # Bail if unable to apply changes
31
+ return unless result[:applied]
32
+
33
+ # Clean up internal representation of fixed diagnostics
34
+ diagnostics_update = diagnostics_manager.delete_applied(diagnostic_hashes)
35
+
36
+ # Send updated diagnostics to client
37
+ diagnostics_update
38
+ .map do |relative_path, diagnostics|
39
+ bridge.send_notification('textDocument/publishDiagnostics', {
40
+ uri: file_uri(storage.path(relative_path)),
41
+ diagnostics: diagnostics.map(&:to_h),
42
+ })
43
+ storage.path(relative_path)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ module LanguageServer
5
+ class RunChecksExecuteCommandProvider < ExecuteCommandProvider
6
+ include URIHelper
7
+
8
+ command "runChecks"
9
+
10
+ def initialize(diagnostics_engine, root_path, root_config)
11
+ @diagnostics_engine = diagnostics_engine
12
+ @root_path = root_path
13
+ @root_config = root_config
14
+ end
15
+
16
+ def execute(_args)
17
+ @diagnostics_engine.analyze_and_send_offenses(@root_path, @root_config, force: true)
18
+ nil
19
+ end
20
+ end
21
+ end
22
+ end
@@ -17,7 +17,16 @@ module ThemeCheck
17
17
  triggerCharacters: ['.', '{{ ', '{% '],
18
18
  context: true,
19
19
  },
20
+ codeActionProvider: {
21
+ codeActionKinds: CodeActionProvider.all.map(&:kind),
22
+ resolveProvider: false,
23
+ workDoneProgress: false,
24
+ },
20
25
  documentLinkProvider: true,
26
+ executeCommandProvider: {
27
+ workDoneProgress: false,
28
+ commands: ExecuteCommandProvider.all.map(&:command),
29
+ },
21
30
  textDocumentSync: {
22
31
  openClose: true,
23
32
  change: TextDocumentSyncKind::FULL,
@@ -36,52 +45,94 @@ module ThemeCheck
36
45
  # Tell the client we don't support anything if there's no rootPath
37
46
  return @bridge.send_response(id, { capabilities: {} }) if @root_path.nil?
38
47
 
39
- @bridge.supports_work_done_progress = params.dig('capabilities', 'window', 'workDoneProgress') || false
48
+ @client_capabilities = ClientCapabilities.new(params.dig(:capabilities) || {})
49
+ @configuration = Configuration.new(@bridge, @client_capabilities)
50
+ @bridge.supports_work_done_progress = @client_capabilities.supports_work_done_progress?
40
51
  @storage = in_memory_storage(@root_path)
52
+ @diagnostics_manager = DiagnosticsManager.new
41
53
  @completion_engine = CompletionEngine.new(@storage)
42
54
  @document_link_engine = DocumentLinkEngine.new(@storage)
43
- @diagnostics_engine = DiagnosticsEngine.new(@bridge)
55
+ @diagnostics_engine = DiagnosticsEngine.new(@storage, @bridge, @diagnostics_manager)
56
+ @execute_command_engine = ExecuteCommandEngine.new
57
+ @execute_command_engine << CorrectionExecuteCommandProvider.new(@storage, @bridge, @diagnostics_manager)
58
+ @execute_command_engine << RunChecksExecuteCommandProvider.new(@diagnostics_engine, @root_path, config_for_path(@root_path))
59
+ @code_action_engine = CodeActionEngine.new(@storage, @diagnostics_manager)
44
60
  @bridge.send_response(id, {
45
61
  capabilities: CAPABILITIES,
46
62
  serverInfo: SERVER_INFO,
47
63
  })
48
64
  end
49
65
 
66
+ def on_initialized(_id, _params)
67
+ @configuration.fetch
68
+ @configuration.register_did_change_capability
69
+ end
70
+
71
+ def on_shutdown(id, _params)
72
+ @bridge.send_response(id, nil)
73
+ end
74
+
50
75
  def on_exit(_id, _params)
51
76
  close!
52
77
  end
53
- alias_method :on_shutdown, :on_exit
54
78
 
55
- def on_text_document_did_change(_id, params)
79
+ def on_text_document_did_open(_id, params)
56
80
  relative_path = relative_path_from_text_document_uri(params)
57
- @storage.write(relative_path, content_changes_text(params))
81
+ @storage.write(relative_path, text_document_text(params), text_document_version(params))
82
+ analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_open?
58
83
  end
59
84
 
60
- def on_text_document_did_close(_id, params)
85
+ def on_text_document_did_change(_id, params)
61
86
  relative_path = relative_path_from_text_document_uri(params)
62
- @storage.write(relative_path, "")
87
+ @storage.write(relative_path, content_changes_text(params), text_document_version(params))
88
+ analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_change?
63
89
  end
64
90
 
65
- def on_text_document_did_open(_id, params)
91
+ def on_text_document_did_close(_id, params)
66
92
  relative_path = relative_path_from_text_document_uri(params)
67
- @storage.write(relative_path, text_document_text(params))
68
- analyze_and_send_offenses(text_document_uri(params)) if @diagnostics_engine.first_run?
93
+ file_system_content = Pathname.new(text_document_uri(params)).read(mode: 'rb', encoding: 'UTF-8')
94
+ # On close, the file system becomes the source of truth
95
+ @storage.write(relative_path, file_system_content, nil)
69
96
  end
70
97
 
71
98
  def on_text_document_did_save(_id, params)
72
- analyze_and_send_offenses(text_document_uri(params))
99
+ analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_save?
73
100
  end
74
101
 
75
102
  def on_text_document_document_link(id, params)
76
103
  relative_path = relative_path_from_text_document_uri(params)
77
- @bridge.send_response(id, document_links(relative_path))
104
+ @bridge.send_response(id, @document_link_engine.document_links(relative_path))
78
105
  end
79
106
 
80
107
  def on_text_document_completion(id, params)
81
108
  relative_path = relative_path_from_text_document_uri(params)
82
- line = params.dig('position', 'line')
83
- col = params.dig('position', 'character')
84
- @bridge.send_response(id, completions(relative_path, line, col))
109
+ line = params.dig(:position, :line)
110
+ col = params.dig(:position, :character)
111
+ @bridge.send_response(id, @completion_engine.completions(relative_path, line, col))
112
+ end
113
+
114
+ def on_text_document_code_action(id, params)
115
+ absolute_path = text_document_uri(params)
116
+ start_position = range_element(params, :start)
117
+ end_position = range_element(params, :end)
118
+ only_code_action_kinds = params.dig(:context, :only) || []
119
+ @bridge.send_response(id, @code_action_engine.code_actions(
120
+ absolute_path,
121
+ start_position,
122
+ end_position,
123
+ only_code_action_kinds,
124
+ ))
125
+ end
126
+
127
+ def on_workspace_execute_command(id, params)
128
+ @bridge.send_response(id, @execute_command_engine.execute(
129
+ params[:command],
130
+ params[:arguments],
131
+ ))
132
+ end
133
+
134
+ def on_workspace_did_change_configuration(_id, _params)
135
+ @configuration.fetch(force: true)
85
136
  end
86
137
 
87
138
  private
@@ -95,16 +146,16 @@ module ThemeCheck
95
146
  ignored_patterns: config.ignored_patterns
96
147
  )
97
148
 
98
- # Turn that into a hash of empty buffers
149
+ # Turn that into a hash of buffers
99
150
  files = fs.files
100
- .map { |fn| [fn, ""] }
151
+ .map { |fn| [fn, fs.read(fn)] }
101
152
  .to_h
102
153
 
103
- InMemoryStorage.new(files, config.root)
154
+ VersionedInMemoryStorage.new(files, config.root)
104
155
  end
105
156
 
106
157
  def text_document_uri(params)
107
- file_path(params.dig('textDocument', 'uri'))
158
+ file_path(params.dig(:textDocument, :uri))
108
159
  end
109
160
 
110
161
  def relative_path_from_text_document_uri(params)
@@ -112,8 +163,8 @@ module ThemeCheck
112
163
  end
113
164
 
114
165
  def root_path_from_params(params)
115
- root_uri = params["rootUri"]
116
- root_path = params["rootPath"]
166
+ root_uri = params[:rootUri]
167
+ root_path = params[:rootPath]
117
168
  if root_uri
118
169
  file_path(root_uri)
119
170
  elsif root_path
@@ -122,11 +173,15 @@ module ThemeCheck
122
173
  end
123
174
 
124
175
  def text_document_text(params)
125
- params.dig('textDocument', 'text')
176
+ params.dig(:textDocument, :text)
177
+ end
178
+
179
+ def text_document_version(params)
180
+ params.dig(:textDocument, :version)
126
181
  end
127
182
 
128
183
  def content_changes_text(params)
129
- params.dig('contentChanges', 0, 'text')
184
+ params.dig(:contentChanges, 0, :text)
130
185
  end
131
186
 
132
187
  def config_for_path(path)
@@ -141,12 +196,11 @@ module ThemeCheck
141
196
  )
142
197
  end
143
198
 
144
- def completions(relative_path, line, col)
145
- @completion_engine.completions(relative_path, line, col)
146
- end
147
-
148
- def document_links(relative_path)
149
- @document_link_engine.document_links(relative_path)
199
+ def range_element(params, start_or_end)
200
+ [
201
+ params.dig(:range, start_or_end, :line),
202
+ params.dig(:range, start_or_end, :character),
203
+ ]
150
204
  end
151
205
 
152
206
  def log(message)
@@ -3,10 +3,18 @@
3
3
  module ThemeCheck
4
4
  module LanguageServer
5
5
  class IOMessenger < Messenger
6
+ def self.err_stream
7
+ if ThemeCheck.debug_log_file
8
+ File.open(ThemeCheck.debug_log_file, "w")
9
+ else
10
+ STDERR
11
+ end
12
+ end
13
+
6
14
  def initialize(
7
15
  in_stream: STDIN,
8
16
  out_stream: STDOUT,
9
- err_stream: STDERR
17
+ err_stream: IOMessenger.err_stream
10
18
  )
11
19
  validate!([in_stream, out_stream, err_stream])
12
20
 
@@ -40,6 +48,8 @@ module ThemeCheck
40
48
  content += chunk
41
49
  end
42
50
  content.lstrip!
51
+ rescue IOError
52
+ raise DoneStreaming
43
53
  end
44
54
 
45
55
  def send_message(message_body)
@@ -37,5 +37,9 @@ module ThemeCheck
37
37
  FULL = 1
38
38
  INCREMENTAL = 2
39
39
  end
40
+
41
+ module ErrorCodes
42
+ INTERNAL_ERROR = -32603
43
+ end
40
44
  end
41
45
  end
@@ -37,7 +37,7 @@ module ThemeCheck
37
37
  @handlers = []
38
38
 
39
39
  # The error queue holds blocks the main thread. When filled, we exit the program.
40
- @error = SizedQueue.new(1)
40
+ @error = SizedQueue.new(number_of_threads)
41
41
 
42
42
  @should_raise_errors = should_raise_errors
43
43
  end
@@ -55,9 +55,9 @@ module ThemeCheck
55
55
  @json_rpc_thread = Thread.new do
56
56
  loop do
57
57
  message = @bridge.read_message
58
- if message['method'] == 'initialize'
58
+ if message[:method] == 'initialize'
59
59
  handle_message(message)
60
- elsif message.key?('result')
60
+ elsif message.key?(:result)
61
61
  # Responses are handled on the main thread to prevent
62
62
  # a potential deadlock caused by all handlers waiting
63
63
  # for a responses.
@@ -94,27 +94,36 @@ module ThemeCheck
94
94
 
95
95
  rescue Exception => e # rubocop:disable Lint/RescueException
96
96
  raise e if should_raise_errors
97
- @bridge.log(e)
98
- @bridge.log(e.backtrace)
97
+ @bridge.log("#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
99
98
  2
100
99
  end
101
100
 
102
101
  private
103
102
 
104
103
  def handle_message(message)
105
- id = message['id']
106
- method_name = message['method']
104
+ id = message[:id]
105
+ method_name = message[:method]
107
106
  method_name &&= "on_#{to_snake_case(method_name)}"
108
- params = message['params']
107
+ params = message[:params]
109
108
 
110
109
  if @handler.respond_to?(method_name)
111
110
  @handler.send(method_name, id, params)
112
111
  end
112
+
113
+ rescue DoneStreaming => e
114
+ raise e
115
+ rescue StandardError => e
116
+ is_request = id
117
+ raise e unless is_request
118
+ # Errors obtained in request handlers should be sent
119
+ # back as internal errors instead of closing the program.
120
+ @bridge.log("#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
121
+ @bridge.send_internal_error(id, e)
113
122
  end
114
123
 
115
124
  def handle_response(message)
116
- id = message['id']
117
- result = message['result']
125
+ id = message[:id]
126
+ result = message[:result]
118
127
  @bridge.receive_response(id, result)
119
128
  end
120
129
 
@@ -135,7 +144,16 @@ module ThemeCheck
135
144
 
136
145
  # Hijack the status_code if an error occurred while cleaning up.
137
146
  # 👀 unit tests.
138
- return status_code_from_error(@error.pop) unless @error.empty?
147
+ until @error.empty?
148
+ code = status_code_from_error(@error.pop)
149
+ # Promote the status_code to ERROR if one of the threads
150
+ # resulted in an error, otherwise leave the status_code as
151
+ # is. That's because one thread could end successfully in a
152
+ # DoneStreaming error while the other failed with an
153
+ # internal error. If we had an internal error, we should
154
+ # return with a status_code that fits.
155
+ status_code = code if code > status_code
156
+ end
139
157
  status_code
140
158
  ensure
141
159
  @messenger.close_output
@@ -15,6 +15,7 @@ module ThemeCheck
15
15
  #
16
16
  # Exists because of https://github.com/Shopify/theme-check/issues/360
17
17
  def file_uri(absolute_path)
18
+ return if absolute_path.nil?
18
19
  "file://" + absolute_path
19
20
  .to_s
20
21
  .split('/')
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ class VersionedInMemoryStorage < InMemoryStorage
5
+ Version = Struct.new(:id, :version)
6
+
7
+ attr_reader :versions
8
+
9
+ def initialize(files, root = "/dev/null")
10
+ super(files, root)
11
+ @versions = {}
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+ # Motivations:
16
+ # - Need way for LanguageServer to know on which version of a file
17
+ # the check was run on, because we need to know where the
18
+ # TextEdit goes. If the text changed, our TextEdit might not be
19
+ # in the right spot. e.g.
20
+ #
21
+ # Example:
22
+ #
23
+ # ```
24
+ # Hi
25
+ # {{world}}
26
+ # ```
27
+ #
28
+ # Would produce two "SpaceInsideBrace" errors:
29
+ #
30
+ # - One after {{ at index 5 to 6
31
+ # - One before }} at index 10 to 11
32
+ #
33
+ # If the user goes in and changes Hi to Sup, and _then_
34
+ # right clicks to apply the code edit at index 5 to 6, he'd
35
+ # get the following:
36
+ #
37
+ # ```
38
+ # Sup
39
+ # { {world}}
40
+ # ```
41
+ #
42
+ # Which is not a fix at all.
43
+ #
44
+ # Solution:
45
+ # - Have the LanguageServer store the version on textDocument/did{Open,Change,Close}
46
+ # - Have ThemeFile store the version right after @storage.read.
47
+ # - Add version to the diagnostic meta data
48
+ # - Use diagnostic meta data to determine if we can make a code edit or not
49
+ # - Only offer fixes on "clean" files (or offer the change but specify the version so the editor knows what to do with it)
50
+ def write(relative_path, content, version)
51
+ @mutex.synchronize do
52
+ @versions[relative_path] = version
53
+ super(relative_path, content)
54
+ end
55
+ end
56
+
57
+ def read_version(relative_path)
58
+ @mutex.synchronize { [read(relative_path), version(relative_path)] }
59
+ end
60
+
61
+ def versioned?
62
+ true
63
+ end
64
+
65
+ def version(relative_path)
66
+ @versions[relative_path.to_s]
67
+ end
68
+ end
69
+ end
@@ -1,31 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative "language_server/protocol"
3
3
  require_relative "language_server/constants"
4
+ require_relative "language_server/configuration"
4
5
  require_relative "language_server/channel"
5
6
  require_relative "language_server/messenger"
6
7
  require_relative "language_server/io_messenger"
7
8
  require_relative "language_server/bridge"
8
9
  require_relative "language_server/uri_helper"
9
- require_relative "language_server/handler"
10
10
  require_relative "language_server/server"
11
11
  require_relative "language_server/tokens"
12
12
  require_relative "language_server/variable_lookup_finder"
13
+ require_relative "language_server/diagnostic"
14
+ require_relative "language_server/diagnostics_manager"
15
+ require_relative "language_server/diagnostics_engine"
16
+ require_relative "language_server/document_change_corrector"
17
+ require_relative "language_server/versioned_in_memory_storage"
18
+ require_relative "language_server/client_capabilities"
19
+
13
20
  require_relative "language_server/completion_helper"
14
21
  require_relative "language_server/completion_provider"
15
22
  require_relative "language_server/completion_engine"
23
+ Dir[__dir__ + "/language_server/completion_providers/*.rb"].each do |file|
24
+ require file
25
+ end
26
+
16
27
  require_relative "language_server/document_link_provider"
17
28
  require_relative "language_server/document_link_engine"
18
- require_relative "language_server/diagnostics_tracker"
19
- require_relative "language_server/diagnostics_engine"
29
+ Dir[__dir__ + "/language_server/document_link_providers/*.rb"].each do |file|
30
+ require file
31
+ end
20
32
 
21
- Dir[__dir__ + "/language_server/completion_providers/*.rb"].each do |file|
33
+ require_relative "language_server/execute_command_provider"
34
+ require_relative "language_server/execute_command_engine"
35
+ Dir[__dir__ + "/language_server/execute_command_providers/*.rb"].each do |file|
22
36
  require file
23
37
  end
24
38
 
25
- Dir[__dir__ + "/language_server/document_link_providers/*.rb"].each do |file|
39
+ require_relative "language_server/code_action_provider"
40
+ require_relative "language_server/code_action_engine"
41
+ Dir[__dir__ + "/language_server/code_action_providers/*.rb"].each do |file|
26
42
  require file
27
43
  end
28
44
 
45
+ require_relative "language_server/handler"
46
+
29
47
  module ThemeCheck
30
48
  module LanguageServer
31
49
  def self.start