|  | 
| 1 | 1 | use super::MIXED_ATTRIBUTES_STYLE; | 
| 2 | 2 | use clippy_utils::diagnostics::span_lint; | 
| 3 |  | -use rustc_ast::AttrStyle; | 
| 4 |  | -use rustc_lint::EarlyContext; | 
|  | 3 | +use rustc_ast::{AttrKind, AttrStyle, Attribute}; | 
|  | 4 | +use rustc_data_structures::fx::FxHashSet; | 
|  | 5 | +use rustc_lint::{LateContext, LintContext}; | 
|  | 6 | +use rustc_span::source_map::SourceMap; | 
|  | 7 | +use rustc_span::{SourceFile, Span, Symbol}; | 
|  | 8 | +use std::sync::Arc; | 
| 5 | 9 | 
 | 
| 6 |  | -pub(super) fn check(cx: &EarlyContext<'_>, item: &rustc_ast::Item) { | 
| 7 |  | -    let mut has_outer = false; | 
| 8 |  | -    let mut has_inner = false; | 
|  | 10 | +#[derive(Hash, PartialEq, Eq)] | 
|  | 11 | +enum SimpleAttrKind { | 
|  | 12 | +    Doc, | 
|  | 13 | +    /// A normal attribute, with its name symbols. | 
|  | 14 | +    Normal(Vec<Symbol>), | 
|  | 15 | +} | 
|  | 16 | + | 
|  | 17 | +impl From<&AttrKind> for SimpleAttrKind { | 
|  | 18 | +    fn from(value: &AttrKind) -> Self { | 
|  | 19 | +        match value { | 
|  | 20 | +            AttrKind::Normal(attr) => { | 
|  | 21 | +                let path_symbols = attr | 
|  | 22 | +                    .item | 
|  | 23 | +                    .path | 
|  | 24 | +                    .segments | 
|  | 25 | +                    .iter() | 
|  | 26 | +                    .map(|seg| seg.ident.name) | 
|  | 27 | +                    .collect::<Vec<_>>(); | 
|  | 28 | +                Self::Normal(path_symbols) | 
|  | 29 | +            }, | 
|  | 30 | +            AttrKind::DocComment(..) => Self::Doc, | 
|  | 31 | +        } | 
|  | 32 | +    } | 
|  | 33 | +} | 
|  | 34 | + | 
|  | 35 | +pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute]) { | 
|  | 36 | +    let mut inner_attr_kind: FxHashSet<SimpleAttrKind> = FxHashSet::default(); | 
|  | 37 | +    let mut outer_attr_kind: FxHashSet<SimpleAttrKind> = FxHashSet::default(); | 
|  | 38 | + | 
|  | 39 | +    let source_map = cx.sess().source_map(); | 
|  | 40 | +    let item_src = source_map.lookup_source_file(item_span.lo()); | 
| 9 | 41 | 
 | 
| 10 |  | -    for attr in &item.attrs { | 
| 11 |  | -        if attr.span.from_expansion() { | 
|  | 42 | +    for attr in attrs { | 
|  | 43 | +        if attr.span.from_expansion() || !attr_in_same_src_as_item(source_map, &item_src, attr.span) { | 
| 12 | 44 |             continue; | 
| 13 | 45 |         } | 
|  | 46 | + | 
|  | 47 | +        let kind: SimpleAttrKind = (&attr.kind).into(); | 
| 14 | 48 |         match attr.style { | 
| 15 |  | -            AttrStyle::Inner => has_inner = true, | 
| 16 |  | -            AttrStyle::Outer => has_outer = true, | 
| 17 |  | -        } | 
|  | 49 | +            AttrStyle::Inner => { | 
|  | 50 | +                if outer_attr_kind.contains(&kind) { | 
|  | 51 | +                    lint_mixed_attrs(cx, attrs); | 
|  | 52 | +                    return; | 
|  | 53 | +                } | 
|  | 54 | +                inner_attr_kind.insert(kind); | 
|  | 55 | +            }, | 
|  | 56 | +            AttrStyle::Outer => { | 
|  | 57 | +                if inner_attr_kind.contains(&kind) { | 
|  | 58 | +                    lint_mixed_attrs(cx, attrs); | 
|  | 59 | +                    return; | 
|  | 60 | +                } | 
|  | 61 | +                outer_attr_kind.insert(kind); | 
|  | 62 | +            }, | 
|  | 63 | +        }; | 
| 18 | 64 |     } | 
| 19 |  | -    if !has_outer || !has_inner { | 
|  | 65 | +} | 
|  | 66 | + | 
|  | 67 | +fn lint_mixed_attrs(cx: &LateContext<'_>, attrs: &[Attribute]) { | 
|  | 68 | +    let mut attrs_iter = attrs.iter().filter(|attr| !attr.span.from_expansion()); | 
|  | 69 | +    let span = if let (Some(first), Some(last)) = (attrs_iter.next(), attrs_iter.last()) { | 
|  | 70 | +        first.span.with_hi(last.span.hi()) | 
|  | 71 | +    } else { | 
| 20 | 72 |         return; | 
| 21 |  | -    } | 
| 22 |  | -    let mut attrs_iter = item.attrs.iter().filter(|attr| !attr.span.from_expansion()); | 
| 23 |  | -    let span = attrs_iter.next().unwrap().span; | 
|  | 73 | +    }; | 
| 24 | 74 |     span_lint( | 
| 25 | 75 |         cx, | 
| 26 | 76 |         MIXED_ATTRIBUTES_STYLE, | 
| 27 |  | -        span.with_hi(attrs_iter.last().unwrap().span.hi()), | 
|  | 77 | +        span, | 
| 28 | 78 |         "item has both inner and outer attributes", | 
| 29 | 79 |     ); | 
| 30 | 80 | } | 
|  | 81 | + | 
|  | 82 | +fn attr_in_same_src_as_item(source_map: &SourceMap, item_src: &Arc<SourceFile>, attr_span: Span) -> bool { | 
|  | 83 | +    let attr_src = source_map.lookup_source_file(attr_span.lo()); | 
|  | 84 | +    Arc::ptr_eq(item_src, &attr_src) | 
|  | 85 | +} | 
0 commit comments