From bde2e5cb50ae78efd1d50b523c88772f3d760490 Mon Sep 17 00:00:00 2001 From: anri Date: Sun, 26 Apr 2026 19:33:08 +0000 Subject: [PATCH 1/5] parser: allow the user to specify multiple files to the parser cli: allow the user to read a directory as input --- Cargo.lock | 1 + benches/parser.rs | 19 +++-- packet-generator-cli/Cargo.toml | 1 + packet-generator-cli/src/main.rs | 73 +++++++++++------ src/kdl_parser/errors.rs | 3 + src/kdl_parser/mod.rs | 1 + src/kdl_parser/parser/mod.rs | 136 +++++++++++++++++++++---------- src/kdl_parser/schema/mod.rs | 9 ++ src/lib.rs | 19 +++-- tests/e2e.rs | 5 +- tools/lsp/src/main.rs | 14 ++-- 11 files changed, 194 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4f66fd..41c3533 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1054,6 +1054,7 @@ version = "0.1.0" dependencies = [ "atomicow", "bpaf", + "glob", "itertools 0.14.0", "miette", "mimalloc", diff --git a/benches/parser.rs b/benches/parser.rs index d71d85e..12d223d 100644 --- a/benches/parser.rs +++ b/benches/parser.rs @@ -4,17 +4,16 @@ use criterion::{Criterion, criterion_group, criterion_main}; use packet_generator::{ intermediate::DefinitionRegistry, - kdl_parser::{ParserOpts, ParsingError, ParsingWarnings}, + kdl_parser::{ParserOpts, ParsingError, ParsingWarnings, UnparsedKdl}, vfs::{InMemoryFS, VfsPath}, }; use std::path::PathBuf; fn parse_files( - main_content: &'static str, - path: &PathBuf, + unparsed_kdls: &[UnparsedKdl<'_>], parser_opts: &ParserOpts, ) -> Result<(DefinitionRegistry, ParsingWarnings), ParsingError> { - let res = packet_generator::parse_kdl(main_content, path, parser_opts); + let res = packet_generator::parse_kdl(unparsed_kdls, parser_opts); assert!( res.is_ok(), @@ -74,7 +73,11 @@ fn criterion_benchmark(c: &mut Criterion) { group.bench_with_input( criterion::BenchmarkId::from_parameter("stress-test files"), &(main_content, path, &opts), - |b, input| b.iter_with_large_drop(|| parse_files(input.0, &input.1, input.2)), + |b, input| { + let unparsed_kdl = UnparsedKdl::new(input.0, &input.1); + let files = &[unparsed_kdl]; + b.iter_with_large_drop(|| parse_files(files, input.2)); + }, ); } @@ -83,7 +86,11 @@ fn criterion_benchmark(c: &mut Criterion) { group.bench_with_input( criterion::BenchmarkId::from_parameter("Brave Frontier files"), &(main_content, path, &opts), - |b, input| b.iter_with_large_drop(|| parse_files(input.0, &input.1, input.2)), + |b, input| { + let unparsed_kdl = UnparsedKdl::new(input.0, &input.1); + let files = &[unparsed_kdl]; + b.iter_with_large_drop(|| parse_files(files, input.2)); + }, ); } diff --git a/packet-generator-cli/Cargo.toml b/packet-generator-cli/Cargo.toml index dac44f4..4543e06 100644 --- a/packet-generator-cli/Cargo.toml +++ b/packet-generator-cli/Cargo.toml @@ -15,6 +15,7 @@ atomicow = { workspace = true } miette = { workspace = true } itertools = { workspace = true } thiserror = { workspace = true } +glob = { workspace = true } mimalloc = { version = "0.1.37", optional = true } diff --git a/packet-generator-cli/src/main.rs b/packet-generator-cli/src/main.rs index 4d9a5d4..94e6450 100644 --- a/packet-generator-cli/src/main.rs +++ b/packet-generator-cli/src/main.rs @@ -6,8 +6,10 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; use std::{env::current_dir, path::PathBuf}; -use miette::{Context, miette}; +use itertools::Itertools; +use miette::{Context, IntoDiagnostic, miette}; use packet_generator::generators::write_sources; +use packet_generator::kdl_parser::UnparsedKdl; use packet_generator::{ generators::{CxxGenerator, GenerationError, Generator, GlazeGenerator, WithAddons}, kdl_parser::{Diagnostic, ParserOpts, ParsingError}, @@ -37,13 +39,14 @@ fn main() -> Result<(), miette::Report> { cli::CliArgs::DumpRepresentation { input } => { let path = PathBuf::from(input); - let doc_str = std::fs::read_to_string(&path) + let kdl_document_content = std::fs::read_to_string(&path) .map_err(|e| miette::miette!(e)) .wrap_err_with(|| format!("cannot open file: {}", path.display()))?; + let unparsed_document = UnparsedKdl::new(&kdl_document_content, &path); + let (doc, warnings) = packet_generator::kdl_parser::raw_parse_kdl( - doc_str, - &path, + &[unparsed_document], &ParserOpts::default(), )?; @@ -68,6 +71,47 @@ fn main() -> Result<(), miette::Report> { None => current_dir().map_err(|e| miette::miette!(e))?, }; + let input_path = PathBuf::from(&input); + + let (doc, warnings) = if input_path.is_dir() { + let files_to_read = glob::glob(&format!("{input}/**/*.kdl")) + .into_diagnostic() + .wrap_err_with(|| format!("error creating glob pattern for '{}'", input))?; + + let paths = files_to_read + .process_results(|maybe_paths| { + maybe_paths + .map(|p| -> Result<_, miette::Report> { + let kdl_document_content = std::fs::read_to_string(&p) + .into_diagnostic() + .wrap_err("cannot read file")?; + + Ok(UnparsedKdl::new_owned(kdl_document_content, p)) + }) + .collect::, _>>() + }) + .into_diagnostic() + .wrap_err("cannot read globs")??; + + packet_generator::kdl_parser::raw_parse_kdl(&paths, &ParserOpts::default())? + } else { + let kdl_document_content = std::fs::read_to_string(&input_path) + .map_err(|e| miette::miette!(e)) + .wrap_err_with(|| format!("cannot open file: {}", input_path.display()))?; + + let unparsed_document = UnparsedKdl::new(&kdl_document_content, &input_path); + packet_generator::kdl_parser::raw_parse_kdl( + &[unparsed_document], + &ParserOpts::default(), + )? + }; + + warnings.print_warnings_if_any(); + + let doc = doc.finalize()?; + + let definitions = packet_generator::kdl_parser::document_to_definitions(doc)?; + let generator = match language { cli::ProgrammingLanguage::Cxx(options) => { let mut cxx_generator = CxxGenerator::new(); @@ -94,24 +138,6 @@ fn main() -> Result<(), miette::Report> { } }; - let input_path = PathBuf::from(input); - - let doc_str = std::fs::read_to_string(&input_path) - .map_err(|e| miette::miette!(e)) - .wrap_err_with(|| format!("cannot open file: {}", input_path.display()))?; - - let (doc, warnings) = packet_generator::kdl_parser::raw_parse_kdl( - doc_str, - &input_path, - &ParserOpts::default(), - )?; - - warnings.print_warnings_if_any(); - - let doc = doc.finalize()?; - - let definitions = packet_generator::kdl_parser::document_to_definitions(doc)?; - let sources = generator .generate( &definitions, @@ -131,7 +157,8 @@ fn main() -> Result<(), miette::Report> { ) })?, ) - .map_err(|e| miette::miette!("could not generate sources: {e}"))?; + .into_diagnostic() + .wrap_err("could not generate sources")?; write_sources(&output_directory, &sources)?; } diff --git a/src/kdl_parser/errors.rs b/src/kdl_parser/errors.rs index 74fe57c..2615436 100644 --- a/src/kdl_parser/errors.rs +++ b/src/kdl_parser/errors.rs @@ -145,6 +145,9 @@ pub enum ParsingError { #[error(transparent)] KdlError(#[from] KdlError), + #[error("no document found")] + NoDocumentProvided, + #[error("problems when parsing packet definition")] Diagnostics { source_info: Arc, diff --git a/src/kdl_parser/mod.rs b/src/kdl_parser/mod.rs index cd903d0..8add84b 100644 --- a/src/kdl_parser/mod.rs +++ b/src/kdl_parser/mod.rs @@ -10,6 +10,7 @@ mod to_intermediate; // Exports pub use errors::{Diagnostic, ParsingError, ParsingWarnings, SourceInfo}; +pub use parser::UnparsedKdl; pub use parser::raw_parse_kdl; /// A valid KDL document. diff --git a/src/kdl_parser/parser/mod.rs b/src/kdl_parser/parser/mod.rs index 4596953..3273a9b 100644 --- a/src/kdl_parser/parser/mod.rs +++ b/src/kdl_parser/parser/mod.rs @@ -356,6 +356,30 @@ impl KdlNodeUtilsExt for KdlNode { } } +#[derive(Debug, Clone)] +pub struct UnparsedKdl<'a> { + content: Cow<'a, str>, + path: Cow<'a, Path>, +} + +impl<'a> UnparsedKdl<'a> { + #[must_use] + pub const fn new(content: &'a str, path: &'a Path) -> Self { + Self { + content: Cow::Borrowed(content), + path: Cow::Borrowed(path), + } + } + + #[must_use] + pub const fn new_owned(content: String, path: PathBuf) -> Self { + Self { + content: Cow::Owned(content), + path: Cow::Owned(path), + } + } +} + fn parse_all_definitions( raw_document: &mut RawDocument, definitions: &[KdlNode], @@ -508,22 +532,29 @@ where .collect() } -fn parse_single_document, V: Vfs>( - document: S, - filepath: &PathBuf, - visited_documents: &mut HashSet, +struct ParseSingleDocumentResult { + document: RawDocument, + warnings: ParsingWarnings, +} + +fn parse_single_document( + document: &UnparsedKdl<'_>, + previously_visited_documents: &mut HashSet, opts: &ParserOpts, -) -> Result<(RawDocument, ParsingWarnings), ParsingError> { - let kdl_document = KdlDocument::parse_v2(document.as_ref())?; +) -> Result { + let kdl_document = KdlDocument::parse_v2(&document.content)?; - let source_info = Arc::new(SourceInfo::new(filepath.to_string_lossy(), document)); + let source_info = Arc::new(SourceInfo::new( + document.path.to_string_lossy(), + &document.content, + )); let mut erroring_diagnostics = vec![]; let mut non_erroring_diagnostics = vec![]; let mut raw_document = RawDocument { - filepath: Some(filepath.into()), + filepath: Some(document.path.clone().into()), json_definitions: vec![], http_definitions: vec![], enum_definitions: vec![], @@ -542,46 +573,44 @@ fn parse_single_document, V: Vfs>( related: vec![], }); - return Ok(( - raw_document, - ParsingWarnings { + return Ok(ParseSingleDocumentResult { + document: raw_document, + warnings: ParsingWarnings { source_info: source_info.clone(), diagnostics: non_erroring_diagnostics, }, - )); + }); } - let current_directory = filepath + let current_directory = document + .path .parent() .map_or_else(|| PathBuf::from(""), ToOwned::to_owned); let unvisited_includes = extract_unvisited_filepaths::( children, &source_info, - visited_documents, + previously_visited_documents, ¤t_directory, &mut non_erroring_diagnostics, )?; for (path, canonical_path) in unvisited_includes { - let other_document = opts.vfs.read_file_to_string(&V::normalize_path(&path)?)?; - visited_documents.insert(canonical_path); - let (other_root, other_diagnostics) = - parse_single_document(other_document, &path, visited_documents, opts)?; - - raw_document - .json_definitions - .extend(other_root.json_definitions); + let other_document_content = opts.vfs.read_file_to_string(&V::normalize_path(&path)?)?; + let other_document = UnparsedKdl { + content: Cow::Owned(other_document_content), + path: Cow::Owned(path), + }; - raw_document - .http_definitions - .extend(other_root.http_definitions); + previously_visited_documents.insert(canonical_path); - raw_document - .enum_definitions - .extend(other_root.enum_definitions); + let ParseSingleDocumentResult { + document: other_root, + warnings: other_warnings, + } = parse_single_document(&other_document, previously_visited_documents, opts)?; - non_erroring_diagnostics.extend(other_diagnostics.diagnostics); + raw_document.extend(other_root); + non_erroring_diagnostics.extend(other_warnings.diagnostics); } { @@ -590,13 +619,13 @@ fn parse_single_document, V: Vfs>( } if erroring_diagnostics.is_empty() { - Ok(( - raw_document, - ParsingWarnings { + Ok(ParseSingleDocumentResult { + document: raw_document, + warnings: ParsingWarnings { source_info, diagnostics: non_erroring_diagnostics, }, - )) + }) } else { Err(ParsingError::Diagnostics { source_info, @@ -605,8 +634,8 @@ fn parse_single_document, V: Vfs>( } } -/// Parses a `document` (as string) to obtain [`RawDocument`] that can be -/// inspected for further analysis. +/// Parses one or more `document` (as string) to obtain [`RawDocument`] that can +/// be inspected for further analysis. /// /// The [`RawDocument`] can be later converted to a /// [`Document`](super::Document) used for generating the IR by calling @@ -616,13 +645,38 @@ fn parse_single_document, V: Vfs>( /// /// Returns `Err` with [`ParsingError`] if there were any errors when parsing /// the file. -pub fn raw_parse_kdl, V: Vfs>( - document: S, - filepath: &PathBuf, +pub fn raw_parse_kdl( + kdl_documents: &[UnparsedKdl<'_>], opts: &ParserOpts, ) -> Result<(RawDocument, ParsingWarnings), ParsingError> { - let root_canonical_path = V::normalize_path(filepath)?; + let mut documents_iter = kdl_documents.iter(); + + if let Some(document) = documents_iter.next() { + let mut visited_paths = HashSet::new(); + visited_paths.insert(V::normalize_path(&document.path)?); + + let ParseSingleDocumentResult { + mut document, + mut warnings, + } = parse_single_document(document, &mut visited_paths, opts)?; + + for other_unparsed_document in documents_iter { + let vfs_path = V::normalize_path(&other_unparsed_document.path)?; + if visited_paths.insert(vfs_path) { + // The file was not visited, so we add it. - let mut visited_documents = HashSet::from_iter([root_canonical_path]); - parse_single_document(document, filepath, &mut visited_documents, opts) + let ParseSingleDocumentResult { + document: other_document, + warnings: other_warnings, + } = parse_single_document(other_unparsed_document, &mut visited_paths, opts)?; + + document.extend(other_document); + warnings.diagnostics.extend(other_warnings.diagnostics); + } + } + + Ok((document, warnings)) + } else { + Err(ParsingError::NoDocumentProvided) + } } diff --git a/src/kdl_parser/schema/mod.rs b/src/kdl_parser/schema/mod.rs index 046e965..9dad224 100644 --- a/src/kdl_parser/schema/mod.rs +++ b/src/kdl_parser/schema/mod.rs @@ -34,6 +34,15 @@ impl RawDocument { pub const fn finalize(self) -> Result { validator::validate(self) } + + /// Extends this [`RawDocument`] with another one. + /// + /// Does not check for uniqueness of definitions. + pub(crate) fn extend(&mut self, other: Self) { + self.json_definitions.extend(other.json_definitions); + self.enum_definitions.extend(other.enum_definitions); + self.http_definitions.extend(other.http_definitions); + } } #[derive(Debug)] diff --git a/src/lib.rs b/src/lib.rs index 4634611..bde59d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,10 +13,8 @@ pub mod kdl_parser; pub mod vfs; -use std::path::PathBuf; - use crate::{ - kdl_parser::{ParserOpts, ParsingError, ParsingWarnings}, + kdl_parser::{ParserOpts, ParsingError, ParsingWarnings, UnparsedKdl}, vfs::Vfs, }; @@ -27,7 +25,7 @@ use crate::{ /// /// ```rust /// # use std::path::PathBuf; -/// use packet_generator::kdl_parser::ParserOpts; +/// use packet_generator::kdl_parser::{ParserOpts, UnparsedKdl}; /// /// # fn main() { /// let doc = r#" @@ -40,11 +38,15 @@ use crate::{ /// } /// "#; /// +/// let path = PathBuf::from("foo.kdl"); +/// +/// let unparsed_kdl = UnparsedKdl::new(&doc, &path); +/// /// let opts = ParserOpts::default(); /// # let filemap = packet_generator::vfs::InMemoryFS::new(); /// # let opts = ParserOpts::new(filemap); /// -/// match packet_generator::parse_kdl(doc, &PathBuf::from("foo.kdl"), &opts) { +/// match packet_generator::parse_kdl(&[unparsed_kdl], &opts) { /// Ok((registry, _warnings)) => { /// println!("{:#?}", registry.find("Foo")); /// } @@ -59,12 +61,11 @@ use crate::{ /// Will return `Err` if it was not possible to parse the file in `document` /// and its includes. /// See [`ParsingError`]. -pub fn parse_kdl, V: Vfs>( - document: S, - filepath: &PathBuf, +pub fn parse_kdl( + documents: &[UnparsedKdl], opts: &ParserOpts, ) -> Result<(intermediate::DefinitionRegistry, ParsingWarnings), ParsingError> { - let (raw_document, warnings) = kdl_parser::raw_parse_kdl(document, filepath, opts)?; + let (raw_document, warnings) = kdl_parser::raw_parse_kdl(documents, opts)?; let document = raw_document.finalize()?; diff --git a/tests/e2e.rs b/tests/e2e.rs index 7819581..a990e43 100644 --- a/tests/e2e.rs +++ b/tests/e2e.rs @@ -10,7 +10,7 @@ use std::{ use packet_generator::{ generators::{self, Generator, GlazeGenerator, WithAddons, write_sources}, intermediate::DefinitionRegistry, - kdl_parser::{ParserOpts, ParsingWarnings}, + kdl_parser::{ParserOpts, ParsingWarnings, UnparsedKdl}, }; use stringcase::Caser; @@ -19,7 +19,8 @@ const PROJECT_DIR: &str = env!("CARGO_MANIFEST_DIR"); fn setup_e2e_registry(main_file: PathBuf) -> (DefinitionRegistry, ParsingWarnings) { let path = PathBuf::from(PROJECT_DIR).join(main_file); let document = std::fs::read_to_string(&path).expect("this is a test, we control the string"); - packet_generator::parse_kdl(&document, &path, &ParserOpts::default()) + let unparsed_kdl = UnparsedKdl::new(&document, &path); + packet_generator::parse_kdl(&[unparsed_kdl], &ParserOpts::default()) .map_err(miette::Report::from) .expect("we control the files") } diff --git a/tools/lsp/src/main.rs b/tools/lsp/src/main.rs index ed1ffda..b8cfe80 100644 --- a/tools/lsp/src/main.rs +++ b/tools/lsp/src/main.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use dashmap::DashMap; use packet_generator::kdl_parser::{ - ParserOpts, ParsingError, + ParserOpts, ParsingError, UnparsedKdl, schema::{JsonDefinition, RawDocument}, }; use ropey::Rope; @@ -245,11 +245,13 @@ impl LanguageServer for Backend { let path = params.text_document.uri.path(); - let res = packet_generator::kdl_parser::raw_parse_kdl( - file.to_string(), - &PathBuf::from(path), - &ParserOpts::default(), - ); + let content = file.to_string(); + let path = PathBuf::from(path); + + let unparsed_kdl = UnparsedKdl::new_owned(content, path); + + let res = + packet_generator::kdl_parser::raw_parse_kdl(&[unparsed_kdl], &ParserOpts::default()); match res { Ok((document, warnings)) => { From d1663070cac38df12b78c81ea0bcaaa5feaa5e7a Mon Sep 17 00:00:00 2001 From: anri Date: Mon, 27 Apr 2026 20:37:53 +0000 Subject: [PATCH 2/5] chore: add proptest-regressions --- proptest-regressions/generators/utils.txt | 7 +++++++ proptest-regressions/intermediate/registry.txt | 7 +++++++ proptest-regressions/kdl_parser/parser/type_parser.txt | 9 +++++++++ 3 files changed, 23 insertions(+) create mode 100644 proptest-regressions/generators/utils.txt create mode 100644 proptest-regressions/intermediate/registry.txt create mode 100644 proptest-regressions/kdl_parser/parser/type_parser.txt diff --git a/proptest-regressions/generators/utils.txt b/proptest-regressions/generators/utils.txt new file mode 100644 index 0000000..df32e5e --- /dev/null +++ b/proptest-regressions/generators/utils.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 6adeca63f3f014f05ec11ba454b185269d9e83ac1320d7aca9fba4808ec8dae3 # shrinks to doc = "𑤌", tab = "", indent = 1 diff --git a/proptest-regressions/intermediate/registry.txt b/proptest-regressions/intermediate/registry.txt new file mode 100644 index 0000000..b476720 --- /dev/null +++ b/proptest-regressions/intermediate/registry.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 89581cfe6d9463e75009e1c9464165eccbd263960c6e145addbc4e3a578f658d # shrinks to datatype = Unknown { encoding: Json, name: "" } diff --git a/proptest-regressions/kdl_parser/parser/type_parser.txt b/proptest-regressions/kdl_parser/parser/type_parser.txt new file mode 100644 index 0000000..d903ce0 --- /dev/null +++ b/proptest-regressions/kdl_parser/parser/type_parser.txt @@ -0,0 +1,9 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 0beef09667ce9e4819061ad18d2ecf41d590fe249a63477cd180a537071bf103 # shrinks to s = "%{ i32::int => bool::str::{, } }" +cc 2374380bc15faedf91d9c03c7edc7e0d5f52338269eba148a5fea520f05e393f # shrinks to s = "[%{ Aj => Qk-_dF_--GdF1_7A-y }]::{size(14154322344), sep(comma)}" +cc 3b42edb86ce65be096352ef35061bd038d38a1391f733c917e1e4e672895d21c # shrinks to s = "%{ [[m82]::sep(comma)]::{size(7235)} => %{ %{ [bool]::{size(n), sep(comma)} => Dc467gpDYWOau7znNP07kYZ79zs338kZ } => %{ bEY87nzP9C4 => %{ XYGf1 => u321CN1E8eK3rG1l5DSB6Tn } } } }" From 13bbfd0416c54343812be7939715488df4d5af5c Mon Sep 17 00:00:00 2001 From: anri Date: Fri, 1 May 2026 11:04:29 +0000 Subject: [PATCH 3/5] cli: add test that verifies that the CLI can correctly read directories --- packet-generator-cli/src/lib.rs | 95 +++++++++++++++++++ packet-generator-cli/src/main.rs | 26 +---- packet-generator-cli/tests/assets/foo.kdl | 10 ++ .../tests/assets/nested/bar.kdl | 8 ++ .../tests/assets/nested/super-nested/baz.kdl | 8 ++ 5 files changed, 125 insertions(+), 22 deletions(-) create mode 100644 packet-generator-cli/src/lib.rs create mode 100644 packet-generator-cli/tests/assets/foo.kdl create mode 100644 packet-generator-cli/tests/assets/nested/bar.kdl create mode 100644 packet-generator-cli/tests/assets/nested/super-nested/baz.kdl diff --git a/packet-generator-cli/src/lib.rs b/packet-generator-cli/src/lib.rs new file mode 100644 index 0000000..ba8ea85 --- /dev/null +++ b/packet-generator-cli/src/lib.rs @@ -0,0 +1,95 @@ +use std::path::Path; + +use itertools::Itertools; +use miette::{Context, IntoDiagnostic}; +use packet_generator::kdl_parser::{ParserOpts, ParsingWarnings, UnparsedKdl, schema::RawDocument}; + +/// Reads KDL in a directory for the CLI. +/// +/// # Errors +/// +/// Errors if we cannot read the files from the filesystem. +pub fn read_all_kdls_from_directory( + input: &Path, +) -> Result<(RawDocument, ParsingWarnings), miette::Report> { + let files_to_read = glob::glob(&format!("{}/**/*.kdl", input.display())) + .into_diagnostic() + .wrap_err_with(|| format!("error creating glob pattern for '{}'", input.display()))?; + + let paths = files_to_read + .process_results(|maybe_paths| { + maybe_paths + .map(|p| -> Result<_, miette::Report> { + let kdl_document_content = std::fs::read_to_string(&p) + .into_diagnostic() + .wrap_err("cannot read file")?; + + Ok(UnparsedKdl::new_owned(kdl_document_content, p)) + }) + .collect::, _>>() + }) + .into_diagnostic() + .wrap_err("cannot read globs")??; + + Ok(packet_generator::kdl_parser::raw_parse_kdl( + &paths, + &ParserOpts::default(), + )?) +} + +#[cfg(test)] +mod tests { + use packet_generator::intermediate::schema::Definition; + + use super::*; + use std::path::PathBuf; + + #[test] + fn read_directory_works() -> Result<(), miette::Report> { + let assets_dir = PathBuf::from_iter(&[env!("CARGO_MANIFEST_DIR"), "tests", "assets"]); + + dbg!(&assets_dir); + + let (document, warnings) = read_all_kdls_from_directory(&assets_dir)?; + + warnings.clone().print_warnings_if_any(); + + assert!(!warnings.are_there_any(), "There were warnings :("); + + let document = document.finalize()?; + + let definitions = packet_generator::kdl_parser::document_to_definitions(document)?; + + dbg!(&definitions); + + let (foo, _) = definitions.find("Foo").wrap_err("Cannot find Foo")?; + let (bar, _) = definitions.find("Bar").wrap_err("Cannot find Bar")?; + let (baz, _) = definitions.find("Baz").wrap_err("Cannot find Baz")?; + + assert_eq!(foo.name(), "Foo"); + if let Definition::Json(j) = foo { + assert_eq!(j.fields.len(), 1); + assert!(j.fields.contains("bar")); + } else { + panic!("Foo is not a JSON!") + } + + assert_eq!(bar.name(), "Bar"); + if let Definition::Json(j) = bar { + assert_eq!(j.fields.len(), 1); + assert!(j.fields.contains("baz")); + } else { + panic!("Bar is not a JSON!") + } + + assert_eq!(baz.name(), "Baz"); + if let Definition::StringEnum(j) = baz { + assert_eq!(j.variants.len(), 1); + assert!(j.variants.contains("test")); + } else { + panic!("Baz is not a string enum!") + } + + Ok(()) + } +} diff --git a/packet-generator-cli/src/main.rs b/packet-generator-cli/src/main.rs index 94e6450..64e37f9 100644 --- a/packet-generator-cli/src/main.rs +++ b/packet-generator-cli/src/main.rs @@ -6,7 +6,6 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; use std::{env::current_dir, path::PathBuf}; -use itertools::Itertools; use miette::{Context, IntoDiagnostic, miette}; use packet_generator::generators::write_sources; use packet_generator::kdl_parser::UnparsedKdl; @@ -15,6 +14,8 @@ use packet_generator::{ kdl_parser::{Diagnostic, ParserOpts, ParsingError}, }; +use packet_generator_cli::read_all_kdls_from_directory; + use crate::cli::CxxSerializer; mod cli; @@ -74,26 +75,7 @@ fn main() -> Result<(), miette::Report> { let input_path = PathBuf::from(&input); let (doc, warnings) = if input_path.is_dir() { - let files_to_read = glob::glob(&format!("{input}/**/*.kdl")) - .into_diagnostic() - .wrap_err_with(|| format!("error creating glob pattern for '{}'", input))?; - - let paths = files_to_read - .process_results(|maybe_paths| { - maybe_paths - .map(|p| -> Result<_, miette::Report> { - let kdl_document_content = std::fs::read_to_string(&p) - .into_diagnostic() - .wrap_err("cannot read file")?; - - Ok(UnparsedKdl::new_owned(kdl_document_content, p)) - }) - .collect::, _>>() - }) - .into_diagnostic() - .wrap_err("cannot read globs")??; - - packet_generator::kdl_parser::raw_parse_kdl(&paths, &ParserOpts::default())? + read_all_kdls_from_directory(&input_path)? } else { let kdl_document_content = std::fs::read_to_string(&input_path) .map_err(|e| miette::miette!(e)) @@ -123,7 +105,7 @@ fn main() -> Result<(), miette::Report> { CxxSerializer::Simdjson => { return Err(miette::miette!( - "Simdjson secondary generator for Cxx is not implemented!" + "Simdjson generator for C++ is not implemented!" )); } } diff --git a/packet-generator-cli/tests/assets/foo.kdl b/packet-generator-cli/tests/assets/foo.kdl new file mode 100644 index 0000000..97fdfea --- /dev/null +++ b/packet-generator-cli/tests/assets/foo.kdl @@ -0,0 +1,10 @@ +// No explicit include is present. + +json Foo { + doc "Contains a Bar" + + field bar type="Bar" { + key "bar" + doc "The Bar" + } +} diff --git a/packet-generator-cli/tests/assets/nested/bar.kdl b/packet-generator-cli/tests/assets/nested/bar.kdl new file mode 100644 index 0000000..7d772f0 --- /dev/null +++ b/packet-generator-cli/tests/assets/nested/bar.kdl @@ -0,0 +1,8 @@ +json Bar { + doc "Contains a Baz" + + field baz type="Baz" { + key "baz" + doc "The Baz" + } +} diff --git a/packet-generator-cli/tests/assets/nested/super-nested/baz.kdl b/packet-generator-cli/tests/assets/nested/super-nested/baz.kdl new file mode 100644 index 0000000..5bc5053 --- /dev/null +++ b/packet-generator-cli/tests/assets/nested/super-nested/baz.kdl @@ -0,0 +1,8 @@ +str-enum Baz { + doc "A Baz" + + variant test { + doc "Test" + value "Test" + } +} From 7197b65c56a5ff03cea41fffc0d2c986f4484e86 Mon Sep 17 00:00:00 2001 From: anri Date: Wed, 20 May 2026 03:21:12 +0000 Subject: [PATCH 4/5] chore: appease our savior Clippy chore: enable more Clippy lints --- .clippy.toml | 3 ++ Cargo.toml | 30 +++++++++++++++-- benches/parser.rs | 12 ++++--- packet-generator-cli/src/lib.rs | 14 ++++---- packet-generator-cli/src/main.rs | 17 ++-------- src/generators/cpp/mod.rs | 4 +-- src/generators/utils.rs | 18 +++++++---- src/intermediate/registry.rs | 13 ++++---- src/kdl_parser/errors.rs | 2 +- src/kdl_parser/mod.rs | 2 +- src/kdl_parser/parser/enum_parser.rs | 48 ++++++++++++++-------------- src/kdl_parser/parser/json_parser.rs | 38 +++++++++++----------- src/kdl_parser/parser/mod.rs | 42 ++++++++++++------------ src/kdl_parser/parser/type_parser.rs | 16 +++++++--- src/kdl_parser/schema/mod.rs | 5 +-- src/kdl_parser/schema/validator.rs | 2 +- src/vfs.rs | 8 +++-- tests/e2e.rs | 2 +- 18 files changed, 155 insertions(+), 121 deletions(-) diff --git a/.clippy.toml b/.clippy.toml index 0358cdb..7e97361 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,2 +1,5 @@ allow-unwrap-in-tests = true allow-expect-in-tests = true +allow-indexing-slicing-in-tests = true +allow-panic-in-tests = true +allow-dbg-in-tests = true diff --git a/Cargo.toml b/Cargo.toml index ff37811..511710a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,10 +68,29 @@ expect-used = "deny" expect-fun-call = "deny" unwrap-in-result = "forbid" unwrap-or-default = "forbid" +panic-in-result-fn = "deny" get-unwrap = "forbid" missing-errors-doc = "forbid" missing-panics-doc = "forbid" missing-safety-doc = "forbid" +undocumented-unsafe-blocks = "forbid" +unchecked-time-subtraction = "forbid" +string-slice = "forbid" +let-underscore-future = "forbid" +let-underscore-must-use = "forbid" +unused-result-ok = "forbid" +map-err-ignore = "forbid" +assertions-on-result-states = "forbid" +await-holding-lock = "forbid" +await-holding-refcell-ref = "forbid" +if-let-mutex = "forbid" +float-cmp = "forbid" +float-cmp-const = "forbid" +lossy-float-literal = "forbid" +cast-sign-loss = "forbid" +invalid-upcast-comparisons = "forbid" +allow-attributes = "warn" +allow-attributes-without-reason = "forbid" explicit-iter-loop = "deny" items-after-statements = "deny" ref-option-ref = "deny" @@ -82,17 +101,22 @@ if-not-else = "deny" redundant-closure-for-method-calls = "deny" single-char-pattern = "deny" use-self = "deny" +clone-on-ref-ptr = "deny" +cast-possible-wrap = "warn" +cast-precision-loss = "warn" +cast-possible-truncation = "warn" +large-futures = "warn" +future-not-send = "warn" +# cognitive-complexity = "warn" redundant-pub-crate = "deny" option-if-let-else = "allow" unnecessary-wraps = "allow" -future-not-send = "warn" -# cognitive-complexity = "warn" uninlined-format-args = "allow" wildcard-imports = "allow" too-many-lines = "allow" needless-raw-string-hashes = "allow" pedantic = { level = "warn", priority = -1 } -nursery = { level = "warn", priority = -1 } +nursery = { level = "warn", priority = -3 } # Needed for -Zminimal-versions to pass. # The dependency is not used in the project itself. diff --git a/benches/parser.rs b/benches/parser.rs index 12d223d..7c2f084 100644 --- a/benches/parser.rs +++ b/benches/parser.rs @@ -1,5 +1,9 @@ -#![allow(clippy::expect_used, reason = "Sir, this is a benchmark")] -#![allow(clippy::unwrap_used, reason = "Sir, this is a benchmark")] +#![allow( + clippy::unwrap_used, + clippy::panic_in_result_fn, + clippy::expect_used, + reason = "Sir, this is a benchmark" +)] use criterion::{Criterion, criterion_group, criterion_main}; use packet_generator::{ @@ -34,7 +38,7 @@ fn add_all_paths(prefix: &str, directory: &str, fs: &mut InMemoryFS) { let vfs_path = path.strip_prefix(prefix).expect("can remove assets prefix"); - let _ = fs.add_file(VfsPath::new(vfs_path), &content); + let _file = fs.add_file(VfsPath::new(vfs_path), &content); } } @@ -58,7 +62,7 @@ fn build_gamefrontier_input() -> (&'static str, PathBuf, ParserOpts) add_all_paths("assets", "mst", &mut fs); add_all_paths("assets", "net", &mut fs); - let _ = fs.add_file(VfsPath::new("all.kdl"), main_content); + let _file = fs.add_file(VfsPath::new("all.kdl"), main_content); let opts = ParserOpts::new(fs); diff --git a/packet-generator-cli/src/lib.rs b/packet-generator-cli/src/lib.rs index ba8ea85..2a35c44 100644 --- a/packet-generator-cli/src/lib.rs +++ b/packet-generator-cli/src/lib.rs @@ -39,6 +39,7 @@ pub fn read_all_kdls_from_directory( #[cfg(test)] mod tests { + #![allow(clippy::panic_in_result_fn, reason = "Sir, this is a test")] use packet_generator::intermediate::schema::Definition; use super::*; @@ -52,9 +53,10 @@ mod tests { let (document, warnings) = read_all_kdls_from_directory(&assets_dir)?; - warnings.clone().print_warnings_if_any(); - - assert!(!warnings.are_there_any(), "There were warnings :("); + if !warnings.are_there_any() { + warnings.print_warnings_if_any(); + miette::bail!("There were warnings"); + } let document = document.finalize()?; @@ -71,7 +73,7 @@ mod tests { assert_eq!(j.fields.len(), 1); assert!(j.fields.contains("bar")); } else { - panic!("Foo is not a JSON!") + miette::bail!("Foo is not a JSON!") } assert_eq!(bar.name(), "Bar"); @@ -79,7 +81,7 @@ mod tests { assert_eq!(j.fields.len(), 1); assert!(j.fields.contains("baz")); } else { - panic!("Bar is not a JSON!") + miette::bail!("Bar is not a JSON!") } assert_eq!(baz.name(), "Baz"); @@ -87,7 +89,7 @@ mod tests { assert_eq!(j.variants.len(), 1); assert!(j.variants.contains("test")); } else { - panic!("Baz is not a string enum!") + miette::bail!("Baz is not a string enum!") } Ok(()) diff --git a/packet-generator-cli/src/main.rs b/packet-generator-cli/src/main.rs index 64e37f9..c81b9ee 100644 --- a/packet-generator-cli/src/main.rs +++ b/packet-generator-cli/src/main.rs @@ -10,8 +10,8 @@ use miette::{Context, IntoDiagnostic, miette}; use packet_generator::generators::write_sources; use packet_generator::kdl_parser::UnparsedKdl; use packet_generator::{ - generators::{CxxGenerator, GenerationError, Generator, GlazeGenerator, WithAddons}, - kdl_parser::{Diagnostic, ParserOpts, ParsingError}, + generators::{CxxGenerator, Generator, GlazeGenerator, WithAddons}, + kdl_parser::ParserOpts, }; use packet_generator_cli::read_all_kdls_from_directory; @@ -20,19 +20,6 @@ use crate::cli::CxxSerializer; mod cli; -#[derive(Debug, thiserror::Error)] -#[expect(dead_code)] -enum ApplicationError { - #[error(transparent)] - MietteReport(#[from] ParsingError), - - #[error(transparent)] - Diagnostic(#[from] Diagnostic), - - #[error(transparent)] - Generation(#[from] GenerationError), -} - fn main() -> Result<(), miette::Report> { let args = cli::parse_args(); diff --git a/src/generators/cpp/mod.rs b/src/generators/cpp/mod.rs index 4b8ac9f..4005014 100644 --- a/src/generators/cpp/mod.rs +++ b/src/generators/cpp/mod.rs @@ -427,7 +427,7 @@ mod tests { json.add_field(JsonField { index: 0, - name: arced_name.clone(), + name: Arc::clone(&arced_name), key: String::new(), type_: DataType::String, optional: false, @@ -439,7 +439,7 @@ mod tests { int_enum.add_variant(IntEnumVariant { index: 0, - name: arced_name.clone(), + name: Arc::clone(&arced_name), doc: String::new(), value: None, }); diff --git a/src/generators/utils.rs b/src/generators/utils.rs index 41495dd..ce3f4d7 100644 --- a/src/generators/utils.rs +++ b/src/generators/utils.rs @@ -42,6 +42,8 @@ pub fn split_documentation(doc: &str, tab: &str, comment_str: &str, indent_level #[cfg(test)] mod tests { + // #![expect(clippy::string_slice, reason = "Sir this is a test")] + use super::*; use proptest::prelude::*; @@ -61,15 +63,19 @@ Bar. Baz. Quox. let res = split_documentation(DOC_FIELD, TAB, COMMENT_STR, INDENT_LEVEL); for (line, doc_line) in res.lines().zip(DOC_FIELD.lines()) { - let until_tab = &line[0..INDENT_LEVEL]; + let until_tab = line.get(0..INDENT_LEVEL).expect("Valid UTF-8 boundary"); assert_eq!(until_tab, TAB.repeat(INDENT_LEVEL)); - let until_comment_str = &line[(INDENT_LEVEL)..(INDENT_LEVEL + COMMENT_STR.len())]; + let until_comment_str = line + .get((INDENT_LEVEL)..(INDENT_LEVEL + COMMENT_STR.len())) + .expect("Valid UTF-8 boundary"); assert_eq!(until_comment_str, COMMENT_STR); let skip_whitespace = usize::from(!doc_line.is_empty()); - let rest_of_line = &line[(INDENT_LEVEL + COMMENT_STR.len() + skip_whitespace)..]; + let rest_of_line = line + .get((INDENT_LEVEL + COMMENT_STR.len() + skip_whitespace)..) + .expect("Valid UTF-8 boundary"); assert_eq!(rest_of_line, doc_line); } @@ -83,14 +89,14 @@ Bar. Baz. Quox. for (line, doc_line) in res.lines().zip(doc.lines()) { if !tab_repeats.is_empty() { - let until_tab = &line[..(tab_repeats.len())]; + let until_tab = line.get(..(tab_repeats.len())).expect("Valid UTF-8 buondary"); assert_eq!(until_tab, tab_repeats); } - let until_comment_str = &line[(tab_repeats.len())..(tab_repeats.len() + comment_str.len())]; + let until_comment_str = line.get((tab_repeats.len())..(tab_repeats.len() + comment_str.len())).expect("Valid UTF-8 boundary"); assert_eq!(until_comment_str, &comment_str); - let rest_of_line = &line[(tab_repeats.len() + comment_str.len() + 1)..]; + let rest_of_line = line.get((tab_repeats.len() + comment_str.len() + 1)..).expect("Valid UTF-8 boundary"); assert_eq!(rest_of_line, doc_line); diff --git a/src/intermediate/registry.rs b/src/intermediate/registry.rs index c1eff10..f2eb015 100644 --- a/src/intermediate/registry.rs +++ b/src/intermediate/registry.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::marker::PhantomData; +use std::sync::Arc; use super::schema::*; @@ -151,7 +152,7 @@ impl DefinitionRegistry { /// Inserts a [`Definition`] into the registry and returns a /// [reference](DefinitionRef) to it. pub fn insert(&mut self, definition: Definition) -> DefinitionRef { - #[allow(clippy::single_match_else, reason = "May add more cases in the future")] + #[expect(clippy::single_match_else, reason = "May add more cases in the future")] match definition { Definition::Json(ref json) => { let mut dependencies = vec![]; @@ -187,7 +188,7 @@ impl DefinitionRegistry { /// /// Return `Err` if the [`Definition`]s reference non-existing /// [`Definition`]s. - #[allow(clippy::result_large_err, reason = "We can take the performance hit.")] + #[expect(clippy::result_large_err, reason = "We can take the performance hit.")] pub fn finalize(mut self) -> Result { let all_nodes: Vec<_> = self.definitions.node_indices().collect(); let mut missing_edges = vec![]; @@ -206,7 +207,7 @@ impl DefinitionRegistry { let idx = self.names.get(name).ok_or_else(|| Diagnostic { message: format!("could not find definition `{name}`"), severity: miette::Severity::Error, - source_info: json.source.clone(), + source_info: Arc::clone(&json.source), span: field.span, help: None, label: None, @@ -216,7 +217,7 @@ impl DefinitionRegistry { json.name, field.name ), severity: miette::Severity::Advice, - source_info: json.source.clone(), + source_info: Arc::clone(&json.source), span: field.span, help: None, label: None, @@ -236,7 +237,7 @@ impl DefinitionRegistry { json.name ), severity: miette::Severity::Error, - source_info: json.source.clone(), + source_info: Arc::clone(&json.source), span: json.span, help: None, label: None, @@ -350,7 +351,7 @@ mod tests { String::from("Foo"), 0, String::from("some documentation"), - source.clone(), + Arc::clone(&source), SourceSpan::from((0, 0)), ); s.add_field(field); diff --git a/src/kdl_parser/errors.rs b/src/kdl_parser/errors.rs index 2615436..f4fd559 100644 --- a/src/kdl_parser/errors.rs +++ b/src/kdl_parser/errors.rs @@ -169,7 +169,7 @@ pub enum ParsingError { impl From for ParsingError { fn from(diag: Diagnostic) -> Self { Self::Diagnostics { - source_info: diag.source_info.clone(), + source_info: Arc::clone(&diag.source_info), diagnostics: vec![diag], } } diff --git a/src/kdl_parser/mod.rs b/src/kdl_parser/mod.rs index 8add84b..49d71ae 100644 --- a/src/kdl_parser/mod.rs +++ b/src/kdl_parser/mod.rs @@ -44,7 +44,7 @@ impl ParserOpts { } #[must_use = "Converting a `Document` to the IR representation implies that you want to use the resulting registry."] -#[allow(clippy::result_large_err, reason = "We can take the performance hit.")] +#[expect(clippy::result_large_err, reason = "We can take the performance hit.")] /// Converts a [`Document`] into a [`DefinitionRegistry`] (IR), returning /// diagnostics in the process. /// diff --git a/src/kdl_parser/parser/enum_parser.rs b/src/kdl_parser/parser/enum_parser.rs index 05c9a20..7cd9ea4 100644 --- a/src/kdl_parser/parser/enum_parser.rs +++ b/src/kdl_parser/parser/enum_parser.rs @@ -19,7 +19,7 @@ pub fn parse_int_enum_definition( let name = definition.extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(source_code), context: "definition".into(), not_found_help: Some("add a name to the enum definition".into()), wrong_type_help: Some("give it a name as a string".into()), @@ -30,7 +30,7 @@ pub fn parse_int_enum_definition( let start = definition.extract_property_int( "start", ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(source_code), context: "int enum definition".into(), not_found_help: Some( "specify a `start` property to the int enum, for example `start=0`".into(), @@ -46,7 +46,7 @@ pub fn parse_int_enum_definition( ParsingError::from(Diagnostic { message: "integer enum definition has no children".to_owned(), severity: Severity::Error, - source_info: source_code.clone(), + source_info: Arc::clone(source_code), span: definition.span(), help: Some("specify children for the variants".to_owned()), label: None, @@ -58,7 +58,7 @@ pub fn parse_int_enum_definition( .extract_child_node( "doc", ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(source_code), context: format!("integer enum definition `{name}`").into(), not_found_help: Some("specify child `doc \"Example\"`".into()), wrong_type_help: None, @@ -67,7 +67,7 @@ pub fn parse_int_enum_definition( .extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(source_code), context: format!("integer enum definition `{name}`").into(), not_found_help: Some("specify child `doc \"Example\"`".into()), wrong_type_help: None, @@ -79,12 +79,12 @@ pub fn parse_int_enum_definition( .iter() .filter(|&node| node.name().value() == ENUM_VARIANT_FIELD_NAME) .enumerate() - .map(|(index, node)| parse_int_enum_variant(node, source_code.clone(), name, index)) + .map(|(index, node)| parse_int_enum_variant(node, Arc::clone(source_code), name, index)) .collect::, ParsingError>>()?; Ok(IntEnumDefinition { index, - source_info: source_code.clone(), + source_info: Arc::clone(source_code), span: definition.span(), name: name.into(), doc: doc.into(), @@ -104,7 +104,7 @@ fn parse_int_enum_variant( let name = node.extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("variant definition in integer enum `{enum_name}`").into(), not_found_help: Some( format!("specify a name for the variant in integer enum `{enum_name}`").into(), @@ -114,7 +114,7 @@ fn parse_int_enum_variant( )?; let children = node.extract_children(ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("integer enum variant `{enum_name}::{name}`").into(), not_found_help: Some("specify a child `doc \"Example\"`.".into()), wrong_type_help: None, @@ -124,7 +124,7 @@ fn parse_int_enum_variant( .extract_child_node( "doc", ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("integer enum variant definition `{enum_name}::{name}`").into(), not_found_help: Some("specify a child `doc \"Example\"`.".into()), wrong_type_help: Some("specify a child `doc \"Example\"`.".into()), @@ -133,7 +133,7 @@ fn parse_int_enum_variant( .extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!( "child `doc` in integer enum variant definition `{enum_name}::{name}``" ) @@ -152,7 +152,7 @@ fn parse_int_enum_variant( entry.value().as_integer().ok_or_else(|| ParsingError::from(Diagnostic { message: format!("first argument of child `value` in integer enum variant definition `{enum_name}::{name}` is not an integer"), severity: Severity::Error, - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), span: entry.span(), help: None, label: None, @@ -179,7 +179,7 @@ pub fn parse_string_enum_definition( let name = definition.extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(source_code), context: "definition".into(), not_found_help: Some("add a name to the enum definition".into()), wrong_type_help: Some("give it a name as a string".into()), @@ -190,7 +190,7 @@ pub fn parse_string_enum_definition( ParsingError::from(Diagnostic { message: "string enum definition has no children".to_owned(), severity: Severity::Error, - source_info: source_code.clone(), + source_info: Arc::clone(source_code), span: definition.span(), help: Some("specify children for the enum".to_owned()), label: None, @@ -202,7 +202,7 @@ pub fn parse_string_enum_definition( .extract_child_node( "doc", ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(source_code), context: format!("string enum definition `{name}`").into(), not_found_help: Some("specify child `doc \"Example\"`".into()), wrong_type_help: None, @@ -211,7 +211,7 @@ pub fn parse_string_enum_definition( .extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(source_code), context: format!("string enum definition `{name}`").into(), not_found_help: Some("specify child `doc \"Example\"`".into()), wrong_type_help: None, @@ -223,12 +223,12 @@ pub fn parse_string_enum_definition( .iter() .filter(|&node| node.name().value() == ENUM_VARIANT_FIELD_NAME) .enumerate() - .map(|(index, node)| parse_string_enum_variant(node, source_code.clone(), name, index)) + .map(|(index, node)| parse_string_enum_variant(node, Arc::clone(source_code), name, index)) .collect::, ParsingError>>()?; Ok(StringEnumDefinition { index, - source_info: source_code.clone(), + source_info: Arc::clone(source_code), span: definition.span(), name: name.into(), doc: doc.into(), @@ -247,7 +247,7 @@ fn parse_string_enum_variant( let name = node.extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("variant definition in string enum `{enum_name}`").into(), not_found_help: Some( format!("specify a name for the variant in string enum `{enum_name}`").into(), @@ -257,7 +257,7 @@ fn parse_string_enum_variant( )?; let children = node.extract_children(ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("integer enum variant `{enum_name}::{name}`").into(), not_found_help: Some("specify a child `doc \"Example\"`.".into()), wrong_type_help: None, @@ -266,7 +266,7 @@ fn parse_string_enum_variant( let value = children .extract_child_node(VALUE_PROPERTY, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("string enum variant definition `{enum_name}::{name}`").into(), not_found_help: Some(format!("specify a child `{VALUE_PROPERTY} \"Example\"`.").into()), wrong_type_help: Some(format!("specify a child `{VALUE_PROPERTY} \"Example\"`.").into()), @@ -275,7 +275,7 @@ fn parse_string_enum_variant( .extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!( "child `{VALUE_PROPERTY}` in string enum variant definition `{enum_name}::{name}``" ) @@ -289,7 +289,7 @@ fn parse_string_enum_variant( .extract_child_node( "doc", ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("string enum variant definition `{enum_name}::{name}`").into(), not_found_help: Some("specify a child `doc \"Example\"`.".into()), wrong_type_help: Some("specify a child `doc \"Example\"`.".into()), @@ -298,7 +298,7 @@ fn parse_string_enum_variant( .extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!( "child `doc` in string enum variant definition `{enum_name}::{name}``" ) diff --git a/src/kdl_parser/parser/json_parser.rs b/src/kdl_parser/parser/json_parser.rs index 0e974f6..7db0367 100644 --- a/src/kdl_parser/parser/json_parser.rs +++ b/src/kdl_parser/parser/json_parser.rs @@ -28,7 +28,7 @@ pub fn parse_data_definition( let name = definition.extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(source_code), context: "definition".into(), not_found_help: Some("add a name to the definition".into()), wrong_type_help: Some("give it a name as a string".into()), @@ -42,7 +42,7 @@ pub fn parse_data_definition( ParsingError::from(Diagnostic { message: format!("property `{DEFAULT_ENCODING_PROPERTY}` in JSON definition `Foo` is not a string"), severity: Severity::Error, - source_info: source_code.clone(), + source_info: Arc::clone(source_code), span: definition.span(), help: Some("provide one of `str` or `int`".to_owned()), label: None, @@ -57,7 +57,7 @@ pub fn parse_data_definition( ParsingError::from(Diagnostic { message: e, severity: Severity::Warning, - source_info: source_code.clone(), + source_info: Arc::clone(source_code), span: definition.span(), help: None, label: None, @@ -69,7 +69,7 @@ pub fn parse_data_definition( ParsingError::from(Diagnostic { message: "JSON definition has no children".to_owned(), severity: Severity::Error, - source_info: source_code.clone(), + source_info: Arc::clone(source_code), span: definition.span(), help: Some(format!( "specify children `{HASH_CHILD}`, `{DOC_CHILD}` and some `{FIELD_DEFINITION}`s" @@ -85,7 +85,7 @@ pub fn parse_data_definition( node.extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(source_code), context: "JSON definition `{name}`".into(), not_found_help: None, wrong_type_help: None, @@ -99,7 +99,7 @@ pub fn parse_data_definition( .extract_child_node( DOC_CHILD, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(source_code), context: format!("JSON definition `{name}`").into(), not_found_help: Some(format!("specify child `{DOC_CHILD} \"Example\"`").into()), wrong_type_help: None, @@ -108,7 +108,7 @@ pub fn parse_data_definition( .extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(source_code), context: format!("JSON definition `{name}`").into(), not_found_help: Some(format!("specify child `{DOC_CHILD} \"Example\"`").into()), wrong_type_help: None, @@ -123,7 +123,7 @@ pub fn parse_data_definition( .map(|(index, node)| { parse_field( node, - source_code.clone(), + Arc::clone(source_code), name, maybe_default_encoding, index, @@ -133,7 +133,7 @@ pub fn parse_data_definition( Ok(JsonDefinition { index, - source_info: source_code.clone(), + source_info: Arc::clone(source_code), span: definition.span(), name: name.into(), doc: doc.into(), @@ -152,7 +152,7 @@ fn parse_field( let field_node = node.extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("field definition in JSON {data_name}").into(), not_found_help: Some("add a name to the field".into()), wrong_type_help: None, @@ -168,7 +168,7 @@ fn parse_field( ParsingError::from(Diagnostic { message: e, severity: Severity::Warning, - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), span: node.span(), help: None, label: None, @@ -185,7 +185,7 @@ fn parse_field( "property `{TYPE_PROPERTY}` not provided for JSON field definition `{data_name}::{field_node}`", ), severity: Severity::Error, - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), span: node.span(), help: Some(format!("specify `{TYPE_PROPERTY}=\"...\"`.")), label: None, @@ -198,7 +198,7 @@ fn parse_field( "property `{TYPE_PROPERTY}`, of JSON field definition `{data_name}::{field_node}`, is not a string", ), severity: Severity::Error, - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), span: datatype_entry.span(), help: Some(format!("specify `{TYPE_PROPERTY}=\"...\"`.")), label: None, @@ -214,7 +214,7 @@ fn parse_field( }?; let children = node.extract_children(ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: "field definition".into(), not_found_help: Some(format!("specify children `{KEY_CHILD}`, `{DOC_CHILD}`").into()), wrong_type_help: None, @@ -226,7 +226,7 @@ fn parse_field( c.extract_argument_bool( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("field definition `{data_name}::{field_node}`").into(), not_found_help: None, wrong_type_help: None, @@ -239,7 +239,7 @@ fn parse_field( let key_node = children.extract_child_node( "key", ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("field definition `{data_name}::{field_node}`").into(), not_found_help: Some( format!( @@ -259,7 +259,7 @@ fn parse_field( .extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("field definition `{data_name}::{field_node}`").into(), not_found_help: None, wrong_type_help: None, @@ -271,7 +271,7 @@ fn parse_field( .extract_child_node( DOC_CHILD, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("field definition `{data_name}::{field_node}`").into(), not_found_help: Some(format!("specify child `{DOC_CHILD} \"Example\"`").into()), wrong_type_help: None, @@ -280,7 +280,7 @@ fn parse_field( .extract_argument_string( 0, ErrorContext { - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), context: format!("field definition `{data_name}::{field_node}`").into(), not_found_help: None, wrong_type_help: None, diff --git a/src/kdl_parser/parser/mod.rs b/src/kdl_parser/parser/mod.rs index 3273a9b..2aa003c 100644 --- a/src/kdl_parser/parser/mod.rs +++ b/src/kdl_parser/parser/mod.rs @@ -37,7 +37,7 @@ struct ErrorContext<'a> { wrong_type_help: Option>, } -#[allow(dead_code, reason = "the remaining methods may be used at some point")] +#[expect(dead_code, reason = "the remaining methods may be used at some point")] trait KdlNodeUtilsExt { fn extract_argument_string( &self, @@ -120,7 +120,7 @@ impl KdlNodeUtilsExt for KdlNode { error_context.context ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: self.span(), help: error_context.not_found_help.map(Cow::into_owned), label: None, @@ -136,7 +136,7 @@ impl KdlNodeUtilsExt for KdlNode { index + 1 ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: entry.span(), help: error_context.wrong_type_help.map(Cow::into_owned), label: None, @@ -158,7 +158,7 @@ impl KdlNodeUtilsExt for KdlNode { error_context.context ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: self.span(), help: error_context.not_found_help.map(Cow::into_owned), label: None, @@ -174,7 +174,7 @@ impl KdlNodeUtilsExt for KdlNode { index + 1 ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: entry.span(), help: error_context.wrong_type_help.map(Cow::into_owned), label: None, @@ -196,7 +196,7 @@ impl KdlNodeUtilsExt for KdlNode { error_context.context ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: self.span(), help: error_context.not_found_help.map(Cow::into_owned), label: None, @@ -212,7 +212,7 @@ impl KdlNodeUtilsExt for KdlNode { index + 1 ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: entry.span(), help: error_context.wrong_type_help.map(Cow::into_owned), label: None, @@ -236,7 +236,7 @@ impl KdlNodeUtilsExt for KdlNode { error_context.context ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: self.span(), help: error_context.not_found_help.map(Cow::into_owned), label: None, @@ -252,7 +252,7 @@ impl KdlNodeUtilsExt for KdlNode { error_context.context ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: entry.span(), help: error_context.wrong_type_help.map(Cow::into_owned), label: None, @@ -276,7 +276,7 @@ impl KdlNodeUtilsExt for KdlNode { error_context.context ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: self.span(), help: error_context.not_found_help.map(Cow::into_owned), label: None, @@ -292,7 +292,7 @@ impl KdlNodeUtilsExt for KdlNode { error_context.context ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: self.span(), help: error_context.wrong_type_help.map(Cow::into_owned), label: None, @@ -316,7 +316,7 @@ impl KdlNodeUtilsExt for KdlNode { error_context.context ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: self.span(), help: error_context.not_found_help.map(Cow::into_owned), label: None, @@ -332,7 +332,7 @@ impl KdlNodeUtilsExt for KdlNode { error_context.context ), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: entry.span(), help: error_context.wrong_type_help.map(Cow::into_owned), label: None, @@ -346,7 +346,7 @@ impl KdlNodeUtilsExt for KdlNode { ParsingError::from(Diagnostic { message: format!("{} does not have any child", error_context.context), severity: Severity::Error, - source_info: error_context.source_info.clone(), + source_info: Arc::clone(&error_context.source_info), span: self.span(), help: error_context.not_found_help.map(Cow::into_owned), label: None, @@ -438,7 +438,7 @@ fn parse_all_definitions( HTTP_DEFINITION_NAME | XML_DEFINITION_NAME | PLIST_DEFINITION_NAME => {} // Ignore the "import" node. - #[allow( + #[expect( clippy::match_same_arms, reason = "ignore the `import` node since it is not a definition" )] @@ -448,7 +448,7 @@ fn parse_all_definitions( all_diagnostics.push(Diagnostic { message: format!("unrecognized node `{other}`"), severity: Severity::Warning, - source_info: source_info.clone(), + source_info: Arc::clone(source_info), span: definition.span(), help: None, label: None, @@ -483,7 +483,7 @@ where .extract_argument_string( 0, ErrorContext { - source_info: callee_source_info.clone(), + source_info: Arc::clone(callee_source_info), context: "document definition".into(), not_found_help: Some("".into()), wrong_type_help: Some("".into()), @@ -506,7 +506,7 @@ where warnings.push(Diagnostic { message: format!("cycle detected when reading \"{cyclic_path}\""), severity: Severity::Warning, - source_info: callee_source_info.clone(), + source_info: Arc::clone(callee_source_info), span: node.span(), help: Some(format!("{cyclic_path} imports\n-> {source_path}, which imports\n--> {cyclic_path} and so on...\nThe parser will parse \"{cyclic_path}\" once, however keep in mind that this cycle implies that the definitions _may_ have cycles between datatypes.")), label: Some("this file imports the current one".to_owned()), @@ -514,7 +514,7 @@ where Diagnostic { message: "break the cycle between `import`s".to_owned(), severity: Severity::Advice, - source_info: callee_source_info.clone(), + source_info: Arc::clone(callee_source_info), span: node.span(), help: None, label: None, @@ -566,7 +566,7 @@ fn parse_single_document( non_erroring_diagnostics.push(Diagnostic { message: "the file is empty".to_owned(), severity: Severity::Warning, - source_info: source_info.clone(), + source_info: Arc::clone(&source_info), span: kdl_document.span(), help: Some("add some definitions".to_owned()), label: None, @@ -576,7 +576,7 @@ fn parse_single_document( return Ok(ParseSingleDocumentResult { document: raw_document, warnings: ParsingWarnings { - source_info: source_info.clone(), + source_info: Arc::clone(&source_info), diagnostics: non_erroring_diagnostics, }, }); diff --git a/src/kdl_parser/parser/type_parser.rs b/src/kdl_parser/parser/type_parser.rs index 6294d06..266a5ca 100644 --- a/src/kdl_parser/parser/type_parser.rs +++ b/src/kdl_parser/parser/type_parser.rs @@ -40,7 +40,7 @@ pub fn generic_parse( Err(ParsingError::from(convert_error_to_diagnostic( inner, - source_code.clone(), + Arc::clone(source_code), new_span, ))) } @@ -58,7 +58,7 @@ fn convert_error_to_diagnostic( .map(|e| Diagnostic { message: e.message.clone(), severity: e.severity, - source_info: source_code.clone(), + source_info: Arc::clone(&source_code), span, help: e.help.clone(), label: None, @@ -89,7 +89,6 @@ fn convert_error_to_diagnostic( } } -#[allow(dead_code)] mod combinators { use std::fmt::Display; use std::num::NonZeroUsize; @@ -118,6 +117,8 @@ mod combinators { pub struct Error { pub cause: Option, pub context: Vec, + + #[expect(dead_code, reason = "We don't record span info yet")] pub span: Option, } @@ -258,6 +259,11 @@ mod combinators { Named { name: &'a str, + + #[expect( + dead_code, + reason = "The map type does not yet use the named parameter" + )] params: Vec<(&'a str, &'a str)>, }, } @@ -876,7 +882,7 @@ mod combinators { size_or_separator.prop_map(move |extra| format!("[{inner}]{extra}")) } - #[allow( + #[expect( clippy::needless_pass_by_value, reason = "proptest and rustc throw a fit if we use references" )] @@ -926,7 +932,7 @@ mod combinators { }; let val = parse_datatype(&mut input); println!("{val:?}"); - assert!(val.is_err()); + val.unwrap_err(); } #[test] diff --git a/src/kdl_parser/schema/mod.rs b/src/kdl_parser/schema/mod.rs index 9dad224..b729c46 100644 --- a/src/kdl_parser/schema/mod.rs +++ b/src/kdl_parser/schema/mod.rs @@ -30,7 +30,7 @@ impl RawDocument { /// /// Returns `Err` if after this post-processing the [`Document`] is still /// not valid. - #[allow(clippy::result_large_err)] + #[expect(clippy::result_large_err, reason = "A Diagnostic is huge.")] pub const fn finalize(self) -> Result { validator::validate(self) } @@ -46,7 +46,6 @@ impl RawDocument { } #[derive(Debug)] -#[allow(dead_code)] pub enum EnumDefinition { StringEnum(StringEnumDefinition), @@ -232,7 +231,6 @@ pub struct JsonDefinition { } #[derive(Debug, Clone, Copy)] -#[allow(dead_code)] pub enum ArraySeparator { /// Array separated by ',' /// @@ -319,7 +317,6 @@ impl From for crate::intermediate::schema::ArraySize { } #[derive(Debug, Clone)] -#[allow(dead_code)] pub enum DataType { I32 { encoding: IntLikeEncoding, diff --git a/src/kdl_parser/schema/validator.rs b/src/kdl_parser/schema/validator.rs index ab15737..87d4338 100644 --- a/src/kdl_parser/schema/validator.rs +++ b/src/kdl_parser/schema/validator.rs @@ -2,7 +2,7 @@ use crate::kdl_parser::{Document, schema::RawDocument}; use crate::kdl_parser::Diagnostic; -#[allow(clippy::result_large_err)] +#[expect(clippy::result_large_err, reason = "A Diagnostic is huge.")] #[inline] pub const fn validate(document: RawDocument) -> Result { Ok(Document(document)) diff --git a/src/vfs.rs b/src/vfs.rs index 83597cd..4b73615 100644 --- a/src/vfs.rs +++ b/src/vfs.rs @@ -240,8 +240,12 @@ mod tests { assert_eq!(path_a.to_string_lossy(), Cow::Borrowed("a")); #[cfg(not(windows))] - let os_str = - unsafe { std::ffi::OsStr::from_encoded_bytes_unchecked(&[0x61, 0x62, 0xE3, 0x82]) }; + let os_str = { + // SAFETY: + // the string is _not_ valid UTF-8, but this is necessary for test + // purposes. + unsafe { std::ffi::OsStr::from_encoded_bytes_unchecked(&[0x61, 0x62, 0xE3, 0x82]) } + }; #[cfg(windows)] let os_string = { diff --git a/tests/e2e.rs b/tests/e2e.rs index a990e43..e442040 100644 --- a/tests/e2e.rs +++ b/tests/e2e.rs @@ -39,7 +39,7 @@ fn generic_e2e_cxx_glaze_harness(path_entrypoint: PathBuf, test_name: &str) { let generation_basepath = PathBuf::from_iter([PROJECT_DIR, "target", &format!("test-e2e-{test_name}")]); - let _ = std::fs::create_dir_all(&generation_basepath); + let _file = std::fs::create_dir_all(&generation_basepath); let mut generator = generators::CxxGenerator::new(); generator.add_addon(GlazeGenerator {}); From 2817fbb04789f41df6e380f5434b5a8ef82dd46a Mon Sep 17 00:00:00 2001 From: anri Date: Wed, 20 May 2026 21:31:15 +0000 Subject: [PATCH 5/5] cli: fix test --- packet-generator-cli/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packet-generator-cli/src/lib.rs b/packet-generator-cli/src/lib.rs index 2a35c44..24a6a4e 100644 --- a/packet-generator-cli/src/lib.rs +++ b/packet-generator-cli/src/lib.rs @@ -53,7 +53,7 @@ mod tests { let (document, warnings) = read_all_kdls_from_directory(&assets_dir)?; - if !warnings.are_there_any() { + if warnings.are_there_any() { warnings.print_warnings_if_any(); miette::bail!("There were warnings"); }