From 4a08c0fc1c9948ff8a2145e9a44ec809318f0d07 Mon Sep 17 00:00:00 2001 From: harehare Date: Sat, 27 Jun 2026 22:57:20 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(mq-lint):=20add=20DeprecatedFu?= =?UTF-8?q?nctionCall=20rule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/mq-lint/src/message.rs | 15 +- crates/mq-lint/src/rules/correctness.rs | 2 + .../correctness/deprecated_function_call.rs | 147 ++++++++++++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 crates/mq-lint/src/rules/correctness/deprecated_function_call.rs diff --git a/crates/mq-lint/src/message.rs b/crates/mq-lint/src/message.rs index 8b34da584..76e2a916e 100644 --- a/crates/mq-lint/src/message.rs +++ b/crates/mq-lint/src/message.rs @@ -17,6 +17,7 @@ pub enum RuleId { UnusedImport, UnreachableCode, InfiniteLoop, + DeprecatedFunctionCall, DuplicateMatchArm, ShadowVariable, MissingElseInExpr, @@ -43,12 +44,13 @@ pub enum RuleId { impl RuleId { /// All known rule IDs. - pub const ALL: &'static [RuleId] = &[ + pub const ALL: &'static [RuleId; 28] = &[ RuleId::UnusedVariable, RuleId::UnusedFunction, RuleId::UnusedImport, RuleId::UnreachableCode, RuleId::InfiniteLoop, + RuleId::DeprecatedFunctionCall, RuleId::DuplicateMatchArm, RuleId::ShadowVariable, RuleId::MissingElseInExpr, @@ -82,6 +84,7 @@ impl RuleId { RuleId::UnreachableCode => "unreachable_code", RuleId::InfiniteLoop => "infinite_loop", RuleId::DuplicateMatchArm => "duplicate_match_arm", + RuleId::DeprecatedFunctionCall => "deprecated_function_call", RuleId::ShadowVariable => "shadow_variable", RuleId::MissingElseInExpr => "missing_else_in_expr", RuleId::AlwaysTrueCondition => "always_true_condition", @@ -145,6 +148,9 @@ pub enum LintMessage { DuplicateMatchArm { pattern: String, }, + DeprecatedFunctionCall { + name: String, + }, ShadowVariable { name: String, }, @@ -221,6 +227,7 @@ impl LintMessage { LintMessage::UnreachableCode { .. } => RuleId::UnreachableCode, LintMessage::InfiniteLoop => RuleId::InfiniteLoop, LintMessage::DuplicateMatchArm { .. } => RuleId::DuplicateMatchArm, + LintMessage::DeprecatedFunctionCall { .. } => RuleId::DeprecatedFunctionCall, LintMessage::ShadowVariable { .. } => RuleId::ShadowVariable, LintMessage::MissingElseInExpr => RuleId::MissingElseInExpr, LintMessage::AlwaysTrueCondition { .. } => RuleId::AlwaysTrueCondition, @@ -261,6 +268,9 @@ impl LintMessage { LintMessage::DuplicateMatchArm { .. } => { Some("remove or merge this arm with the earlier identical pattern".to_string()) } + LintMessage::DeprecatedFunctionCall { name } => Some(format!( + "deprecated function `{name}`; consider using an alternative or removing the call" + )), LintMessage::ShadowVariable { .. } => Some("consider renaming to avoid confusion".to_string()), LintMessage::MissingElseInExpr => { Some("add `else: ` to provide a value for the false branch".to_string()) @@ -341,6 +351,9 @@ impl fmt::Display for LintMessage { LintMessage::UnreachableCode { keyword } => write!(f, "unreachable code after `{keyword}`"), LintMessage::InfiniteLoop => write!(f, "loop without `break` may run forever"), LintMessage::DuplicateMatchArm { pattern } => write!(f, "duplicate match arm pattern `{pattern}`"), + LintMessage::DeprecatedFunctionCall { name } => { + write!(f, "call to deprecated function `{name}`") + } LintMessage::ShadowVariable { name } => { write!(f, "variable `{name}` shadows a variable in an outer scope") } diff --git a/crates/mq-lint/src/rules/correctness.rs b/crates/mq-lint/src/rules/correctness.rs index 06297dad4..39e3ccc62 100644 --- a/crates/mq-lint/src/rules/correctness.rs +++ b/crates/mq-lint/src/rules/correctness.rs @@ -1,4 +1,5 @@ pub mod always_true_condition; +pub mod deprecated_function_call; pub mod duplicate_match_arm; pub mod infinite_loop; pub mod missing_else_in_expr; @@ -17,6 +18,7 @@ pub fn all() -> Vec> { Box::new(unused_import::UnusedImport), Box::new(unreachable_code::UnreachableCode), Box::new(infinite_loop::InfiniteLoop), + Box::new(deprecated_function_call::DeprecatedFunctionCall), Box::new(duplicate_match_arm::DuplicateMatchArm), Box::new(shadow_variable::ShadowVariable), Box::new(missing_else_in_expr::MissingElseInExpr), diff --git a/crates/mq-lint/src/rules/correctness/deprecated_function_call.rs b/crates/mq-lint/src/rules/correctness/deprecated_function_call.rs new file mode 100644 index 000000000..6ac700e42 --- /dev/null +++ b/crates/mq-lint/src/rules/correctness/deprecated_function_call.rs @@ -0,0 +1,147 @@ +use crate::{Diagnostic, LintContext, LintMessage, LintRule, RuleId, Severity}; +use mq_hir::SymbolKind; +use rustc_hash::FxHashSet; + +pub struct DeprecatedFunctionCall; + +impl LintRule for DeprecatedFunctionCall { + fn id(&self) -> RuleId { + RuleId::DeprecatedFunctionCall + } + + fn severity(&self) -> Severity { + Severity::Warn + } + + fn check(&self, ctx: &LintContext<'_>) -> Vec { + let deprecated_functions: FxHashSet<&str> = ctx + .hir + .symbols() + .filter(|(_, s)| s.is_function() && s.is_deprecated()) + .filter_map(|(_, s)| s.value.as_deref()) + .collect(); + + ctx.all_symbols() + .filter_map(|(_, s)| { + if !matches!(s.kind, SymbolKind::Call) { + return None; + } + + let name = s.value.as_deref()?; + + if !deprecated_functions.contains(name) { + return None; + } + + let mut d = Diagnostic::new( + LintMessage::DeprecatedFunctionCall { name: name.to_string() }, + self.severity(), + ); + if let Some(range) = s.source.text_range { + d = d.with_range(range); + } + Some(d) + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use mq_hir::Hir; + + use super::*; + use crate::{LintConfig, LintContext}; + + fn check(code: &str) -> Vec { + let mut hir = Hir::default(); + let (source_id, _) = hir.add_code(None, code); + let config = LintConfig::default(); + let ctx = LintContext::new(&hir, source_id, &config); + DeprecatedFunctionCall.check(&ctx) + } + + #[test] + fn detects_call_to_deprecated_function() { + let diags = check("# deprecated\ndef old(): 1; | old()"); + assert_eq!(diags.len(), 1); + assert!(diags[0].message().contains("old")); + assert_eq!(diags[0].severity, Severity::Warn); + } + + #[test] + fn no_diagnostic_for_non_deprecated_function() { + let diags = check("def current(): 1; | current()"); + assert_eq!(diags.len(), 0); + } + + #[test] + fn no_diagnostic_when_deprecated_function_not_called() { + let diags = check("# deprecated\ndef old(): 1;"); + assert_eq!(diags.len(), 0); + } + + #[test] + fn detects_multiple_calls_to_same_deprecated_function() { + let diags = check("# deprecated\ndef old(): 1; | old() | old()"); + assert_eq!(diags.len(), 2); + assert!(diags.iter().all(|d| d.message().contains("old"))); + } + + #[test] + fn detects_calls_to_multiple_deprecated_functions() { + let diags = check("# deprecated\ndef old_a(): 1;\n# deprecated\ndef old_b(): 2; | old_a() | old_b()"); + assert_eq!(diags.len(), 2); + let messages: Vec<_> = diags.iter().map(|d| d.message()).collect(); + assert!(messages.iter().any(|m| m.contains("old_a"))); + assert!(messages.iter().any(|m| m.contains("old_b"))); + } + + #[test] + fn detects_deprecated_with_uppercase_marker() { + let diags = check("# Deprecated\ndef old(): 1; | old()"); + assert_eq!(diags.len(), 1); + } + + #[test] + fn detects_deprecated_with_all_caps_marker() { + let diags = check("# DEPRECATED\ndef old(): 1; | old()"); + assert_eq!(diags.len(), 1); + } + + #[test] + fn no_diagnostic_when_different_function_called() { + let diags = check("# deprecated\ndef old(): 1;\ndef current(): 2; | current()"); + assert_eq!(diags.len(), 0); + } + + #[test] + fn detects_deprecated_call_inside_function_body() { + let diags = check("# deprecated\ndef old(): 1;\ndef wrapper(): old();"); + assert_eq!(diags.len(), 1); + assert!(diags[0].message().contains("old")); + } + + #[test] + fn detects_deprecated_call_with_message_suffix() { + let diags = check("# deprecated: use new_fn instead\ndef old(): 1; | old()"); + assert_eq!(diags.len(), 1); + } + + #[test] + fn diagnostic_message_contains_function_name() { + let diags = check("# deprecated\ndef my_old_func(): 1; | my_old_func()"); + assert_eq!(diags.len(), 1); + assert!(diags[0].message().contains("my_old_func")); + } + + #[test] + fn rule_id_is_deprecated_function_call() { + assert_eq!(DeprecatedFunctionCall.id(), RuleId::DeprecatedFunctionCall); + } + + #[test] + fn severity_is_warn() { + assert_eq!(DeprecatedFunctionCall.severity(), Severity::Warn); + } +}