use perl_diagnostics_codes::DiagnosticCode;
use perl_parser_core::ast::{Node, NodeKind};
use perl_semantic_analyzer::symbol::{SymbolKind, SymbolTable};
use super::super::walker::walk_node;
use perl_lsp_diagnostic_types::{Diagnostic, DiagnosticSeverity, RelatedInformation};
pub fn check_common_mistakes(
node: &Node,
symbol_table: &SymbolTable,
diagnostics: &mut Vec<Diagnostic>,
) {
walk_node(node, &mut |n| {
match &n.kind {
NodeKind::If { condition, .. } | NodeKind::While { condition, .. } => {
check_assignment_in_condition(condition, diagnostics);
}
NodeKind::Binary { op, left, right } => {
if (op == "==" || op == "!=")
&& (might_be_undef(left, symbol_table) || might_be_undef(right, symbol_table))
{
diagnostics.push(Diagnostic {
range: (n.location.start, n.location.end),
severity: DiagnosticSeverity::Warning,
code: Some(DiagnosticCode::NumericComparisonWithUndef.as_str().to_string()),
message: format!("Using '{}' with potentially undefined value -- use 'defined()' to check first", op),
related_information: vec![RelatedInformation {
location: (n.location.start, n.location.end),
message: "Consider using 'defined' check or '//' operator".to_string(),
}],
tags: Vec::new(),
suggestion: Some("Guard with 'defined($var)' or use the '//' (defined-or) operator".to_string()),
});
}
}
_ => {}
}
});
}
fn check_assignment_in_condition(condition: &Node, diagnostics: &mut Vec<Diagnostic>) {
match &condition.kind {
NodeKind::Binary { op, .. } if op == "=" => {
diagnostics.push(Diagnostic {
range: (condition.location.start, condition.location.end),
severity: DiagnosticSeverity::Warning,
code: Some(DiagnosticCode::AssignmentInCondition.as_str().to_string()),
message: "Assignment in condition - did you mean '=='?".to_string(),
related_information: vec![
RelatedInformation {
location: (condition.location.start, condition.location.end),
message: "💡 Use '==' for comparison or 'eq' for string comparison".to_string(),
},
RelatedInformation {
location: (condition.location.start, condition.location.end),
message: "ℹ️ Assignment (=) in conditions is usually a mistake. If intentional, wrap in parentheses: if (($x = value))".to_string(),
}
],
tags: Vec::new(),
suggestion: Some("Replace '=' with '==' for numeric comparison or 'eq' for string comparison".to_string()),
});
}
NodeKind::Assignment { .. } => {
diagnostics.push(Diagnostic {
range: (condition.location.start, condition.location.end),
severity: DiagnosticSeverity::Warning,
code: Some(DiagnosticCode::AssignmentInCondition.as_str().to_string()),
message: "Assignment in condition - did you mean '=='?".to_string(),
related_information: vec![
RelatedInformation {
location: (condition.location.start, condition.location.end),
message: "💡 Use '==' for comparison or 'eq' for string comparison".to_string(),
},
RelatedInformation {
location: (condition.location.start, condition.location.end),
message: "ℹ️ Assignment in conditions is usually a mistake. If intentional, wrap in parentheses: if (($x = value))".to_string(),
}
],
tags: Vec::new(),
suggestion: Some("Replace '=' with '==' for numeric comparison or 'eq' for string comparison".to_string()),
});
}
_ => {}
}
}
fn might_be_undef(node: &Node, symbol_table: &SymbolTable) -> bool {
match &node.kind {
NodeKind::Variable { name, .. } => {
symbol_table.find_symbol(name, 0, SymbolKind::scalar()).is_empty()
}
NodeKind::Undef => true,
_ => false,
}
}