use std::path::PathBuf;
use skif_core::{
DependencyKind, Diagnostic, Fix, FixOp, FixSafety, JsonAction, Location, PathSeg, RuleMeta, Span, TextEdit,
diagnostic,
};
use skif_lint::Emitter;
use skif_workspace::{DependencyEntry, Package};
use crate::analysis::JsAnalysis;
use crate::fixture::in_fixture_directory;
use crate::graph::Module;
pub(super) fn module_location(module: &Module, span: Option<Span>) -> Location {
Location::new(module.path.clone(), span)
}
pub(super) fn is_fixture(module: &Module) -> bool {
in_fixture_directory(&module.path)
}
pub(super) fn entry_location(package: &Package, entry: &DependencyEntry) -> Location {
Location::new(
package.manifest.path.clone(),
Some(
package
.manifest
.line_index()
.span(entry.key_span.start, entry.value_span.end),
),
)
}
pub(super) fn remove_dependency_fix(package: &Package, entry: &DependencyEntry) -> Fix {
Fix::new(
format!("Remove `{}` from `{}`", entry.name, entry.kind.field()),
FixSafety::Unsafe,
vec![FixOp::json(
package.manifest.path.clone(),
vec![
PathSeg::Key(entry.kind.field().to_owned()),
PathSeg::Key(entry.name.clone()),
],
JsonAction::Remove,
)],
)
}
pub(super) fn delete_range(module: &Module, start: u32, end: u32) -> FixOp {
FixOp::text(module.path.clone(), TextEdit::new(start, end, String::new()))
}
pub(super) fn report_unused_dependencies(
meta: &'static RuleMeta,
kinds: &[DependencyKind],
analysis: &JsAnalysis<'_>,
out: &mut Emitter<'_>,
) {
for (id, package) in analysis.workspace.iter() {
if analysis.is_skipped(id) || analysis.entries_incomplete(id) {
continue;
}
for entry in package.dependencies.iter().filter(|entry| kinds.contains(&entry.kind)) {
if analysis.dependencies.is_referenced(id, &entry.name)
|| analysis.ignores_dependency(id, &entry.name)
|| (entry.kind == DependencyKind::PeerDependencies && is_optional_peer(package, &entry.name))
{
continue;
}
out.emit(unused_dependency(meta, package, entry));
}
}
}
fn is_optional_peer(package: &Package, name: &str) -> bool {
package
.manifest
.value()
.get("peerDependenciesMeta")
.and_then(|meta| meta.get(name))
.and_then(|entry| entry.get("optional"))
.and_then(serde_json::Value::as_bool)
.unwrap_or(false)
}
fn unused_dependency(meta: &'static RuleMeta, package: &Package, entry: &DependencyEntry) -> Diagnostic {
diagnostic(
meta,
format!("`{}` is listed in `{}` but never used", entry.name, entry.kind.field()),
entry_location(package, entry),
)
.package(package.display_name())
.dependency(entry.name.clone())
.help("remove it, or add it to `ignore-dependencies` when a tool uses it without importing it")
.fix(remove_dependency_fix(package, entry))
.build()
}
pub(super) fn file_location(path: PathBuf) -> Location {
Location::new(path, None)
}