@@ -176,6 +176,52 @@ declare_clippy_lint! {
176
176
"empty line after outer attribute"
177
177
}
178
178
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
+
179
225
declare_clippy_lint ! {
180
226
/// ### What it does
181
227
/// Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category.
@@ -292,6 +338,30 @@ declare_clippy_lint! {
292
338
"ensures that all `allow` and `expect` attributes have a reason"
293
339
}
294
340
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
+
295
365
declare_lint_pass ! ( Attributes => [
296
366
ALLOW_ATTRIBUTES_WITHOUT_REASON ,
297
367
INLINE_ALWAYS ,
@@ -604,6 +674,8 @@ impl_lint_pass!(EarlyAttributes => [
604
674
DEPRECATED_CFG_ATTR ,
605
675
MISMATCHED_TARGET_OS ,
606
676
EMPTY_LINE_AFTER_OUTER_ATTR ,
677
+ EMPTY_LINE_AFTER_DOC_COMMENTS ,
678
+ NON_MINIMAL_CFG ,
607
679
] ) ;
608
680
609
681
impl EarlyLintPass for EarlyAttributes {
@@ -614,15 +686,22 @@ impl EarlyLintPass for EarlyAttributes {
614
686
fn check_attribute ( & mut self , cx : & EarlyContext < ' _ > , attr : & Attribute ) {
615
687
check_deprecated_cfg_attr ( cx, attr, & self . msrv ) ;
616
688
check_mismatched_target_os ( cx, attr) ;
689
+ check_minimal_cfg_condition ( cx, attr) ;
617
690
}
618
691
619
692
extract_msrv_attr ! ( EarlyContext ) ;
620
693
}
621
694
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.
622
701
fn check_empty_line_after_outer_attr ( cx : & EarlyContext < ' _ > , item : & rustc_ast:: Item ) {
623
702
let mut iter = item. attrs . iter ( ) . peekable ( ) ;
624
703
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 ( .. ) ) )
626
705
&& attr. style == AttrStyle :: Outer
627
706
&& is_present_in_source ( cx, attr. span )
628
707
{
@@ -639,13 +718,20 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
639
718
let lines = without_block_comments ( lines) ;
640
719
641
720
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) ;
649
735
}
650
736
}
651
737
}
@@ -690,6 +776,48 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msr
690
776
}
691
777
}
692
778
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
+
693
821
fn check_mismatched_target_os ( cx : & EarlyContext < ' _ > , attr : & Attribute ) {
694
822
fn find_os ( name : & str ) -> Option < & ' static str > {
695
823
UNIX_SYSTEMS
0 commit comments