@@ -176,6 +176,52 @@ declare_clippy_lint! {
176176 "empty line after outer attribute"
177177}
178178
179+ declare_clippy_lint ! {
180+ /// ### What it does
181+ /// Checks for empty lines after documenation comments.
182+ ///
183+ /// ### Why is this bad?
184+ /// The documentation comment was most likely meant to be an inner attribute or regular comment.
185+ /// If it was intended to be a documentation comment, then the empty line should be removed to
186+ /// be more idiomatic.
187+ ///
188+ /// ### Known problems
189+ /// Only detects empty lines immediately following the documentation. If the doc comment is followed
190+ /// by an attribute and then an empty line, this lint will not trigger. Use `empty_line_after_outer_attr`
191+ /// in combination with this lint to detect both cases.
192+ ///
193+ /// Does not detect empty lines after doc attributes (e.g. `#[doc = ""]`).
194+ ///
195+ /// ### Example
196+ /// ```rust
197+ /// /// Some doc comment with a blank line after it.
198+ ///
199+ /// fn not_quite_good_code() { }
200+ /// ```
201+ ///
202+ /// Use instead:
203+ /// ```rust
204+ /// /// Good (no blank line)
205+ /// fn this_is_fine() { }
206+ /// ```
207+ ///
208+ /// ```rust
209+ /// // Good (convert to a regular comment)
210+ ///
211+ /// fn this_is_fine_too() { }
212+ /// ```
213+ ///
214+ /// ```rust
215+ /// //! Good (convert to a comment on an inner attribute)
216+ ///
217+ /// fn this_is_fine_as_well() { }
218+ /// ```
219+ #[ clippy:: version = "1.70.0" ]
220+ pub EMPTY_LINE_AFTER_DOC_COMMENTS ,
221+ nursery,
222+ "empty line after documentation comments"
223+ }
224+
179225declare_clippy_lint ! {
180226 /// ### What it does
181227 /// Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category.
@@ -292,6 +338,30 @@ declare_clippy_lint! {
292338 "ensures that all `allow` and `expect` attributes have a reason"
293339}
294340
341+ declare_clippy_lint ! {
342+ /// ### What it does
343+ /// Checks for `any` and `all` combinators in `cfg` with only one condition.
344+ ///
345+ /// ### Why is this bad?
346+ /// If there is only one condition, no need to wrap it into `any` or `all` combinators.
347+ ///
348+ /// ### Example
349+ /// ```rust
350+ /// #[cfg(any(unix))]
351+ /// pub struct Bar;
352+ /// ```
353+ ///
354+ /// Use instead:
355+ /// ```rust
356+ /// #[cfg(unix)]
357+ /// pub struct Bar;
358+ /// ```
359+ #[ clippy:: version = "1.71.0" ]
360+ pub NON_MINIMAL_CFG ,
361+ style,
362+ "ensure that all `cfg(any())` and `cfg(all())` have more than one condition"
363+ }
364+
295365declare_lint_pass ! ( Attributes => [
296366 ALLOW_ATTRIBUTES_WITHOUT_REASON ,
297367 INLINE_ALWAYS ,
@@ -604,6 +674,8 @@ impl_lint_pass!(EarlyAttributes => [
604674 DEPRECATED_CFG_ATTR ,
605675 MISMATCHED_TARGET_OS ,
606676 EMPTY_LINE_AFTER_OUTER_ATTR ,
677+ EMPTY_LINE_AFTER_DOC_COMMENTS ,
678+ NON_MINIMAL_CFG ,
607679] ) ;
608680
609681impl EarlyLintPass for EarlyAttributes {
@@ -614,15 +686,22 @@ impl EarlyLintPass for EarlyAttributes {
614686 fn check_attribute ( & mut self , cx : & EarlyContext < ' _ > , attr : & Attribute ) {
615687 check_deprecated_cfg_attr ( cx, attr, & self . msrv ) ;
616688 check_mismatched_target_os ( cx, attr) ;
689+ check_minimal_cfg_condition ( cx, attr) ;
617690 }
618691
619692 extract_msrv_attr ! ( EarlyContext ) ;
620693}
621694
695+ /// Check for empty lines after outer attributes.
696+ ///
697+ /// Attributes and documenation comments are both considered outer attributes
698+ /// by the AST. However, the average user likely considers them to be different.
699+ /// Checking for empty lines after each of these attributes is split into two different
700+ /// lints but can share the same logic.
622701fn check_empty_line_after_outer_attr ( cx : & EarlyContext < ' _ > , item : & rustc_ast:: Item ) {
623702 let mut iter = item. attrs . iter ( ) . peekable ( ) ;
624703 while let Some ( attr) = iter. next ( ) {
625- if matches ! ( attr. kind, AttrKind :: Normal ( ..) )
704+ if ( matches ! ( attr. kind, AttrKind :: Normal ( ..) ) || matches ! ( attr . kind , AttrKind :: DocComment ( .. ) ) )
626705 && attr. style == AttrStyle :: Outer
627706 && is_present_in_source ( cx, attr. span )
628707 {
@@ -639,13 +718,20 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
639718 let lines = without_block_comments ( lines) ;
640719
641720 if lines. iter ( ) . filter ( |l| l. trim ( ) . is_empty ( ) ) . count ( ) > 2 {
642- span_lint (
643- cx,
644- EMPTY_LINE_AFTER_OUTER_ATTR ,
645- begin_of_attr_to_item,
646- "found an empty line after an outer attribute. \
647- Perhaps you forgot to add a `!` to make it an inner attribute?",
648- ) ;
721+ let ( lint_msg, lint_type) = match attr. kind {
722+ AttrKind :: DocComment ( ..) => (
723+ "found an empty line after a doc comment. \
724+ Perhaps you need to use `//!` to make a comment on a module, remove the empty line, or make a regular comment with `//`?",
725+ EMPTY_LINE_AFTER_DOC_COMMENTS ,
726+ ) ,
727+ AttrKind :: Normal ( ..) => (
728+ "found an empty line after an outer attribute. \
729+ Perhaps you forgot to add a `!` to make it an inner attribute?",
730+ EMPTY_LINE_AFTER_OUTER_ATTR ,
731+ ) ,
732+ } ;
733+
734+ span_lint ( cx, lint_type, begin_of_attr_to_item, lint_msg) ;
649735 }
650736 }
651737 }
@@ -690,6 +776,48 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msr
690776 }
691777}
692778
779+ fn check_nested_cfg ( cx : & EarlyContext < ' _ > , items : & [ NestedMetaItem ] ) {
780+ for item in items. iter ( ) {
781+ if let NestedMetaItem :: MetaItem ( meta) = item {
782+ if !meta. has_name ( sym:: any) && !meta. has_name ( sym:: all) {
783+ continue ;
784+ }
785+ if let MetaItemKind :: List ( list) = & meta. kind {
786+ check_nested_cfg ( cx, list) ;
787+ if list. len ( ) == 1 {
788+ span_lint_and_then (
789+ cx,
790+ NON_MINIMAL_CFG ,
791+ meta. span ,
792+ "unneeded sub `cfg` when there is only one condition" ,
793+ |diag| {
794+ if let Some ( snippet) = snippet_opt ( cx, list[ 0 ] . span ( ) ) {
795+ diag. span_suggestion ( meta. span , "try" , snippet, Applicability :: MaybeIncorrect ) ;
796+ }
797+ } ,
798+ ) ;
799+ } else if list. is_empty ( ) && meta. has_name ( sym:: all) {
800+ span_lint_and_then (
801+ cx,
802+ NON_MINIMAL_CFG ,
803+ meta. span ,
804+ "unneeded sub `cfg` when there is no condition" ,
805+ |_| { } ,
806+ ) ;
807+ }
808+ }
809+ }
810+ }
811+ }
812+
813+ fn check_minimal_cfg_condition ( cx : & EarlyContext < ' _ > , attr : & Attribute ) {
814+ if attr. has_name ( sym:: cfg) &&
815+ let Some ( items) = attr. meta_item_list ( )
816+ {
817+ check_nested_cfg ( cx, & items) ;
818+ }
819+ }
820+
693821fn check_mismatched_target_os ( cx : & EarlyContext < ' _ > , attr : & Attribute ) {
694822 fn find_os ( name : & str ) -> Option < & ' static str > {
695823 UNIX_SYSTEMS
0 commit comments