1use jlens_core::ast::{Node, Span};
8use jlens_core::signature::Signature;
9use jlens_core::{Diagnostic, Severity, parse_recover};
10use serde::{Deserialize, Serialize};
11
12use crate::shape::{Field, Schema, Shape};
13use crate::typing::{Event, Scope, Symbols, Ty, Walker};
14
15const PLACEHOLDER: &str = "__jlens_cursor__";
17
18const OPERATOR_CHARS: &str = ".[]{}(),@#;:?+-*/%|=<>^&!~\"'`";
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
22#[serde(rename_all = "lowercase")]
23pub enum CompletionKind {
24 Field,
26 Variable,
28 Function,
31 Keyword,
33 Operator,
35}
36
37#[derive(Debug, Clone, Serialize)]
39#[serde(rename_all = "camelCase")]
40pub struct CompletionItem {
41 pub label: String,
43 pub kind: CompletionKind,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub detail: Option<String>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub documentation: Option<String>,
51 pub insert_text: String,
53}
54
55#[derive(Debug, Clone, Serialize)]
57pub struct Completions {
58 pub range: Span,
60 pub items: Vec<CompletionItem>,
62}
63
64#[derive(Debug, Clone, Serialize)]
66pub struct Hover {
67 pub range: Span,
69 pub contents: String,
71}
72
73#[derive(Debug, Clone, Serialize)]
75#[serde(rename_all = "camelCase")]
76pub struct SignatureHelp {
77 pub name: String,
79 pub signature: String,
81 pub params: Vec<String>,
83 pub active_param: usize,
85 pub summary: String,
87}
88
89#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
95#[serde(default, deny_unknown_fields)]
96pub struct FunctionDecl {
97 pub name: String,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub signature: Option<String>,
102 pub params: Vec<String>,
104 pub summary: String,
106}
107
108impl FunctionDecl {
109 pub fn new(name: impl Into<String>, summary: impl Into<String>) -> Self {
111 FunctionDecl {
112 name: name.into(),
113 summary: summary.into(),
114 ..Self::default()
115 }
116 }
117
118 pub fn signature(mut self, signature: impl Into<String>) -> Self {
120 self.signature = Some(signature.into());
121 self
122 }
123
124 pub fn params<S: Into<String>>(mut self, params: impl IntoIterator<Item = S>) -> Self {
126 self.params = params.into_iter().map(Into::into).collect();
127 self
128 }
129}
130
131struct FunctionView {
133 name: String,
134 signature: String,
135 params: Vec<String>,
136 summary: String,
137}
138
139struct CallSite {
141 span: Span,
143 name: String,
144 count: usize,
146 args: Option<Vec<(Span, Option<Symbols>)>>,
148}
149
150#[derive(Debug, Clone, Default)]
164pub struct LanguageService {
165 schema: Option<Schema>,
166 variables: Vec<(String, Option<Shape>)>,
167 functions: Vec<FunctionDecl>,
168}
169
170#[derive(Debug, PartialEq)]
172enum Lexical {
173 Code,
174 Literal,
176 Backquote(usize),
178}
179
180fn lexical_state(text: &str, offset: usize) -> Lexical {
181 let bytes = text.as_bytes();
182 let mut i = 0;
183 while i < offset.min(bytes.len()) {
184 match bytes[i] {
185 q @ (b'"' | b'\'') => {
186 i += 1;
187 while i < bytes.len() && bytes[i] != q {
188 if bytes[i] == b'\\' {
189 i += 1;
190 }
191 i += 1;
192 }
193 if i >= offset {
194 return Lexical::Literal;
195 }
196 }
197 b'`' => {
198 let start = i;
199 i += 1;
200 while i < bytes.len() && bytes[i] != b'`' {
201 i += 1;
202 }
203 if i >= offset {
204 return Lexical::Backquote(start);
205 }
206 }
207 b'/' if bytes.get(i + 1) == Some(&b'*') => match text[i + 2..].find("*/") {
208 Some(end) if i + 2 + end + 2 <= offset => i += 2 + end + 1,
209 _ => return Lexical::Literal,
210 },
211 _ => {}
212 }
213 i += 1;
214 }
215 Lexical::Code
216}
217
218fn is_word_char(c: char) -> bool {
219 !c.is_whitespace() && !OPERATOR_CHARS.contains(c)
220}
221
222fn field_insert(name: &str) -> String {
224 let plain = !name.is_empty()
225 && name.chars().all(is_word_char)
226 && !name.starts_with(|c: char| c.is_ascii_digit() || c == '$')
227 && !matches!(name, "and" | "or" | "in" | "true" | "false" | "null");
228 if plain {
229 name.to_owned()
230 } else {
231 format!("`{name}`")
232 }
233}
234
235fn shape_docs(shape: &Shape, field: Option<&Field>) -> Option<String> {
236 let mut parts = Vec::new();
237 if let Some(label) = &shape.label {
238 parts.push(format!("**{label}**"));
239 }
240 if let Some(d) = &shape.description {
241 parts.push(d.clone());
242 }
243 if let Some(f) = field
244 && !f.required
245 && f.presence > 0.0
246 {
247 parts.push(format!(
248 "Optional: present in {:.0}% of the samples.",
249 f.presence * 100.0
250 ));
251 }
252 if let Some(format) = &shape.format {
253 parts.push(format!("Format: `{format}`"));
254 }
255 if let Some(values) = &shape.enumeration {
256 let v: Vec<String> = values.iter().map(|v| format!("`{v}`")).collect();
257 parts.push(format!("Values: {}", v.join(", ")));
258 } else if !shape.examples.is_empty() {
259 let v: Vec<String> = shape.examples.iter().map(|v| format!("`{v}`")).collect();
260 parts.push(format!("Examples: {}", v.join(", ")));
261 }
262 (!parts.is_empty()).then(|| parts.join("\n\n"))
263}
264
265#[derive(Default)]
267struct Probe<'s> {
268 ctx: Option<Ty<'s>>,
270 scope: Scope<'s>,
271 found_variable: bool,
272}
273
274impl LanguageService {
275 pub fn new(schema: Option<Schema>) -> Self {
278 LanguageService {
279 schema,
280 variables: Vec::new(),
281 functions: Vec::new(),
282 }
283 }
284
285 pub fn with_variable(mut self, name: impl Into<String>, shape: Option<Shape>) -> Self {
288 self.variables.push((name.into(), shape));
289 self
290 }
291
292 pub fn with_function(mut self, function: FunctionDecl) -> Self {
296 self.functions.retain(|f| f.name != function.name);
297 self.functions.push(function);
298 self
299 }
300
301 fn functions(&self) -> Vec<FunctionView> {
303 let declared = self.functions.iter().map(|f| FunctionView {
304 name: f.name.clone(),
305 signature: f.signature.clone().unwrap_or_default(),
306 params: f.params.clone(),
307 summary: f.summary.clone(),
308 });
309 let built_in = jlens_core::function_catalog()
310 .into_iter()
311 .filter(|b| self.functions.iter().all(|f| f.name != b.name))
312 .map(|b| FunctionView {
313 name: b.name.to_owned(),
314 signature: b.signature,
315 params: b.params.iter().map(|p| (*p).to_owned()).collect(),
316 summary: b.summary.to_owned(),
317 });
318 declared.chain(built_in).collect()
319 }
320
321 fn function(&self, name: &str) -> Option<FunctionView> {
322 self.functions().into_iter().find(|f| f.name == name)
323 }
324
325 fn root_ty(&self) -> Ty<'_> {
326 match &self.schema {
327 Some(s) => Ty::of(&s.root),
328 None => Ty::unknown(),
329 }
330 }
331
332 fn externals(&self) -> Vec<(String, Ty<'_>)> {
333 self.variables
334 .iter()
335 .map(|(n, s)| (n.clone(), s.as_ref().map_or_else(Ty::unknown, Ty::of)))
336 .collect()
337 }
338
339 fn walk<'s>(&'s self, root: &Node, visit: &mut dyn FnMut(Event<'_, 's>)) {
341 let mut externals = self.externals();
343 externals.extend(
344 self.functions
345 .iter()
346 .map(|f| (f.name.clone(), Ty::unknown())),
347 );
348 let mut walker = Walker {
349 root: self.root_ty(),
350 externals: &externals,
351 visit,
352 };
353 let ctx = walker.root.clone();
354 walker.walk(root, &ctx, &mut Vec::new());
355 }
356
357 pub fn complete(&self, text: &str, offset: usize) -> Completions {
359 let mut offset = offset.min(text.len());
360 while !text.is_char_boundary(offset) {
361 offset -= 1;
362 }
363 let empty = |range| Completions {
364 range,
365 items: Vec::new(),
366 };
367 match lexical_state(text, offset) {
368 Lexical::Literal => return empty(Span::new(offset, offset)),
369 Lexical::Backquote(start) => {
370 let end = if text[offset..].starts_with('`') {
372 offset + 1
373 } else {
374 offset
375 };
376 let prefix = &text[start + 1..offset];
377 let modified = format!("{}{PLACEHOLDER}{}", &text[..start], &text[end..]);
378 let probe = self.probe(&modified, false);
379 let mut items = self.field_items(probe.ctx.as_ref(), prefix);
380 for item in &mut items {
381 item.insert_text = format!("`{}`", item.label);
382 }
383 return Completions {
384 range: Span::new(start, end),
385 items,
386 };
387 }
388 Lexical::Code => {}
389 }
390
391 let start = text[..offset]
393 .char_indices()
394 .rev()
395 .take_while(|(_, c)| is_word_char(*c))
396 .last()
397 .map_or(offset, |(i, _)| i);
398 let end = offset
399 + text[offset..]
400 .chars()
401 .take_while(|c| is_word_char(*c))
402 .map(char::len_utf8)
403 .sum::<usize>();
404 let prefix = &text[start..offset];
405 let range = Span::new(start, end);
406 let before = text[..start].trim_end();
407 let after_dot = before.ends_with('.') && !before.ends_with("..");
408 let previous = before.chars().last();
409
410 let previous_word: String = before
412 .chars()
413 .rev()
414 .take_while(|c| is_word_char(*c))
415 .collect::<String>()
416 .chars()
417 .rev()
418 .collect();
419 let operator_position = !prefix.starts_with('$')
420 && before.len() < start
421 && previous.is_some_and(|c| is_word_char(c) || ")]}\"'`".contains(c))
422 && !before.ends_with('$')
423 && !matches!(previous_word.as_str(), "and" | "or" | "in");
424 if operator_position && !after_dot {
425 let ops = [
426 ("and", "Both conditions hold"),
427 ("or", "Either condition holds"),
428 ("in", "Value is one of the items of an array"),
429 ];
430 let items = ops
431 .iter()
432 .filter(|(op, _)| op.starts_with(prefix))
433 .map(|(op, doc)| CompletionItem {
434 label: (*op).into(),
435 kind: CompletionKind::Operator,
436 detail: None,
437 documentation: Some((*doc).into()),
438 insert_text: (*op).into(),
439 })
440 .collect();
441 return Completions { range, items };
442 }
443
444 let variable_mode = prefix.starts_with('$');
445 let placeholder = if variable_mode {
446 format!("${PLACEHOLDER}")
447 } else {
448 PLACEHOLDER.to_owned()
449 };
450 let modified = format!("{}{placeholder}{}", &text[..start], &text[end..]);
451 let probe = self.probe(&modified, variable_mode);
452
453 let mut items = Vec::new();
454 if variable_mode {
455 items.extend(self.variable_items(&probe.scope, &prefix[1..]));
456 items.extend(self.function_items(&prefix[1..]));
457 } else {
458 items.extend(self.field_items(probe.ctx.as_ref(), prefix));
459 if !after_dot {
460 items.extend(self.variable_items(&probe.scope, prefix));
461 items.extend(self.function_items(prefix));
462 for kw in ["true", "false", "null"] {
463 if kw.starts_with(prefix) && !prefix.is_empty() {
464 items.push(CompletionItem {
465 label: kw.into(),
466 kind: CompletionKind::Keyword,
467 detail: None,
468 documentation: None,
469 insert_text: kw.into(),
470 });
471 }
472 }
473 }
474 }
475 Completions { range, items }
476 }
477
478 fn probe<'s>(&'s self, modified: &str, variable: bool) -> Probe<'s> {
480 let ast = parse_recover(modified);
481 let mut probe = Probe::default();
482 let mut visit = |event: Event<'_, 's>| match event {
483 Event::Field {
484 name, ctx, scope, ..
485 } if !variable && name == PLACEHOLDER => {
486 probe.ctx = Some(ctx.clone());
487 probe.scope = scope.clone();
488 }
489 Event::Variable { name, scope, .. } if variable && name == PLACEHOLDER => {
490 probe.scope = scope.clone();
491 probe.found_variable = true;
492 }
493 _ => {}
494 };
495 self.walk(&ast.root, &mut visit);
496 if probe.ctx.is_none() && !probe.found_variable && !variable {
497 probe.ctx = Some(self.root_ty());
499 }
500 probe
501 }
502
503 fn field_items(&self, ctx: Option<&Ty<'_>>, prefix: &str) -> Vec<CompletionItem> {
504 let Some(ctx) = ctx else {
505 return Vec::new();
506 };
507 let lower = prefix.to_lowercase();
508 let mut fields: Vec<(&str, &Field)> = ctx
509 .fields()
510 .into_iter()
511 .filter(|(name, f)| !f.shape.hidden && name.to_lowercase().starts_with(&lower))
512 .collect();
513 fields.sort_by(|a, b| {
515 b.1.required
516 .cmp(&a.1.required)
517 .then(b.1.presence.total_cmp(&a.1.presence))
518 });
519 fields
520 .into_iter()
521 .map(|(name, f)| CompletionItem {
522 label: name.to_owned(),
523 kind: CompletionKind::Field,
524 detail: Some(f.shape.type_summary()),
525 documentation: shape_docs(&f.shape, Some(f)),
526 insert_text: field_insert(name),
527 })
528 .collect()
529 }
530
531 fn variable_items(&self, scope: &Scope<'_>, prefix: &str) -> Vec<CompletionItem> {
532 let mut seen: Vec<String> = Vec::new();
533 let mut items = Vec::new();
534 let externals = self.externals();
535 let candidates = scope
536 .iter()
537 .rev()
538 .chain(externals.iter())
539 .map(|(n, t)| (n.clone(), t.clone()));
540 for (name, ty) in candidates {
541 if name.starts_with(prefix) && !seen.contains(&name) && !name.is_empty() {
542 let detail = match ty.shapes.as_slice() {
543 [shape] => Some(shape.type_summary()),
544 _ => None,
545 };
546 items.push(CompletionItem {
547 label: format!("${name}"),
548 kind: CompletionKind::Variable,
549 detail,
550 documentation: None,
551 insert_text: format!("${name}"),
552 });
553 seen.push(name);
554 }
555 }
556 for (name, doc) in [("$", "The context value"), ("$$", "The input document")] {
557 if prefix.is_empty() || name[1..].starts_with(prefix) {
558 items.push(CompletionItem {
559 label: name.into(),
560 kind: CompletionKind::Variable,
561 detail: None,
562 documentation: Some(doc.into()),
563 insert_text: name.into(),
564 });
565 }
566 }
567 items
568 }
569
570 fn function_items(&self, prefix: &str) -> Vec<CompletionItem> {
571 self.functions()
572 .into_iter()
573 .filter(|f| f.name.starts_with(prefix))
574 .map(|f| CompletionItem {
575 label: format!("${}", f.name),
576 kind: CompletionKind::Function,
577 detail: (!f.signature.is_empty()).then(|| f.signature.clone()),
578 documentation: (!f.summary.is_empty()).then_some(f.summary),
579 insert_text: format!("${}", f.name),
580 })
581 .collect()
582 }
583
584 pub fn diagnostics(&self, text: &str) -> Vec<Diagnostic> {
589 let ast = parse_recover(text);
590 let mut out = ast.diagnostics.clone();
591 let mut calls: Vec<CallSite> = Vec::new();
594 let mut variables: Vec<(Span, String)> = Vec::new();
595 let mut bound: Vec<String> = Vec::new();
596 let mut visit = |event: Event<'_, '_>| match event {
597 Event::Field {
598 node, name, ctx, ..
599 } if self.schema.is_some()
600 && ctx.is_known_objects()
601 && ctx.fields().iter().all(|(n, _)| *n != name) =>
602 {
603 let known: Vec<&str> = ctx.fields().iter().map(|(n, _)| *n).collect();
604 let quoted = text[node.span.start..].starts_with('`');
605 let len = name.len() + if quoted { 2 } else { 0 };
606 let span = Span::new(node.span.start, (node.span.start + len).min(node.span.end));
607 out.push(Diagnostic::new(
608 Severity::Warning,
609 "W0201",
610 span,
611 &[("token", name), ("hint", &did_you_mean(name, &known, ""))],
612 ));
613 }
614 Event::Call {
615 node,
616 name,
617 args,
618 types,
619 } => calls.push(CallSite {
620 span: node.span,
621 name: name.to_owned(),
622 count: args,
623 args: types.map(|t| t.iter().map(|(s, ty)| (*s, ty.symbols())).collect()),
624 }),
625 Event::Variable { node, name, .. } => variables.push((node.span, name.to_owned())),
626 Event::Bind { name } => bound.push(name.to_owned()),
627 Event::Field { .. } => {}
628 };
629 self.walk(&ast.root, &mut visit);
630 out.extend(self.call_diagnostics(&calls, &bound));
631 out.extend(self.variable_diagnostics(&variables, &calls, &bound));
632 out.sort_by_key(|d| d.span.start);
633 out
634 }
635
636 fn call_diagnostics(&self, calls: &[CallSite], bound: &[String]) -> Vec<Diagnostic> {
640 let functions = self.functions();
641 let mut out = Vec::new();
642 for CallSite {
643 span,
644 name,
645 count,
646 args,
647 } in calls
648 {
649 if name.is_empty()
650 || name == "$"
651 || bound.contains(name)
652 || self.variables.iter().any(|(v, _)| v == name)
653 {
654 continue;
655 }
656 let Some(function) = functions.iter().find(|f| f.name == *name) else {
657 let known: Vec<&str> = functions.iter().map(|f| f.name.as_str()).collect();
658 out.push(Diagnostic::new(
659 Severity::Warning,
660 "W0202",
661 *span,
662 &[("token", name), ("hint", &did_you_mean(name, &known, "$"))],
663 ));
664 continue;
665 };
666 let Some((min, max)) = arity(&function.signature) else {
667 continue;
668 };
669 if *count < min || max.is_some_and(|max| *count > max) {
670 let arguments = |n: usize| match n {
671 1 => "1 argument".to_owned(),
672 n => format!("{n} arguments"),
673 };
674 let expected = match max {
675 Some(max) if max == min => arguments(max),
676 Some(max) if min == 0 => format!("at most {}", arguments(max)),
677 Some(max) => format!("{min} to {max} arguments"),
678 None => format!("at least {}", arguments(min)),
679 };
680 out.push(Diagnostic::new(
681 Severity::Warning,
682 "W0203",
683 *span,
684 &[
685 ("token", name),
686 ("expected", &expected),
687 ("count", &count.to_string()),
688 ],
689 ));
690 continue;
691 }
692 if let (Some(args), Ok(signature)) = (args, Signature::parse(&function.signature))
693 && let Some(index) = mismatch(&signature, args)
694 && let (arg_span, Some(symbols)) = &args[index]
695 {
696 out.push(Diagnostic::new(
697 Severity::Warning,
698 "W0205",
699 *arg_span,
700 &[
701 ("index", &(index + 1).to_string()),
702 ("token", name),
703 ("type", &describe(symbols)),
704 ("signature", &function.signature),
705 ],
706 ));
707 }
708 }
709 out
710 }
711
712 fn variable_diagnostics(
716 &self,
717 references: &[(Span, String)],
718 calls: &[CallSite],
719 bound: &[String],
720 ) -> Vec<Diagnostic> {
721 if self.variables.is_empty() {
722 return Vec::new();
723 }
724 let functions = self.functions();
725 let mut known: Vec<&str> = self.variables.iter().map(|(n, _)| n.as_str()).collect();
726 known.extend(bound.iter().map(String::as_str));
727 references
728 .iter()
729 .filter(|(span, name)| {
730 !name.is_empty()
731 && name != "$"
732 && !known.contains(&name.as_str())
733 && functions.iter().all(|f| f.name != *name)
734 && calls.iter().all(|c| c.span != *span)
736 })
737 .map(|(span, name)| {
738 Diagnostic::new(
739 Severity::Warning,
740 "W0204",
741 *span,
742 &[("token", name), ("hint", &did_you_mean(name, &known, "$"))],
743 )
744 })
745 .collect()
746 }
747
748 pub fn hover(&self, text: &str, offset: usize) -> Option<Hover> {
750 let ast = parse_recover(text);
751 let mut best: Option<(Span, String)> = None;
752 let mut consider = |span: Span, contents: String| {
753 if span.contains(offset)
754 && best
755 .as_ref()
756 .is_none_or(|(b, _)| span.end - span.start <= b.end - b.start)
757 {
758 best = Some((span, contents));
759 }
760 };
761 let mut visit = |event: Event<'_, '_>| match event {
762 Event::Field {
763 node, name, ctx, ..
764 } => {
765 let span = Span::new(
766 node.span.start,
767 (node.span.start + name.len()).min(node.span.end),
768 );
769 let field = ctx
770 .fields()
771 .into_iter()
772 .find(|(n, _)| *n == name)
773 .map(|(_, f)| f);
774 let contents = match field {
775 Some(f) => {
776 let mut s = format!("`{name}`: {}", f.shape.type_summary());
777 if let Some(docs) = shape_docs(&f.shape, Some(f)) {
778 s.push_str("\n\n");
779 s.push_str(&docs);
780 }
781 s
782 }
783 None if ctx.is_known_objects() => format!("`{name}`: not in the schema"),
784 None => format!("`{name}`"),
785 };
786 consider(span, contents);
787 }
788 Event::Variable { node, name, ty, .. } => {
789 let contents = match self.function(name) {
790 Some(f) => format!("`${}{}`\n\n{}", f.name, f.signature, f.summary),
791 None => match ty.shapes.as_slice() {
792 [shape] => format!("`${name}`: {}", shape.type_summary()),
793 _ => format!("`${name}`"),
794 },
795 };
796 consider(node.span, contents);
797 }
798 Event::Call { .. } | Event::Bind { .. } => {}
799 };
800 self.walk(&ast.root, &mut visit);
801 best.map(|(range, contents)| Hover { range, contents })
802 }
803
804 pub fn signature_help(&self, text: &str, offset: usize) -> Option<SignatureHelp> {
806 let offset = offset.min(text.len());
807 let bytes = text.as_bytes();
808 let mut depth = 0i32;
809 let mut commas = 0;
810 let mut i = offset;
811 while i > 0 {
812 i -= 1;
813 if lexical_state(text, i) != Lexical::Code {
814 continue;
815 }
816 match bytes[i] {
817 b')' | b']' | b'}' => depth += 1,
818 b'[' | b'{' if depth > 0 => depth -= 1,
819 b'[' | b'{' => return None,
820 b'(' if depth > 0 => depth -= 1,
821 b'(' => {
822 let name_end = text[..i].trim_end().len();
823 let name_start = text[..name_end]
824 .char_indices()
825 .rev()
826 .take_while(|(_, c)| is_word_char(*c))
827 .last()
828 .map_or(name_end, |(j, _)| j);
829 let name = text[name_start..name_end].strip_prefix('$')?;
830 let f = self.function(name)?;
831 return Some(SignatureHelp {
832 name: format!("${name}"),
833 signature: f.signature,
834 params: f.params.iter().map(|p| format!("${p}")).collect(),
835 active_param: commas,
836 summary: f.summary,
837 });
838 }
839 b',' if depth == 0 => commas += 1,
840 _ => {}
841 }
842 }
843 None
844 }
845}
846
847fn arity(signature: &str) -> Option<(usize, Option<usize>)> {
851 if signature.is_empty() {
852 return None;
853 }
854 let params = Signature::parse(signature).ok()?.params;
855 let min = params.iter().filter(|p| !p.pattern.ends_with('?')).count();
856 let variadic = params.iter().any(|p| p.pattern.ends_with('+'));
857 Some((min, (!variadic).then_some(params.len())))
858}
859
860struct Piece<'p> {
863 set: &'p str,
864 min: usize,
865 max: usize,
866 items: Option<char>,
867}
868
869impl Piece<'_> {
870 fn accepts(&self, arg: Option<&Symbols>) -> bool {
872 let Some(arg) = arg else {
873 return true;
874 };
875 let mut values = arg.values.chars().chain(arg.items.chars());
877 let mut items = arg
878 .items
879 .chars()
880 .chain(arg.values.chars().filter(|c| *c != 'a'));
881 values.any(|c| self.set.contains(c))
882 && self.items.is_none_or(|want| items.any(|c| c == want))
883 }
884}
885
886fn mismatch(signature: &Signature, args: &[(Span, Option<Symbols>)]) -> Option<usize> {
889 let pieces: Vec<Piece> = signature
890 .params
891 .iter()
892 .map(|p| {
893 let (set, quantifier) = match p.pattern.strip_prefix('[') {
894 Some(rest) => rest.split_once(']').unwrap_or((rest, "")),
895 None => p.pattern.split_at(p.pattern.len().min(1)),
896 };
897 let (min, max) = match quantifier {
898 q if q.contains('+') => (1, usize::MAX),
899 q if q.contains('?') => (0, 1),
900 _ => (1, 1),
901 };
902 let items = match (p.array, p.subtype.as_deref().and_then(|s| s.chars().next())) {
903 (true, Some(c)) if "snblo".contains(c) => Some(c),
904 _ => None,
905 };
906 Piece {
907 set,
908 min,
909 max,
910 items,
911 }
912 })
913 .collect();
914 let args: Vec<Option<&Symbols>> = args.iter().map(|(_, s)| s.as_ref()).collect();
915 if match_pieces(&pieces, &args, 0, true).is_some() {
916 return None;
917 }
918 let mut good_to = 0;
920 for index in 0..pieces.len() {
921 match match_pieces(&pieces[..=index], &args, 0, false) {
922 Some(end) => good_to = end,
923 None => break,
924 }
925 }
926 args.get(good_to).copied().flatten().map(|_| good_to)
927}
928
929fn match_pieces(
932 pieces: &[Piece],
933 args: &[Option<&Symbols>],
934 pos: usize,
935 anchored: bool,
936) -> Option<usize> {
937 let Some((p, rest)) = pieces.split_first() else {
938 return (!anchored || pos == args.len()).then_some(pos);
939 };
940 let mut n = 0;
941 while n < p.max && pos + n < args.len() && p.accepts(args[pos + n]) {
942 n += 1;
943 }
944 loop {
945 if n < p.min {
946 return None;
947 }
948 if let Some(end) = match_pieces(rest, args, pos + n, anchored) {
949 return Some(end);
950 }
951 if n == 0 {
952 return None;
953 }
954 n -= 1;
955 }
956}
957
958fn describe(symbols: &Symbols) -> String {
960 let plural = |c| match c {
961 's' => "strings",
962 'n' => "numbers",
963 'b' => "booleans",
964 'l' => "nulls",
965 'o' => "objects",
966 _ => "arrays",
967 };
968 let parts: Vec<String> = symbols
969 .values
970 .chars()
971 .map(|c| match c {
972 's' => "a string".to_owned(),
973 'n' => "a number".to_owned(),
974 'b' => "a boolean".to_owned(),
975 'l' => "null".to_owned(),
976 'o' => "an object".to_owned(),
977 _ => {
978 let items: Vec<&str> = symbols.items.chars().map(plural).collect();
979 format!("an array of {}", items.join(" or "))
980 }
981 })
982 .collect();
983 parts.join(" or ")
984}
985
986fn did_you_mean(name: &str, candidates: &[&str], prefix: &str) -> String {
988 match closest(name, candidates) {
989 Some(s) => format!("; did you mean `{prefix}{s}`?"),
990 None => String::new(),
991 }
992}
993
994fn closest<'a>(name: &str, candidates: &[&'a str]) -> Option<&'a str> {
996 fn distance(a: &str, b: &str) -> usize {
997 let a: Vec<char> = a.to_lowercase().chars().collect();
998 let b: Vec<char> = b.to_lowercase().chars().collect();
999 let mut prev: Vec<usize> = (0..=b.len()).collect();
1000 for (i, ca) in a.iter().enumerate() {
1001 let mut cur = vec![i + 1; b.len() + 1];
1002 for (j, cb) in b.iter().enumerate() {
1003 cur[j + 1] = (prev[j] + usize::from(ca != cb))
1004 .min(prev[j + 1] + 1)
1005 .min(cur[j] + 1);
1006 }
1007 prev = cur;
1008 }
1009 prev[b.len()]
1010 }
1011 candidates
1012 .iter()
1013 .map(|c| (distance(name, c), *c))
1014 .filter(|(d, _)| *d <= 2)
1015 .min_by_key(|(d, _)| *d)
1016 .map(|(_, c)| c)
1017}