Skip to content

[Feat]: Make it possible to use AstPathSegment and Ident as an emission node #275

@Veetaha

Description

@Veetaha

Summary

I tried to update my ToStringOnCowStr lint to 0.3.0, but I couldn't make it work seamlessly as I wanted.

Here is the code that I have and I want to focus on:

ctx.emit_lint(
    TO_STRING_ON_COW_STR,
    method,
    "replace this with .into_owned()",
)
.span(method.method().ident().span());

And here is the full code for more context:

Details
use marker_api::prelude::*;
use marker_api::{LintPass, LintPassInfo, LintPassInfoBuilder};

#[derive(Default)]
struct ToStringOnCowStrPass {}
marker_api::export_lint_pass!(ToStringOnCowStrPass);

marker_api::declare_lint! {
    /// # What it does
    /// This lints detects the usage of `.to_string()` on a `Cow<str>`.
    ///
    /// # Example
    /// ```rs
    /// PathBuf::from("foo").to_string_lossy().to_string()
    /// ```
    ///
    /// Use instead:
    /// ```rs
    /// PathBuf::from("foo").to_string_lossy().into_owned()
    /// ```
    ///
    /// If you intended to clone the `Cow`, then use `.clone()` or `.to_owned()`.
    TO_STRING_ON_COW_STR,
    Deny,
}

impl LintPass for ToStringOnCowStrPass {
    fn info(&self) -> LintPassInfo {
        LintPassInfoBuilder::new(Box::new([TO_STRING_ON_COW_STR])).build()
    }

    // Comment here
    fn check_expr<'ast>(
        // comment there
        &mut self,
        ctx: &'ast MarkerContext<'ast>,
        expr: ExprKind<'ast>,
    ) {
        let ExprKind::Method(method) = expr else {
            return;
        };

        if method.method().ident().name() != "to_string" {
            return;
        }

        let sem::TyKind::Adt(reciever) = method.receiver().ty() else {
            return;
        };

        if !ctx
            .resolve_ty_ids("std::borrow::Cow")
            .contains(&reciever.def_id())
        {
            return;
        }

        let [sem::GenericArgKind::Ty(str)] = reciever.generics().args() else {
            return;
        };

        if !matches!(str, sem::TyKind::Text(str) if str.is_str()) {
            return;
        }

        ctx.emit_lint(
            TO_STRING_ON_COW_STR,
            method,
            "replace this with .into_owned()",
        )
        .span(method.method().ident().span());
    }
}

I couldn't just write this:

ctx.emit_lint(
    TO_STRING_ON_COW_STR,
    method.method(),
    "replace this with .into_owned()",
)

or this:

ctx.emit_lint(
    TO_STRING_ON_COW_STR,
    method.method().ident(),
    "replace this with .into_owned()",
)

because neither AstPathSegment nor Ident implement EmissionNode. If I just use method then the span used in the error message is this:

6 |     let val = cow.to_string();
  |               ^^^^^^^^^^^^^^^

But what I want it to be is this:

6 |     let val = cow.to_string();
  |                   ^^^^^^^^^

Intuitively I wanted to use the method name with the generics included (AstPathSegment) or at least the method's identifier as the span, but I couldn't, so I had to manually override the span.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-apiArea: Stable APIC-enhancementCategory: New feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions