1use line_index::LineIndex;
2use log::info;
3use rowan::{TextRange, TextSize};
4use salsa::Setter;
5use serde::{Deserialize, Serialize};
6use squawk_ide::builtins::builtins_line_index;
7use squawk_ide::db::{self, Database, File};
8use squawk_ide::folding_ranges::{FoldKind, folding_ranges};
9use squawk_ide::goto_definition::FileId;
10use squawk_ide::semantic_tokens::{SemanticTokenType, semantic_tokens};
11use squawk_syntax::ast::AstNode;
12use wasm_bindgen::prelude::*;
13use web_sys::js_sys::Error;
14
15const SEMANTIC_TOKEN_TYPES: &[&str] = &[
16 "comment",
17 "function",
18 "keyword",
19 "namespace",
20 "number",
21 "operator",
22 "parameter",
23 "property",
24 "string",
25 "struct",
26 "type",
27 "variable",
28];
29
30const SEMANTIC_TOKEN_MODIFIERS: &[&str] = &["declaration", "definition", "readonly"];
31
32fn semantic_token_type_name(ty: SemanticTokenType) -> &'static str {
33 match ty {
34 SemanticTokenType::Bool | SemanticTokenType::Keyword => "keyword",
35 SemanticTokenType::Column => "variable",
36 SemanticTokenType::Comment => "comment",
37 SemanticTokenType::Function => "function",
38 SemanticTokenType::Name | SemanticTokenType::NameRef => "variable",
39 SemanticTokenType::Number => "number",
40 SemanticTokenType::Operator | SemanticTokenType::Punctuation => "operator",
41 SemanticTokenType::Parameter | SemanticTokenType::PositionalParam => "parameter",
42 SemanticTokenType::Schema => "namespace",
43 SemanticTokenType::String => "string",
44 SemanticTokenType::Table => "struct",
45 SemanticTokenType::Type => "type",
46 }
47}
48
49fn semantic_token_type_index(ty: SemanticTokenType) -> u32 {
50 let name = semantic_token_type_name(ty);
51 SEMANTIC_TOKEN_TYPES
52 .iter()
53 .position(|it| *it == name)
54 .unwrap() as u32
55}
56
57struct EncodedSemanticToken {
58 line: u32,
59 start: u32,
60 length: u32,
61 token_type: SemanticTokenType,
62 modifiers: u32,
63}
64
65struct SemanticTokenEncoder {
66 data: Vec<u32>,
67 prev_line: u32,
68 prev_start: u32,
69}
70
71impl SemanticTokenEncoder {
72 fn with_capacity(token_count: usize) -> Self {
73 Self {
74 data: Vec::with_capacity(token_count * 5),
75 prev_line: 0,
76 prev_start: 0,
77 }
78 }
79
80 fn push(&mut self, token: EncodedSemanticToken) {
81 let delta_line = token.line - self.prev_line;
82 let delta_start = if delta_line == 0 {
83 token.start - self.prev_start
84 } else {
85 token.start
86 };
87
88 self.data.extend_from_slice(&[
89 delta_line,
90 delta_start,
91 token.length,
92 semantic_token_type_index(token.token_type),
93 token.modifiers,
94 ]);
95
96 self.prev_line = token.line;
97 self.prev_start = token.start;
98 }
99
100 fn finish(self) -> Vec<u32> {
101 self.data
102 }
103}
104
105#[wasm_bindgen(start)]
106pub fn run() {
107 use log::Level;
108
109 #[cfg(feature = "console_error_panic_hook")]
116 console_error_panic_hook::set_once();
117 console_log::init_with_level(Level::Debug).expect("Initializing logger went wrong.");
118 info!("init!");
119}
120
121#[wasm_bindgen]
122pub struct SquawkDatabase {
123 db: Database,
124 file: Option<File>,
125}
126
127#[wasm_bindgen]
128#[allow(clippy::new_without_default)]
129impl SquawkDatabase {
130 #[wasm_bindgen(constructor)]
131 pub fn new() -> SquawkDatabase {
132 SquawkDatabase {
133 db: Database::default(),
134 file: None,
135 }
136 }
137
138 pub fn open_file(&mut self, content: String) {
139 let file = File::new(&self.db, content.into());
140 self.file = Some(file);
141 }
142
143 pub fn update_file(&mut self, content: String) {
144 if let Some(file) = self.file {
145 file.set_content(&mut self.db).to(content.into());
146 }
147 }
148
149 fn file(&self) -> Result<File, Error> {
150 self.file
151 .ok_or_else(|| Error::new("No file open. Call open_file first."))
152 }
153
154 pub fn dump_cst(&self) -> Result<String, Error> {
155 let file = self.file()?;
156 let parse = db::parse(&self.db, file);
157 Ok(format!("{:#?}", parse.syntax_node()))
158 }
159
160 pub fn dump_tokens(&self) -> Result<String, Error> {
161 let file = self.file()?;
162 let content = file.content(&self.db);
163 let tokens = squawk_lexer::tokenize(content);
164 let mut start = 0;
165 let mut out = String::new();
166 for token in tokens {
167 let end = start + token.len;
168 let text = &content[start as usize..(end) as usize];
169 out += &format!("{:?}@{start}..{end} {:?}\n", token.kind, text);
170 start += token.len;
171 }
172 Ok(out)
173 }
174
175 pub fn lint(&self) -> Result<JsValue, Error> {
176 let file = self.file()?;
177 let content = file.content(&self.db);
178 let mut linter = squawk_linter::Linter::with_default_rules();
179 let parse = db::parse(&self.db, file);
180 let parse_errors = parse.errors();
181
182 let line_index = db::line_index(&self.db, file);
183
184 let parse_errors = parse_errors.iter().map(|x| {
185 let range_start = x.range().start();
186 let range_end = x.range().end();
187 let start = line_index.line_col(range_start);
188 let end = line_index.line_col(range_end);
189 let start = line_index
190 .to_wide(line_index::WideEncoding::Utf16, start)
191 .unwrap();
192 let end = line_index
193 .to_wide(line_index::WideEncoding::Utf16, end)
194 .unwrap();
195 LintError {
196 severity: Severity::Error,
197 code: "syntax-error".to_string(),
198 message: x.message().to_string(),
199 start_line_number: start.line,
200 start_column: start.col,
201 end_line_number: end.line,
202 end_column: end.col,
203 range_start: range_start.into(),
204 range_end: range_end.into(),
205 messages: vec![],
206 fix: None,
207 }
208 });
209
210 let lint_errors = linter.lint(&parse, content);
211 let errors = lint_errors.into_iter().map(|x| {
212 let start = line_index.line_col(x.text_range.start());
213 let end = line_index.line_col(x.text_range.end());
214 let start = line_index
215 .to_wide(line_index::WideEncoding::Utf16, start)
216 .unwrap();
217 let end = line_index
218 .to_wide(line_index::WideEncoding::Utf16, end)
219 .unwrap();
220
221 let messages = x.help.into_iter().collect();
222
223 let fix = x.fix.map(|fix| {
224 let edits = fix
225 .edits
226 .into_iter()
227 .map(|edit| {
228 let start_pos = line_index.line_col(edit.text_range.start());
229 let end_pos = line_index.line_col(edit.text_range.end());
230 let start_wide = line_index
231 .to_wide(line_index::WideEncoding::Utf16, start_pos)
232 .unwrap();
233 let end_wide = line_index
234 .to_wide(line_index::WideEncoding::Utf16, end_pos)
235 .unwrap();
236
237 TextEdit {
238 start_line_number: start_wide.line,
239 start_column: start_wide.col,
240 end_line_number: end_wide.line,
241 end_column: end_wide.col,
242 text: edit.text.unwrap_or_default(),
243 }
244 })
245 .collect();
246
247 Fix {
248 title: fix.title,
249 edits,
250 }
251 });
252
253 LintError {
254 code: x.code.to_string(),
255 range_start: x.text_range.start().into(),
256 range_end: x.text_range.end().into(),
257 message: x.message.clone(),
258 messages,
259 severity: Severity::Warning,
260 start_line_number: start.line,
261 start_column: start.col,
262 end_line_number: end.line,
263 end_column: end.col,
264 fix,
265 }
266 });
267
268 let mut errors_to_dump = errors.chain(parse_errors).collect::<Vec<_>>();
269 errors_to_dump.sort_by_key(|k| (k.start_line_number, k.start_column));
270
271 serde_wasm_bindgen::to_value(&errors_to_dump).map_err(into_error)
272 }
273
274 pub fn goto_definition(&self, line: u32, col: u32) -> Result<JsValue, Error> {
275 let file = self.file()?;
276 let current_line_index = db::line_index(&self.db, file);
277 let offset = position_to_offset(¤t_line_index, line, col)?;
278 let builtins_li = builtins_line_index(&self.db);
279 let result = squawk_ide::goto_definition::goto_definition(&self.db, file, offset);
280
281 let response: Vec<LocationRange> = result
282 .into_iter()
283 .map(|location| {
284 let range = location.range;
285 let (file, line_index) = match location.file {
286 FileId::Current => ("current", ¤t_line_index),
287 FileId::Builtins => ("builtins", &builtins_li),
288 };
289 let start = line_index.line_col(range.start());
290 let end = line_index.line_col(range.end());
291 let start_wide = line_index
292 .to_wide(line_index::WideEncoding::Utf16, start)
293 .unwrap();
294 let end_wide = line_index
295 .to_wide(line_index::WideEncoding::Utf16, end)
296 .unwrap();
297
298 LocationRange {
299 file: file.to_string(),
300 start_line: start_wide.line,
301 start_column: start_wide.col,
302 end_line: end_wide.line,
303 end_column: end_wide.col,
304 }
305 })
306 .collect();
307
308 serde_wasm_bindgen::to_value(&response).map_err(into_error)
309 }
310
311 pub fn hover(&self, line: u32, col: u32) -> Result<JsValue, Error> {
312 let file = self.file()?;
313 let line_index = db::line_index(&self.db, file);
314 let offset = position_to_offset(&line_index, line, col)?;
315 let result = squawk_ide::hover::hover(&self.db, file, offset);
316
317 let converted = result.map(|hover| WasmHover {
318 snippet: hover.snippet,
319 comment: hover.comment,
320 });
321
322 serde_wasm_bindgen::to_value(&converted).map_err(into_error)
323 }
324
325 pub fn find_references(&self, line: u32, col: u32) -> Result<JsValue, Error> {
326 let file = self.file()?;
327 let line_index = db::line_index(&self.db, file);
328 let offset = position_to_offset(&line_index, line, col)?;
329 let references = squawk_ide::find_references::find_references(&self.db, file, offset);
330 let builtins_li = builtins_line_index(&self.db);
331 let locations: Vec<LocationRange> = references
332 .iter()
333 .map(|loc| {
334 let (li, file) = match loc.file {
335 FileId::Current => (&line_index, "current"),
336 FileId::Builtins => (&builtins_li, "builtins"),
337 };
338 let start = li.line_col(loc.range.start());
339 let end = li.line_col(loc.range.end());
340 let start_wide = li.to_wide(line_index::WideEncoding::Utf16, start).unwrap();
341 let end_wide = li.to_wide(line_index::WideEncoding::Utf16, end).unwrap();
342
343 LocationRange {
344 file: file.to_string(),
345 start_line: start_wide.line,
346 start_column: start_wide.col,
347 end_line: end_wide.line,
348 end_column: end_wide.col,
349 }
350 })
351 .collect();
352
353 serde_wasm_bindgen::to_value(&locations).map_err(into_error)
354 }
355
356 pub fn document_symbols(&self) -> Result<JsValue, Error> {
357 let file = self.file()?;
358 let line_index = db::line_index(&self.db, file);
359 let symbols = squawk_ide::document_symbols::document_symbols(&self.db, file);
360
361 let converted: Vec<WasmDocumentSymbol> = symbols
362 .into_iter()
363 .map(|s| convert_document_symbol(&line_index, s))
364 .collect();
365
366 serde_wasm_bindgen::to_value(&converted).map_err(into_error)
367 }
368
369 pub fn code_actions(&self, line: u32, col: u32) -> Result<JsValue, Error> {
370 let file = self.file()?;
371 let line_index = db::line_index(&self.db, file);
372 let offset = position_to_offset(&line_index, line, col)?;
373 let actions = squawk_ide::code_actions::code_actions(&self.db, file, offset);
374
375 let converted = actions.map(|actions| {
376 actions
377 .into_iter()
378 .map(|action| {
379 let edits = action
380 .edits
381 .into_iter()
382 .map(|edit| {
383 let start_pos = line_index.line_col(edit.text_range.start());
384 let end_pos = line_index.line_col(edit.text_range.end());
385 let start_wide = line_index
386 .to_wide(line_index::WideEncoding::Utf16, start_pos)
387 .unwrap();
388 let end_wide = line_index
389 .to_wide(line_index::WideEncoding::Utf16, end_pos)
390 .unwrap();
391
392 TextEdit {
393 start_line_number: start_wide.line,
394 start_column: start_wide.col,
395 end_line_number: end_wide.line,
396 end_column: end_wide.col,
397 text: edit.text.unwrap_or_default(),
398 }
399 })
400 .collect();
401
402 WasmCodeAction {
403 title: action.title,
404 edits,
405 kind: match action.kind {
406 squawk_ide::code_actions::ActionKind::QuickFix => "quickfix",
407 squawk_ide::code_actions::ActionKind::RefactorRewrite => {
408 "refactor.rewrite"
409 }
410 }
411 .to_string(),
412 }
413 })
414 .collect::<Vec<_>>()
415 });
416
417 serde_wasm_bindgen::to_value(&converted).map_err(into_error)
418 }
419
420 pub fn inlay_hints(&self) -> Result<JsValue, Error> {
421 let file = self.file()?;
422 let line_index = db::line_index(&self.db, file);
423 let hints = squawk_ide::inlay_hints::inlay_hints(&self.db, file);
424
425 let converted: Vec<WasmInlayHint> = hints
426 .into_iter()
427 .map(|hint| {
428 let position = line_index.line_col(hint.position);
429 let position_wide = line_index
430 .to_wide(line_index::WideEncoding::Utf16, position)
431 .unwrap();
432
433 WasmInlayHint {
434 line: position_wide.line,
435 column: position_wide.col,
436 label: hint.label,
437 kind: match hint.kind {
438 squawk_ide::inlay_hints::InlayHintKind::Type => "type",
439 squawk_ide::inlay_hints::InlayHintKind::Parameter => "parameter",
440 }
441 .to_string(),
442 }
443 })
444 .collect();
445
446 serde_wasm_bindgen::to_value(&converted).map_err(into_error)
447 }
448
449 pub fn folding_ranges(&self) -> Result<JsValue, Error> {
450 let file = self.file()?;
451 let line_index = db::line_index(&self.db, file);
452 let folds = folding_ranges(&self.db, file);
453
454 let converted: Vec<WasmFoldingRange> = folds
455 .into_iter()
456 .map(|fold| {
457 let start = line_index.line_col(fold.range.start());
458 let end = line_index.line_col(fold.range.end());
459 let start_wide = line_index
460 .to_wide(line_index::WideEncoding::Utf16, start)
461 .unwrap();
462 let end_wide = line_index
463 .to_wide(line_index::WideEncoding::Utf16, end)
464 .unwrap();
465
466 WasmFoldingRange {
467 start_line: start_wide.line,
468 end_line: end_wide.line,
469 kind: match fold.kind {
470 FoldKind::Comment => "comment",
471 _ => "region",
472 }
473 .to_string(),
474 }
475 })
476 .collect();
477
478 serde_wasm_bindgen::to_value(&converted).map_err(into_error)
479 }
480
481 pub fn selection_ranges(&self, positions: Vec<JsValue>) -> Result<JsValue, Error> {
482 let file = self.file()?;
483 let parse = db::parse(&self.db, file);
484 let line_index = db::line_index(&self.db, file);
485 let tree = parse.tree();
486 let root = tree.syntax();
487
488 let mut results: Vec<Vec<WasmSelectionRange>> = vec![];
489
490 for pos in positions {
491 let pos: Position = serde_wasm_bindgen::from_value(pos).map_err(into_error)?;
492 let offset = position_to_offset(&line_index, pos.line, pos.column)?;
493
494 let mut ranges = vec![];
495 let mut range = TextRange::new(offset, offset);
496
497 for _ in 0..20 {
498 let next = squawk_ide::expand_selection::extend_selection(root, range);
499 if next == range {
500 break;
501 }
502
503 let start = line_index.line_col(next.start());
504 let end = line_index.line_col(next.end());
505 let start_wide = line_index
506 .to_wide(line_index::WideEncoding::Utf16, start)
507 .unwrap();
508 let end_wide = line_index
509 .to_wide(line_index::WideEncoding::Utf16, end)
510 .unwrap();
511
512 ranges.push(WasmSelectionRange {
513 start_line: start_wide.line,
514 start_column: start_wide.col,
515 end_line: end_wide.line,
516 end_column: end_wide.col,
517 });
518
519 range = next;
520 }
521
522 results.push(ranges);
523 }
524
525 serde_wasm_bindgen::to_value(&results).map_err(into_error)
526 }
527
528 pub fn semantic_tokens(&self) -> Result<Vec<u32>, Error> {
529 let file = self.file()?;
530 let line_index = db::line_index(&self.db, file);
531 let content = file.content(&self.db);
532 let tokens = semantic_tokens(&self.db, file, None);
533
534 let mut encoder = SemanticTokenEncoder::with_capacity(tokens.len());
535
536 for token in &tokens {
538 for mut text_range in line_index.lines(token.range) {
542 if content[text_range].ends_with('\n') {
543 text_range =
544 TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n'));
545 }
546 let start_lc = line_index.line_col(text_range.start());
547 let end_lc = line_index.line_col(text_range.end());
548 let start_wide = line_index
549 .to_wide(line_index::WideEncoding::Utf16, start_lc)
550 .unwrap();
551 let end_wide = line_index
552 .to_wide(line_index::WideEncoding::Utf16, end_lc)
553 .unwrap();
554
555 encoder.push(EncodedSemanticToken {
556 line: start_wide.line,
557 start: start_wide.col,
558 length: end_wide.col - start_wide.col,
559 token_type: token.token_type,
560 modifiers: 0,
562 });
563 }
564 }
565
566 Ok(encoder.finish())
567 }
568
569 pub fn semantic_tokens_legend() -> Result<JsValue, Error> {
570 let legend = SemanticTokensLegend {
571 token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
572 token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
573 };
574 serde_wasm_bindgen::to_value(&legend).map_err(into_error)
575 }
576
577 pub fn completion(&self, line: u32, col: u32) -> Result<JsValue, Error> {
578 let file = self.file()?;
579 let line_index = db::line_index(&self.db, file);
580 let offset = position_to_offset(&line_index, line, col)?;
581 let items = squawk_ide::completion::completion(&self.db, file, offset);
582
583 let converted: Vec<WasmCompletionItem> = items
584 .into_iter()
585 .map(|item| WasmCompletionItem {
586 label: item.label,
587 kind: match item.kind {
588 squawk_ide::completion::CompletionItemKind::Keyword => "keyword",
589 squawk_ide::completion::CompletionItemKind::Table => "table",
590 squawk_ide::completion::CompletionItemKind::Column => "column",
591 squawk_ide::completion::CompletionItemKind::Function => "function",
592 squawk_ide::completion::CompletionItemKind::Schema => "schema",
593 squawk_ide::completion::CompletionItemKind::Type => "type",
594 squawk_ide::completion::CompletionItemKind::Snippet => "snippet",
595 squawk_ide::completion::CompletionItemKind::Operator => "operator",
596 }
597 .to_string(),
598 detail: item.detail,
599 insert_text: item.insert_text,
600 insert_text_format: item.insert_text_format.map(|fmt| {
601 match fmt {
602 squawk_ide::completion::CompletionInsertTextFormat::PlainText => {
603 "plainText"
604 }
605 squawk_ide::completion::CompletionInsertTextFormat::Snippet => "snippet",
606 }
607 .to_string()
608 }),
609 trigger_completion_after_insert: item.trigger_completion_after_insert,
610 })
611 .collect();
612
613 serde_wasm_bindgen::to_value(&converted).map_err(into_error)
614 }
615}
616
617fn into_error<E: std::fmt::Display>(err: E) -> Error {
618 Error::new(&err.to_string())
619}
620
621fn position_to_offset(
622 line_index: &LineIndex,
623 line: u32,
624 col: u32,
625) -> Result<rowan::TextSize, Error> {
626 let wide_pos = line_index::WideLineCol { line, col };
627
628 let pos = line_index
629 .to_utf8(line_index::WideEncoding::Utf16, wide_pos)
630 .ok_or_else(|| Error::new("Invalid position"))?;
631
632 line_index
633 .offset(pos)
634 .ok_or_else(|| Error::new("Invalid position offset"))
635}
636
637fn convert_document_symbol(
638 line_index: &LineIndex,
639 symbol: squawk_ide::document_symbols::DocumentSymbol,
640) -> WasmDocumentSymbol {
641 let full_start = line_index.line_col(symbol.full_range.start());
642 let full_end = line_index.line_col(symbol.full_range.end());
643 let full_start_wide = line_index
644 .to_wide(line_index::WideEncoding::Utf16, full_start)
645 .unwrap();
646 let full_end_wide = line_index
647 .to_wide(line_index::WideEncoding::Utf16, full_end)
648 .unwrap();
649
650 let focus_start = line_index.line_col(symbol.focus_range.start());
651 let focus_end = line_index.line_col(symbol.focus_range.end());
652 let focus_start_wide = line_index
653 .to_wide(line_index::WideEncoding::Utf16, focus_start)
654 .unwrap();
655 let focus_end_wide = line_index
656 .to_wide(line_index::WideEncoding::Utf16, focus_end)
657 .unwrap();
658
659 WasmDocumentSymbol {
660 name: symbol.name,
661 detail: symbol.detail,
662 kind: match symbol.kind {
663 squawk_ide::document_symbols::DocumentSymbolKind::Schema => "schema",
664 squawk_ide::document_symbols::DocumentSymbolKind::Table => "table",
665 squawk_ide::document_symbols::DocumentSymbolKind::View => "view",
666 squawk_ide::document_symbols::DocumentSymbolKind::MaterializedView => {
667 "materialized_view"
668 }
669 squawk_ide::document_symbols::DocumentSymbolKind::Function => "function",
670 squawk_ide::document_symbols::DocumentSymbolKind::Aggregate => "aggregate",
671 squawk_ide::document_symbols::DocumentSymbolKind::Procedure => "procedure",
672 squawk_ide::document_symbols::DocumentSymbolKind::EventTrigger => "event_trigger",
673 squawk_ide::document_symbols::DocumentSymbolKind::Role => "role",
674 squawk_ide::document_symbols::DocumentSymbolKind::Policy => "policy",
675 squawk_ide::document_symbols::DocumentSymbolKind::PropertyGraph => "property_graph",
676 squawk_ide::document_symbols::DocumentSymbolKind::Type => "type",
677 squawk_ide::document_symbols::DocumentSymbolKind::Enum => "enum",
678 squawk_ide::document_symbols::DocumentSymbolKind::Index => "index",
679 squawk_ide::document_symbols::DocumentSymbolKind::Domain => "domain",
680 squawk_ide::document_symbols::DocumentSymbolKind::Sequence => "sequence",
681 squawk_ide::document_symbols::DocumentSymbolKind::Trigger => "trigger",
682 squawk_ide::document_symbols::DocumentSymbolKind::Tablespace => "tablespace",
683 squawk_ide::document_symbols::DocumentSymbolKind::Database => "database",
684 squawk_ide::document_symbols::DocumentSymbolKind::Server => "server",
685 squawk_ide::document_symbols::DocumentSymbolKind::Extension => "extension",
686 squawk_ide::document_symbols::DocumentSymbolKind::Column => "column",
687 squawk_ide::document_symbols::DocumentSymbolKind::Variant => "variant",
688 squawk_ide::document_symbols::DocumentSymbolKind::Cursor => "cursor",
689 squawk_ide::document_symbols::DocumentSymbolKind::PreparedStatement => {
690 "prepared_statement"
691 }
692 squawk_ide::document_symbols::DocumentSymbolKind::Channel => "channel",
693 }
694 .to_string(),
695 start_line: full_start_wide.line,
696 start_column: full_start_wide.col,
697 end_line: full_end_wide.line,
698 end_column: full_end_wide.col,
699 selection_start_line: focus_start_wide.line,
700 selection_start_column: focus_start_wide.col,
701 selection_end_line: focus_end_wide.line,
702 selection_end_column: focus_end_wide.col,
703 children: symbol
704 .children
705 .into_iter()
706 .map(|child| convert_document_symbol(line_index, child))
707 .collect(),
708 }
709}
710
711#[expect(unused)]
712#[derive(Serialize)]
713enum Severity {
714 Hint,
715 Info,
716 Warning,
717 Error,
718}
719
720#[derive(Serialize)]
721struct LintError {
722 severity: Severity,
723 code: String,
724 message: String,
725 start_line_number: u32,
726 start_column: u32,
727 end_line_number: u32,
728 end_column: u32,
729 range_start: usize,
730 range_end: usize,
731 messages: Vec<String>,
732 fix: Option<Fix>,
733}
734
735#[derive(Serialize)]
736struct Fix {
737 title: String,
738 edits: Vec<TextEdit>,
739}
740
741#[derive(Serialize)]
742struct TextEdit {
743 start_line_number: u32,
744 start_column: u32,
745 end_line_number: u32,
746 end_column: u32,
747 text: String,
748}
749
750#[derive(Serialize)]
751struct LocationRange {
752 file: String,
753 start_line: u32,
754 start_column: u32,
755 end_line: u32,
756 end_column: u32,
757}
758
759#[derive(Serialize)]
760struct WasmCodeAction {
761 title: String,
762 edits: Vec<TextEdit>,
763 kind: String,
764}
765
766#[derive(Serialize)]
767struct WasmHover {
768 snippet: String,
769 comment: Option<String>,
770}
771
772#[derive(Serialize)]
773struct WasmDocumentSymbol {
774 name: String,
775 detail: Option<String>,
776 kind: String,
777 start_line: u32,
778 start_column: u32,
779 end_line: u32,
780 end_column: u32,
781 selection_start_line: u32,
782 selection_start_column: u32,
783 selection_end_line: u32,
784 selection_end_column: u32,
785 children: Vec<WasmDocumentSymbol>,
786}
787
788#[derive(Serialize)]
789struct WasmInlayHint {
790 line: u32,
791 column: u32,
792 label: String,
793 kind: String,
794}
795
796#[derive(Serialize)]
797struct WasmFoldingRange {
798 start_line: u32,
799 end_line: u32,
800 kind: String,
801}
802
803#[derive(Serialize)]
804struct WasmSelectionRange {
805 start_line: u32,
806 start_column: u32,
807 end_line: u32,
808 end_column: u32,
809}
810
811#[derive(Serialize)]
812struct SemanticTokensLegend {
813 #[serde(rename = "tokenTypes")]
814 token_types: Vec<&'static str>,
815 #[serde(rename = "tokenModifiers")]
816 token_modifiers: Vec<&'static str>,
817}
818
819#[derive(Serialize)]
820struct WasmCompletionItem {
821 label: String,
822 kind: String,
823 detail: Option<String>,
824 insert_text: Option<String>,
825 insert_text_format: Option<String>,
826 trigger_completion_after_insert: bool,
827}
828
829#[derive(Deserialize)]
830struct Position {
831 line: u32,
832 column: u32,
833}