1#![doc = include_str!("../README.md")]
2#![allow(deprecated, clippy::new_without_default, clippy::too_many_lines, clippy::result_unit_err)]
3#![warn(clippy::must_use_candidate)]
4pub mod context;
7pub mod diagnostics;
8pub mod events;
9pub mod features;
10mod options;
11mod type_mappings;
12pub mod types;
13pub mod utilities;
14
15#[cfg(feature = "ezno-parser")]
16pub mod synthesis;
17
18use std::{
19 collections::{HashMap, HashSet},
20 path::{Path, PathBuf},
21 time::Duration,
22};
23
24use context::{
25 information::{LocalInformation, ModuleInformation},
26 Names,
27};
28
29use diagnostics::{TypeCheckError, TypeCheckWarning};
30pub(crate) use utilities::{map::Map, range_map::RangeMap, serialization::BinarySerializable};
31
32use features::{
33 functions::SynthesisableFunction, modules::CouldNotOpenFile, modules::SynthesisedModule,
34};
35
36pub use context::{Environment, GeneralContext, RootContext, Scope, VariableRegisterArguments};
37pub use diagnostics::{Diagnostic, DiagnosticKind, DiagnosticsContainer};
38pub use options::TypeCheckOptions;
39pub use types::{
40 calling::call_type_handle_errors, generics::GenericTypeParameters, properties::PropertyValue,
41 subtyping, Constant, Type, TypeId, TypeStore,
42};
43
44pub use source_map::{self, SourceId, Span};
45use source_map::{FileSystem, MapFileStore, Nullable, SpanWithSource, WithPathMap};
46pub use type_mappings::*;
47
48pub const INTERNAL_DEFINITION_FILE_PATH: &str = "internal.ts.d.bin";
49pub const INTERNAL_DEFINITION_FILE: &[u8] = include_bytes!("../definitions/internal.ts.d.bin");
50
51pub trait ReadFromFS {
52 fn read_file(&self, path: &std::path::Path) -> Option<Vec<u8>>;
55}
56
57impl<T, U> ReadFromFS for T
58where
59 T: Fn(&std::path::Path) -> Option<U>,
60 U: Into<Vec<u8>>,
61{
62 fn read_file(&self, path: &std::path::Path) -> Option<Vec<u8>> {
63 (self)(path).map(Into::into)
64 }
65}
66
67use levenshtein::levenshtein;
68
69pub trait ASTImplementation: Sized {
70 type ParseOptions;
71 type ParserRequirements;
73
74 type ParseError: Into<Diagnostic>;
75
76 type Module<'a>;
77 type OwnedModule;
79
80 type DefinitionFile<'a>;
81
82 type TypeAnnotation<'a>;
83 type TypeParameter<'a>;
84 type Expression<'a>;
85 type Block<'a>;
87 type MultipleExpression<'a>;
88 type ForStatementInitiliser<'a>;
89
90 type VariableField<'a>;
92
93 type ClassMethod<'a>: SynthesisableFunction<Self>;
94
95 fn module_from_string(
96 source_id: SourceId,
97 string: String,
98 options: Self::ParseOptions,
99 parser_requirements: &mut Self::ParserRequirements,
100 ) -> Result<Self::Module<'static>, Self::ParseError>;
101
102 fn definition_module_from_string(
103 source_id: SourceId,
104 string: String,
105 parser_requirements: &mut Self::ParserRequirements,
106 ) -> Result<Self::DefinitionFile<'static>, Self::ParseError>;
107
108 #[allow(clippy::needless_lifetimes)]
109 fn synthesise_module<'a, T: crate::ReadFromFS>(
110 module: &Self::Module<'a>,
111 source_id: SourceId,
112 module_context: &mut Environment,
113 checking_data: &mut crate::CheckingData<T, Self>,
114 );
115
116 #[allow(clippy::needless_lifetimes)]
117 fn synthesise_definition_module<'a, T: crate::ReadFromFS>(
118 module: &Self::DefinitionFile<'a>,
119 source: SourceId,
120 root: &RootContext,
121 checking_data: &mut CheckingData<T, Self>,
122 ) -> (Names, LocalInformation);
123
124 fn synthesise_expression<T: crate::ReadFromFS>(
126 expression: &Self::Expression<'_>,
127 expected_type: TypeId,
128 environment: &mut Environment,
129 checking_data: &mut crate::CheckingData<T, Self>,
130 ) -> TypeId;
131
132 fn synthesise_multiple_expression<'a, T: crate::ReadFromFS>(
134 expression: &'a Self::MultipleExpression<'a>,
135 expected_type: TypeId,
136 environment: &mut Environment,
137 checking_data: &mut crate::CheckingData<T, Self>,
138 ) -> TypeId;
139
140 fn synthesise_type_parameter_extends<T: crate::ReadFromFS>(
141 parameter: &Self::TypeParameter<'_>,
142 environment: &mut Environment,
143 checking_data: &mut crate::CheckingData<T, Self>,
144 ) -> TypeId;
145
146 fn synthesise_type_annotation<'a, T: crate::ReadFromFS>(
147 annotation: &'a Self::TypeAnnotation<'a>,
148 environment: &mut Environment,
149 checking_data: &mut crate::CheckingData<T, Self>,
150 ) -> TypeId;
151
152 fn synthesise_block<'a, T: crate::ReadFromFS>(
153 block: &'a Self::Block<'a>,
154 environment: &mut Environment,
155 checking_data: &mut crate::CheckingData<T, Self>,
156 );
157
158 fn synthesise_for_loop_initialiser<'a, T: crate::ReadFromFS>(
160 for_loop_initialiser: &'a Self::ForStatementInitiliser<'a>,
161 environment: &mut Environment,
162 checking_data: &mut crate::CheckingData<T, Self>,
163 );
164
165 fn expression_position<'a>(expression: &'a Self::Expression<'a>) -> Span;
166
167 fn multiple_expression_position<'a>(expression: &'a Self::MultipleExpression<'a>) -> Span;
168
169 fn type_parameter_name<'a>(parameter: &'a Self::TypeParameter<'a>) -> &'a str;
170
171 fn type_annotation_position<'a>(annotation: &'a Self::TypeAnnotation<'a>) -> Span;
172
173 fn parameter_constrained<'a>(parameter: &'a Self::TypeParameter<'a>) -> bool;
174
175 #[allow(clippy::fn_params_excessive_bools)]
176 fn parse_options(
177 is_js: bool,
178 extra_syntax: bool,
179 parse_comments: bool,
180 lsp_mode: bool,
181 ) -> Self::ParseOptions;
182
183 fn owned_module_from_module(m: Self::Module<'static>) -> Self::OwnedModule;
184
185 fn declare_and_assign_to_fields<'a, T: crate::ReadFromFS>(
187 field: &'a Self::VariableField<'a>,
188 environment: &mut Environment,
189 checking_data: &mut crate::CheckingData<T, Self>,
190 arguments: VariableRegisterArguments,
191 );
192}
193
194pub struct ModuleData<'a, FileReader, AST: ASTImplementation> {
198 pub(crate) file_reader: &'a FileReader,
199 pub(crate) parser_requirements: AST::ParserRequirements,
200 pub(crate) current_working_directory: PathBuf,
201 pub(crate) files: MapFileStore<WithPathMap>,
203 pub(crate) _currently_checking_modules: HashSet<PathBuf>,
205 pub(crate) synthesised_modules: HashMap<SourceId, SynthesisedModule<AST::OwnedModule>>,
207}
208
209impl<'a, T, A> ModuleData<'a, T, A>
210where
211 T: crate::ReadFromFS,
212 A: ASTImplementation,
213{
214 pub(crate) fn new(
215 file_resolver: &'a T,
216 current_working_directory: PathBuf,
217 files: Option<MapFileStore<WithPathMap>>,
218 parser_requirements: A::ParserRequirements,
219 ) -> Self {
220 Self {
221 files: files.unwrap_or_default(),
222 synthesised_modules: Default::default(),
223 _currently_checking_modules: Default::default(),
224 file_reader: file_resolver,
226 current_working_directory,
227 parser_requirements,
228 }
229 }
230
231 pub(crate) fn get_file(
232 &mut self,
233 path: &Path,
234 chronometer: Option<&mut Chronometer>,
235 ) -> Option<File> {
236 if let Some("bin") = path.extension().and_then(|s| s.to_str()) {
238 return self.file_reader.read_file(path).map(|s| File::Binary(s.clone()));
239 }
240
241 let get_source_at_path = self.files.get_source_at_path(path);
242
243 if let Some(source) = get_source_at_path {
251 Some(File::Source(source, self.files.get_file_content(source)))
252 } else {
253 let current = chronometer.is_some().then(std::time::Instant::now);
255 let content = self.file_reader.read_file(path)?;
256 if let Ok(content) = String::from_utf8(content) {
257 if let Some(current) = current {
258 chronometer.unwrap().fs += current.elapsed();
259 }
260 let source_id = self.files.new_source_id(path.to_path_buf(), content);
261 Some(File::Source(source_id, self.files.get_file_content(source_id)))
262 } else {
263 eprintln!("{} is not valid Utf-8", path.display());
264 None
265 }
266 }
267 }
268}
269
270pub enum File {
271 Binary(Vec<u8>),
272 Source(SourceId, String),
273}
274
275#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, binary_serialize_derive::BinarySerializable)]
276pub struct VariableId(pub SourceId, pub u32);
277
278#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, binary_serialize_derive::BinarySerializable)]
280pub struct FunctionId(pub SourceId, pub u32);
281
282#[derive(Debug)]
283pub enum Decidable<T> {
284 Known(T),
285 Unknown(TypeId),
287}
288
289pub enum PredicateBound<T> {
291 Always(T),
292}
293
294pub trait GenericTypeParameter {
295 fn get_name(&self) -> &str;
296}
297
298#[derive(Default)]
299pub struct Chronometer {
300 pub cached: Duration,
302 pub fs: Duration,
304 pub parse: Duration,
306 pub check: Duration,
308 pub lines: usize,
310}
311
312pub struct CheckingData<'a, FSResolver, ModuleAST: ASTImplementation> {
316 pub diagnostics_container: DiagnosticsContainer,
320 pub local_type_mappings: TypeMappings,
322 pub(crate) modules: ModuleData<'a, FSResolver, ModuleAST>,
324 pub(crate) options: TypeCheckOptions,
326
327 pub types: TypeStore,
329
330 pub(crate) chronometer: Chronometer,
331
332 unimplemented_items: HashSet<&'static str>,
334}
335
336impl<'a, T, A> CheckingData<'a, T, A>
337where
338 T: crate::ReadFromFS,
339 A: crate::ASTImplementation,
340{
341 pub fn new(
343 options: TypeCheckOptions,
344 resolver: &'a T,
345 existing_files: Option<MapFileStore<WithPathMap>>,
346 parser_requirements: A::ParserRequirements,
347 ) -> Self {
348 let cwd = Default::default();
350 let modules = ModuleData::new(resolver, cwd, existing_files, parser_requirements);
351
352 Self {
353 options,
354 modules,
355 diagnostics_container: Default::default(),
356 local_type_mappings: Default::default(),
357 types: Default::default(),
358 unimplemented_items: Default::default(),
359 chronometer: Default::default(),
360 }
361 }
362
363 pub fn raise_decidable_result_error(&mut self, span: SpanWithSource, value: bool) {
365 self.diagnostics_container.add_error(TypeCheckWarning::DeadBranch {
366 expression_span: span,
367 expression_value: value,
368 });
369 }
370
371 pub fn raise_unimplemented_error(&mut self, item: &'static str, position: SpanWithSource) {
373 if self.unimplemented_items.insert(item) {
374 self.diagnostics_container
375 .add_warning(TypeCheckWarning::Unimplemented { item, position });
376 }
377 }
378
379 pub fn add_expression_mapping(&mut self, span: SpanWithSource, instance: Instance) {
380 self.local_type_mappings.expressions_to_instances.push(span, instance);
381 }
382}
383
384pub struct CheckOutput<A: crate::ASTImplementation> {
386 pub types: crate::types::TypeStore,
387 pub module_contents: MapFileStore<WithPathMap>,
388 pub modules: HashMap<SourceId, SynthesisedModule<A::OwnedModule>>,
389 pub diagnostics: crate::DiagnosticsContainer,
390 pub top_level_information: LocalInformation,
391 pub chronometer: crate::Chronometer,
392}
393
394impl<A: crate::ASTImplementation> CheckOutput<A> {
395 #[must_use]
396 pub fn get_type_at_position(&self, path: &str, pos: u32, debug: bool) -> Option<String> {
397 let source = self.module_contents.get_source_at_path(path.as_ref())?;
398 let module = &self.modules.get(&source)?;
399
400 module.get_instance_at_position(pos).map(|instance| {
401 crate::types::printing::print_type(
402 instance.get_value_on_ref(),
403 &self.types,
404 &ModuleInformation { top: &self.top_level_information, module: &module.info },
405 debug,
406 )
407 })
408 }
409
410 #[must_use]
411 pub fn get_type_at_position_with_span(
412 &self,
413 path: &str,
414 pos: u32,
415 debug: bool,
416 ) -> Option<(String, SpanWithSource)> {
417 let source = self.module_contents.get_source_at_path(path.as_ref())?;
418 if let Some(module) = self.modules.get(&source) {
419 module.get_instance_at_position_with_span(pos).map(|(instance, range)| {
420 (
421 crate::types::printing::print_type(
422 instance.get_value_on_ref(),
423 &self.types,
424 &ModuleInformation {
425 top: &self.top_level_information,
426 module: &module.info,
427 },
428 debug,
429 ),
430 SpanWithSource { start: range.start, end: range.end, source },
431 )
432 })
433 } else {
434 eprintln!("no module here???");
435 None
436 }
437 }
438
439 #[must_use]
440 pub fn get_module(&self, path: &str) -> Option<&A::OwnedModule> {
441 let source_id = self.module_contents.get_source_at_path(path.as_ref())?;
442 Some(&self.modules.get(&source_id).expect("no module").content)
443 }
444
445 #[must_use]
446 pub fn empty() -> Self {
447 Self {
448 types: Default::default(),
449 module_contents: Default::default(),
450 modules: Default::default(),
451 diagnostics: Default::default(),
452 top_level_information: Default::default(),
453 chronometer: Default::default(),
454 }
455 }
456}
457
458#[allow(clippy::needless_pass_by_value)]
459pub fn check_project<T: crate::ReadFromFS, A: crate::ASTImplementation>(
460 entry_points: Vec<PathBuf>,
461 type_definition_files: Vec<PathBuf>,
462 resolver: &T,
463 options: TypeCheckOptions,
464 parser_requirements: A::ParserRequirements,
465 existing_files: Option<MapFileStore<WithPathMap>>,
466) -> CheckOutput<A> {
467 let mut checking_data =
468 CheckingData::<T, A>::new(options, resolver, existing_files, parser_requirements);
469
470 let mut root = crate::context::RootContext::new_with_primitive_references();
471
472 crate::utilities::notify!("--- Reading definition files from {:?} ---", type_definition_files);
473
474 if !checking_data.options.debug_dts {
476 crate::utilities::pause_debug_mode();
477 }
478 add_definition_files_to_root(type_definition_files, &mut root, &mut checking_data);
479 crate::utilities::unpause_debug_mode();
480
481 if checking_data.diagnostics_container.contains_error() {
482 return CheckOutput {
483 types: checking_data.types,
484 module_contents: checking_data.modules.files,
485 modules: Default::default(),
486 diagnostics: checking_data.diagnostics_container,
487 top_level_information: Default::default(),
488 chronometer: checking_data.chronometer,
489 };
490 }
491
492 crate::utilities::notify!("--- Finished definition file ---");
493
494 for point in &entry_points {
495 let current = checking_data.options.measure_time.then(std::time::Instant::now);
497
498 let entry_content = if let Some(source) =
499 checking_data.modules.files.get_source_at_path(point)
500 {
501 Some((source, checking_data.modules.files.get_file_content(source)))
502 } else if let Some(content) = checking_data.modules.file_reader.read_file(point) {
503 let content = String::from_utf8(content).expect("invalid entry point encoding");
504 let source = checking_data.modules.files.new_source_id(point.clone(), content.clone());
505 Some((source, content))
506 } else {
507 None
508 };
509 if let Some(current) = current {
510 checking_data.chronometer.fs += current.elapsed();
511 }
512
513 if let Some((source, content)) = entry_content {
514 let module = parse_source(point, source, content, &mut checking_data);
515
516 let current = checking_data.options.measure_time.then(std::time::Instant::now);
517 match module {
518 Ok(module) => {
519 let _module = root.new_module_context(source, module, &mut checking_data);
520 if let Some(current) = current {
521 checking_data.chronometer.check += current.elapsed();
522 }
523 }
524 Err(err) => {
525 checking_data.diagnostics_container.add_error(err);
526 }
527 }
528 } else {
529 checking_data.diagnostics_container.add_error(TypeCheckError::CannotOpenFile {
530 file: CouldNotOpenFile(point.clone()),
531 import_position: None,
532 possibles: checking_data
533 .modules
534 .files
535 .get_paths()
536 .keys()
537 .filter_map(|path| path.to_str())
538 .collect(),
539 partial_import_path: point.to_str().unwrap_or(""),
540 });
541 continue;
542 }
543 }
544
545 let CheckingData {
546 diagnostics_container,
547 local_type_mappings: _,
548 modules,
549 options: _,
550 types,
551 unimplemented_items: _,
552 chronometer,
553 } = checking_data;
554
555 CheckOutput {
556 types,
557 module_contents: modules.files,
558 modules: modules.synthesised_modules,
559 diagnostics: diagnostics_container,
560 top_level_information: root.info,
561 chronometer,
562 }
563}
564
565fn parse_source<T: crate::ReadFromFS, A: crate::ASTImplementation>(
566 path: &Path,
567 source: SourceId,
568 content: String,
569 checking_data: &mut CheckingData<T, A>,
570) -> Result<<A as ASTImplementation>::Module<'static>, <A as ASTImplementation>::ParseError> {
571 if checking_data.options.measure_time {
572 let code_lines =
573 content.lines().filter(|c| !(c.is_empty() || c.trim_start().starts_with('/'))).count();
574 checking_data.chronometer.lines += code_lines;
575 }
576
577 let current = checking_data.options.measure_time.then(std::time::Instant::now);
579
580 let is_js = path.extension().and_then(|s| s.to_str()).map_or(false, |s| s.ends_with("js"));
582
583 let parse_options = A::parse_options(
584 is_js,
585 checking_data.options.extra_syntax,
586 checking_data.options.parse_comments,
587 checking_data.options.lsp_mode,
588 );
589
590 let result = A::module_from_string(
591 source,
592 content,
593 parse_options,
594 &mut checking_data.modules.parser_requirements,
595 );
596
597 if let Some(current) = current {
598 checking_data.chronometer.parse += current.elapsed();
599 }
600
601 result
602}
603
604const CACHE_MARKER: &[u8] = b"ezno-cache-file";
605
606#[derive(binary_serialize_derive::BinarySerializable)]
607pub(crate) struct Cache {
608 pub(crate) variables: HashMap<String, features::variables::VariableOrImport>,
609 pub(crate) named_types: HashMap<String, TypeId>,
610 pub(crate) info: LocalInformation,
611 pub(crate) types: TypeStore,
612 }
615
616pub(crate) fn add_definition_files_to_root<T: crate::ReadFromFS, A: crate::ASTImplementation>(
617 type_definition_files: Vec<PathBuf>,
618 root: &mut RootContext,
619 checking_data: &mut CheckingData<T, A>,
620) {
621 let length = type_definition_files.len();
622 for path in type_definition_files {
623 let chronometer =
624 checking_data.options.measure_time.then_some(&mut checking_data.chronometer);
625
626 let file = if path == PathBuf::from(crate::INTERNAL_DEFINITION_FILE_PATH) {
627 File::Binary(crate::INTERNAL_DEFINITION_FILE.to_owned())
628 } else if let Some(file) = checking_data.modules.get_file(&path, chronometer) {
629 file
630 } else {
631 checking_data.diagnostics_container.add_error(Diagnostic::Global {
632 reason: format!("could not find {}", path.display()),
633 kind: crate::DiagnosticKind::Error,
634 });
635 continue;
636 };
637
638 match file {
639 File::Binary(data) => {
640 let current = checking_data.options.measure_time.then(std::time::Instant::now);
641 deserialize_cache(length, data, checking_data, root);
642 if let Some(current) = current {
643 checking_data.chronometer.cached += current.elapsed();
644 }
645 }
646 File::Source(source_id, source) => {
647 if checking_data.options.measure_time {
648 let code_lines = source
649 .lines()
650 .filter(|c| !(c.is_empty() || c.trim_start().starts_with('/')))
651 .count();
652 checking_data.chronometer.lines += code_lines;
653 }
654
655 let current = checking_data.options.measure_time.then(std::time::Instant::now);
656 let result = A::definition_module_from_string(
657 source_id,
658 source,
659 &mut checking_data.modules.parser_requirements,
660 );
661 if let Some(current) = current {
662 checking_data.chronometer.parse += current.elapsed();
663 }
664
665 match result {
666 Ok(tdm) => {
667 let current =
668 checking_data.options.measure_time.then(std::time::Instant::now);
669
670 let (names, info) =
671 A::synthesise_definition_module(&tdm, source_id, root, checking_data);
672
673 if let Some(current) = current {
675 checking_data.chronometer.check += current.elapsed();
676 }
677
678 root.variables.extend(names.variables);
679 root.named_types.extend(names.named_types);
680 root.variable_names.extend(names.variable_names);
681 root.info.extend(info, None);
682 }
683 Err(err) => {
684 checking_data.diagnostics_container.add_error(err);
685 continue;
686 }
687 }
688 }
689 }
690 }
691}
692
693fn deserialize_cache<T: ReadFromFS, A: ASTImplementation>(
694 length: usize,
695 mut content: Vec<u8>,
696 checking_data: &mut CheckingData<T, A>,
697 root: &mut RootContext,
698) {
699 crate::utilities::notify!("Using cache :)");
700 assert_eq!(length, 1, "only a single cache is current supported");
701
702 let end_content =
703 content[CACHE_MARKER.len()..(CACHE_MARKER.len() + U32_BYTES as usize)].to_owned();
704
705 let at_end =
706 <u32 as BinarySerializable>::deserialize(&mut end_content.into_iter(), SourceId::NULL);
707
708 let source_id = {
709 let mut drain =
711 content.drain((CACHE_MARKER.len() + U32_BYTES as usize + at_end as usize)..);
712
713 let (_source_id, path) =
715 <(SourceId, String) as BinarySerializable>::deserialize(&mut drain, SourceId::NULL);
716
717 let get_source_at_path = checking_data.modules.files.get_source_at_path(Path::new(&path));
718
719 if let Some(source_id) = get_source_at_path {
720 eprintln!("reusing source id {source_id:?}");
721 source_id
722 } else {
723 let source_content = String::from_utf8(drain.collect::<Vec<_>>()).unwrap();
725 checking_data.modules.files.new_source_id(path.into(), source_content)
726 }
727 };
728
729 let mut bytes = content.drain((CACHE_MARKER.len() + U32_BYTES as usize)..);
730
731 let Cache { variables, named_types, info, types } = Cache::deserialize(&mut bytes, source_id);
733
734 root.variables = variables;
735 root.named_types = named_types;
736 root.info = info;
737 checking_data.types = types;
738}
739
740const U32_BYTES: u32 = u32::BITS / u8::BITS;
741
742pub fn generate_cache<T: crate::ReadFromFS, A: crate::ASTImplementation>(
743 on: &Path,
744 read: &T,
745 parser_requirements: A::ParserRequirements,
746) -> Vec<u8> {
747 let mut checking_data =
748 CheckingData::<T, A>::new(Default::default(), read, None, parser_requirements);
749
750 let mut root = crate::context::RootContext::new_with_primitive_references();
751
752 {
753 add_definition_files_to_root(vec![on.to_path_buf()], &mut root, &mut checking_data);
754
755 assert!(
756 !checking_data.diagnostics_container.contains_error(),
757 "found error in definition file {:#?}",
758 checking_data.diagnostics_container.get_diagnostics()
759 );
760 }
761
762 let mut buf = CACHE_MARKER.to_vec();
763
764 buf.extend_from_slice(&[0u8; (u32::BITS / u8::BITS) as usize]);
766
767 let cache = Cache {
768 variables: root.variables,
769 named_types: root.named_types,
770 info: root.info,
771 types: checking_data.types,
772 };
773
774 cache.serialize(&mut buf);
775
776 {
778 let cache_len: usize = buf.len() - CACHE_MARKER.len() - U32_BYTES as usize;
779 buf[CACHE_MARKER.len()..(CACHE_MARKER.len() + U32_BYTES as usize)]
781 .copy_from_slice(&(cache_len as u32).to_le_bytes());
782
783 let Some(File::Source(source, content)) = checking_data.modules.get_file(on, None) else {
785 panic!()
786 };
787
788 let path = on.to_str().unwrap().to_owned();
789 (source, path).serialize(&mut buf);
790 buf.extend_from_slice(content.as_bytes());
791 }
792
793 buf
794}
795
796pub fn get_closest<'a, 'b>(
797 items: impl Iterator<Item = &'a str>,
798 closest_one: &'b str,
799) -> Option<Vec<&'a str>> {
800 const MIN_DISTANCE: usize = 2;
801 let candidates =
802 items.filter(|item| levenshtein(closest_one, item) <= MIN_DISTANCE).collect::<Vec<&str>>();
803 match candidates.len() {
804 0 => None,
805 1.. => Some(candidates),
806 }
807}