diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-21 15:59:01 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-21 15:59:01 +0800 |
| commit | 05a06b59d2a27428d253308f067aa0e5a841eb5e (patch) | |
| tree | b1176b831e42a7dff4a8b0bcbb35fc7755164c77 | |
| parent | 770d738829076e0be2152452ab68933a47a483e2 (diff) | |
feat(lints): add quote dependency and improve variable name detection
Use `quote` token stream matching instead of debug formatting
to detect variable references in `unnecessary_render_result_creation`,
fixing false positives from string-based matching.
| -rw-r--r-- | mingling_cli/Cargo.lock | 1 | ||||
| -rw-r--r-- | mingling_cli/Cargo.toml | 1 | ||||
| -rw-r--r-- | mingling_cli/src/lints.rs | 37 | ||||
| -rw-r--r-- | mingling_cli/src/lints/unnecessary_render_result_creation.rs | 16 | ||||
| -rw-r--r-- | mingling_cli/tmpls/lints.tmpl | 35 |
5 files changed, 42 insertions, 48 deletions
diff --git a/mingling_cli/Cargo.lock b/mingling_cli/Cargo.lock index 6d34351..e617d0a 100644 --- a/mingling_cli/Cargo.lock +++ b/mingling_cli/Cargo.lock @@ -254,6 +254,7 @@ dependencies = [ "just_template", "mingling", "proc-macro2", + "quote", "serde", "serde_json", "syn 3.0.2", diff --git a/mingling_cli/Cargo.toml b/mingling_cli/Cargo.toml index ab51d9e..5b36c6b 100644 --- a/mingling_cli/Cargo.toml +++ b/mingling_cli/Cargo.toml @@ -40,6 +40,7 @@ annotate-snippets = "0.12.16" # Code analyze proc-macro2 = { version = "1.0.107", features = ["span-locations"] } syn = { version = "3.0.2", features = ["full", "extra-traits"] } +quote = "1.0.47" # Configure & Serialization serde = { version = "1.0.229", features = ["derive"] } diff --git a/mingling_cli/src/lints.rs b/mingling_cli/src/lints.rs index 8fab924..1db4c6b 100644 --- a/mingling_cli/src/lints.rs +++ b/mingling_cli/src/lints.rs @@ -12,21 +12,17 @@ pub fn run_all_lints(file: &syn::File, source: &str) -> Vec<MlintReport> { 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; } } @@ -35,45 +31,38 @@ pub fn run_all_lints(file: &syn::File, source: &str) -> Vec<MlintReport> { } } - // 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::Allow => r.level = MlintLevel::Help, + MlintLevelOverride::Deny => r.level = MlintLevel::Error, MlintLevelOverride::Warn => { - if r.level != MlintLevel::Error { - r.level = MlintLevel::Warning; - } + if r.level != MlintLevel::Error { r.level = MlintLevel::Warning; } } } } } - // Remove allowed reports (sentinel = Help) reports.retain(|r| r.level != MlintLevel::Help); reports } #[macro_export] macro_rules! assert_detected { - ($linter:expr, $ast_type:ty => $code:tt) => { - // Parse the string into a syn::ItemFn - let ast: $ast_type = syn::parse_str(&stringify!($code)).unwrap(); - assert!(!$linter(ast, &stringify!($code).to_string()).is_empty()); + ($linter:expr, $ast_type:ty => { $($code:tt)* }) => { + // $($code:tt)* captures tokens INSIDE the braces, not including the braces + // e.g. `fn foo() { ... }` — exactly what syn::ItemFn expects + let source = stringify!($($code)*); + let ast: $ast_type = syn::parse_str(&source).unwrap(); + assert!(!$linter(ast, &source).is_empty()); }; } #[macro_export] macro_rules! assert_not_detected { - ($linter:expr, $ast_type:ty => $code:tt) => { - // Parse the string into a syn::ItemFn - let ast: $ast_type = syn::parse_str(&stringify!($code)).unwrap(); - assert!($linter(ast, &stringify!($code).to_string()).is_empty()); + ($linter:expr, $ast_type:ty => { $($code:tt)* }) => { + let source = stringify!($($code)*); + let ast: $ast_type = syn::parse_str(&source).unwrap(); + assert!($linter(ast, &source).is_empty()); }; }
\ No newline at end of file diff --git a/mingling_cli/src/lints/unnecessary_render_result_creation.rs b/mingling_cli/src/lints/unnecessary_render_result_creation.rs index 06c47d9..03f81d2 100644 --- a/mingling_cli/src/lints/unnecessary_render_result_creation.rs +++ b/mingling_cli/src/lints/unnecessary_render_result_creation.rs @@ -15,6 +15,7 @@ //! Default: `warn` use crate::linter::mlint_report::{MlintLevel, MlintReport}; +use quote::ToTokens; pub fn linter(ast: syn::ItemFn, source: &str) -> Vec<MlintReport> { if !has_renderer_attr(&ast) { @@ -169,8 +170,19 @@ fn check_stmt_usage(stmt: &syn::Stmt, r_name: &str, print_count: &mut usize) -> } } - let s = format!("{stmt:#?}"); - !s.contains(&format!("\"{r_name}\"")) + // Any other reference to r → not allowed + // Check the token stream for the variable name + let ts_string = stmt.to_token_stream().to_string(); + if ts_string.contains(&format!("({r_name})")) + || ts_string.contains(&format!(" {r_name})")) + || ts_string.contains(&format!("(&mut {r_name})")) + || ts_string.contains(&format!("&{r_name}")) + || ts_string.contains(&format!("move {r_name}")) + || ts_string.contains(&format!(",{r_name},")) + { + return false; + } + true } #[cfg(test)] diff --git a/mingling_cli/tmpls/lints.tmpl b/mingling_cli/tmpls/lints.tmpl index fae1126..3b20ed4 100644 --- a/mingling_cli/tmpls/lints.tmpl +++ b/mingling_cli/tmpls/lints.tmpl @@ -12,11 +12,9 @@ pub fn run_all_lints(file: &syn::File, source: &str) -> Vec<MlintReport> { >>>>>>>>>> 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; } } @@ -25,46 +23,39 @@ pub fn run_all_lints(file: &syn::File, source: &str) -> Vec<MlintReport> { @@@ <<< } } - // 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::Allow => r.level = MlintLevel::Help, + MlintLevelOverride::Deny => r.level = MlintLevel::Error, MlintLevelOverride::Warn => { - if r.level != MlintLevel::Error { - r.level = MlintLevel::Warning; - } + if r.level != MlintLevel::Error { r.level = MlintLevel::Warning; } } } } } - // Remove allowed reports (sentinel = Help) reports.retain(|r| r.level != MlintLevel::Help); reports } #[macro_export] macro_rules! assert_detected { - ($linter:expr, $ast_type:ty => $code:tt) => { - // Parse the string into a syn::ItemFn - let ast: $ast_type = syn::parse_str(&stringify!($code)).unwrap(); - assert!(!$linter(ast, &stringify!($code).to_string()).is_empty()); + ($linter:expr, $ast_type:ty => { $($code:tt)* }) => { + // $($code:tt)* captures tokens INSIDE the braces, not including the braces + // e.g. `fn foo() { ... }` — exactly what syn::ItemFn expects + let source = stringify!($($code)*); + let ast: $ast_type = syn::parse_str(&source).unwrap(); + assert!(!$linter(ast, &source).is_empty()); }; } #[macro_export] macro_rules! assert_not_detected { - ($linter:expr, $ast_type:ty => $code:tt) => { - // Parse the string into a syn::ItemFn - let ast: $ast_type = syn::parse_str(&stringify!($code)).unwrap(); - assert!($linter(ast, &stringify!($code).to_string()).is_empty()); + ($linter:expr, $ast_type:ty => { $($code:tt)* }) => { + let source = stringify!($($code)*); + let ast: $ast_type = syn::parse_str(&source).unwrap(); + assert!($linter(ast, &source).is_empty()); }; } |
