theme-check 1.7.2 → 1.9.2

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