use std::collections::HashMap;
use std::path::Path;
use perl_diagnostics_codes::DiagnosticCode;
use perl_lsp_diagnostic_types::{Diagnostic, DiagnosticSeverity};
use perl_parser_core::ast::{Node, NodeKind};
use super::super::walker::walk_node;
pub fn check_missing_package_declaration(
node: &Node,
source: &str,
source_path: Option<&Path>,
diagnostics: &mut Vec<Diagnostic>,
) {
let statements = match &node.kind {
NodeKind::Program { statements } => statements,
_ => return,
};
if should_skip_missing_package_declaration(source, source_path) {
return;
}
let has_package = statements.iter().any(|stmt| matches!(&stmt.kind, NodeKind::Package { .. }));
if !has_package {
diagnostics.push(Diagnostic {
range: (0, 0),
severity: DiagnosticSeverity::Warning,
code: Some(DiagnosticCode::MissingPackageDeclaration.as_str().to_string()),
message: "This file has no package declaration. \
Add 'package MyModule;' to declare the package namespace."
.to_string(),
related_information: Vec::new(),
tags: Vec::new(),
suggestion: Some("Add 'package MyModule;' at the top of the file".to_string()),
});
}
}
fn should_skip_missing_package_declaration(source: &str, source_path: Option<&Path>) -> bool {
if let Some(extension) =
source_path.and_then(|path| path.extension()).and_then(|ext| ext.to_str())
{
let extension = extension.to_ascii_lowercase();
if matches!(extension.as_str(), "pl" | "t" | "cgi" | "psgi" | "plx") {
return true;
}
}
source.trim_start().starts_with("#!")
}
pub fn check_duplicate_package(node: &Node, diagnostics: &mut Vec<Diagnostic>) {
let mut seen: HashMap<String, usize> = HashMap::new();
walk_node(node, &mut |n| {
if let NodeKind::Package { name, name_span, .. } = &n.kind {
let count = seen.entry(name.clone()).or_insert(0);
*count += 1;
if *count > 1 {
diagnostics.push(Diagnostic {
range: (name_span.start, name_span.end),
severity: DiagnosticSeverity::Warning,
code: Some(DiagnosticCode::DuplicatePackage.as_str().to_string()),
message: format!("Package '{}' is declared more than once in this file", name),
related_information: Vec::new(),
tags: Vec::new(),
suggestion: Some(format!(
"Remove the duplicate 'package {};' declaration",
name
)),
});
}
}
});
}
pub fn check_duplicate_subroutine(node: &Node, diagnostics: &mut Vec<Diagnostic>) {
let mut subs: Vec<(String, (usize, usize))> = Vec::new();
let mut current_package = String::from("main");
walk_node(node, &mut |n| {
match &n.kind {
NodeKind::Package { name, .. } => {
current_package = name.clone();
}
NodeKind::Subroutine { name: Some(name), name_span: Some(span), .. } => {
let qualified = if name.contains("::") {
name.clone()
} else {
format!("{}::{}", current_package, name)
};
subs.push((qualified, (span.start, span.end)));
}
_ => {}
}
});
let mut seen: HashMap<String, usize> = HashMap::new();
for (qualified, span) in subs {
let count = seen.entry(qualified.clone()).or_insert(0);
*count += 1;
if *count > 1 {
let display_name = qualified.rsplit("::").next().unwrap_or(&qualified).to_string();
diagnostics.push(Diagnostic {
range: span,
severity: DiagnosticSeverity::Warning,
code: Some(DiagnosticCode::DuplicateSubroutine.as_str().to_string()),
message: format!("Subroutine '{}' is defined more than once", display_name),
related_information: Vec::new(),
tags: Vec::new(),
suggestion: Some(format!(
"Remove or rename the duplicate 'sub {}' definition",
display_name
)),
});
}
}
}