diff options
24 files changed, 2746 insertions, 31 deletions
diff --git a/mingling_cli/.gitignore b/mingling_cli/.gitignore new file mode 100644 index 0000000..6a6d575 --- /dev/null +++ b/mingling_cli/.gitignore @@ -0,0 +1 @@ +registry.json diff --git a/mingling_cli/Cargo.lock b/mingling_cli/Cargo.lock index e6098d5..6d34351 100644 --- a/mingling_cli/Cargo.lock +++ b/mingling_cli/Cargo.lock @@ -3,11 +3,28 @@ version = 4 [[package]] +name = "annotate-snippets" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" +dependencies = [ + "anstyle", + "memchr", + "unicode-width", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] name = "arg-picker" version = "0.1.0" dependencies = [ "arg-picker-macros", - "just_fmt", + "just_fmt 0.2.0", ] [[package]] @@ -20,6 +37,18 @@ dependencies = [ ] [[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] name = "camino" version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -46,6 +75,7 @@ checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ "camino", "cargo-platform", + "derive_builder", "semver", "serde", "serde_json", @@ -53,6 +83,100 @@ dependencies = [ ] [[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.119", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -60,11 +184,53 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "just_fmt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e" + +[[package]] +name = "just_fmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96" [[package]] +name = "just_template" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb99a3c1dee7299c57b26ef927f15535da0fbc93d6deb1d1114cae1337be4fb" +dependencies = [ + "just_fmt 0.1.2", + "just_template_macros", +] + +[[package]] +name = "just_template_macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1471eb68722ecefeb71debdde2859e8725341f171d3f42b3a98a0862ad19416e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "libc" +version = "0.2.187" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7743783ea728ef5c31194c6590797eed286449b4a4e87d626d8a51f0a94e732" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -77,34 +243,36 @@ dependencies = [ "arg-picker", "mingling_core", "mingling_macros", - "serde", ] [[package]] name = "mingling-cli" version = "0.3.0" dependencies = [ + "annotate-snippets", "cargo_metadata", + "just_template", "mingling", + "proc-macro2", "serde", "serde_json", + "syn 3.0.2", + "tokio", ] [[package]] name = "mingling_core" version = "0.3.0" dependencies = [ - "just_fmt", + "just_fmt 0.2.0", "mingling_pathf", - "serde", - "serde_json", ] [[package]] name = "mingling_macros" version = "0.3.0" dependencies = [ - "just_fmt", + "just_fmt 0.2.0", "proc-macro2", "quote", "syn 2.0.119", @@ -114,12 +282,52 @@ dependencies = [ name = "mingling_pathf" version = "0.3.0" dependencies = [ - "just_fmt", + "just_fmt 0.2.0", "proc-macro2", "syn 2.0.119", ] [[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] name = "proc-macro2" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -138,6 +346,21 @@ dependencies = [ ] [[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -191,6 +414,38 @@ dependencies = [ ] [[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] name = "syn" version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -233,12 +488,67 @@ dependencies = [ ] [[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/mingling_cli/Cargo.toml b/mingling_cli/Cargo.toml index 93021f0..bc8d59f 100644 --- a/mingling_cli/Cargo.toml +++ b/mingling_cli/Cargo.toml @@ -21,7 +21,7 @@ features = [ "picker", "pathf", "extra_macros", - "structural_renderer" + "async", ] [build-dependencies.mingling] @@ -32,8 +32,18 @@ features = [ ] [dependencies] -cargo_metadata = "0.23.1" +annotate-snippets = "0.12.16" +cargo_metadata = { version = "0.23.1", features = ["builder"] } +just_template = "0.2.0" +proc-macro2 = { version = "1.0.107", features = ["span-locations"] } serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" +syn = { version = "3.0.2", features = ["full", "extra-traits"] } +tokio = { version = "1.53.1", features = ["full"] } + +[build-dependencies] +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" +just_template = "0.2.0" [workspace] diff --git a/mingling_cli/about_mling_lint.md b/mingling_cli/about_mling_lint.md new file mode 100644 index 0000000..87fedc8 --- /dev/null +++ b/mingling_cli/about_mling_lint.md @@ -0,0 +1,35 @@ +# 关于 Mingling Linter 设计 + +Mingling Linter 是独立于 `clippy` 和 `check` 之外的检查器,用于检查 Mingling 项目的代码质量 + +## 关于 Linter 定义 + +已完成!所有的定义都被输出到 registry.json 中,可以读取 + +## 忽略规则 + +Linter 在检查时,会检查开头的 mlint 标识,用局部读写直到没有#![.*]这样的结构为止,判断当前文件需要参与的 Linter + +默认来讲,一个文件所有默认为 deny、warn 的 linter 都会参与,直到被显式 allow 覆盖。 + +```rust +#![mlint(allow(linter))] +#![mlint(warn(linter))] +#![mlint(deny(linter))] +``` + +## 检查阶段 + +1. 为每个包的内部 rs 文件(必须被包含)检查,并按照忽略规则,计算每个文件需要哪些 linter 检查(仅 file 级别的) +2. 使用 tokio 并行检查 +3. 同时,在检查过程中,计算更细粒度的哪些 Linter 需要在哪些行(或 AST 层级,待定)检查,检查的程度是什么 (warn还是deny) +4. 使用 tokio 并行检查 所有 细粒度的 Linter +5. 输出检查结果 + +## 性能 + +请使用 tokio 并行化执行 linter,但是结果放在缓冲区,计算完成后按照文件层级(主:文件深度,次:名字 a-Z,辅:名字 0-9)排序,后输出 + +## 关于架构层面 + +第一版可以直接使用 tokio 并行化,这不会很难 diff --git a/mingling_cli/build.rs b/mingling_cli/build.rs index 2a36e37..b74d8a0 100644 --- a/mingling_cli/build.rs +++ b/mingling_cli/build.rs @@ -1,5 +1,12 @@ use mingling::build::analyze_and_build_type_mapping; +pub mod pre; + fn main() { + // Perform path analysis and build type mapping table analyze_and_build_type_mapping().unwrap(); + + // Generate lint registry + pre::gen_mod_file().unwrap(); + pre::gen_lint_registry().unwrap(); } diff --git a/mingling_cli/expand.rs b/mingling_cli/expand.rs new file mode 100644 index 0000000..2e8bc4f --- /dev/null +++ b/mingling_cli/expand.rs @@ -0,0 +1,1121 @@ +#![feature(prelude_import)] +extern crate std; +#[prelude_import] +use std::prelude::rust_2024::*; +use crate::{linter::MinglingLinterSetup, metadata::MinglingMetadataSetup}; +use mingling::{ + macros::gen_program, + setup::{ExitCodeSetup, picker::{HelpFlagSetup, StructuralRendererSetup}}, +}; +pub mod errors { + pub mod serde_json { + use mingling::macros::{buffer, group, r_println, renderer}; + pub type ErrorSerdeJson = serde_json::Error; + #[allow(non_camel_case_types)] + mod internal_group_serde_json_error { + use crate::ThisProgram as __MinglingProgram; + #[allow(unused_imports)] + use serde_json::Error; + use super::ErrorSerdeJson; + impl ::mingling::Grouped<__MinglingProgram> for ErrorSerdeJson { + fn member_id() -> __MinglingProgram { + __MinglingProgram::ErrorSerdeJson + } + } + } + #[doc(hidden)] + #[allow(non_camel_case_types)] + pub struct __internal_renderer_render_error_serde_json; + impl ::mingling::Renderer for __internal_renderer_render_error_serde_json { + type Previous = ErrorSerdeJson; + fn render(prev: Self::Previous) -> ::mingling::RenderResult { + let __renderer_result = { render_error_serde_json(prev) }; + ::std::convert::Into::into(__renderer_result) + } + } + pub fn render_error_serde_json( + _err: ErrorSerdeJson, + ) -> ::mingling::RenderResult { + let mut __render_result_buffer = ::mingling::RenderResult::new(); + { + __render_result_buffer + .println( + ::alloc::__export::must_use({ + ::alloc::fmt::format(format_args!("serde")) + }), + ); + } + __render_result_buffer + } + } +} +pub mod linter { + use mingling::{Program, macros::program_setup}; + use crate::linter::cmd_mlint::CMDMinglingLinter; + pub mod cmd_mlint { + use mingling::macros::{chain, dispatcher}; + pub struct CMDMinglingLinter; + #[automatically_derived] + impl ::core::fmt::Debug for CMDMinglingLinter { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::write_str(f, "CMDMinglingLinter") + } + } + #[automatically_derived] + impl ::core::default::Default for CMDMinglingLinter { + #[inline] + fn default() -> CMDMinglingLinter { + CMDMinglingLinter {} + } + } + pub struct EntryMinglingLinter { + pub(crate) inner: Vec<String>, + } + impl EntryMinglingLinter { + /// Creates a new instance of the wrapper type + pub fn new(inner: Vec<String>) -> Self { + Self { inner } + } + } + impl From<Vec<String>> for EntryMinglingLinter { + fn from(inner: Vec<String>) -> Self { + Self::new(inner) + } + } + impl From<EntryMinglingLinter> for Vec<String> { + fn from(wrapper: EntryMinglingLinter) -> Vec<String> { + wrapper.inner + } + } + impl ::std::convert::AsRef<Vec<String>> for EntryMinglingLinter { + fn as_ref(&self) -> &Vec<String> { + &self.inner + } + } + impl ::std::convert::AsMut<Vec<String>> for EntryMinglingLinter { + fn as_mut(&mut self) -> &mut Vec<String> { + &mut self.inner + } + } + impl ::std::ops::Deref for EntryMinglingLinter { + type Target = Vec<String>; + fn deref(&self) -> &Self::Target { + &self.inner + } + } + impl ::std::ops::DerefMut for EntryMinglingLinter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } + } + impl ::std::default::Default for EntryMinglingLinter + where + Vec<String>: ::std::default::Default, + { + fn default() -> Self { + Self::new(::std::default::Default::default()) + } + } + impl Into<mingling::AnyOutput<crate::ThisProgram>> for EntryMinglingLinter { + fn into(self) -> mingling::AnyOutput<crate::ThisProgram> { + mingling::AnyOutput::new(self) + } + } + impl Into<mingling::ChainProcess<crate::ThisProgram>> for EntryMinglingLinter { + fn into(self) -> mingling::ChainProcess<crate::ThisProgram> { + mingling::AnyOutput::new(self).route_chain() + } + } + impl ::mingling::Grouped<crate::ThisProgram> for EntryMinglingLinter { + fn member_id() -> crate::ThisProgram { + crate::ThisProgram::EntryMinglingLinter + } + } + impl ::mingling::Dispatcher<crate::ThisProgram> for CMDMinglingLinter { + fn node(&self) -> ::mingling::Node { + mingling::Node::default().join("mlint") + } + fn begin( + &self, + args: Vec<String>, + ) -> ::mingling::ChainProcess<crate::ThisProgram> { + use ::mingling::Grouped; + ::mingling::Routable::to_chain(EntryMinglingLinter::new(args)) + } + fn clone_dispatcher( + &self, + ) -> Box<dyn ::mingling::Dispatcher<crate::ThisProgram>> { + Box::new(CMDMinglingLinter) + } + } + #[doc(hidden)] + #[allow(non_camel_case_types)] + pub struct __internal_chain_handle_mlint; + impl ::mingling::Chain<crate::ThisProgram> for __internal_chain_handle_mlint { + type Previous = EntryMinglingLinter; + fn proc( + prev: EntryMinglingLinter, + ) -> ::mingling::ChainProcess<crate::ThisProgram> { + handle_mlint(prev); + <crate::ResultEmpty as ::mingling::Routable< + crate::ThisProgram, + >>::to_chain(crate::ResultEmpty) + } + } + pub fn handle_mlint(_args: EntryMinglingLinter) {} + } + #[doc(hidden)] + pub struct MinglingLinterSetup; + impl ::mingling::setup::ProgramSetup<crate::ThisProgram> for MinglingLinterSetup { + fn setup(self, program: &mut ::mingling::Program<crate::ThisProgram>) { + mingling_linter_setup(program); + } + } + pub fn mingling_linter_setup(program: &mut Program<crate::ThisProgram>) { + { + program.with_setup(MinglingLinterCommandSetup); + } + } + #[doc(hidden)] + pub struct MinglingLinterCommandSetup; + impl ::mingling::setup::ProgramSetup<crate::ThisProgram> + for MinglingLinterCommandSetup { + fn setup(self, program: &mut ::mingling::Program<crate::ThisProgram>) { + mingling_linter_command_setup(program); + } + } + pub fn mingling_linter_command_setup(program: &mut Program<crate::ThisProgram>) { + { + program.with_dispatcher(CMDMinglingLinter); + } + } +} +pub mod metadata { + use cargo_metadata::Metadata; + use mingling::{ + AnyOutput, Program, ProgramCollect, RenderResult, + macros::{buffer, group_structural, program_setup, r_println, renderer, routeify}, + }; + use crate::metadata::{cmd_metadata::CMDMetadata, setup::CargoMetadataSetup}; + pub mod cmd_metadata { + use cargo_metadata::Metadata; + use mingling::{LazyRes, macros::{chain, dispatcher, pack_structural}}; + use crate::metadata::setup::ResMetadata; + pub struct CMDMetadata; + #[automatically_derived] + impl ::core::fmt::Debug for CMDMetadata { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::write_str(f, "CMDMetadata") + } + } + #[automatically_derived] + impl ::core::default::Default for CMDMetadata { + #[inline] + fn default() -> CMDMetadata { + CMDMetadata {} + } + } + pub struct EntryMetadata { + pub(crate) inner: Vec<String>, + } + impl EntryMetadata { + /// Creates a new instance of the wrapper type + pub fn new(inner: Vec<String>) -> Self { + Self { inner } + } + } + impl From<Vec<String>> for EntryMetadata { + fn from(inner: Vec<String>) -> Self { + Self::new(inner) + } + } + impl From<EntryMetadata> for Vec<String> { + fn from(wrapper: EntryMetadata) -> Vec<String> { + wrapper.inner + } + } + impl ::std::convert::AsRef<Vec<String>> for EntryMetadata { + fn as_ref(&self) -> &Vec<String> { + &self.inner + } + } + impl ::std::convert::AsMut<Vec<String>> for EntryMetadata { + fn as_mut(&mut self) -> &mut Vec<String> { + &mut self.inner + } + } + impl ::std::ops::Deref for EntryMetadata { + type Target = Vec<String>; + fn deref(&self) -> &Self::Target { + &self.inner + } + } + impl ::std::ops::DerefMut for EntryMetadata { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } + } + impl ::std::default::Default for EntryMetadata + where + Vec<String>: ::std::default::Default, + { + fn default() -> Self { + Self::new(::std::default::Default::default()) + } + } + impl Into<mingling::AnyOutput<crate::ThisProgram>> for EntryMetadata { + fn into(self) -> mingling::AnyOutput<crate::ThisProgram> { + mingling::AnyOutput::new(self) + } + } + impl Into<mingling::ChainProcess<crate::ThisProgram>> for EntryMetadata { + fn into(self) -> mingling::ChainProcess<crate::ThisProgram> { + mingling::AnyOutput::new(self).route_chain() + } + } + impl ::mingling::Grouped<crate::ThisProgram> for EntryMetadata { + fn member_id() -> crate::ThisProgram { + crate::ThisProgram::EntryMetadata + } + } + impl ::mingling::Dispatcher<crate::ThisProgram> for CMDMetadata { + fn node(&self) -> ::mingling::Node { + mingling::Node::default().join("metadata") + } + fn begin( + &self, + args: Vec<String>, + ) -> ::mingling::ChainProcess<crate::ThisProgram> { + use ::mingling::Grouped; + ::mingling::Routable::to_chain(EntryMetadata::new(args)) + } + fn clone_dispatcher( + &self, + ) -> Box<dyn ::mingling::Dispatcher<crate::ThisProgram>> { + Box::new(CMDMetadata) + } + } + pub struct ResultMetadata { + pub inner: ResMetadata, + } + #[doc(hidden)] + #[allow( + non_upper_case_globals, + unused_attributes, + unused_qualifications, + clippy::absolute_paths, + )] + const _: () = { + #[allow(unused_extern_crates, clippy::useless_attribute)] + extern crate serde as _serde; + #[automatically_derived] + impl _serde::Serialize for ResultMetadata { + fn serialize<__S>( + &self, + __serializer: __S, + ) -> _serde::__private229::Result<__S::Ok, __S::Error> + where + __S: _serde::Serializer, + { + let mut __serde_state = _serde::Serializer::serialize_struct( + __serializer, + "ResultMetadata", + false as usize + 1, + )?; + _serde::ser::SerializeStruct::serialize_field( + &mut __serde_state, + "inner", + &self.inner, + )?; + _serde::ser::SerializeStruct::end(__serde_state) + } + } + }; + impl ResultMetadata { + pub fn new(inner: ResMetadata) -> Self { + Self { inner } + } + } + impl From<ResMetadata> for ResultMetadata { + fn from(inner: ResMetadata) -> Self { + Self::new(inner) + } + } + impl From<ResultMetadata> for ResMetadata { + fn from(wrapper: ResultMetadata) -> ResMetadata { + wrapper.inner + } + } + impl ::std::convert::AsRef<ResMetadata> for ResultMetadata { + fn as_ref(&self) -> &ResMetadata { + &self.inner + } + } + impl ::std::convert::AsMut<ResMetadata> for ResultMetadata { + fn as_mut(&mut self) -> &mut ResMetadata { + &mut self.inner + } + } + impl ::std::ops::Deref for ResultMetadata { + type Target = ResMetadata; + fn deref(&self) -> &Self::Target { + &self.inner + } + } + impl ::std::ops::DerefMut for ResultMetadata { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } + } + impl ::std::default::Default for ResultMetadata + where + ResMetadata: ::std::default::Default, + { + fn default() -> Self { + Self::new(::std::default::Default::default()) + } + } + impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> + for ResultMetadata {} + impl ::mingling::__private::StructuralData<crate::ThisProgram> + for ResultMetadata {} + impl Into<::mingling::AnyOutput<crate::ThisProgram>> for ResultMetadata { + fn into(self) -> ::mingling::AnyOutput<crate::ThisProgram> { + ::mingling::AnyOutput::new(self) + } + } + impl Into<::mingling::ChainProcess<crate::ThisProgram>> for ResultMetadata { + fn into(self) -> ::mingling::ChainProcess<crate::ThisProgram> { + ::mingling::AnyOutput::new(self).route_chain() + } + } + impl ::mingling::Grouped<crate::ThisProgram> for ResultMetadata { + fn member_id() -> crate::ThisProgram { + crate::ThisProgram::ResultMetadata + } + } + #[doc(hidden)] + #[allow(non_camel_case_types)] + pub struct __internal_chain_handle_metadata; + impl ::mingling::Chain<crate::ThisProgram> for __internal_chain_handle_metadata { + type Previous = EntryMetadata; + fn proc( + prev: EntryMetadata, + ) -> ::mingling::ChainProcess<crate::ThisProgram> { + let __chain_result = { + ::mingling::this::<crate::ThisProgram>() + .__modify_res_and_return_route(| + metadata: &mut LazyRes<ResMetadata>| + { + ::mingling::Routable::< + crate::ThisProgram, + >::to_chain(handle_metadata(prev, metadata)) + }) + }; + ::mingling::Routable::<crate::ThisProgram>::to_chain(__chain_result) + } + } + pub fn handle_metadata( + _: EntryMetadata, + metadata: &mut LazyRes<ResMetadata>, + ) -> Metadata { + let metadata = metadata.get_ref().clone(); + metadata.data().clone() + } + } + pub mod setup { + use std::{env::current_dir, path::PathBuf}; + use cargo_metadata::{CargoOpt, MetadataCommand}; + use mingling::{ + LazyInit, Program, consts::REMAINS, macros::program_setup, + picker::{IntoPicker, value::Flag}, + prelude::*, res::ResCurrentDir, + }; + /// Name of Cargo manifest file + pub const CARGO_TOML: &str = "Cargo.toml"; + use cargo_metadata::Metadata; + use serde::Serialize; + pub struct ResMetadata { + data: Option<Metadata>, + } + #[automatically_derived] + impl ::core::default::Default for ResMetadata { + #[inline] + fn default() -> ResMetadata { + ResMetadata { + data: ::core::default::Default::default(), + } + } + } + #[automatically_derived] + impl ::core::clone::Clone for ResMetadata { + #[inline] + fn clone(&self) -> ResMetadata { + ResMetadata { + data: ::core::clone::Clone::clone(&self.data), + } + } + } + #[doc(hidden)] + #[allow( + non_upper_case_globals, + unused_attributes, + unused_qualifications, + clippy::absolute_paths, + )] + const _: () = { + #[allow(unused_extern_crates, clippy::useless_attribute)] + extern crate serde as _serde; + #[automatically_derived] + impl _serde::Serialize for ResMetadata { + fn serialize<__S>( + &self, + __serializer: __S, + ) -> _serde::__private229::Result<__S::Ok, __S::Error> + where + __S: _serde::Serializer, + { + let mut __serde_state = _serde::Serializer::serialize_struct( + __serializer, + "ResMetadata", + false as usize + 1, + )?; + _serde::ser::SerializeStruct::serialize_field( + &mut __serde_state, + "data", + &self.data, + )?; + _serde::ser::SerializeStruct::end(__serde_state) + } + } + }; + impl ResMetadata { + /// Returns a reference to the parsed `cargo metadata`. + /// + /// # Panics + /// + /// This function does **not** panic in practice, because `ResMetadata` is + /// always initialized via [`LazyInit`] inside the program setup, and the + /// initialization either succeeds (setting `data` to `Some(...)`) or fails + /// by propagating the error from `cmd.exec().unwrap()`. Therefore, by the + /// time this getter is called, `self.data` is guaranteed to be `Some`. + pub fn data(&self) -> &Metadata { + self.data.as_ref().unwrap() + } + } + #[doc(hidden)] + pub struct CargoMetadataSetup; + impl ::mingling::setup::ProgramSetup<crate::ThisProgram> for CargoMetadataSetup { + fn setup(self, program: &mut ::mingling::Program<crate::ThisProgram>) { + cargo_metadata_setup(program); + } + } + pub fn cargo_metadata_setup(program: &mut Program<crate::ThisProgram>) { + { + let args = program.take_args(); + let ( + feature_args, + manifest_path, + message_format, + enable_all_features, + no_default_features, + no_deps, + args, + ) = args + .pick( + &::mingling::picker::PickerArg::<Vec<String>> { + full: &["features"], + short: ::std::option::Option::None, + positional: false, + internal_type: ::std::marker::PhantomData, + }, + ) + .pick( + &::mingling::picker::PickerArg::<Option<String>> { + full: &["manifest_path"], + short: ::std::option::Option::None, + positional: false, + internal_type: ::std::marker::PhantomData, + }, + ) + .pick_or( + &::mingling::picker::PickerArg::<String> { + full: &["message_format"], + short: ::std::option::Option::None, + positional: false, + internal_type: ::std::marker::PhantomData, + }, + || "disable".to_string(), + ) + .pick( + &::mingling::picker::PickerArg::<Flag> { + full: &["all_features"], + short: ::std::option::Option::None, + positional: false, + internal_type: ::std::marker::PhantomData, + }, + ) + .pick( + &::mingling::picker::PickerArg::<Flag> { + full: &["no_default_features"], + short: ::std::option::Option::None, + positional: false, + internal_type: ::std::marker::PhantomData, + }, + ) + .pick( + &::mingling::picker::PickerArg::<Flag> { + full: &["no_deps"], + short: ::std::option::Option::None, + positional: false, + internal_type: ::std::marker::PhantomData, + }, + ) + .pick(&REMAINS) + .unwrap(); + program.replace_args(args.into()); + program.structural_renderer_name = message_format.into(); + let current_dir = current_dir().unwrap(); + let feature_args_leaked: &[String] = Box::leak( + feature_args.into_boxed_slice(), + ); + let manifest_path_clone = manifest_path.clone(); + let current_dir_clone = current_dir.clone(); + program + .with_resource( + ResMetadata::lazy_init(move || { + let metadata_path = match manifest_path_clone { + Some(ref path_str) => PathBuf::from(path_str), + None => find_manifest().unwrap(), + }; + let mut cmd = MetadataCommand::new(); + cmd.manifest_path(metadata_path) + .current_dir(¤t_dir_clone); + if *enable_all_features { + cmd.features(CargoOpt::AllFeatures); + } + if *no_default_features { + cmd.features(CargoOpt::NoDefaultFeatures); + } + if *no_deps { + cmd.no_deps(); + } + cmd.features( + CargoOpt::SomeFeatures(feature_args_leaked.to_vec()), + ); + ResMetadata { + data: Some(cmd.exec().unwrap()), + } + }), + ); + program.with_resource(ResCurrentDir::from(current_dir)); + } + } + /// Find `Cargo.toml` by searching upward from `current_dir`. + fn find_manifest() -> Option<PathBuf> { + let mut dir = current_dir().ok()?; + loop { + let candidate = dir.join(CARGO_TOML); + if candidate.is_file() { + return Some(candidate); + } + if !dir.pop() { + return None; + } + } + } + } + #[doc(hidden)] + pub struct MinglingMetadataSetup; + impl ::mingling::setup::ProgramSetup<crate::ThisProgram> for MinglingMetadataSetup { + fn setup(self, program: &mut ::mingling::Program<crate::ThisProgram>) { + mingling_metadata_setup(program); + } + } + pub fn mingling_metadata_setup(program: &mut Program<crate::ThisProgram>) { + { + program.with_setup(CargoMetadataSetup); + program.with_dispatcher(CMDMetadata); + } + } + #[allow(non_camel_case_types)] + mod internal_group_metadata { + use crate::ThisProgram as __MinglingProgram; + #[allow(unused_imports)] + use super::Metadata; + impl ::mingling::Grouped<__MinglingProgram> for Metadata { + fn member_id() -> __MinglingProgram { + __MinglingProgram::Metadata + } + } + impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> + for Metadata {} + impl ::mingling::__private::StructuralData<crate::ThisProgram> for Metadata {} + } + #[doc(hidden)] + #[allow(non_camel_case_types)] + pub struct __internal_renderer_render_metadata; + impl ::mingling::Renderer for __internal_renderer_render_metadata { + type Previous = Metadata; + fn render(prev: Self::Previous) -> ::mingling::RenderResult { + let __renderer_result = { render_metadata(prev) }; + ::std::convert::Into::into(__renderer_result) + } + } + pub fn render_metadata(metadata: Metadata) -> RenderResult { + let mut r = RenderResult::new(); + r.println( + ::alloc::__export::must_use({ + ::alloc::fmt::format( + format_args!("{0}", serde_json::to_string(&metadata)?), + ) + }), + ); + r + } +} +fn main() { + let mut program = ThisProgram::new(); + program.with_setup(HelpFlagSetup::default()); + program.with_setup(ExitCodeSetup::default()); + program.with_setup(StructuralRendererSetup); + program.with_setup(MinglingMetadataSetup); + program.with_setup(MinglingLinterSetup); + program.exec_and_exit(); +} +/// Alias for the current program type `crate::ThisProgram` +pub type Next = ::mingling::ChainProcess<crate::ThisProgram>; +impl ::mingling::Routable<crate::ThisProgram> +for ::mingling::ChainProcess<crate::ThisProgram> { + fn to_chain(self) -> ::mingling::ChainProcess<crate::ThisProgram> { + match self { + ::mingling::ChainProcess::Ok((any, _)) => { + ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Chain)) + } + other => other, + } + } + fn to_render(self) -> ::mingling::ChainProcess<crate::ThisProgram> { + match self { + ::mingling::ChainProcess::Ok((any, _)) => { + ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Renderer)) + } + other => other, + } + } +} +pub struct ErrorRendererNotFound { + pub(crate) inner: String, +} +impl ErrorRendererNotFound { + /// Creates a new instance of the wrapper type + pub fn new(inner: String) -> Self { + Self { inner } + } +} +impl From<String> for ErrorRendererNotFound { + fn from(inner: String) -> Self { + Self::new(inner) + } +} +impl From<ErrorRendererNotFound> for String { + fn from(wrapper: ErrorRendererNotFound) -> String { + wrapper.inner + } +} +impl ::std::convert::AsRef<String> for ErrorRendererNotFound { + fn as_ref(&self) -> &String { + &self.inner + } +} +impl ::std::convert::AsMut<String> for ErrorRendererNotFound { + fn as_mut(&mut self) -> &mut String { + &mut self.inner + } +} +impl ::std::ops::Deref for ErrorRendererNotFound { + type Target = String; + fn deref(&self) -> &Self::Target { + &self.inner + } +} +impl ::std::ops::DerefMut for ErrorRendererNotFound { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} +impl ::std::default::Default for ErrorRendererNotFound +where + String: ::std::default::Default, +{ + fn default() -> Self { + Self::new(::std::default::Default::default()) + } +} +impl Into<mingling::AnyOutput<crate::ThisProgram>> for ErrorRendererNotFound { + fn into(self) -> mingling::AnyOutput<crate::ThisProgram> { + mingling::AnyOutput::new(self) + } +} +impl Into<mingling::ChainProcess<crate::ThisProgram>> for ErrorRendererNotFound { + fn into(self) -> mingling::ChainProcess<crate::ThisProgram> { + mingling::AnyOutput::new(self).route_chain() + } +} +impl ::mingling::Grouped<crate::ThisProgram> for ErrorRendererNotFound { + fn member_id() -> crate::ThisProgram { + crate::ThisProgram::ErrorRendererNotFound + } +} +pub struct ErrorDispatcherNotFound { + pub(crate) inner: Vec<String>, +} +impl ErrorDispatcherNotFound { + /// Creates a new instance of the wrapper type + pub fn new(inner: Vec<String>) -> Self { + Self { inner } + } +} +impl From<Vec<String>> for ErrorDispatcherNotFound { + fn from(inner: Vec<String>) -> Self { + Self::new(inner) + } +} +impl From<ErrorDispatcherNotFound> for Vec<String> { + fn from(wrapper: ErrorDispatcherNotFound) -> Vec<String> { + wrapper.inner + } +} +impl ::std::convert::AsRef<Vec<String>> for ErrorDispatcherNotFound { + fn as_ref(&self) -> &Vec<String> { + &self.inner + } +} +impl ::std::convert::AsMut<Vec<String>> for ErrorDispatcherNotFound { + fn as_mut(&mut self) -> &mut Vec<String> { + &mut self.inner + } +} +impl ::std::ops::Deref for ErrorDispatcherNotFound { + type Target = Vec<String>; + fn deref(&self) -> &Self::Target { + &self.inner + } +} +impl ::std::ops::DerefMut for ErrorDispatcherNotFound { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} +impl ::std::default::Default for ErrorDispatcherNotFound +where + Vec<String>: ::std::default::Default, +{ + fn default() -> Self { + Self::new(::std::default::Default::default()) + } +} +impl Into<mingling::AnyOutput<crate::ThisProgram>> for ErrorDispatcherNotFound { + fn into(self) -> mingling::AnyOutput<crate::ThisProgram> { + mingling::AnyOutput::new(self) + } +} +impl Into<mingling::ChainProcess<crate::ThisProgram>> for ErrorDispatcherNotFound { + fn into(self) -> mingling::ChainProcess<crate::ThisProgram> { + mingling::AnyOutput::new(self).route_chain() + } +} +impl ::mingling::Grouped<crate::ThisProgram> for ErrorDispatcherNotFound { + fn member_id() -> crate::ThisProgram { + crate::ThisProgram::ErrorDispatcherNotFound + } +} +pub struct ResultEmpty; +#[doc(hidden)] +#[allow( + non_upper_case_globals, + unused_attributes, + unused_qualifications, + clippy::absolute_paths, +)] +const _: () = { + #[allow(unused_extern_crates, clippy::useless_attribute)] + extern crate serde as _serde; + #[automatically_derived] + impl _serde::Serialize for ResultEmpty { + fn serialize<__S>( + &self, + __serializer: __S, + ) -> _serde::__private229::Result<__S::Ok, __S::Error> + where + __S: _serde::Serializer, + { + _serde::Serializer::serialize_unit_struct(__serializer, "ResultEmpty") + } + } +}; +impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for ResultEmpty {} +impl ::mingling::__private::StructuralData<crate::ThisProgram> for ResultEmpty {} +impl ::mingling::Grouped<crate::ThisProgram> for ResultEmpty { + fn member_id() -> crate::ThisProgram { + crate::ThisProgram::ResultEmpty + } +} +impl ::std::convert::Into<::mingling::AnyOutput<crate::ThisProgram>> for ResultEmpty { + fn into(self) -> ::mingling::AnyOutput<crate::ThisProgram> { + ::mingling::AnyOutput::new(self) + } +} +impl ::std::convert::Into<::mingling::ChainProcess<crate::ThisProgram>> for ResultEmpty { + fn into(self) -> ::mingling::ChainProcess<crate::ThisProgram> { + ::mingling::AnyOutput::new(self).route_chain() + } +} +#[automatically_derived] +impl ::core::default::Default for ResultEmpty { + #[inline] + fn default() -> ResultEmpty { + ResultEmpty {} + } +} +#[repr(u8)] +#[allow(nonstandard_style)] +pub enum ThisProgram { + EntryMetadata, + EntryMinglingLinter, + ErrorDispatcherNotFound, + ErrorRendererNotFound, + ErrorSerdeJson, + Metadata, + ResultEmpty, + ResultMetadata, +} +#[automatically_derived] +#[allow(nonstandard_style)] +impl ::core::fmt::Debug for ThisProgram { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::write_str( + f, + match self { + ThisProgram::EntryMetadata => "EntryMetadata", + ThisProgram::EntryMinglingLinter => "EntryMinglingLinter", + ThisProgram::ErrorDispatcherNotFound => "ErrorDispatcherNotFound", + ThisProgram::ErrorRendererNotFound => "ErrorRendererNotFound", + ThisProgram::ErrorSerdeJson => "ErrorSerdeJson", + ThisProgram::Metadata => "Metadata", + ThisProgram::ResultEmpty => "ResultEmpty", + ThisProgram::ResultMetadata => "ResultMetadata", + }, + ) + } +} +#[automatically_derived] +#[allow(nonstandard_style)] +impl ::core::marker::StructuralPartialEq for ThisProgram {} +#[automatically_derived] +#[allow(nonstandard_style)] +impl ::core::cmp::PartialEq for ThisProgram { + #[inline] + fn eq(&self, other: &ThisProgram) -> bool { + let __self_discr = ::core::intrinsics::discriminant_value(self); + let __arg1_discr = ::core::intrinsics::discriminant_value(other); + __self_discr == __arg1_discr + } +} +#[automatically_derived] +#[allow(nonstandard_style)] +impl ::core::cmp::Eq for ThisProgram { + #[doc(hidden)] + #[coverage(off)] + fn assert_fields_are_eq(&self) {} +} +#[automatically_derived] +#[allow(nonstandard_style)] +impl ::core::clone::Clone for ThisProgram { + #[inline] + fn clone(&self) -> ThisProgram { + match self { + ThisProgram::EntryMetadata => ThisProgram::EntryMetadata, + ThisProgram::EntryMinglingLinter => ThisProgram::EntryMinglingLinter, + ThisProgram::ErrorDispatcherNotFound => ThisProgram::ErrorDispatcherNotFound, + ThisProgram::ErrorRendererNotFound => ThisProgram::ErrorRendererNotFound, + ThisProgram::ErrorSerdeJson => ThisProgram::ErrorSerdeJson, + ThisProgram::Metadata => ThisProgram::Metadata, + ThisProgram::ResultEmpty => ThisProgram::ResultEmpty, + ThisProgram::ResultMetadata => ThisProgram::ResultMetadata, + } + } +} +impl ::std::fmt::Display for ThisProgram { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match self { + ThisProgram::EntryMetadata => f.write_fmt(format_args!("EntryMetadata")), + ThisProgram::EntryMinglingLinter => { + f.write_fmt(format_args!("EntryMinglingLinter")) + } + ThisProgram::ErrorDispatcherNotFound => { + f.write_fmt(format_args!("ErrorDispatcherNotFound")) + } + ThisProgram::ErrorRendererNotFound => { + f.write_fmt(format_args!("ErrorRendererNotFound")) + } + ThisProgram::ErrorSerdeJson => f.write_fmt(format_args!("ErrorSerdeJson")), + ThisProgram::Metadata => f.write_fmt(format_args!("Metadata")), + ThisProgram::ResultEmpty => f.write_fmt(format_args!("ResultEmpty")), + ThisProgram::ResultMetadata => f.write_fmt(format_args!("ResultMetadata")), + } + } +} +impl ::mingling::ProgramCollect for ThisProgram { + type Enum = ThisProgram; + type ErrorDispatcherNotFound = ErrorDispatcherNotFound; + type ErrorRendererNotFound = ErrorRendererNotFound; + type ResultEmpty = ResultEmpty; + fn build_renderer_not_found( + member_id: Self::Enum, + ) -> ::mingling::AnyOutput<Self::Enum> { + ::mingling::AnyOutput::new(ErrorRendererNotFound::new(member_id.to_string())) + } + fn build_dispatcher_not_found( + args: Vec<String>, + ) -> ::mingling::AnyOutput<Self::Enum> { + ::mingling::AnyOutput::new(ErrorDispatcherNotFound::new(args)) + } + fn build_empty_result() -> ::mingling::AnyOutput<Self::Enum> { + ::mingling::AnyOutput::new(ResultEmpty) + } + fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { + match any.member_id { + Self::ErrorSerdeJson => { + let value = unsafe { + any.downcast::<crate::errors::serde_json::ErrorSerdeJson>() + .unwrap_unchecked() + }; + <crate::errors::serde_json::__internal_renderer_render_error_serde_json as ::mingling::Renderer>::render( + value, + ) + } + Self::Metadata => { + let value = unsafe { + any.downcast::<cargo_metadata::Metadata>().unwrap_unchecked() + }; + <crate::metadata::__internal_renderer_render_metadata as ::mingling::Renderer>::render( + value, + ) + } + _ => ::mingling::RenderResult::default(), + } + } + fn do_chain( + any: ::mingling::AnyOutput<Self::Enum>, + ) -> ::mingling::ChainProcess<Self::Enum> { + match any.member_id { + Self::EntryMetadata => { + let value = unsafe { + any.downcast::<crate::metadata::cmd_metadata::EntryMetadata>() + .unwrap_unchecked() + }; + <crate::metadata::cmd_metadata::__internal_chain_handle_metadata as ::mingling::Chain< + Self::Enum, + >>::proc(value) + } + Self::EntryMinglingLinter => { + let value = unsafe { + any.downcast::<crate::linter::cmd_mlint::EntryMinglingLinter>() + .unwrap_unchecked() + }; + <crate::linter::cmd_mlint::__internal_chain_handle_mlint as ::mingling::Chain< + Self::Enum, + >>::proc(value) + } + _ => { + ::core::panicking::panic_fmt( + format_args!("No chain found for type id: {0:?}", any.type_id), + ); + } + } + } + fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { + #[allow(unused_imports)] + use crate::metadata::cmd_metadata::__internal_chain_handle_metadata; + use crate::metadata::cmd_metadata::ResultMetadata; + use crate::linter::cmd_mlint::__internal_chain_handle_mlint; + use crate::metadata::__internal_renderer_render_metadata; + use crate::metadata::cmd_metadata::CMDMetadata; + use crate::metadata::setup::ResMetadata; + use crate::metadata::cmd_metadata::EntryMetadata; + use crate::linter::cmd_mlint::CMDMinglingLinter; + use crate::errors::serde_json::ErrorSerdeJson; + use cargo_metadata::Metadata; + use crate::errors::serde_json::__internal_renderer_render_error_serde_json; + use crate::linter::cmd_mlint::EntryMinglingLinter; + match any.member_id { + _ => ::mingling::RenderResult::default(), + } + } + fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool { + match any.member_id { + Self::ErrorSerdeJson => true, + Self::Metadata => true, + _ => false, + } + } + fn has_chain(any: &::mingling::AnyOutput<Self::Enum>) -> bool { + match any.member_id { + Self::EntryMetadata => true, + Self::EntryMinglingLinter => true, + _ => false, + } + } + fn structural_render( + any: ::mingling::AnyOutput<Self::Enum>, + setting: &::mingling::StructuralRendererSetting, + ) -> Result< + ::mingling::RenderResult, + ::mingling::error::StructuralRendererSerializeError, + > { + #[allow(unused_imports)] + use crate::metadata::cmd_metadata::__internal_chain_handle_metadata; + use crate::metadata::cmd_metadata::ResultMetadata; + use crate::linter::cmd_mlint::__internal_chain_handle_mlint; + use crate::metadata::__internal_renderer_render_metadata; + use crate::metadata::cmd_metadata::CMDMetadata; + use crate::metadata::setup::ResMetadata; + use crate::metadata::cmd_metadata::EntryMetadata; + use crate::linter::cmd_mlint::CMDMinglingLinter; + use crate::errors::serde_json::ErrorSerdeJson; + use cargo_metadata::Metadata; + use crate::errors::serde_json::__internal_renderer_render_error_serde_json; + use crate::linter::cmd_mlint::EntryMinglingLinter; + match any.member_id { + Self::Metadata => { + let raw = unsafe { any.restore::<Metadata>().unwrap_unchecked() }; + let mut __renderer_inner_result = ::mingling::RenderResult::default(); + ::mingling::StructuralRenderer::render( + &raw, + setting, + &mut __renderer_inner_result, + )?; + Ok(__renderer_inner_result) + } + _ => { + let mut r = ::mingling::RenderResult::default(); + ::mingling::StructuralRenderer::render(&ResultEmpty, setting, &mut r)?; + Ok(r) + } + } + } +} +impl ThisProgram { + /// Creates a new `Program<#name>` instance with default configuration. + pub fn new() -> ::mingling::Program<ThisProgram> { + ::mingling::Program::new() + } + /// Returns a static reference to the global `Program<#name>` singleton. + pub fn this() -> &'static ::mingling::Program<ThisProgram> { + &::mingling::this::<ThisProgram>() + } +} diff --git a/mingling_cli/pre/lint_registry.rs b/mingling_cli/pre/lint_registry.rs new file mode 100644 index 0000000..09d67f9 --- /dev/null +++ b/mingling_cli/pre/lint_registry.rs @@ -0,0 +1,192 @@ +use std::{fs, io::Error}; + +use just_template::{Template, tmpl}; + +/// Generate lint module registry file (src/lints/mod.rs) +/// +/// Read all Rust source files in the src/lints/ directory (excluding mod.rs itself), +/// automatically generate module declarations and pub use export statements, and write them to mod.rs. +pub fn gen_mod_file() -> Result<(), Error> { + let root = std::env::current_dir()?; + let lints_dir = root.join("src").join("lints"); + let tmpl_file = root.join("tmpls").join("lints.tmpl"); + let mod_file = root.join("src").join("lints.rs"); + + let mut template = Template::from(fs::read_to_string(tmpl_file).unwrap()); + + // Collect all .rs file names (without extension), excluding mod + let mut entries: Vec<String> = fs::read_dir(&lints_dir)? + .filter_map(|e| e.ok()) + .filter(|e| e.path().is_file()) + .filter_map(|e| { + e.path() + .file_stem() + .and_then(|s| s.to_str()) + .map(|s| s.to_string()) + }) + .filter(|name| name != "mod") + .collect(); + + entries.sort(); + + // Generate module declarations and re-export statements + for name in &entries { + tmpl!(template, impls { + mod_name = name + }) + } + + // Generate run_all_lints call arms + for name in &entries { + tmpl!(template, calls { + name = name + }) + } + + fs::write(&mod_file, template.to_string())?; + + Ok(()) +} + +/// Generate lint metadata registry +/// +/// Parses each `.rs` file in `src/lints/` (excluding mod.rs and _init.rs), +/// extracts doc-comment metadata, and writes the result as JSON. +pub fn gen_lint_registry() -> Result<(), Error> { + let root = std::env::current_dir()?; + let lints_dir = root.join("src").join("lints"); + + let mut lints: Vec<serde_json::Value> = Vec::new(); + + for entry in fs::read_dir(&lints_dir)? { + let entry = entry?; + let path = entry.path(); + if !path.is_file() || path.extension().and_then(|e| e.to_str()) != Some("rs") { + continue; + } + let stem = match path.file_stem().and_then(|s| s.to_str()) { + Some(s) => s.to_string(), + None => continue, + }; + if stem == "mod" { + continue; + } + + let content = fs::read_to_string(&path)?; + if let Some(meta) = parse_lint_file(&content, &stem) { + lints.push(meta); + } + } + + lints.sort_by(|a, b| { + a["name"] + .as_str() + .unwrap_or("") + .cmp(b["name"].as_str().unwrap_or("")) + }); + + let json = serde_json::json!({ "lints": lints }); + let out_path = root.join("registry.json"); + fs::write(&out_path, serde_json::to_string_pretty(&json).unwrap())?; + Ok(()) +} + +/// Parse a single lint `.rs` file and return its metadata as JSON. +fn parse_lint_file(content: &str, name: &str) -> Option<serde_json::Value> { + // Collect leading doc comment lines (//!) + let mut doc_lines: Vec<String> = Vec::new(); + for line in content.lines() { + let trimmed = line.trim(); + if let Some(rest) = trimmed.strip_prefix("//!") { + doc_lines.push(rest.trim().to_string()); + } else if !doc_lines.is_empty() { + break; + } + } + + // Title: first non-empty doc line + let title = doc_lines + .iter() + .find(|l| !l.is_empty()) + .cloned() + .unwrap_or_default(); + + // Summary: between ## Summary and ## Metadata + let mut summary = String::new(); + let mut in_summary = false; + for line in &doc_lines { + if line.contains("## Summary") { + in_summary = true; + continue; + } + if line.contains("## Metadata") { + in_summary = false; + } + if in_summary && !line.starts_with("## ") { + if !summary.is_empty() { + summary.push('\n'); + } + summary.push_str(line.trim()); + } + } + + // Metadata section + let mut author = String::new(); + let mut default_level = String::from("warn"); + let mut active_on = String::from("File"); + let mut in_metadata = false; + + for line in &doc_lines { + if line.contains("## Metadata") { + in_metadata = true; + continue; + } + if in_metadata { + if line.starts_with("## ") { + break; + } + if let Some(val) = line + .strip_prefix("Author:") + .or_else(|| line.strip_prefix("Author:")) + { + author = val.trim().trim_matches('`').to_string(); + } + if let Some(val) = line + .strip_prefix("Default:") + .or_else(|| line.strip_prefix("Default:")) + { + let raw = val.trim().trim_matches('`'); + if raw == "warn" || raw == "allow" || raw == "deny" { + default_level = raw.to_string(); + } + } + } + } + + // Extract active_on from function signature (last occurrence = actual fn) + if let Some(pos) = content.rfind("pub fn linter(") { + let after = &content[pos..]; + if let Some(colon) = after.find(':') { + let type_part = &after[colon + 1..].trim(); + let raw = type_part + .trim_start_matches("syn::") + .split([' ', ')', ',', '\n']) + .next() + .unwrap_or("File"); + // Strip "Item" prefix: ItemFn → Fn, ItemStruct → Struct, etc. + active_on = raw.strip_prefix("Item").unwrap_or(raw).to_string(); + } + } + + let mut meta = serde_json::Map::new(); + meta.insert("author".into(), serde_json::Value::String(author)); + meta.insert("default".into(), serde_json::Value::String(default_level)); + meta.insert("active_on".into(), serde_json::Value::String(active_on)); + + Some(serde_json::json!({ + "name": name, + "title": title, + "summary": summary.trim(), + "metadata": meta, + })) +} diff --git a/mingling_cli/pre/mod.rs b/mingling_cli/pre/mod.rs new file mode 100644 index 0000000..8eacef4 --- /dev/null +++ b/mingling_cli/pre/mod.rs @@ -0,0 +1,2 @@ +mod lint_registry; +pub use lint_registry::*; diff --git a/mingling_cli/src/diagnostic.rs b/mingling_cli/src/diagnostic.rs new file mode 100644 index 0000000..527e879 --- /dev/null +++ b/mingling_cli/src/diagnostic.rs @@ -0,0 +1,99 @@ +use annotate_snippets::level::{ERROR, HELP, NOTE, WARNING}; +use annotate_snippets::{AnnotationKind, Group, Renderer, Snippet}; +use cargo_metadata::diagnostic::Diagnostic; +use mingling::macros::{buffer, group, r_println, renderer}; + +group!(Diagnostic); + +#[renderer(buffer)] +pub fn render_diagnostic(diagnostic: Diagnostic) { + let report = diagnostic_to_report(&diagnostic); + let renderer = Renderer::styled(); + let rendered = renderer.render(&report); + r_println!("{rendered}"); +} + +fn cargo_level_to_annotate( + level: cargo_metadata::diagnostic::DiagnosticLevel, +) -> &'static annotate_snippets::Level<'static> { + use cargo_metadata::diagnostic::DiagnosticLevel; + match level { + DiagnosticLevel::Ice | DiagnosticLevel::Error => &ERROR, + DiagnosticLevel::Warning => &WARNING, + DiagnosticLevel::Note | DiagnosticLevel::FailureNote => &NOTE, + DiagnosticLevel::Help => &HELP, + _ => &ERROR, + } +} + +/// 把 1-based char offset 转成 0-based byte offset +fn char_offset_to_byte_offset(s: &str, char_offset: usize) -> usize { + s.char_indices() + .nth(char_offset.saturating_sub(1)) + .map(|(i, _)| i) + .unwrap_or(s.len()) +} + +fn diagnostic_to_report<'a>(d: &'a Diagnostic) -> Vec<Group<'a>> { + let level = cargo_level_to_annotate(d.level); + let mut title = level.clone().primary_title(d.message.as_str()); + if let Some(ref code) = d.code { + title = title.id(code.code.as_str()); + } + + let mut group = Group::with_title(title); + + for span in &d.spans { + if !span.is_primary { + continue; + } + + let source: String = span + .text + .iter() + .map(|l| l.text.as_str()) + .collect::<Vec<_>>() + .join("\n"); + + let byte_range = if span.text.len() == 1 { + let line = &span.text[0]; + let start = char_offset_to_byte_offset(&line.text, line.highlight_start); + let end = char_offset_to_byte_offset(&line.text, line.highlight_end); + start..end + } else { + let first = &span.text[0]; + let last = span.text.last().unwrap(); + let start = char_offset_to_byte_offset(&first.text, first.highlight_start); + let prefix_len: usize = span.text[..span.text.len() - 1] + .iter() + .map(|l| l.text.len() + 1) + .sum(); + let end = prefix_len + char_offset_to_byte_offset(&last.text, last.highlight_end); + start..end + }; + + let mut snippet = Snippet::source(source) + .line_start(span.line_start) + .path(span.file_name.as_str()); + + if let Some(ref label) = span.label { + let annotation = AnnotationKind::Primary + .span(byte_range) + .label(label.as_str()); + snippet = snippet.annotation(annotation); + } else { + let annotation = AnnotationKind::Primary.span(byte_range); + snippet = snippet.annotation(annotation); + } + + group = group.element(snippet); + } + + for child in &d.children { + let msg_level = cargo_level_to_annotate(child.level); + let msg = msg_level.clone().message(child.message.as_str()); + group = group.element(msg); + } + + vec![group] +} diff --git a/mingling_cli/src/errors.rs b/mingling_cli/src/errors.rs new file mode 100644 index 0000000..cef9616 --- /dev/null +++ b/mingling_cli/src/errors.rs @@ -0,0 +1 @@ +pub mod serde_json; diff --git a/mingling_cli/src/errors/serde_json.rs b/mingling_cli/src/errors/serde_json.rs new file mode 100644 index 0000000..c73329c --- /dev/null +++ b/mingling_cli/src/errors/serde_json.rs @@ -0,0 +1,8 @@ +use mingling::macros::{buffer, group, r_println, renderer}; + +group!(ErrorSerdeJson = serde_json::Error); + +#[renderer(buffer)] +pub fn render_error_serde_json(_err: ErrorSerdeJson) { + r_println!("serde"); +} diff --git a/mingling_cli/src/linter.rs b/mingling_cli/src/linter.rs index 870efeb..b3b2e7b 100644 --- a/mingling_cli/src/linter.rs +++ b/mingling_cli/src/linter.rs @@ -3,6 +3,8 @@ use mingling::{Program, macros::program_setup}; use crate::linter::cmd_mlint::CMDMinglingLinter; pub mod cmd_mlint; +pub mod mlint_attr; +pub mod mlint_report; #[program_setup] pub fn mingling_linter_setup(program: &mut Program<crate::ThisProgram>) { diff --git a/mingling_cli/src/linter/cmd_mlint.rs b/mingling_cli/src/linter/cmd_mlint.rs index 1431c43..90871af 100644 --- a/mingling_cli/src/linter/cmd_mlint.rs +++ b/mingling_cli/src/linter/cmd_mlint.rs @@ -1,6 +1,119 @@ -use mingling::macros::{chain, dispatcher}; +use crate::linter::mlint_report::{MlintReport, StateLintReports}; +use cargo_metadata::Metadata; +use mingling::LazyRes; +use mingling::consts::REMAINS; +use mingling::macros::{arg, chain, dispatcher, pack}; +use mingling::picker::EntryPicker; -dispatcher!("mlint", CMDMinglingLinter => EntryMinglingLinter); +dispatcher!("lint", CMDMinglingLinter => EntryMinglingLinter); + +/// Main linting function that processes all packages in the metadata. +/// +/// Iterates through all packages and their targets (e.g., binaries, libraries, tests), +/// reads Rust source files (`.rs`), parses them into ASTs, runs lint checks, +/// and enriches each report with metadata information. +async fn linter_main(metadata: &Metadata) -> Vec<MlintReport> { + // Vector to accumulate all lint reports + let mut all_reports = Vec::new(); + + // Iterate over all packages in the metadata + for package in &metadata.packages { + // Iterate over all targets within a package (e.g., bin, lib, test, etc.) + for target in &package.targets { + let path = &target.src_path; + // Only process Rust source files (with `.rs` extension) + if !path.as_str().ends_with(".rs") { + continue; + } + // Read the source file content + let source = match std::fs::read_to_string(path.as_str()) { + Ok(s) => s, + Err(_) => continue, + }; + // Parse the source file into an AST (Abstract Syntax Tree) + let ast = match syn::parse_file(&source) { + Ok(f) => f, + Err(_) => continue, + }; + + // Run all lint checks and collect reports + let reports = crate::lints::run_all_lints(&ast, &source); + // + for mut r in reports { + // Enrich report with metadata information + r.file_name = path.as_str().to_string(); + r.source_code = source.clone(); + r.package_id = Some(package.id.to_string()); + r.target_name = Some(target.name.clone()); + r.target_kind = target.kind.first().map(|k| k.to_string()); + r.target_src_path = Some(path.as_str().to_string()); + all_reports.push(r); + } + } + } + + all_reports +} + +pack!(StateBeginLinter = ()); + +#[chain] +pub fn handle_lint(args: EntryMinglingLinter) -> StateBeginLinter { + let (with_checker, checker_args) = args + .pick_or(&arg![with_checker: Option<String>], || { + Some("cargo,check".to_string()) + }) + .pick(&REMAINS) + .unwrap(); + + // If with_checker is not set, proceed directly to the mingling lint phase + let Some(with_checker) = with_checker else { + return StateBeginLinter::new(()); + }; + + let with_checker: Vec<&str> = with_checker.split(',').collect(); + let checker_args: Vec<String> = checker_args.into(); + + // Run the outer checker (e.g. cargo check) with output passed through directly + execute_checker(&with_checker, checker_args.as_slice()); + + StateBeginLinter::new(()) +} + +/// Run the outer checker (e.g. cargo check) with output passed through directly. +fn execute_checker(with_checker: &[&str], checker_args: &[String]) { + if with_checker.is_empty() { + return; + } + + let checker_str = with_checker.join(" "); + let args_str = checker_args.join(" "); + let full_cmd = if args_str.is_empty() { + checker_str + } else { + format!("{} {}", checker_str, args_str) + }; + + let mut cmd = if cfg!(target_os = "windows") { + let mut c = std::process::Command::new("cmd"); + c.args(["/C", &full_cmd]); + c + } else { + let mut c = std::process::Command::new("sh"); + c.args(["-c", &full_cmd]); + c + }; + + // Pass through stdin/stdout/stderr so the user sees everything + let _ = cmd.status(); +} #[chain] -pub fn handle_mlint(_args: EntryMinglingLinter) {} +pub async fn handle_state_begin_linter( + _: StateBeginLinter, + metadata: &mut LazyRes<crate::metadata::setup::ResMetadata>, +) -> StateLintReports { + let metadata = metadata.get_ref().data(); + let reports = linter_main(metadata).await; + StateLintReports::new(reports) +} diff --git a/mingling_cli/src/linter/mlint_attr.rs b/mingling_cli/src/linter/mlint_attr.rs new file mode 100644 index 0000000..24528d4 --- /dev/null +++ b/mingling_cli/src/linter/mlint_attr.rs @@ -0,0 +1,39 @@ +/// Result of checking `mlint(allow/warn/deny(...))` attributes. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum MlintLevelOverride { + Allow, + Warn, + Deny, +} + +/// Parse `mlint(allow/warn/deny(lint_name))` from attributes. +/// Returns `None` if the lint is not mentioned in any mlint attribute. +pub fn get_mlint_override(attrs: &[syn::Attribute], lint_name: &str) -> Option<MlintLevelOverride> { + for attr in attrs { + if !attr.path().is_ident("mlint") { + continue; + } + let list = match attr.meta.require_list() { + Ok(l) => l, + Err(_) => continue, + }; + // TokenStream::to_string() adds spaces between tokens, + // e.g. `allow(xxx)` → `allow ( xxx )`. Remove all spaces to compare. + let flat: String = list + .tokens + .to_string() + .chars() + .filter(|c| !c.is_whitespace()) + .collect(); + for (keyword, variant) in [ + ("allow", MlintLevelOverride::Allow), + ("warn", MlintLevelOverride::Warn), + ("deny", MlintLevelOverride::Deny), + ] { + if flat.contains(&format!("{keyword}({lint_name})")) { + return Some(variant); + } + } + } + None +} diff --git a/mingling_cli/src/linter/mlint_report.rs b/mingling_cli/src/linter/mlint_report.rs new file mode 100644 index 0000000..3c52828 --- /dev/null +++ b/mingling_cli/src/linter/mlint_report.rs @@ -0,0 +1,413 @@ +use cargo_metadata::diagnostic::{ + DiagnosticCodeBuilder, DiagnosticLevel as CargoLevel, DiagnosticSpanBuilder, + DiagnosticSpanLineBuilder, +}; + +use cargo_metadata::{Message, PackageId}; + +use annotate_snippets::level::{ERROR, HELP, NOTE, WARNING}; +use annotate_snippets::{AnnotationKind, Group, Renderer, Snippet}; +use mingling::macros::{buffer, chain, pack, r_append, r_eprintln, renderer}; +use mingling::{AnyOutput, ProgramCollect, Routable}; + +use crate::Next; +use crate::metadata::setup::ResUsingJson; + +/// Complete structure of a Lint report, containing inspection results and associated metadata. +pub struct MlintReport { + /// Source file name + pub file_name: String, + + /// Full source text of the file, used to extract line content and compute byte offsets + pub source_code: String, + + /// Severity level of the report + pub level: MlintLevel, + + /// Name of the Lint + pub lint_code: String, + + /// Content of the report + pub message: String, + + /// Source code locations + pub spans: Vec<LintSpan>, + + /// Attached sub-reports for this report + pub attached_reports: Vec<MlintReport>, + + /// Package ID that this report belongs to + pub package_id: Option<String>, + + /// Compilation target name that this report belongs to + pub target_name: Option<String>, + + /// Compilation target type that this report belongs to + pub target_kind: Option<String>, + + /// Compilation target source file path that this report belongs to + pub target_src_path: Option<String>, +} + +/// Report severity level, indicating the seriousness of the Lint result. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum MlintLevel { + Error, + Warning, + Note, + Help, +} + +/// Source code location span, representing a range of source code and its associated text information. +pub struct LintSpan { + /// Starting line number (1-based) + pub line_start: usize, + /// Ending line number (1-based) + pub line_end: usize, + /// Starting column (1-based char offset) + pub column_start: usize, + /// Ending column (1-based char offset) + pub column_end: usize, + /// Source lines at this location + pub text: Vec<LintSpanLine>, + /// Optional label description + pub label: Option<String>, +} + +/// A single line of text in a source location with highlight range. +pub struct LintSpanLine { + /// Full source line text (no trailing `\n`) + pub text: String, + /// Highlight start (1-based char offset) + pub highlight_start: usize, + /// Highlight end (1-based char offset) + pub highlight_end: usize, +} + +impl MlintReport { + /// Build a `LintSpan` from a syn spanned item and the source text. + pub fn span_from_syn<T: syn::spanned::Spanned>(value: &T, source: &str) -> LintSpan { + let span = value.span(); + let start = span.start(); + let end = span.end(); + + // Extract line content from source + let lines: Vec<&str> = source.lines().collect(); + let text = if start.line == end.line && start.line <= lines.len() { + let line_text = lines[start.line.saturating_sub(1)]; + let hl_start = proc_macro2_byte_col_to_char_1based(line_text, start.column); + let hl_end = proc_macro2_byte_col_to_char_1based(line_text, end.column); + vec![LintSpanLine { + text: line_text.to_string(), + highlight_start: hl_start, + highlight_end: hl_end, + }] + } else { + // Multi-line: generate line by line + (start.line..=end.line.min(lines.len())) + .map(|i| { + let line_text = lines[i.saturating_sub(1)]; + let (hl_start, hl_end) = if i == start.line { + ( + proc_macro2_byte_col_to_char_1based(line_text, start.column), + line_text.chars().count(), + ) + } else if i == end.line { + ( + 1, + proc_macro2_byte_col_to_char_1based(line_text, end.column), + ) + } else { + (1, line_text.chars().count()) + }; + LintSpanLine { + text: line_text.to_string(), + highlight_start: hl_start, + highlight_end: hl_end, + } + }) + .collect::<Vec<_>>() + }; + + LintSpan { + line_start: start.line, + line_end: end.line, + column_start: proc_macro2_byte_col_to_char_1based( + lines.get(start.line.saturating_sub(1)).unwrap_or(&""), + start.column, + ), + column_end: proc_macro2_byte_col_to_char_1based( + lines.get(end.line.saturating_sub(1)).unwrap_or(&""), + end.column, + ), + text, + label: None, + } + } + + /// Compute byte offset from (line, column) within source. + /// line: 1-based, column: 1-based char offset. + pub fn line_col_to_byte_offset(&self, line: usize, col: usize) -> usize { + let mut byte_pos = 0usize; + for (i, line_str) in self.source_code.lines().enumerate() { + if i + 1 == line { + return byte_pos + char_1based_to_byte_offset(line_str, col); + } + byte_pos += line_str.len() + 1; // +1 for \n + } + self.source_code.len() + } +} + +/// proc-macro2's LineColumn.column is **0-based byte offset**. +/// Convert to 1-based char offset. +fn proc_macro2_byte_col_to_char_1based(line: &str, byte_col: usize) -> usize { + line.char_indices() + .position(|(i, _)| i >= byte_col) + .map(|pos| pos + 1) // → 1-based + .unwrap_or(line.chars().count().max(1)) +} + +/// 1-based char offset → byte offset within a string +fn char_1based_to_byte_offset(s: &str, char_1based: usize) -> usize { + s.char_indices() + .nth(char_1based.saturating_sub(1)) + .map(|(i, _)| i) + .unwrap_or(s.len()) +} + +impl MlintReport { + pub fn to_annotate_snippet_render(&self) -> String { + let level = mlinit_level_to_annotate(&self.level); + let title = level.clone().primary_title(&self.message); + // code 不放在 title 里,改放在 note 中 + let mut group = Group::with_title(title); + + for span in &self.spans { + let source = span + .text + .iter() + .map(|l| l.text.as_str()) + .collect::<Vec<_>>() + .join("\n"); + + let byte_range = if span.text.len() == 1 { + let line = &span.text[0]; + let start = char_1based_to_byte_offset(&line.text, line.highlight_start); + let end = char_1based_to_byte_offset(&line.text, line.highlight_end); + start..end + } else { + let first = &span.text[0]; + let last = span.text.last().unwrap(); + let start = char_1based_to_byte_offset(&first.text, first.highlight_start); + let prefix_len: usize = span.text[..span.text.len() - 1] + .iter() + .map(|l| l.text.len() + 1) + .sum(); + let end = prefix_len + char_1based_to_byte_offset(&last.text, last.highlight_end); + start..end + }; + + let mut snippet = Snippet::source(source) + .line_start(span.line_start) + .path(&self.file_name); + + let annotation = match &span.label { + Some(label) => AnnotationKind::Primary + .span(byte_range) + .label(label.as_str()), + None => AnnotationKind::Primary.span(byte_range), + }; + snippet = snippet.annotation(annotation); + group = group.element(snippet); + } + + for child in &self.attached_reports { + let child_level = mlinit_level_to_annotate(&child.level); + let msg = child_level.clone().message(&child.message); + group = group.element(msg); + } + + // 添加 note: `#[mlint(level([code]))]` on by default + if !self.lint_code.is_empty() { + let level_name = match self.level { + MlintLevel::Error => "deny", + MlintLevel::Warning => "warn", + MlintLevel::Note | MlintLevel::Help => "allow", + }; + let note_text = format!( + "`#[mlint({level_name}([{}]))]` on by default", + self.lint_code + ); + let note_msg = NOTE.clone().message(note_text); + group = group.element(note_msg); + } + + let renderer = Renderer::styled(); + renderer.render(&[group]) + } +} + +fn mlinit_level_to_annotate(level: &MlintLevel) -> &'static annotate_snippets::Level<'static> { + match level { + MlintLevel::Error => &ERROR, + MlintLevel::Warning => &WARNING, + MlintLevel::Note => &NOTE, + MlintLevel::Help => &HELP, + } +} + +impl MlintReport { + pub fn to_compiler_message(&self) -> Message { + use cargo_metadata::{CompilerMessageBuilder, Edition, TargetBuilder}; + + let target_kind_str = self.target_kind.as_deref().unwrap_or("bin"); + let target_kind_parsed: cargo_metadata::TargetKind = target_kind_str.into(); + let crate_kind: cargo_metadata::CrateType = target_kind_str.into(); + + let target = TargetBuilder::default() + .name(self.target_name.as_deref().unwrap_or_default()) + .kind(vec![target_kind_parsed]) + .crate_types(vec![crate_kind]) + .required_features(Vec::<String>::new()) + .src_path(self.target_src_path.as_deref().unwrap_or_default()) + .edition(Edition::E2021) + .doctest(false) + .test(false) + .doc(false) + .build() + .unwrap(); + + let diagnostic = self.build_diagnostic( + &self.message, + &self.lint_code, + &self.level, + &self.spans, + &self.attached_reports, + ); + + Message::CompilerMessage( + CompilerMessageBuilder::default() + .package_id(PackageId { + repr: self.package_id.clone().unwrap_or_else(|| "unknown".into()), + }) + .target(target) + .message(diagnostic) + .build() + .unwrap(), + ) + } + + fn build_diagnostic( + &self, + message: &str, + code: &str, + level: &MlintLevel, + spans: &[LintSpan], + children: &[MlintReport], + ) -> cargo_metadata::diagnostic::Diagnostic { + cargo_metadata::diagnostic::DiagnosticBuilder::default() + .message(message) + .code( + DiagnosticCodeBuilder::default() + .code(code) + .explanation(None) + .build() + .unwrap(), + ) + .level(match level { + MlintLevel::Error => CargoLevel::Error, + MlintLevel::Warning => CargoLevel::Warning, + MlintLevel::Note => CargoLevel::Note, + MlintLevel::Help => CargoLevel::Help, + }) + .spans( + spans + .iter() + .map(|s| self.lint_span_to_diagnostic_span(s)) + .collect::<Vec<_>>(), + ) + .children( + children + .iter() + .map(|c| { + self.build_diagnostic( + &c.message, + &c.lint_code, + &c.level, + &c.spans, + &c.attached_reports, + ) + }) + .collect::<Vec<_>>(), + ) + .rendered(None) + .build() + .unwrap() + } + + fn lint_span_to_diagnostic_span( + &self, + span: &LintSpan, + ) -> cargo_metadata::diagnostic::DiagnosticSpan { + let byte_start = self.line_col_to_byte_offset(span.line_start, span.column_start); + let byte_end = self.line_col_to_byte_offset(span.line_end, span.column_end); + + DiagnosticSpanBuilder::default() + .file_name(&self.file_name) + .byte_start(byte_start as u32) + .byte_end(byte_end as u32) + .line_start(span.line_start) + .line_end(span.line_end) + .column_start(span.column_start) + .column_end(span.column_end) + .is_primary(true) + .text( + span.text + .iter() + .map(|l| { + DiagnosticSpanLineBuilder::default() + .text(&l.text) + .highlight_start(l.highlight_start) + .highlight_end(l.highlight_end) + .build() + .unwrap() + }) + .collect::<Vec<_>>(), + ) + .label(span.label.clone()) + .suggested_replacement(None::<String>) + .suggestion_applicability(None::<cargo_metadata::diagnostic::Applicability>) + .expansion(None) + .build() + .unwrap() + } +} + +pack!(StateLintReports = Vec<MlintReport>); +pack!(ResultLintReportsAnnotateSnippet = Vec<MlintReport>); +pack!(ResultLintReportsJson = Vec<MlintReport>); + +#[chain] +pub fn handle_state_lint_reports(reports: StateLintReports, using_json: &ResUsingJson) -> Next { + if using_json.using { + ResultLintReportsJson::new(reports.inner).to_render() + } else { + ResultLintReportsAnnotateSnippet::new(reports.inner).to_render() + } +} + +#[renderer(buffer)] +pub fn render_lint_reports(reports: ResultLintReportsAnnotateSnippet) { + for report in reports.inner { + r_eprintln!("{}", report.to_annotate_snippet_render()); + } +} + +#[renderer(buffer)] +pub fn render_lint_reports_json(reports: ResultLintReportsJson) { + for report in reports.inner { + // DIRTY: Dispatch to the Message renderer using AnyOutput to obtain the render result and append it to the Buffer + r_append!(|| { crate::ThisProgram::render(AnyOutput::new(report.to_compiler_message())) }); + } +} diff --git a/mingling_cli/src/lints.rs b/mingling_cli/src/lints.rs new file mode 100644 index 0000000..5bed483 --- /dev/null +++ b/mingling_cli/src/lints.rs @@ -0,0 +1,61 @@ +#![allow(unused)] +use crate::linter::mlint_report::{MlintLevel, MlintReport}; + +mod template_linter; +pub use template_linter::linter as template_linter; +mod unnecessary_render_result_creation; +pub use unnecessary_render_result_creation::linter as unnecessary_render_result_creation; + +/// Run all registered lints on a parsed file with its source text. +pub fn run_all_lints(file: &syn::File, source: &str) -> Vec<MlintReport> { + use crate::linter::mlint_attr::{get_mlint_override, MlintLevelOverride}; + let mut reports = vec![]; + for item in &file.items { + if let syn::Item::Fn(f) = item { + // Check item-level #[mlint(allow/warn/deny(template_linter))] + let skip = get_mlint_override(&f.attrs, "template_linter") == Some(MlintLevelOverride::Allow); + if !skip { + let mut rs = template_linter::linter(f.clone(), source); + // Apply deny override at item level + if get_mlint_override(&f.attrs, "template_linter") == Some(MlintLevelOverride::Deny) { + for r in &mut rs { r.level = MlintLevel::Error; } + } + reports.extend(rs); + } + // Check item-level #[mlint(allow/warn/deny(unnecessary_render_result_creation))] + let skip = get_mlint_override(&f.attrs, "unnecessary_render_result_creation") == Some(MlintLevelOverride::Allow); + if !skip { + let mut rs = unnecessary_render_result_creation::linter(f.clone(), source); + // Apply deny override at item level + if get_mlint_override(&f.attrs, "unnecessary_render_result_creation") == Some(MlintLevelOverride::Deny) { + for r in &mut rs { r.level = MlintLevel::Error; } + } + reports.extend(rs); + } + + } + } + // Apply file-level #![mlint(allow/warn/deny(...))] overrides + for r in &mut reports { + let name = &r.lint_code; + if let Some(override_level) = get_mlint_override(&file.attrs, name) { + match override_level { + MlintLevelOverride::Allow => { + // remove by marking; filtered below + r.level = MlintLevel::Help; // sentinel: filtered out + } + MlintLevelOverride::Deny => { + r.level = MlintLevel::Error; + } + MlintLevelOverride::Warn => { + if r.level != MlintLevel::Error { + r.level = MlintLevel::Warning; + } + } + } + } + } + // Remove allowed reports (sentinel = Help) + reports.retain(|r| r.level != MlintLevel::Help); + reports +}
\ No newline at end of file diff --git a/mingling_cli/src/lints/template_linter.rs b/mingling_cli/src/lints/template_linter.rs new file mode 100644 index 0000000..4ea2f94 --- /dev/null +++ b/mingling_cli/src/lints/template_linter.rs @@ -0,0 +1,48 @@ +//! Template Linter +//! +//! ## Summary +//! +//! This is a template Linter that introduces how to add a Lint for Mingling. +//! You can write an introduction for this Linter in the Summary section, for example: +//! +//! - Trigger conditions +//! - Why is it necessary? +//! +//! ## Metadata +//! +//! > This section is the **Metadata** section, which needs to be filled in correctly. +//! > These contents will eventually be compiled as the Linter's behavior. +//! +//! Author: `Weicao-CatilGrass` +//! Default: `allow` +// ^^^^^ Supported parameters: `warn`, `allow`, `deny` + +// --- ABOUT AUTO IDENTIFICATION RULES --- +// +// The compiler will treat code with the following structure as a Linter entry point: +// | +// --> your_linter_module.rs +// | +// | pub fn linter(ast: syn::ItemX) -> Vec<MlintReport> { +// | /* ... */ ^^^^^^^^^^ Your linter scope +// | } +// | +// = note: Please ensure your function is `pub`, named `linter`, and returns `Vec<MlintReport>` +// +// --- ABOUT AUTO IDENTIFICATION RULES --- + +use crate::linter::mlint_report::MlintReport; + +pub fn linter(_ast: syn::ItemFn, _source: &str) -> Vec<MlintReport> { + // ^^^^^^^^^^^ Supported parameters: + // | syn::File + // | syn::ItemImpl + // | syn::ItemStruct + // | syn::ItemEnum + // | syn::ItemTrait + // | syn::ItemFn + // | syn::ItemMacro + // | syn::ItemMod + // | syn::ItemUnion + vec![] +} diff --git a/mingling_cli/src/lints/unnecessary_render_result_creation.rs b/mingling_cli/src/lints/unnecessary_render_result_creation.rs new file mode 100644 index 0000000..fa6809b --- /dev/null +++ b/mingling_cli/src/lints/unnecessary_render_result_creation.rs @@ -0,0 +1,157 @@ +//! Unnecessary Manual RenderResult Creation +//! +//! ## Summary +//! +//! Detects `#[renderer]` functions that manually create a `RenderResult` and +//! manage it via `r_println!(r, ...)` style calls, when they could be simplified +//! to `#[renderer(buffer)]` which handles the buffer automatically. +//! +//! This lint will not trigger if `r` is used outside of `r_println!`, +//! `r_eprintln!`, `r_print`, `r_eprint`, or `r_append`. +//! +//! ## Metadata +//! +//! Author: `Weicao-CatilGrass` +//! Default: `warn` + +use crate::linter::mlint_report::{MlintLevel, MlintReport}; + +pub fn linter(ast: syn::ItemFn, source: &str) -> Vec<MlintReport> { + if !has_renderer_attr(&ast) { + return vec![]; + } + + let stmts = &ast.block.stmts; + if stmts.len() < 2 { + return vec![]; + } + + let (r_ident, let_idx) = match find_render_result_new(stmts) { + Some(pair) => pair, + None => return vec![], + }; + + let r_name = r_ident.to_string(); + let mut only_print_and_return = true; + let mut print_count = 0; + + for stmt in &stmts[let_idx + 1..] { + if !check_stmt_usage(stmt, &r_name, &mut print_count) { + only_print_and_return = false; + break; + } + } + + if only_print_and_return && print_count > 0 { + let span = MlintReport::span_from_syn(&ast.sig, source); + vec![MlintReport { + file_name: String::new(), + source_code: source.to_string(), + level: MlintLevel::Warning, + lint_code: "unnecessary_render_result_creation".into(), + message: format!( + "unnecessary manual `RenderResult` creation in `{}`: use `#[renderer(buffer)]` instead", + ast.sig.ident, + ), + spans: vec![span], + attached_reports: vec![MlintReport { + file_name: String::new(), + source_code: String::new(), + level: MlintLevel::Help, + lint_code: String::new(), + message: "change to `#[renderer(buffer)]` and use `r_println!(...)` without the `r` parameter".into(), + spans: vec![], + attached_reports: vec![], + package_id: None, + target_name: None, + target_kind: None, + target_src_path: None, + }], + package_id: None, + target_name: None, + target_kind: None, + target_src_path: None, + }] + } else { + vec![] + } +} + +fn has_renderer_attr(func: &syn::ItemFn) -> bool { + func.attrs.iter().any(|a| { + if !a.path().is_ident("renderer") { + return false; + } + if let Ok(list) = a.meta.require_list() { + let tokens = list.tokens.to_string(); + if tokens.contains("buffer") { + return false; + } + } + true + }) +} + +fn find_render_result_new(stmts: &[syn::Stmt]) -> Option<(proc_macro2::Ident, usize)> { + for (i, stmt) in stmts.iter().enumerate() { + if let syn::Stmt::Local(local) = stmt + && let Some(init) = &local.init + && let syn::Pat::Ident(pat_id) = &local.pat + && pat_id.mutability.is_some() + && let syn::Expr::Call(call) = &*init.expr + && let syn::Expr::Path(expr_path) = call.func.as_ref() + { + let segs = &expr_path.path.segments; + let matches = match segs.len() { + 2 => segs[0].ident == "RenderResult" && segs[1].ident == "new", + 3 => { + segs[0].ident == "mingling" + && segs[1].ident == "RenderResult" + && segs[2].ident == "new" + } + _ => false, + }; + if matches { + return Some((pat_id.ident.clone(), i)); + } + } + } + None +} + +fn check_stmt_usage(stmt: &syn::Stmt, r_name: &str, print_count: &mut usize) -> bool { + // return r; → allowed + if let syn::Stmt::Expr(expr, _) = stmt + && let syn::Expr::Return(ret) = expr + && let Some(ret_expr) = &ret.expr + && let syn::Expr::Path(p) = ret_expr.as_ref() + && p.path.is_ident(r_name) + { + return true; + } + + // r_println!(r, ...) → allowed + if let syn::Stmt::Macro(stmt_mac) = stmt { + let macro_name = stmt_mac + .mac + .path + .segments + .last() + .map(|s| s.ident.to_string()) + .unwrap_or_default(); + let is_r_macro = matches!( + macro_name.as_str(), + "r_println" | "r_eprintln" | "r_print" | "r_eprint" | "r_append" + ); + if is_r_macro + && let Some(first_arg) = stmt_mac.mac.tokens.clone().into_iter().next() + && first_arg.to_string() == *r_name + { + *print_count += 1; + return true; + } + } + + let s = format!("{stmt:#?}"); + !s.contains(&format!("\"{r_name}\"")) +} diff --git a/mingling_cli/src/main.rs b/mingling_cli/src/main.rs index 59fa92d..c822a12 100644 --- a/mingling_cli/src/main.rs +++ b/mingling_cli/src/main.rs @@ -1,28 +1,29 @@ use crate::{linter::MinglingLinterSetup, metadata::MinglingMetadataSetup}; use mingling::{ macros::gen_program, - setup::{ - ExitCodeSetup, - picker::{HelpFlagSetup, StructuralRendererSetup}, - }, + setup::{ExitCodeSetup, picker::HelpFlagSetup}, }; +pub mod diagnostic; +pub mod errors; pub mod linter; +pub mod lints; +pub mod message; pub mod metadata; -fn main() { +#[tokio::main] +async fn main() { let mut program = ThisProgram::new(); // Setups program.with_setup(HelpFlagSetup::default()); program.with_setup(ExitCodeSetup::default()); - program.with_setup(StructuralRendererSetup); program.with_setup(MinglingMetadataSetup); program.with_setup(MinglingLinterSetup); // Exec - program.exec_and_exit(); + program.exec_and_exit().await; } gen_program!(); diff --git a/mingling_cli/src/message.rs b/mingling_cli/src/message.rs new file mode 100644 index 0000000..3e50c30 --- /dev/null +++ b/mingling_cli/src/message.rs @@ -0,0 +1,10 @@ +use cargo_metadata::Message; +use mingling::macros::{buffer, group, r_println, renderer, renderify}; + +group!(Message); + +#[renderer(buffer, renderify)] +pub fn render_message(msg: Message) { + let r = serde_json::to_string(&msg)?; + r_println!("{}", r); +} diff --git a/mingling_cli/src/metadata.rs b/mingling_cli/src/metadata.rs index 0c0b0bf..2e7aeef 100644 --- a/mingling_cli/src/metadata.rs +++ b/mingling_cli/src/metadata.rs @@ -1,4 +1,8 @@ -use mingling::{Program, macros::program_setup}; +use cargo_metadata::Metadata; +use mingling::{ + Program, + macros::{buffer, group, program_setup, r_println, renderer, renderify}, +}; use crate::metadata::{cmd_metadata::CMDMetadata, setup::CargoMetadataSetup}; @@ -10,3 +14,11 @@ pub fn mingling_metadata_setup(program: &mut Program<crate::ThisProgram>) { program.with_setup(CargoMetadataSetup); program.with_dispatcher(CMDMetadata); } + +group!(Metadata); + +#[renderer(buffer, renderify)] +pub fn render_metadata(metadata: Metadata) { + let result = serde_json::to_string(&metadata)?; + r_println!("{}", result); +} diff --git a/mingling_cli/src/metadata/cmd_metadata.rs b/mingling_cli/src/metadata/cmd_metadata.rs index 867e606..18241a2 100644 --- a/mingling_cli/src/metadata/cmd_metadata.rs +++ b/mingling_cli/src/metadata/cmd_metadata.rs @@ -1,16 +1,17 @@ +use cargo_metadata::Metadata; use mingling::{ LazyRes, - macros::{chain, dispatcher, pack_structural}, + macros::{chain, dispatcher, pack}, }; use crate::metadata::setup::ResMetadata; dispatcher!("metadata"); -pack_structural!(ResultMetadata = ResMetadata); +pack!(ResultMetadata = ResMetadata); #[chain] -pub fn handle_metadata(_: EntryMetadata, metadata: &mut LazyRes<ResMetadata>) -> ResultMetadata { +pub fn handle_metadata(_: EntryMetadata, metadata: &mut LazyRes<ResMetadata>) -> Metadata { let metadata = metadata.get_ref().clone(); - ResultMetadata::new(metadata) + metadata.data().clone() } diff --git a/mingling_cli/src/metadata/setup.rs b/mingling_cli/src/metadata/setup.rs index 4a891b6..198baf1 100644 --- a/mingling_cli/src/metadata/setup.rs +++ b/mingling_cli/src/metadata/setup.rs @@ -3,7 +3,6 @@ use std::{env::current_dir, path::PathBuf}; use cargo_metadata::{CargoOpt, MetadataCommand}; use mingling::{ LazyInit, Program, - consts::REMAINS, macros::program_setup, picker::{IntoPicker, value::Flag}, prelude::*, @@ -16,11 +15,28 @@ pub const CARGO_TOML: &str = "Cargo.toml"; use cargo_metadata::Metadata; use serde::Serialize; +/// Resource holding parsed `cargo metadata` output. +/// +/// This is lazily initialized during program setup by calling `cargo metadata` +/// with the appropriate CLI flags (e.g., `--features`, `--all-features`, +/// `--no-default-features`, `--no-deps`, `--message-format`). +/// +/// Access the inner [`Metadata`] via [`ResMetadata::data()`], which panics if +/// called before initialization (guaranteed not to happen in normal usage). #[derive(Default, Clone, Serialize)] pub struct ResMetadata { data: Option<Metadata>, } +/// Resource indicating whether the output format is JSON. +/// +/// Set to `true` when `--message-format json` (or similar) is passed. +/// Used by renderers to decide whether to serialize structs as JSON. +#[derive(Default, Clone, Serialize)] +pub struct ResUsingJson { + pub using: bool, +} + impl ResMetadata { /// Returns a reference to the parsed `cargo metadata`. /// @@ -38,7 +54,8 @@ impl ResMetadata { #[program_setup] pub fn cargo_metadata_setup(program: &mut Program<crate::ThisProgram>) { - let args = program.take_args(); + let args = program.get_args().to_vec(); + let ( feature_args, manifest_path, @@ -46,7 +63,6 @@ pub fn cargo_metadata_setup(program: &mut Program<crate::ThisProgram>) { enable_all_features, no_default_features, no_deps, - args, ) = args .pick(&arg![features: Vec<String>]) .pick(&arg![manifest_path: Option<String>]) @@ -54,12 +70,12 @@ pub fn cargo_metadata_setup(program: &mut Program<crate::ThisProgram>) { .pick(&arg![all_features: Flag]) .pick(&arg![no_default_features: Flag]) .pick(&arg![no_deps: Flag]) - .pick(&REMAINS) .unwrap(); - program.replace_args(args.into()); - // Bind format setting to StructuralRenderer - program.structural_renderer_name = message_format.into(); + // Is Using Json + program.with_resource(ResUsingJson { + using: message_format.contains("json"), + }); // Current Dir let current_dir = current_dir().unwrap(); diff --git a/mingling_cli/tmpls/lints.tmpl b/mingling_cli/tmpls/lints.tmpl new file mode 100644 index 0000000..7507f98 --- /dev/null +++ b/mingling_cli/tmpls/lints.tmpl @@ -0,0 +1,56 @@ +#![allow(unused)] +use crate::linter::mlint_report::{MlintLevel, MlintReport}; + +>>>>>>>>>> impls; + +/// Run all registered lints on a parsed file with its source text. +pub fn run_all_lints(file: &syn::File, source: &str) -> Vec<MlintReport> { + use crate::linter::mlint_attr::{get_mlint_override, MlintLevelOverride}; + let mut reports = vec![]; + for item in &file.items { + if let syn::Item::Fn(f) = item { +>>>>>>>>>> calls; + +@@@ >>> calls + // Check item-level #[mlint(allow/warn/deny(<<<name>>>))] + let skip = get_mlint_override(&f.attrs, "<<<name>>>") == Some(MlintLevelOverride::Allow); + if !skip { + let mut rs = <<<name>>>::linter(f.clone(), source); + // Apply deny override at item level + if get_mlint_override(&f.attrs, "<<<name>>>") == Some(MlintLevelOverride::Deny) { + for r in &mut rs { r.level = MlintLevel::Error; } + } + reports.extend(rs); + } +@@@ <<< + } + } + // Apply file-level #![mlint(allow/warn/deny(...))] overrides + for r in &mut reports { + let name = &r.lint_code; + if let Some(override_level) = get_mlint_override(&file.attrs, name) { + match override_level { + MlintLevelOverride::Allow => { + // remove by marking; filtered below + r.level = MlintLevel::Help; // sentinel: filtered out + } + MlintLevelOverride::Deny => { + r.level = MlintLevel::Error; + } + MlintLevelOverride::Warn => { + if r.level != MlintLevel::Error { + r.level = MlintLevel::Warning; + } + } + } + } + } + // Remove allowed reports (sentinel = Help) + reports.retain(|r| r.level != MlintLevel::Help); + reports +} + +@@@ >>> impls +mod <<<mod_name>>>; +pub use <<<mod_name>>>::linter as <<<mod_name>>>; +@@@ <<< |
