| 
 | 1 | +use rustc_hir as hir;  | 
 | 2 | +use rustc_session::{declare_lint, declare_lint_pass};  | 
 | 3 | +use rustc_span::symbol::kw;  | 
 | 4 | + | 
 | 5 | +use crate::{lints, LateContext, LateLintPass, LintContext};  | 
 | 6 | + | 
 | 7 | +declare_lint! {  | 
 | 8 | +    /// The `unclear_local_imports` lint checks for `use` items that import a local item using a  | 
 | 9 | +    /// path that does not start with `self::`, `super::`, or `crate::`.  | 
 | 10 | +    ///  | 
 | 11 | +    /// ### Example  | 
 | 12 | +    ///  | 
 | 13 | +    /// ```rust,edition2018  | 
 | 14 | +    /// #![warn(unclear_local_imports)]  | 
 | 15 | +    ///  | 
 | 16 | +    /// mod localmod {  | 
 | 17 | +    ///     pub struct S;  | 
 | 18 | +    /// }  | 
 | 19 | +    ///  | 
 | 20 | +    /// use localmod::S;  | 
 | 21 | +    /// ```  | 
 | 22 | +    ///  | 
 | 23 | +    /// {{produces}}  | 
 | 24 | +    ///  | 
 | 25 | +    /// ### Explanation  | 
 | 26 | +    ///  | 
 | 27 | +    /// This lint is meant to be used with the (unstable) rustfmt setting `group_imports = "StdExternalCrate"`.  | 
 | 28 | +    /// That setting makes rustfmt group `self::`, `super::`, and `crate::` imports separately from those  | 
 | 29 | +    /// refering to other crates. However, rustfmt cannot know whether `use c::S;` refers to a local module `c`  | 
 | 30 | +    /// or an external crate `c`, so it always gets categorized as an import from another crate.  | 
 | 31 | +    /// To ensure consistent grouping of imports from the local crate, all local imports must  | 
 | 32 | +    /// start with `self::`, `super::`, or `crate::`. This lint can be used to enforce that style.  | 
 | 33 | +    pub UNCLEAR_LOCAL_IMPORTS,  | 
 | 34 | +    Allow,  | 
 | 35 | +    "`use` of a local item without leading `self::`, `super::`, or `crate::`"  | 
 | 36 | +}  | 
 | 37 | + | 
 | 38 | +declare_lint_pass!(UnclearLocalImports => [UNCLEAR_LOCAL_IMPORTS]);  | 
 | 39 | + | 
 | 40 | +impl<'tcx> LateLintPass<'tcx> for UnclearLocalImports {  | 
 | 41 | +    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {  | 
 | 42 | +        let hir::ItemKind::Use(path, _kind) = item.kind else { return };  | 
 | 43 | +        // `path` has three resolutions for the type, module, value namespaces.  | 
 | 44 | +        // However, it shouldn't be possible for those to be in different crates so we only check the first.  | 
 | 45 | +        let Some(hir::def::Res::Def(_def_kind, def_id)) = path.res.first() else { return };  | 
 | 46 | +        if !def_id.is_local() {  | 
 | 47 | +            return;  | 
 | 48 | +        }  | 
 | 49 | +        // So this does refer to something local. Let's check whether it starts with `self`,  | 
 | 50 | +        // `super`, or `crate`. If the path is empty, that means we have a `use *`, which is  | 
 | 51 | +        // equivalent to `use crate::*` so we don't fire the lint in that case.  | 
 | 52 | +        let Some(first_seg) = path.segments.first() else { return };  | 
 | 53 | +        if matches!(first_seg.ident.name, kw::SelfLower | kw::Super | kw::Crate) {  | 
 | 54 | +            return;  | 
 | 55 | +        }  | 
 | 56 | + | 
 | 57 | +        // This `use` qualifies for our lint!  | 
 | 58 | +        cx.emit_span_lint(  | 
 | 59 | +            UNCLEAR_LOCAL_IMPORTS,  | 
 | 60 | +            first_seg.ident.span,  | 
 | 61 | +            lints::UnclearLocalImportsDiag {},  | 
 | 62 | +        );  | 
 | 63 | +    }  | 
 | 64 | +}  | 
0 commit comments