aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-21 15:59:01 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-21 15:59:01 +0800
commit05a06b59d2a27428d253308f067aa0e5a841eb5e (patch)
treeb1176b831e42a7dff4a8b0bcbb35fc7755164c77
parent770d738829076e0be2152452ab68933a47a483e2 (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.lock1
-rw-r--r--mingling_cli/Cargo.toml1
-rw-r--r--mingling_cli/src/lints.rs37
-rw-r--r--mingling_cli/src/lints/unnecessary_render_result_creation.rs16
-rw-r--r--mingling_cli/tmpls/lints.tmpl35
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());
};
}