| use std::path::Path; |
| |
| use cargo_util_terminal::report::AnnotationKind; |
| use cargo_util_terminal::report::Group; |
| use cargo_util_terminal::report::Level; |
| use cargo_util_terminal::report::Origin; |
| use cargo_util_terminal::report::Patch; |
| use cargo_util_terminal::report::Snippet; |
| use tracing::instrument; |
| |
| use super::RESTRICTION; |
| use crate::CargoResult; |
| use crate::GlobalContext; |
| use crate::diagnostics::Lint; |
| use crate::diagnostics::LintLevel; |
| use crate::diagnostics::LintLevelProduct; |
| use crate::diagnostics::LintLevelSource; |
| use crate::diagnostics::ScopedDiagnosticStats; |
| use crate::diagnostics::get_key_value_span; |
| use crate::diagnostics::workspace_rel_path; |
| use crate::workspace::Package; |
| use crate::workspace::Workspace; |
| |
| pub static LINT: &Lint = &Lint { |
| name: "non_snake_case_packages", |
| desc: "packages should have a snake-case name", |
| primary_group: &RESTRICTION, |
| msrv: None, |
| feature_gate: None, |
| docs: Some( |
| r#" |
| ### What it does |
| |
| Detect package names that are not snake-case. |
| |
| ### Why restrict this? |
| |
| Having multiple naming styles within a workspace can be confusing. |
| |
| ### Drawbacks |
| |
| Users have to mentally translate package names to namespaces in Rust. |
| |
| ### Example |
| |
| ```toml |
| [package] |
| name = "foo_bar" |
| ``` |
| |
| Should be written as: |
| |
| ```toml |
| [package] |
| name = "foo-bar" |
| ``` |
| "#, |
| ), |
| }; |
| |
| #[instrument(skip_all)] |
| pub(crate) fn lint_package( |
| ws: &Workspace<'_>, |
| pkg: &Package, |
| manifest_path: &Path, |
| level: LintLevelProduct, |
| pkg_stats: &mut ScopedDiagnosticStats<'_>, |
| gctx: &GlobalContext, |
| ) -> CargoResult<()> { |
| let LintLevelProduct { |
| level: lint_level, |
| source, |
| } = level; |
| |
| let manifest_path = workspace_rel_path(ws, manifest_path); |
| |
| lint_package_inner(pkg, &manifest_path, lint_level, source, pkg_stats, gctx) |
| } |
| |
| fn lint_package_inner( |
| pkg: &Package, |
| manifest_path: &str, |
| lint_level: LintLevel, |
| source: LintLevelSource, |
| pkg_stats: &mut ScopedDiagnosticStats<'_>, |
| gctx: &GlobalContext, |
| ) -> CargoResult<()> { |
| let manifest = pkg.manifest(); |
| |
| let original_name = &*manifest.name(); |
| let snake_case = heck::ToSnakeCase::to_snake_case(original_name); |
| if snake_case == original_name { |
| return Ok(()); |
| } |
| |
| let document = manifest.document(); |
| let contents = manifest.contents(); |
| let level = lint_level.to_diagnostic_level(); |
| let emitted_source = LINT.emitted_source(lint_level, source); |
| |
| let mut primary = Group::with_title(level.primary_title(LINT.desc)); |
| if let Some(document) = document |
| && let Some(contents) = contents |
| && let Some(span) = get_key_value_span(document, &["package", "name"]) |
| { |
| primary = primary.element( |
| Snippet::source(contents) |
| .path(manifest_path) |
| .annotation(AnnotationKind::Primary.span(span.value)), |
| ); |
| } else { |
| primary = primary.element(Origin::path(manifest_path)); |
| } |
| primary = primary.element(Level::NOTE.message(emitted_source)); |
| let mut report = vec![primary]; |
| if let Some(document) = document |
| && let Some(contents) = contents |
| && let Some(span) = get_key_value_span(document, &["package", "name"]) |
| { |
| let mut help = |
| Group::with_title(Level::HELP.secondary_title( |
| "to change the package name to snake case, convert `package.name`", |
| )); |
| help = help.element( |
| Snippet::source(contents) |
| .path(manifest_path) |
| .patch(Patch::new(span.value, format!("\"{snake_case}\""))), |
| ); |
| report.push(help); |
| } else { |
| let path = pkg.manifest_path(); |
| let display_path = path.as_os_str().to_string_lossy(); |
| let end = display_path.len() - if display_path.ends_with(".rs") { 3 } else { 0 }; |
| let start = path |
| .parent() |
| .map(|p| { |
| let p = p.as_os_str().to_string_lossy(); |
| // Account for trailing slash that was removed |
| p.len() + if p.is_empty() { 0 } else { 1 } |
| }) |
| .unwrap_or(0); |
| let help = Level::HELP |
| .secondary_title("to change the package name to snake case, convert the file stem") |
| .element(Snippet::source(display_path).patch(Patch::new(start..end, snake_case))); |
| report.push(help); |
| } |
| |
| pkg_stats.record_lint(lint_level); |
| gctx.shell().print_report(&report, lint_level.force())?; |
| |
| Ok(()) |
| } |