Repository: rust-analyzer/rust-analyzer
Pull request #10834 (2021-11-22 13:15:08)

Make lint groups work correctly with `warningsAsInfo` and `warningsAsHint`
this change makes it possible to specify

```json
{
   "rust-analyzer.diagnostics.warningsAsInfo": ["unused"],
}
```

to make all lints in the `unused` group show as info, etc.

Emilgardis: as an aside, maybe this should be done differently, separating a `Lint` and a `LintGroup`

Patch:
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index bca380a4d6..fdb639f55d 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -279,13 +279,6 @@ impl HirFileId {
     pub fn is_macro(self) -> bool {
         matches!(self.0, HirFileIdRepr::MacroFile(_))
     }
-
-    pub fn macro_file(self) -> Option<MacroFile> {
-        match self.0 {
-            HirFileIdRepr::FileId(_) => None,
-            HirFileIdRepr::MacroFile(m) => Some(m),
-        }
-    }
 }
 
 impl MacroDefId {
@@ -377,15 +370,12 @@ impl MacroCallId {
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct ExpansionInfo {
     expanded: InFile<SyntaxNode>,
-    /// The argument TokenTree or item for attributes
     arg: InFile<SyntaxNode>,
-    /// The `macro_rules!` or attribute input.
+    /// The `macro_rules!` arguments or attribute input.
     attr_input_or_mac_def: Option<InFile<ast::TokenTree>>,
 
     macro_def: Arc<TokenExpander>,
     macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>,
-    /// A shift built from `macro_arg`'s subtree, relevant for attributes as the item is the macro arg
-    /// and as such we need to shift tokens if they are part of an attributes input instead of their item.
     macro_arg_shift: mbe::Shift,
     exp_map: Arc<mbe::TokenMap>,
 }
@@ -399,20 +389,6 @@ impl ExpansionInfo {
         Some(self.arg.with_value(self.arg.value.parent()?))
     }
 
-    /// Map a token down from macro input into the macro expansion.
-    ///
-    /// The inner workings of this function differ slightly depending on the type of macro we are dealing with:
-    /// - declarative:
-    ///     For declarative macros, we need to accommodate for the macro definition site(which acts as a second unchanging input)
-    ///     , as tokens can mapped in and out of it.
-    ///     To do this we shift all ids in the expansion by the maximum id of the definition site giving us an easy
-    ///     way to map all the tokens.
-    /// - attribute:
-    ///     Attributes have two different inputs, the input tokentree in the attribute node and the item
-    ///     the attribute is annotating. Similarly as for declarative macros we need to do a shift here
-    ///     as well. Currently this is done by shifting the attribute input by the maximum id of the item.
-    /// - function-like and derives:
-    ///     Both of these only have one simple call site input so no special handling is required here.
     pub fn map_token_down(
         &self,
         db: &dyn db::AstDatabase,
@@ -420,10 +396,12 @@ impl ExpansionInfo {
         token: InFile<&SyntaxToken>,
     ) -> Option<impl Iterator<Item = InFile<SyntaxToken>> + '_> {
         assert_eq!(token.file_id, self.arg.file_id);
-        let token_id_in_attr_input = if let Some(item) = item {
+        let token_id = if let Some(item) = item {
             // check if we are mapping down in an attribute input
-            // this is a special case as attributes can have two inputs
-            let call_id = self.expanded.file_id.macro_file()?.macro_call_id;
+            let call_id = match self.expanded.file_id.0 {
+                HirFileIdRepr::FileId(_) => return None,
+                HirFileIdRepr::MacroFile(macro_file) => macro_file.macro_call_id,
+            };
             let loc = db.lookup_intern_macro_call(call_id);
 
             let token_range = token.value.text_range();
@@ -436,12 +414,9 @@ impl ExpansionInfo {
                         {
                             let attr_input_start =
                                 token_tree.left_delimiter_token()?.text_range().start();
-                            let relative_range =
-                                token.value.text_range().checked_sub(attr_input_start)?;
-                            // shift by the item's tree's max id
-                            let token_id = self
-                                .macro_arg_shift
-                                .shift(attr_args.1.token_by_range(relative_range)?);
+                            let range = token.value.text_range().checked_sub(attr_input_start)?;
+                            let token_id =
+                                self.macro_arg_shift.shift(attr_args.1.token_by_range(range)?);
                             Some(token_id)
                         }
                         _ => None,
@@ -453,14 +428,12 @@ impl ExpansionInfo {
             None
         };
 
-        let token_id = match token_id_in_attr_input {
+        let token_id = match token_id {
             Some(token_id) => token_id,
-            // the token is not inside an attribute's input so do the lookup in the macro_arg as ususal
             None => {
-                let relative_range =
+                let range =
                     token.value.text_range().checked_sub(self.arg.value.text_range().start())?;
-                let token_id = self.macro_arg.1.token_by_range(relative_range)?;
-                // conditionally shift the id by a declaratives macro definition
+                let token_id = self.macro_arg.1.token_by_range(range)?;
                 self.macro_def.map_id_down(token_id)
             }
         };
@@ -473,33 +446,28 @@ impl ExpansionInfo {
         Some(tokens.map(move |token| self.expanded.with_value(token)))
     }
 
-    /// Map a token up out of the expansion it resides in into the arguments of the macro call of the expansion.
     pub fn map_token_up(
         &self,
         db: &dyn db::AstDatabase,
         token: InFile<&SyntaxToken>,
     ) -> Option<(InFile<SyntaxToken>, Origin)> {
-        // Fetch the id through its text range,
         let token_id = self.exp_map.token_by_range(token.value.text_range())?;
-        // conditionally unshifting the id to accommodate for macro-rules def site
         let (mut token_id, origin) = self.macro_def.map_id_up(token_id);
 
-        let call_id = self.expanded.file_id.macro_file()?.macro_call_id;
+        let call_id = match self.expanded.file_id.0 {
+            HirFileIdRepr::FileId(_) => return None,
+            HirFileIdRepr::MacroFile(macro_file) => macro_file.macro_call_id,
+        };
         let loc = db.lookup_intern_macro_call(call_id);
 
-        // Attributes are a bit special for us, they have two inputs, the input tokentree and the annotated item.
         let (token_map, tt) = match &loc.kind {
-            MacroCallKind::Attr { attr_args: (_, arg_token_map), .. } => {
-                // try unshifting the the token id, if unshifting fails, the token resides in the non-item attribute input
-                // note that the `TokenExpander::map_id_up` earlier only unshifts for declarative macros, so we don't double unshift with this
-                match self.macro_arg_shift.unshift(token_id) {
-                    Some(unshifted) => {
-                        token_id = unshifted;
-                        (arg_token_map, self.attr_input_or_mac_def.clone()?.syntax().cloned())
-                    }
-                    None => (&self.macro_arg.1, self.arg.clone()),
+            MacroCallKind::Attr { attr_args, .. } => match self.macro_arg_shift.unshift(token_id) {
+                Some(unshifted) => {
+                    token_id = unshifted;
+                    (&attr_args.1, self.attr_input_or_mac_def.clone()?.syntax().cloned())
                 }
-            }
+                None => (&self.macro_arg.1, self.arg.clone()),
+            },
             _ => match origin {
                 mbe::Origin::Call => (&self.macro_arg.1, self.arg.clone()),
                 mbe::Origin::Def => match (&*self.macro_def, &self.attr_input_or_mac_def) {
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index dd77dd3e4b..7c098b13ae 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -22,6 +22,7 @@ use crate::{
     runnables::{runnable_fn, runnable_mod},
     FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, TryToNav,
 };
+
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct HoverConfig {
     pub links_in_hover: bool,
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index aae1fba6e9..97aff0970a 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -282,3 +282,21 @@ pub fn for_each_break_expr(
         }
     }
 }
+
+/// Checks if the given lint is equal or is contained by the other lint which may or may not be a group.
+pub fn lint_eq_or_in_group(lint: &str, lint_is: &str) -> bool {
+    if lint == lint_is {
+        return true;
+    }
+
+    if let Some(group) = generated_lints::DEFAULT_LINT_GROUPS
+        .iter()
+        .chain(generated_lints::CLIPPY_LINT_GROUPS.iter())
+        .chain(generated_lints::RUSTDOC_LINT_GROUPS.iter())
+        .find(|&check| check.lint.label == lint_is)
+    {
+        group.children.contains(&lint)
+    } else {
+        false
+    }
+}
diff --git a/crates/ide_db/src/helpers/generated_lints.rs b/crates/ide_db/src/helpers/generated_lints.rs
index e69b3fbdf8..25b0a463fb 100644
--- a/crates/ide_db/src/helpers/generated_lints.rs
+++ b/crates/ide_db/src/helpers/generated_lints.rs
@@ -4,6 +4,10 @@ pub struct Lint {
     pub label: &'static str,
     pub description: &'static str,
 }
+pub struct LintGroup {
+    pub lint: Lint,
+    pub children: &'static [&'static str],
+}
 pub const DEFAULT_LINTS: &[Lint] = &[
     Lint {
         label: "absolute_paths_not_starting_with_crate",
@@ -30,6 +34,10 @@ pub const DEFAULT_LINTS: &[Lint] = &[
         description: r##"detects pattern bindings with the same name as one of the matched variants"##,
     },
     Lint { label: "box_pointers", description: r##"use of owned (Box type) heap memory"## },
+    Lint {
+        label: "break_with_label_and_loop",
+        description: r##"`break` expression with label and unlabeled loop as value expression"##,
+    },
     Lint {
         label: "cenum_impl_drop_cast",
         description: r##"a C-like enum implementing Drop is cast"##,
@@ -68,11 +76,18 @@ pub const DEFAULT_LINTS: &[Lint] = &[
         label: "deprecated_in_future",
         description: r##"detects use of items that will be deprecated in a future version"##,
     },
+    Lint {
+        label: "deref_into_dyn_supertrait",
+        description: r##"`Deref` implementation usage with a supertrait trait object for output might be shadowed in the future"##,
+    },
     Lint {
         label: "deref_nullptr",
         description: r##"detects when an null pointer is dereferenced"##,
     },
-    Lint { label: "drop_bounds", description: r##"bounds of the form `T: Drop` are useless"## },
+    Lint {
+        label: "drop_bounds",
+        description: r##"bounds of the form `T: Drop` are most likely incorrect"##,
+    },
     Lint {
         label: "dyn_drop",
         description: r##"trait objects of the form `dyn Drop` are useless"##,
@@ -85,6 +100,10 @@ pub const DEFAULT_LINTS: &[Lint] = &[
         label: "ellipsis_inclusive_range_patterns",
         description: r##"`...` range patterns are deprecated"##,
     },
+    Lint {
+        label: "enum_intrinsics_non_enums",
+        description: r##"detects calls to `core::mem::discriminant` and `core::mem::variant_count` with non-enum types"##,
+    },
     Lint {
         label: "explicit_outlives_requirements",
         description: r##"outlives requirements can be inferred"##,
@@ -100,7 +119,7 @@ pub const DEFAULT_LINTS: &[Lint] = &[
     },
     Lint {
         label: "future_incompatible",
-        description: r##"lint group for: forbidden-lint-groups, illegal-floating-point-literal-pattern, private-in-public, pub-use-of-private-extern-crate, invalid-type-param-default, const-err, unaligned-references, patterns-in-fns-without-body, missing-fragment-specifier, late-bound-lifetime-arguments, order-dependent-trait-objects, coherence-leak-check, unstable-name-collisions, where-clauses-object-safety, proc-macro-derive-resolution-fallback, macro-expanded-macro-exports-accessed-by-absolute-paths, ill-formed-attribute-input, conflicting-repr-hints, ambiguous-associated-items, mutable-borrow-reservation-conflict, indirect-structural-match, pointer-structural-match, nontrivial-structural-match, soft-unstable, cenum-impl-drop-cast, const-evaluatable-unchecked, uninhabited-static, unsupported-naked-functions, invalid-doc-attributes, semicolon-in-expressions-from-macros, legacy-derive-helpers, proc-macro-back-compat, unsupported-calling-conventions"##,
+        description: r##"lint group for: forbidden-lint-groups, illegal-floating-point-literal-pattern, private-in-public, pub-use-of-private-extern-crate, invalid-type-param-default, const-err, unaligned-references, patterns-in-fns-without-body, missing-fragment-specifier, late-bound-lifetime-arguments, order-dependent-trait-objects, coherence-leak-check, unstable-name-collisions, where-clauses-object-safety, proc-macro-derive-resolution-fallback, macro-expanded-macro-exports-accessed-by-absolute-paths, ill-formed-attribute-input, conflicting-repr-hints, ambiguous-associated-items, mutable-borrow-reservation-conflict, indirect-structural-match, pointer-structural-match, nontrivial-structural-match, soft-unstable, cenum-impl-drop-cast, const-evaluatable-unchecked, uninhabited-static, unsupported-naked-functions, invalid-doc-attributes, semicolon-in-expressions-from-macros, legacy-derive-helpers, proc-macro-back-compat, unsupported-calling-conventions, deref-into-dyn-supertrait"##,
     },
     Lint {
         label: "ill_formed_attribute_input",
@@ -135,6 +154,10 @@ pub const DEFAULT_LINTS: &[Lint] = &[
         label: "inline_no_sanitize",
         description: r##"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"##,
     },
+    Lint {
+        label: "invalid_atomic_ordering",
+        description: r##"usage of invalid atomic ordering in atomic operations and memory fences"##,
+    },
     Lint {
         label: "invalid_doc_attributes",
         description: r##"detects invalid `#[doc(...)]` attributes"##,
@@ -197,6 +220,10 @@ pub const DEFAULT_LINTS: &[Lint] = &[
         label: "mixed_script_confusables",
         description: r##"detects Unicode scripts whose mixed script confusables codepoints are solely used"##,
     },
+    Lint {
+        label: "must_not_suspend",
+        description: r##"use of a `#[must_not_suspend]` value across a yield point"##,
+    },
     Lint {
         label: "mutable_borrow_reservation_conflict",
         description: r##"reservation of a two-phased borrow conflicts with other shared borrows"##,
@@ -205,6 +232,7 @@ pub const DEFAULT_LINTS: &[Lint] = &[
         label: "mutable_transmutes",
         description: r##"mutating transmuted &mut T from &T may cause undefined behavior"##,
     },
+    Lint { label: "named_asm_labels", description: r##"named labels in inline assembly"## },
     Lint {
         label: "no_mangle_const_items",
         description: r##"const items will not have their symbols exported"##,
@@ -215,6 +243,10 @@ pub const DEFAULT_LINTS: &[Lint] = &[
         label: "non_camel_case_types",
         description: r##"types, variants, traits and type parameters should have camel case names"##,
     },
+    Lint {
+        label: "non_exhaustive_omitted_patterns",
+        description: r##"detect when patterns of types marked `non_exhaustive` are missed"##,
+    },
     Lint {
         label: "non_fmt_panics",
         description: r##"detect single-argument panic!() invocations in which the argument is not a format string"##,
@@ -333,6 +365,14 @@ pub const DEFAULT_LINTS: &[Lint] = &[
         label: "temporary_cstring_as_ptr",
         description: r##"detects getting the inner pointer of a temporary `CString`"##,
     },
+    Lint {
+        label: "text_direction_codepoint_in_comment",
+        description: r##"invisible directionality-changing codepoints in comment"##,
+    },
+    Lint {
+        label: "text_direction_codepoint_in_literal",
+        description: r##"detect special Unicode codepoints that affect the visual representation of text on screen, changing the direction in which text flows"##,
+    },
     Lint {
         label: "trivial_bounds",
         description: r##"these bounds don't depend on an type parameters"##,
@@ -501,6 +541,133 @@ pub const DEFAULT_LINTS: &[Lint] = &[
         description: r##"suggest using `loop { }` instead of `while true { }`"##,
     },
 ];
+pub const DEFAULT_LINT_GROUPS: &[LintGroup] = &[
+    LintGroup {
+        lint: Lint {
+            label: "future_incompatible",
+            description: r##"lint group for: forbidden-lint-groups, illegal-floating-point-literal-pattern, private-in-public, pub-use-of-private-extern-crate, invalid-type-param-default, const-err, unaligned-references, patterns-in-fns-without-body, missing-fragment-specifier, late-bound-lifetime-arguments, order-dependent-trait-objects, coherence-leak-check, unstable-name-collisions, where-clauses-object-safety, proc-macro-derive-resolution-fallback, macro-expanded-macro-exports-accessed-by-absolute-paths, ill-formed-attribute-input, conflicting-repr-hints, ambiguous-associated-items, mutable-borrow-reservation-conflict, indirect-structural-match, pointer-structural-match, nontrivial-structural-match, soft-unstable, cenum-impl-drop-cast, const-evaluatable-unchecked, uninhabited-static, unsupported-naked-functions, invalid-doc-attributes, semicolon-in-expressions-from-macros, legacy-derive-helpers, proc-macro-back-compat, unsupported-calling-conventions, deref-into-dyn-supertrait"##,
+        },
+        children: &[
+            "forbidden_lint_groups",
+            "illegal_floating_point_literal_pattern",
+            "private_in_public",
+            "pub_use_of_private_extern_crate",
+            "invalid_type_param_default",
+            "const_err",
+            "unaligned_references",
+            "patterns_in_fns_without_body",
+            "missing_fragment_specifier",
+            "late_bound_lifetime_arguments",
+            "order_dependent_trait_objects",
+            "coherence_leak_check",
+            "unstable_name_collisions",
+            "where_clauses_object_safety",
+            "proc_macro_derive_resolution_fallback",
+            "macro_expanded_macro_exports_accessed_by_absolute_paths",
+            "ill_formed_attribute_input",
+            "conflicting_repr_hints",
+            "ambiguous_associated_items",
+            "mutable_borrow_reservation_conflict",
+            "indirect_structural_match",
+            "pointer_structural_match",
+            "nontrivial_structural_match",
+            "soft_unstable",
+            "cenum_impl_drop_cast",
+            "const_evaluatable_unchecked",
+            "uninhabited_static",
+            "unsupported_naked_functions",
+            "invalid_doc_attributes",
+            "semicolon_in_expressions_from_macros",
+            "legacy_derive_helpers",
+            "proc_macro_back_compat",
+            "unsupported_calling_conventions",
+            "deref_into_dyn_supertrait",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "nonstandard_style",
+            description: r##"lint group for: non-camel-case-types, non-snake-case, non-upper-case-globals"##,
+        },
+        children: &["non_camel_case_types", "non_snake_case", "non_upper_case_globals"],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "rust_2018_compatibility",
+            description: r##"lint group for: keyword-idents, anonymous-parameters, tyvar-behind-raw-pointer, absolute-paths-not-starting-with-crate"##,
+        },
+        children: &[
+            "keyword_idents",
+            "anonymous_parameters",
+            "tyvar_behind_raw_pointer",
+            "absolute_paths_not_starting_with_crate",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "rust_2018_idioms",
+            description: r##"lint group for: bare-trait-objects, unused-extern-crates, ellipsis-inclusive-range-patterns, elided-lifetimes-in-paths, explicit-outlives-requirements"##,
+        },
+        children: &[
+            "bare_trait_objects",
+            "unused_extern_crates",
+            "ellipsis_inclusive_range_patterns",
+            "elided_lifetimes_in_paths",
+            "explicit_outlives_requirements",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "rust_2021_compatibility",
+            description: r##"lint group for: ellipsis-inclusive-range-patterns, bare-trait-objects, rust-2021-incompatible-closure-captures, rust-2021-incompatible-or-patterns, rust-2021-prelude-collisions, rust-2021-prefixes-incompatible-syntax, array-into-iter, non-fmt-panics"##,
+        },
+        children: &[
+            "ellipsis_inclusive_range_patterns",
+            "bare_trait_objects",
+            "rust_2021_incompatible_closure_captures",
+            "rust_2021_incompatible_or_patterns",
+            "rust_2021_prelude_collisions",
+            "rust_2021_prefixes_incompatible_syntax",
+            "array_into_iter",
+            "non_fmt_panics",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "unused",
+            description: r##"lint group for: unused-imports, unused-variables, unused-assignments, dead-code, unused-mut, unreachable-code, unreachable-patterns, unused-must-use, unused-unsafe, path-statements, unused-attributes, unused-macros, unused-allocation, unused-doc-comments, unused-extern-crates, unused-features, unused-labels, unused-parens, unused-braces, redundant-semicolons"##,
+        },
+        children: &[
+            "unused_imports",
+            "unused_variables",
+            "unused_assignments",
+            "dead_code",
+            "unused_mut",
+            "unreachable_code",
+            "unreachable_patterns",
+            "unused_must_use",
+            "unused_unsafe",
+            "path_statements",
+            "unused_attributes",
+            "unused_macros",
+            "unused_allocation",
+            "unused_doc_comments",
+            "unused_extern_crates",
+            "unused_features",
+            "unused_labels",
+            "unused_parens",
+            "unused_braces",
+            "redundant_semicolons",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "warnings",
+            description: r##"lint group for: all lints that are set to issue warnings"##,
+        },
+        children: &[],
+    },
+];
 
 pub const RUSTDOC_LINTS: &[Lint] = &[
     Lint {
@@ -541,6 +708,23 @@ pub const RUSTDOC_LINTS: &[Lint] = &[
         description: r##"linking from a public item to a private one"##,
     },
 ];
+pub const RUSTDOC_LINT_GROUPS: &[LintGroup] = &[LintGroup {
+    lint: Lint {
+        label: "rustdoc::all",
+        description: r##"lint group for: rustdoc::broken-intra-doc-links, rustdoc::private-intra-doc-links, rustdoc::missing-doc-code-examples, rustdoc::private-doc-tests, rustdoc::invalid-codeblock-attributes, rustdoc::invalid-rust-codeblocks, rustdoc::invalid-html-tags, rustdoc::bare-urls, rustdoc::missing-crate-level-docs"##,
+    },
+    children: &[
+        "rustdoc::broken_intra_doc_links",
+        "rustdoc::private_intra_doc_links",
+        "rustdoc::missing_doc_code_examples",
+        "rustdoc::private_doc_tests",
+        "rustdoc::invalid_codeblock_attributes",
+        "rustdoc::invalid_rust_codeblocks",
+        "rustdoc::invalid_html_tags",
+        "rustdoc::bare_urls",
+        "rustdoc::missing_crate_level_docs",
+    ],
+}];
 
 pub const FEATURES: &[Lint] = &[
     Lint {
@@ -789,6 +973,47 @@ detail of the `global_allocator` feature not intended for use outside the
 compiler.
 
 ------------------------
+"##,
+    },
+    Lint {
+        label: "arbitrary_enum_discriminant",
+        description: r##"# `arbitrary_enum_discriminant`
+
+The tracking issue for this feature is: [#60553]
+
+[#60553]: https://github.com/rust-lang/rust/issues/60553
+
+------------------------
+
+The `arbitrary_enum_discriminant` feature permits tuple-like and
+struct-like enum variants with `#[repr(<int-type>)]` to have explicit discriminants.
+
+## Examples
+
+```rust
+#![feature(arbitrary_enum_discriminant)]
+
+#[allow(dead_code)]
+#[repr(u8)]
+enum Enum {
+    Unit = 3,
+    Tuple(u16) = 2,
+    Struct {
+        a: u8,
+        b: u16,
+    } = 1,
+}
+
+impl Enum {
+    fn tag(&self) -> u8 {
+        unsafe { *(self as *const Self as *const u8) }
+    }
+}
+
+assert_eq!(3, Enum::Unit.tag());
+assert_eq!(2, Enum::Tuple(5).tag());
+assert_eq!(1, Enum::Struct{a: 7, b: 11}.tag());
+```
 "##,
     },
     Lint {
@@ -861,7 +1086,7 @@ assert_eq!(x, 5);
 This will write the value `5` into the `u64` variable `x`.
 You can see that the string literal we use to specify instructions is actually a template string.
 It is governed by the same rules as Rust [format strings][format-syntax].
-The arguments that are inserted into the template however look a bit different then you may
+The arguments that are inserted into the template however look a bit different than you may
 be familiar with. First we need to specify if the variable is an input or an output of the
 inline assembly. In this case it is an output. We declared this by writing `out`.
 We also need to specify in what kind of register the assembly expects the variable.
@@ -901,7 +1126,7 @@ code.
 Second, we can see that inputs are declared by writing `in` instead of `out`.
 
 Third, one of our operands has a type we haven't seen yet, `const`.
-This tells the compiler to expand this argument to value directly inside the assembly template.
+This tells the compiler to expand this argument to a value directly inside the assembly template.
 This is only possible for constants and literals.
 
 Fourth, we can see that we can specify an argument number, or name as in any format string.
@@ -1052,7 +1277,7 @@ unsafe {
 }
 
 println!(
-    "L1 Cache: {}",
+    "L0 Cache: {}",
     ((ebx >> 22) + 1) * (((ebx >> 12) & 0x3ff) + 1) * ((ebx & 0xfff) + 1) * (ecx + 1)
 );
 ```
@@ -1114,7 +1339,7 @@ fn call_foo(arg: i32) -> i32 {
 
 Note that the `fn` or `static` item does not need to be public or `#[no_mangle]`: the compiler will automatically insert the appropriate mangled symbol name into the assembly code.
 
-By default, `asm!` assumes that any register not specified as an output will have its contents preserved by the assembly code. The [`clobber_abi`](#abi-clobbers) argument to `asm!` tells the compiler to automatically insert the necessary clobber operands according to the given calling convention ABI: any register which is not fully preserved in that ABI will be treated as clobbered.
+By default, `asm!` assumes that any register not specified as an output will have its contents preserved by the assembly code. The [`clobber_abi`](#abi-clobbers) argument to `asm!` tells the compiler to automatically insert the necessary clobber operands according to the given calling convention ABI: any register which is not fully preserved in that ABI will be treated as clobbered.  Multiple `clobber_abi` arguments may be provided and all clobbers from all specified ABIs will be inserted.
 
 ## Register template modifiers
 
@@ -1248,10 +1473,10 @@ reg_spec := <register class> / "<explicit register>"
 operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_"
 reg_operand := dir_spec "(" reg_spec ")" operand_expr
 operand := reg_operand / "const" const_expr / "sym" path
-clobber_abi := "clobber_abi(" <abi> ")"
+clobber_abi := "clobber_abi(" <abi> *["," <abi>] [","] ")"
 option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nostack" / "att_syntax" / "raw"
 options := "options(" option *["," option] [","] ")"
-asm := "asm!(" format_string *("," format_string) *("," [ident "="] operand) ["," clobber_abi] *("," options) [","] ")"
+asm := "asm!(" format_string *("," format_string) *("," [ident "="] operand) *("," clobber_abi) *("," options) [","] ")"
 ```
 
 Inline assembly is currently supported on the following architectures:
@@ -1357,9 +1582,12 @@ Here is the list of currently supported register classes:
 | AArch64 | `vreg` | `v[0-31]` | `w` |
 | AArch64 | `vreg_low16` | `v[0-15]` | `x` |
 | AArch64 | `preg` | `p[0-15]`, `ffr` | Only clobbers |
-| ARM | `reg` | `r[0-12]`, `r14` | `r` |
-| ARM (Thumb) | `reg_thumb` | `r[0-r7]` | `l` |
+| ARM (ARM) | `reg` | `r[0-12]`, `r14` | `r` |
+| ARM (Thumb2) | `reg` | `r[0-12]`, `r14` | `r` |
+| ARM (Thumb1) | `reg` | `r[0-7]` | `r` |
 | ARM (ARM) | `reg_thumb` | `r[0-r12]`, `r14` | `l` |
+| ARM (Thumb2) | `reg_thumb` | `r[0-7]` | `l` |
+| ARM (Thumb1) | `reg_thumb` | `r[0-7]` | `l` |
 | ARM | `sreg` | `s[0-31]` | `t` |
 | ARM | `sreg_low16` | `s[0-15]` | `x` |
 | ARM | `dreg` | `d[0-31]` | `w` |
@@ -1594,6 +1822,8 @@ As stated in the previous section, passing an input value smaller than the regis
 
 The `clobber_abi` keyword can be used to apply a default set of clobbers to an `asm` block. This will automatically insert the necessary clobber constraints as needed for calling a function with a particular calling convention: if the calling convention does not fully preserve the value of a register across a call then a `lateout("reg") _` is implicitly added to the operands list.
 
+`clobber_abi` may be specified any number of times. It will insert a clobber for all unique registers in the union of all specified calling conventions.
+
 Generic register class outputs are disallowed by the compiler when `clobber_abi` is used: all outputs must specify an explicit register. Explicit register outputs have precedence over the implicit clobbers inserted by `clobber_abi`: a clobber will only be inserted for a register if that register is not used as an output.
 The following ABIs can be used with `clobber_abi`:
 
@@ -1680,6 +1910,8 @@ The compiler performs some additional checks on options:
     - You are responsible for switching any target-specific state (e.g. thread-local storage, stack bounds).
     - The set of memory locations that you may access is the intersection of those allowed by the `asm!` blocks you entered and exited.
 - You cannot assume that an `asm!` block will appear exactly once in the output binary. The compiler is allowed to instantiate multiple copies of the `asm!` block, for example when the function containing it is inlined in multiple places.
+- On x86, inline assembly must not end with an instruction prefix (such as `LOCK`) that would apply to instructions generated by the compiler.
+  - The compiler is currently unable to detect this due to the way inline assembly is compiled, but may catch and reject this in the future.
 
 > **Note**: As a general rule, the flags covered by `preserves_flags` are those which are *not* preserved when performing a function call.
 "##,
@@ -2782,57 +3014,6 @@ fn main() {
     assert_eq!(adder(2), 5);
 }
 ```
-"##,
-    },
-    Lint {
-        label: "format_args_capture",
-        description: r##"# `format_args_capture`
-
-The tracking issue for this feature is: [#67984]
-
-[#67984]: https://github.com/rust-lang/rust/issues/67984
-
-------------------------
-
-Enables `format_args!` (and macros which use `format_args!` in their implementation, such
-as `format!`, `print!` and `panic!`) to capture variables from the surrounding scope.
-This avoids the need to pass named parameters when the binding in question
-already exists in scope.
-
-```rust
-#![feature(format_args_capture)]
-
-let (person, species, name) = ("Charlie Brown", "dog", "Snoopy");
-
-// captures named argument `person`
-print!("Hello {person}");
-
-// captures named arguments `species` and `name`
-format!("The {species}'s name is {name}.");
-```
-
-This also works for formatting parameters such as width and precision:
-
-```rust
-#![feature(format_args_capture)]
-
-let precision = 2;
-let s = format!("{:.precision$}", 1.324223);
-
-assert_eq!(&s, "1.32");
-```
-
-A non-exhaustive list of macros which benefit from this functionality include:
-- `format!`
-- `print!` and `println!`
-- `eprint!` and `eprintln!`
-- `write!` and `writeln!`
-- `panic!`
-- `unreachable!`
-- `unimplemented!`
-- `todo!`
-- `assert!` and similar
-- macros in many thirdparty crates, such as `log`
 "##,
     },
     Lint {
@@ -3164,7 +3345,7 @@ are concatenated into one or assembled separately.
 constants defined in Rust to be used in assembly code:
 
 ```rust,no_run
-#![feature(global_asm)]
+#![feature(global_asm, asm_const)]
 # #[cfg(any(target_arch="x86", target_arch="x86_64"))]
 # mod x86 {
 const C: i32 = 1234;
@@ -3185,7 +3366,7 @@ override this by adding `options(att_syntax)` at the end of the macro
 arguments list:
 
 ```rust,no_run
-#![feature(global_asm)]
+#![feature(global_asm, asm_const)]
 # #[cfg(any(target_arch="x86", target_arch="x86_64"))]
 # mod x86 {
 global_asm!("movl ${}, %ecx", const 5, options(att_syntax));
@@ -4937,6 +5118,43 @@ let result: Result<i32, ParseIntError> = try {
 };
 assert!(result.is_err());
 ```
+"##,
+    },
+    Lint {
+        label: "type_changing_struct_update",
+        description: r##"# `type_changing_struct_update`
+
+The tracking issue for this feature is: [#86555]
+
+[#86555]: https://github.com/rust-lang/rust/issues/86555
+
+------------------------
+
+This implements [RFC2528]. When turned on, you can create instances of the same struct
+that have different generic type or lifetime parameters.
+
+[RFC2528]: https://github.com/rust-lang/rfcs/blob/master/text/2528-type-changing-struct-update-syntax.md
+
+```rust
+#![allow(unused_variables, dead_code)]
+#![feature(type_changing_struct_update)]
+
+fn main () {
+    struct Foo<T, U> {
+        field1: T,
+        field2: U,
+    }
+
+    let base: Foo<String, i32> = Foo {
+        field1: String::from("hello"),
+        field2: 1234,
+    };
+    let updated: Foo<f64, i32> = Foo {
+        field1: 3.14,
+        ..base
+    };
+}
+```
 "##,
     },
     Lint {
@@ -5574,7 +5792,7 @@ explicitly or vice versa."##,
 explicitly or vice versa."##,
     },
     Lint {
-        label: "clippy::disallowed_method",
+        label: "clippy::disallowed_methods",
         description: r##"Denies the configured methods and functions in clippy.toml"##,
     },
     Lint {
@@ -5593,7 +5811,7 @@ See also: [`non_ascii_idents`].
 [supported_scripts]: https://www.unicode.org/iso15924/iso15924-codes.html"##,
     },
     Lint {
-        label: "clippy::disallowed_type",
+        label: "clippy::disallowed_types",
         description: r##"Denies the configured types in clippy.toml."##,
     },
     Lint {
@@ -5919,10 +6137,6 @@ else branch."##,
         description: r##"Checks for `if/else` with the same body as the *then* part
 and the *else* part."##,
     },
-    Lint {
-        label: "clippy::if_then_panic",
-        description: r##"Detects `if`-then-`panic!` that can be replaced with `assert!`."##,
-    },
     Lint {
         label: "clippy::if_then_some_else_none",
         description: r##"Checks for if-else that could be written to `bool::then`."##,
@@ -5965,6 +6179,11 @@ grouped inconsistently with underscores."##,
         description: r##"Checks for struct constructors where all fields are shorthand and
 the order of the field init shorthand in the constructor is inconsistent
 with the order in the struct definition."##,
+    },
+    Lint {
+        label: "clippy::index_refutable_slice",
+        description: r##"The lint checks for slice bindings in patterns that are only used to
+access individual slice values."##,
     },
     Lint {
         label: "clippy::indexing_slicing",
@@ -6160,7 +6379,8 @@ where expr has a type that implements `Drop`"##,
     },
     Lint {
         label: "clippy::let_underscore_lock",
-        description: r##"Checks for `let _ = sync_lock`"##,
+        description: r##"Checks for `let _ = sync_lock`.
+This supports `mutex` and `rwlock` in `std::sync` and `parking_lot`."##,
     },
     Lint {
         label: "clippy::let_underscore_must_use",
@@ -6190,6 +6410,10 @@ cannot be represented as the underlying type without loss."##,
         label: "clippy::main_recursion",
         description: r##"Checks for recursion using the entrypoint."##,
     },
+    Lint {
+        label: "clippy::manual_assert",
+        description: r##"Detects `if`-then-`panic!` that can be replaced with `assert!`."##,
+    },
     Lint {
         label: "clippy::manual_async_fn",
         description: r##"It checks for manual implementations of `async` functions."##,
@@ -6348,10 +6572,6 @@ and take drastic actions like `panic!`."##,
         label: "clippy::maybe_infinite_iter",
         description: r##"Checks for iteration that may be infinite."##,
     },
-    Lint {
-        label: "clippy::mem_discriminant_non_enum",
-        description: r##"Checks for calls of `mem::discriminant()` on a non-enum type."##,
-    },
     Lint {
         label: "clippy::mem_forget",
         description: r##"Checks for usage of `std::mem::forget(t)` where `t` is
@@ -6410,7 +6630,7 @@ return a `Result` type and warns if there is no `# Errors` section."##,
     },
     Lint {
         label: "clippy::missing_inline_in_public_items",
-        description: r##"it lints if an exported function, method, trait method with default impl,
+        description: r##"It lints if an exported function, method, trait method with default impl,
 or trait method impl is not `#[inline]`."##,
     },
     Lint {
@@ -6578,6 +6798,10 @@ collection just to get the values by index."##,
         label: "clippy::needless_return",
         description: r##"Checks for return statements at the end of a block."##,
     },
+    Lint {
+        label: "clippy::needless_splitn",
+        description: r##"Checks for usages of `str::splitn` (or `str::rsplitn`) where using `str::split` would be the same."##,
+    },
     Lint {
         label: "clippy::needless_update",
         description: r##"Checks for needlessly including a base struct on update
@@ -6988,6 +7212,12 @@ one from a trait, another not from trait."##,
         label: "clippy::semicolon_if_nothing_returned",
         description: r##"Looks for blocks of expressions and fires if the last expression returns
 `()` but is not followed by a semicolon."##,
+    },
+    Lint {
+        label: "clippy::separated_literal_suffix",
+        description: r##"Warns if literal suffixes are separated by an underscore.
+To enforce separated literal suffix style,
+see the `unseparated_literal_suffix` lint."##,
     },
     Lint {
         label: "clippy::serde_api_misuse",
@@ -7107,6 +7337,10 @@ match."##,
         description: r##"Checks for the `as_bytes` method called on string literals
 that contain only ASCII characters."##,
     },
+    Lint {
+        label: "clippy::string_slice",
+        description: r##"Checks for slice operations on strings"##,
+    },
     Lint {
         label: "clippy::string_to_string",
         description: r##"This lint checks for `.to_string()` method calls on values of type `String`."##,
@@ -7206,6 +7440,10 @@ in a macro that does formatting."##,
         description: r##"Checks for function arguments and let bindings denoted as
 `ref`."##,
     },
+    Lint {
+        label: "clippy::trailing_empty_array",
+        description: r##"Displays a warning when a struct with a trailing zero-sized array is declared without a `repr` attribute."##,
+    },
     Lint {
         label: "clippy::trait_duplication_in_bounds",
         description: r##"Checks for cases where generics are being used and multiple
@@ -7231,7 +7469,10 @@ syntax specifications for trait bounds are used simultaneously."##,
         label: "clippy::transmute_int_to_float",
         description: r##"Checks for transmutes from an integer to a float."##,
     },
-    Lint { label: "clippy::transmute_num_to_bytes", description: r##""## },
+    Lint {
+        label: "clippy::transmute_num_to_bytes",
+        description: r##"Checks for transmutes from a number to an array of `u8`"##,
+    },
     Lint {
         label: "clippy::transmute_ptr_to_ptr",
         description: r##"Checks for transmutes from a pointer to a pointer, or
@@ -7310,6 +7551,7 @@ unit literal (`()`)."##,
         description: r##"Checks for comparisons to unit. This includes all binary
 comparisons (like `==` and `<`) and asserts."##,
     },
+    Lint { label: "clippy::unit_hash", description: r##"Detects `().hash(_)`."## },
     Lint {
         label: "clippy::unit_return_expecting_ord",
         description: r##"Checks for functions that expect closures of type
@@ -7418,7 +7660,9 @@ name."##,
     Lint {
         label: "clippy::unseparated_literal_suffix",
         description: r##"Warns if literal suffixes are not separated by an
-underscore."##,
+underscore.
+To enforce unseparated literal suffix style,
+see the `separated_literal_suffix` lint."##,
     },
     Lint {
         label: "clippy::unsound_collection_transmute",
@@ -7460,7 +7704,7 @@ by nibble or byte."##,
     },
     Lint {
         label: "clippy::unwrap_in_result",
-        description: r##"Checks for functions of type Result that contain `expect()` or `unwrap()`"##,
+        description: r##"Checks for functions of type `Result` that contain `expect()` or `unwrap()`"##,
     },
     Lint {
         label: "clippy::unwrap_or_else_default",
@@ -7656,3 +7900,594 @@ architecture."##,
 zero-sized types"##,
     },
 ];
+pub const CLIPPY_LINT_GROUPS: &[LintGroup] = &[
+    LintGroup {
+        lint: Lint {
+            label: "clippy::cargo",
+            description: r##"lint group for: clippy::cargo_common_metadata, clippy::multiple_crate_versions, clippy::negative_feature_names, clippy::redundant_feature_names, clippy::wildcard_dependencies"##,
+        },
+        children: &[
+            "clippy::cargo_common_metadata",
+            "clippy::multiple_crate_versions",
+            "clippy::negative_feature_names",
+            "clippy::redundant_feature_names",
+            "clippy::wildcard_dependencies",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "clippy::complexity",
+            description: r##"lint group for: clippy::bind_instead_of_map, clippy::bool_comparison, clippy::borrowed_box, clippy::char_lit_as_u8, clippy::clone_on_copy, clippy::crosspointer_transmute, clippy::deprecated_cfg_attr, clippy::deref_addrof, clippy::derivable_impls, clippy::diverging_sub_expression, clippy::double_comparisons, clippy::double_parens, clippy::duration_subsec, clippy::explicit_counter_loop, clippy::explicit_write, clippy::extra_unused_lifetimes, clippy::filter_map_identity, clippy::filter_next, clippy::flat_map_identity, clippy::get_last_with_len, clippy::identity_op, clippy::inspect_for_each, clippy::int_plus_one, clippy::iter_count, clippy::manual_filter_map, clippy::manual_find_map, clippy::manual_flatten, clippy::manual_split_once, clippy::manual_strip, clippy::manual_swap, clippy::manual_unwrap_or, clippy::map_identity, clippy::match_as_ref, clippy::match_single_binding, clippy::needless_arbitrary_self_type, clippy::needless_bool, clippy::needless_borrowed_reference, clippy::needless_lifetimes, clippy::needless_option_as_deref, clippy::needless_question_mark, clippy::needless_splitn, clippy::needless_update, clippy::neg_cmp_op_on_partial_ord, clippy::no_effect, clippy::nonminimal_bool, clippy::option_as_ref_deref, clippy::option_filter_map, clippy::option_map_unit_fn, clippy::overflow_check_conditional, clippy::partialeq_ne_impl, clippy::precedence, clippy::ptr_offset_with_cast, clippy::range_zip_with_len, clippy::redundant_closure_call, clippy::redundant_slicing, clippy::ref_in_deref, clippy::repeat_once, clippy::result_map_unit_fn, clippy::search_is_some, clippy::short_circuit_statement, clippy::single_element_loop, clippy::skip_while_next, clippy::string_from_utf8_as_bytes, clippy::strlen_on_c_strings, clippy::temporary_assignment, clippy::too_many_arguments, clippy::transmute_bytes_to_str, clippy::transmute_float_to_int, clippy::transmute_int_to_bool, clippy::transmute_int_to_char, clippy::transmute_int_to_float, clippy::transmute_num_to_bytes, clippy::transmute_ptr_to_ref, clippy::transmutes_expressible_as_ptr_casts, clippy::type_complexity, clippy::unit_arg, clippy::unnecessary_cast, clippy::unnecessary_filter_map, clippy::unnecessary_operation, clippy::unnecessary_sort_by, clippy::unnecessary_unwrap, clippy::unneeded_wildcard_pattern, clippy::useless_asref, clippy::useless_conversion, clippy::useless_format, clippy::vec_box, clippy::while_let_loop, clippy::wildcard_in_or_patterns, clippy::zero_divided_by_zero, clippy::zero_prefixed_literal"##,
+        },
+        children: &[
+            "clippy::bind_instead_of_map",
+            "clippy::bool_comparison",
+            "clippy::borrowed_box",
+            "clippy::char_lit_as_u8",
+            "clippy::clone_on_copy",
+            "clippy::crosspointer_transmute",
+            "clippy::deprecated_cfg_attr",
+            "clippy::deref_addrof",
+            "clippy::derivable_impls",
+            "clippy::diverging_sub_expression",
+            "clippy::double_comparisons",
+            "clippy::double_parens",
+            "clippy::duration_subsec",
+            "clippy::explicit_counter_loop",
+            "clippy::explicit_write",
+            "clippy::extra_unused_lifetimes",
+            "clippy::filter_map_identity",
+            "clippy::filter_next",
+            "clippy::flat_map_identity",
+            "clippy::get_last_with_len",
+            "clippy::identity_op",
+            "clippy::inspect_for_each",
+            "clippy::int_plus_one",
+            "clippy::iter_count",
+            "clippy::manual_filter_map",
+            "clippy::manual_find_map",
+            "clippy::manual_flatten",
+            "clippy::manual_split_once",
+            "clippy::manual_strip",
+            "clippy::manual_swap",
+            "clippy::manual_unwrap_or",
+            "clippy::map_identity",
+            "clippy::match_as_ref",
+            "clippy::match_single_binding",
+            "clippy::needless_arbitrary_self_type",
+            "clippy::needless_bool",
+            "clippy::needless_borrowed_reference",
+            "clippy::needless_lifetimes",
+            "clippy::needless_option_as_deref",
+            "clippy::needless_question_mark",
+            "clippy::needless_splitn",
+            "clippy::needless_update",
+            "clippy::neg_cmp_op_on_partial_ord",
+            "clippy::no_effect",
+            "clippy::nonminimal_bool",
+            "clippy::option_as_ref_deref",
+            "clippy::option_filter_map",
+            "clippy::option_map_unit_fn",
+            "clippy::overflow_check_conditional",
+            "clippy::partialeq_ne_impl",
+            "clippy::precedence",
+            "clippy::ptr_offset_with_cast",
+            "clippy::range_zip_with_len",
+            "clippy::redundant_closure_call",
+            "clippy::redundant_slicing",
+            "clippy::ref_in_deref",
+            "clippy::repeat_once",
+            "clippy::result_map_unit_fn",
+            "clippy::search_is_some",
+            "clippy::short_circuit_statement",
+            "clippy::single_element_loop",
+            "clippy::skip_while_next",
+            "clippy::string_from_utf8_as_bytes",
+            "clippy::strlen_on_c_strings",
+            "clippy::temporary_assignment",
+            "clippy::too_many_arguments",
+            "clippy::transmute_bytes_to_str",
+            "clippy::transmute_float_to_int",
+            "clippy::transmute_int_to_bool",
+            "clippy::transmute_int_to_char",
+            "clippy::transmute_int_to_float",
+            "clippy::transmute_num_to_bytes",
+            "clippy::transmute_ptr_to_ref",
+            "clippy::transmutes_expressible_as_ptr_casts",
+            "clippy::type_complexity",
+            "clippy::unit_arg",
+            "clippy::unnecessary_cast",
+            "clippy::unnecessary_filter_map",
+            "clippy::unnecessary_operation",
+            "clippy::unnecessary_sort_by",
+            "clippy::unnecessary_unwrap",
+            "clippy::unneeded_wildcard_pattern",
+            "clippy::useless_asref",
+            "clippy::useless_conversion",
+            "clippy::useless_format",
+            "clippy::vec_box",
+            "clippy::while_let_loop",
+            "clippy::wildcard_in_or_patterns",
+            "clippy::zero_divided_by_zero",
+            "clippy::zero_prefixed_literal",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "clippy::correctness",
+            description: r##"lint group for: clippy::absurd_extreme_comparisons, clippy::almost_swapped, clippy::approx_constant, clippy::async_yields_async, clippy::bad_bit_mask, clippy::cast_ref_to_mut, clippy::clone_double_ref, clippy::cmp_nan, clippy::deprecated_semver, clippy::derive_hash_xor_eq, clippy::derive_ord_xor_partial_ord, clippy::drop_copy, clippy::drop_ref, clippy::enum_clike_unportable_variant, clippy::eq_op, clippy::erasing_op, clippy::fn_address_comparisons, clippy::forget_copy, clippy::forget_ref, clippy::if_let_mutex, clippy::if_same_then_else, clippy::ifs_same_cond, clippy::ineffective_bit_mask, clippy::infinite_iter, clippy::inherent_to_string_shadow_display, clippy::inline_fn_without_body, clippy::invalid_null_ptr_usage, clippy::invalid_regex, clippy::invisible_characters, clippy::iter_next_loop, clippy::iterator_step_by_zero, clippy::let_underscore_lock, clippy::logic_bug, clippy::match_str_case_mismatch, clippy::mem_replace_with_uninit, clippy::min_max, clippy::mismatched_target_os, clippy::mistyped_literal_suffixes, clippy::modulo_one, clippy::mut_from_ref, clippy::never_loop, clippy::non_octal_unix_permissions, clippy::nonsensical_open_options, clippy::not_unsafe_ptr_arg_deref, clippy::option_env_unwrap, clippy::out_of_bounds_indexing, clippy::panicking_unwrap, clippy::possible_missing_comma, clippy::reversed_empty_ranges, clippy::self_assignment, clippy::serde_api_misuse, clippy::size_of_in_element_count, clippy::suspicious_splitn, clippy::to_string_in_display, clippy::transmuting_null, clippy::undropped_manually_drops, clippy::uninit_assumed_init, clippy::uninit_vec, clippy::unit_cmp, clippy::unit_hash, clippy::unit_return_expecting_ord, clippy::unsound_collection_transmute, clippy::unused_io_amount, clippy::useless_attribute, clippy::vec_resize_to_zero, clippy::vtable_address_comparisons, clippy::while_immutable_condition, clippy::wrong_transmute, clippy::zst_offset"##,
+        },
+        children: &[
+            "clippy::absurd_extreme_comparisons",
+            "clippy::almost_swapped",
+            "clippy::approx_constant",
+            "clippy::async_yields_async",
+            "clippy::bad_bit_mask",
+            "clippy::cast_ref_to_mut",
+            "clippy::clone_double_ref",
+            "clippy::cmp_nan",
+            "clippy::deprecated_semver",
+            "clippy::derive_hash_xor_eq",
+            "clippy::derive_ord_xor_partial_ord",
+            "clippy::drop_copy",
+            "clippy::drop_ref",
+            "clippy::enum_clike_unportable_variant",
+            "clippy::eq_op",
+            "clippy::erasing_op",
+            "clippy::fn_address_comparisons",
+            "clippy::forget_copy",
+            "clippy::forget_ref",
+            "clippy::if_let_mutex",
+            "clippy::if_same_then_else",
+            "clippy::ifs_same_cond",
+            "clippy::ineffective_bit_mask",
+            "clippy::infinite_iter",
+            "clippy::inherent_to_string_shadow_display",
+            "clippy::inline_fn_without_body",
+            "clippy::invalid_null_ptr_usage",
+            "clippy::invalid_regex",
+            "clippy::invisible_characters",
+            "clippy::iter_next_loop",
+            "clippy::iterator_step_by_zero",
+            "clippy::let_underscore_lock",
+            "clippy::logic_bug",
+            "clippy::match_str_case_mismatch",
+            "clippy::mem_replace_with_uninit",
+            "clippy::min_max",
+            "clippy::mismatched_target_os",
+            "clippy::mistyped_literal_suffixes",
+            "clippy::modulo_one",
+            "clippy::mut_from_ref",
+            "clippy::never_loop",
+            "clippy::non_octal_unix_permissions",
+            "clippy::nonsensical_open_options",
+            "clippy::not_unsafe_ptr_arg_deref",
+            "clippy::option_env_unwrap",
+            "clippy::out_of_bounds_indexing",
+            "clippy::panicking_unwrap",
+            "clippy::possible_missing_comma",
+            "clippy::reversed_empty_ranges",
+            "clippy::self_assignment",
+            "clippy::serde_api_misuse",
+            "clippy::size_of_in_element_count",
+            "clippy::suspicious_splitn",
+            "clippy::to_string_in_display",
+            "clippy::transmuting_null",
+            "clippy::undropped_manually_drops",
+            "clippy::uninit_assumed_init",
+            "clippy::uninit_vec",
+            "clippy::unit_cmp",
+            "clippy::unit_hash",
+            "clippy::unit_return_expecting_ord",
+            "clippy::unsound_collection_transmute",
+            "clippy::unused_io_amount",
+            "clippy::useless_attribute",
+            "clippy::vec_resize_to_zero",
+            "clippy::vtable_address_comparisons",
+            "clippy::while_immutable_condition",
+            "clippy::wrong_transmute",
+            "clippy::zst_offset",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "clippy::deprecated",
+            description: r##"lint group for: clippy::assign_ops, clippy::extend_from_slice, clippy::filter_map, clippy::find_map, clippy::if_let_redundant_pattern_matching, clippy::misaligned_transmute, clippy::pub_enum_variant_names, clippy::range_step_by_zero, clippy::regex_macro, clippy::replace_consts, clippy::should_assert_eq, clippy::unsafe_vector_initialization, clippy::unstable_as_mut_slice, clippy::unstable_as_slice, clippy::unused_collect, clippy::wrong_pub_self_convention"##,
+        },
+        children: &[
+            "clippy::assign_ops",
+            "clippy::extend_from_slice",
+            "clippy::filter_map",
+            "clippy::find_map",
+            "clippy::if_let_redundant_pattern_matching",
+            "clippy::misaligned_transmute",
+            "clippy::pub_enum_variant_names",
+            "clippy::range_step_by_zero",
+            "clippy::regex_macro",
+            "clippy::replace_consts",
+            "clippy::should_assert_eq",
+            "clippy::unsafe_vector_initialization",
+            "clippy::unstable_as_mut_slice",
+            "clippy::unstable_as_slice",
+            "clippy::unused_collect",
+            "clippy::wrong_pub_self_convention",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "clippy::nursery",
+            description: r##"lint group for: clippy::branches_sharing_code, clippy::cognitive_complexity, clippy::debug_assert_with_mut_call, clippy::disallowed_methods, clippy::disallowed_types, clippy::empty_line_after_outer_attr, clippy::equatable_if_let, clippy::fallible_impl_from, clippy::future_not_send, clippy::imprecise_flops, clippy::index_refutable_slice, clippy::missing_const_for_fn, clippy::mutex_integer, clippy::nonstandard_macro_braces, clippy::option_if_let_else, clippy::path_buf_push_overwrite, clippy::redundant_pub_crate, clippy::string_lit_as_bytes, clippy::suboptimal_flops, clippy::suspicious_operation_groupings, clippy::trailing_empty_array, clippy::trivial_regex, clippy::use_self, clippy::useless_let_if_seq, clippy::useless_transmute"##,
+        },
+        children: &[
+            "clippy::branches_sharing_code",
+            "clippy::cognitive_complexity",
+            "clippy::debug_assert_with_mut_call",
+            "clippy::disallowed_methods",
+            "clippy::disallowed_types",
+            "clippy::empty_line_after_outer_attr",
+            "clippy::equatable_if_let",
+            "clippy::fallible_impl_from",
+            "clippy::future_not_send",
+            "clippy::imprecise_flops",
+            "clippy::index_refutable_slice",
+            "clippy::missing_const_for_fn",
+            "clippy::mutex_integer",
+            "clippy::nonstandard_macro_braces",
+            "clippy::option_if_let_else",
+            "clippy::path_buf_push_overwrite",
+            "clippy::redundant_pub_crate",
+            "clippy::string_lit_as_bytes",
+            "clippy::suboptimal_flops",
+            "clippy::suspicious_operation_groupings",
+            "clippy::trailing_empty_array",
+            "clippy::trivial_regex",
+            "clippy::use_self",
+            "clippy::useless_let_if_seq",
+            "clippy::useless_transmute",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "clippy::pedantic",
+            description: r##"lint group for: clippy::await_holding_lock, clippy::await_holding_refcell_ref, clippy::case_sensitive_file_extension_comparisons, clippy::cast_lossless, clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_precision_loss, clippy::cast_ptr_alignment, clippy::cast_sign_loss, clippy::checked_conversions, clippy::cloned_instead_of_copied, clippy::copy_iterator, clippy::default_trait_access, clippy::doc_markdown, clippy::empty_enum, clippy::enum_glob_use, clippy::expl_impl_clone_on_copy, clippy::explicit_deref_methods, clippy::explicit_into_iter_loop, clippy::explicit_iter_loop, clippy::filter_map_next, clippy::flat_map_option, clippy::float_cmp, clippy::fn_params_excessive_bools, clippy::from_iter_instead_of_collect, clippy::if_not_else, clippy::implicit_clone, clippy::implicit_hasher, clippy::implicit_saturating_sub, clippy::inconsistent_struct_constructor, clippy::inefficient_to_string, clippy::inline_always, clippy::invalid_upcast_comparisons, clippy::items_after_statements, clippy::iter_not_returning_iterator, clippy::large_digit_groups, clippy::large_stack_arrays, clippy::large_types_passed_by_value, clippy::let_underscore_drop, clippy::let_unit_value, clippy::linkedlist, clippy::macro_use_imports, clippy::manual_assert, clippy::manual_ok_or, clippy::many_single_char_names, clippy::map_flatten, clippy::map_unwrap_or, clippy::match_bool, clippy::match_on_vec_items, clippy::match_same_arms, clippy::match_wild_err_arm, clippy::match_wildcard_for_single_variants, clippy::maybe_infinite_iter, clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::module_name_repetitions, clippy::must_use_candidate, clippy::mut_mut, clippy::naive_bytecount, clippy::needless_bitwise_bool, clippy::needless_continue, clippy::needless_for_each, clippy::needless_pass_by_value, clippy::no_effect_underscore_binding, clippy::option_option, clippy::ptr_as_ptr, clippy::range_minus_one, clippy::range_plus_one, clippy::redundant_closure_for_method_calls, clippy::redundant_else, clippy::ref_binding_to_reference, clippy::ref_option_ref, clippy::same_functions_in_if_condition, clippy::semicolon_if_nothing_returned, clippy::similar_names, clippy::single_match_else, clippy::string_add_assign, clippy::struct_excessive_bools, clippy::too_many_lines, clippy::trait_duplication_in_bounds, clippy::transmute_ptr_to_ptr, clippy::trivially_copy_pass_by_ref, clippy::type_repetition_in_bounds, clippy::unicode_not_nfc, clippy::unnecessary_wraps, clippy::unnested_or_patterns, clippy::unreadable_literal, clippy::unsafe_derive_deserialize, clippy::unused_async, clippy::unused_self, clippy::used_underscore_binding, clippy::verbose_bit_mask, clippy::wildcard_imports, clippy::zero_sized_map_values"##,
+        },
+        children: &[
+            "clippy::await_holding_lock",
+            "clippy::await_holding_refcell_ref",
+            "clippy::case_sensitive_file_extension_comparisons",
+            "clippy::cast_lossless",
+            "clippy::cast_possible_truncation",
+            "clippy::cast_possible_wrap",
+            "clippy::cast_precision_loss",
+            "clippy::cast_ptr_alignment",
+            "clippy::cast_sign_loss",
+            "clippy::checked_conversions",
+            "clippy::cloned_instead_of_copied",
+            "clippy::copy_iterator",
+            "clippy::default_trait_access",
+            "clippy::doc_markdown",
+            "clippy::empty_enum",
+            "clippy::enum_glob_use",
+            "clippy::expl_impl_clone_on_copy",
+            "clippy::explicit_deref_methods",
+            "clippy::explicit_into_iter_loop",
+            "clippy::explicit_iter_loop",
+            "clippy::filter_map_next",
+            "clippy::flat_map_option",
+            "clippy::float_cmp",
+            "clippy::fn_params_excessive_bools",
+            "clippy::from_iter_instead_of_collect",
+            "clippy::if_not_else",
+            "clippy::implicit_clone",
+            "clippy::implicit_hasher",
+            "clippy::implicit_saturating_sub",
+            "clippy::inconsistent_struct_constructor",
+            "clippy::inefficient_to_string",
+            "clippy::inline_always",
+            "clippy::invalid_upcast_comparisons",
+            "clippy::items_after_statements",
+            "clippy::iter_not_returning_iterator",
+            "clippy::large_digit_groups",
+            "clippy::large_stack_arrays",
+            "clippy::large_types_passed_by_value",
+            "clippy::let_underscore_drop",
+            "clippy::let_unit_value",
+            "clippy::linkedlist",
+            "clippy::macro_use_imports",
+            "clippy::manual_assert",
+            "clippy::manual_ok_or",
+            "clippy::many_single_char_names",
+            "clippy::map_flatten",
+            "clippy::map_unwrap_or",
+            "clippy::match_bool",
+            "clippy::match_on_vec_items",
+            "clippy::match_same_arms",
+            "clippy::match_wild_err_arm",
+            "clippy::match_wildcard_for_single_variants",
+            "clippy::maybe_infinite_iter",
+            "clippy::missing_errors_doc",
+            "clippy::missing_panics_doc",
+            "clippy::module_name_repetitions",
+            "clippy::must_use_candidate",
+            "clippy::mut_mut",
+            "clippy::naive_bytecount",
+            "clippy::needless_bitwise_bool",
+            "clippy::needless_continue",
+            "clippy::needless_for_each",
+            "clippy::needless_pass_by_value",
+            "clippy::no_effect_underscore_binding",
+            "clippy::option_option",
+            "clippy::ptr_as_ptr",
+            "clippy::range_minus_one",
+            "clippy::range_plus_one",
+            "clippy::redundant_closure_for_method_calls",
+            "clippy::redundant_else",
+            "clippy::ref_binding_to_reference",
+            "clippy::ref_option_ref",
+            "clippy::same_functions_in_if_condition",
+            "clippy::semicolon_if_nothing_returned",
+            "clippy::similar_names",
+            "clippy::single_match_else",
+            "clippy::string_add_assign",
+            "clippy::struct_excessive_bools",
+            "clippy::too_many_lines",
+            "clippy::trait_duplication_in_bounds",
+            "clippy::transmute_ptr_to_ptr",
+            "clippy::trivially_copy_pass_by_ref",
+            "clippy::type_repetition_in_bounds",
+            "clippy::unicode_not_nfc",
+            "clippy::unnecessary_wraps",
+            "clippy::unnested_or_patterns",
+            "clippy::unreadable_literal",
+            "clippy::unsafe_derive_deserialize",
+            "clippy::unused_async",
+            "clippy::unused_self",
+            "clippy::used_underscore_binding",
+            "clippy::verbose_bit_mask",
+            "clippy::wildcard_imports",
+            "clippy::zero_sized_map_values",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "clippy::perf",
+            description: r##"lint group for: clippy::box_collection, clippy::boxed_local, clippy::cmp_owned, clippy::expect_fun_call, clippy::extend_with_drain, clippy::format_in_format_args, clippy::iter_nth, clippy::large_const_arrays, clippy::large_enum_variant, clippy::manual_memcpy, clippy::manual_str_repeat, clippy::map_entry, clippy::mutex_atomic, clippy::needless_collect, clippy::or_fun_call, clippy::redundant_allocation, clippy::redundant_clone, clippy::single_char_pattern, clippy::slow_vector_initialization, clippy::stable_sort_primitive, clippy::to_string_in_format_args, clippy::useless_vec, clippy::vec_init_then_push"##,
+        },
+        children: &[
+            "clippy::box_collection",
+            "clippy::boxed_local",
+            "clippy::cmp_owned",
+            "clippy::expect_fun_call",
+            "clippy::extend_with_drain",
+            "clippy::format_in_format_args",
+            "clippy::iter_nth",
+            "clippy::large_const_arrays",
+            "clippy::large_enum_variant",
+            "clippy::manual_memcpy",
+            "clippy::manual_str_repeat",
+            "clippy::map_entry",
+            "clippy::mutex_atomic",
+            "clippy::needless_collect",
+            "clippy::or_fun_call",
+            "clippy::redundant_allocation",
+            "clippy::redundant_clone",
+            "clippy::single_char_pattern",
+            "clippy::slow_vector_initialization",
+            "clippy::stable_sort_primitive",
+            "clippy::to_string_in_format_args",
+            "clippy::useless_vec",
+            "clippy::vec_init_then_push",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "clippy::restriction",
+            description: r##"lint group for: clippy::as_conversions, clippy::clone_on_ref_ptr, clippy::create_dir, clippy::dbg_macro, clippy::decimal_literal_representation, clippy::default_numeric_fallback, clippy::disallowed_script_idents, clippy::else_if_without_else, clippy::exhaustive_enums, clippy::exhaustive_structs, clippy::exit, clippy::expect_used, clippy::filetype_is_file, clippy::float_arithmetic, clippy::float_cmp_const, clippy::fn_to_numeric_cast_any, clippy::get_unwrap, clippy::if_then_some_else_none, clippy::implicit_return, clippy::indexing_slicing, clippy::inline_asm_x86_att_syntax, clippy::inline_asm_x86_intel_syntax, clippy::integer_arithmetic, clippy::integer_division, clippy::let_underscore_must_use, clippy::lossy_float_literal, clippy::map_err_ignore, clippy::mem_forget, clippy::missing_docs_in_private_items, clippy::missing_enforced_import_renames, clippy::missing_inline_in_public_items, clippy::mod_module_files, clippy::modulo_arithmetic, clippy::multiple_inherent_impl, clippy::non_ascii_literal, clippy::panic, clippy::panic_in_result_fn, clippy::pattern_type_mismatch, clippy::print_stderr, clippy::print_stdout, clippy::rc_buffer, clippy::rc_mutex, clippy::rest_pat_in_fully_bound_structs, clippy::same_name_method, clippy::self_named_module_files, clippy::separated_literal_suffix, clippy::shadow_reuse, clippy::shadow_same, clippy::shadow_unrelated, clippy::str_to_string, clippy::string_add, clippy::string_slice, clippy::string_to_string, clippy::todo, clippy::undocumented_unsafe_blocks, clippy::unimplemented, clippy::unnecessary_self_imports, clippy::unneeded_field_pattern, clippy::unreachable, clippy::unseparated_literal_suffix, clippy::unwrap_in_result, clippy::unwrap_used, clippy::use_debug, clippy::verbose_file_reads, clippy::wildcard_enum_match_arm"##,
+        },
+        children: &[
+            "clippy::as_conversions",
+            "clippy::clone_on_ref_ptr",
+            "clippy::create_dir",
+            "clippy::dbg_macro",
+            "clippy::decimal_literal_representation",
+            "clippy::default_numeric_fallback",
+            "clippy::disallowed_script_idents",
+            "clippy::else_if_without_else",
+            "clippy::exhaustive_enums",
+            "clippy::exhaustive_structs",
+            "clippy::exit",
+            "clippy::expect_used",
+            "clippy::filetype_is_file",
+            "clippy::float_arithmetic",
+            "clippy::float_cmp_const",
+            "clippy::fn_to_numeric_cast_any",
+            "clippy::get_unwrap",
+            "clippy::if_then_some_else_none",
+            "clippy::implicit_return",
+            "clippy::indexing_slicing",
+            "clippy::inline_asm_x86_att_syntax",
+            "clippy::inline_asm_x86_intel_syntax",
+            "clippy::integer_arithmetic",
+            "clippy::integer_division",
+            "clippy::let_underscore_must_use",
+            "clippy::lossy_float_literal",
+            "clippy::map_err_ignore",
+            "clippy::mem_forget",
+            "clippy::missing_docs_in_private_items",
+            "clippy::missing_enforced_import_renames",
+            "clippy::missing_inline_in_public_items",
+            "clippy::mod_module_files",
+            "clippy::modulo_arithmetic",
+            "clippy::multiple_inherent_impl",
+            "clippy::non_ascii_literal",
+            "clippy::panic",
+            "clippy::panic_in_result_fn",
+            "clippy::pattern_type_mismatch",
+            "clippy::print_stderr",
+            "clippy::print_stdout",
+            "clippy::rc_buffer",
+            "clippy::rc_mutex",
+            "clippy::rest_pat_in_fully_bound_structs",
+            "clippy::same_name_method",
+            "clippy::self_named_module_files",
+            "clippy::separated_literal_suffix",
+            "clippy::shadow_reuse",
+            "clippy::shadow_same",
+            "clippy::shadow_unrelated",
+            "clippy::str_to_string",
+            "clippy::string_add",
+            "clippy::string_slice",
+            "clippy::string_to_string",
+            "clippy::todo",
+            "clippy::undocumented_unsafe_blocks",
+            "clippy::unimplemented",
+            "clippy::unnecessary_self_imports",
+            "clippy::unneeded_field_pattern",
+            "clippy::unreachable",
+            "clippy::unseparated_literal_suffix",
+            "clippy::unwrap_in_result",
+            "clippy::unwrap_used",
+            "clippy::use_debug",
+            "clippy::verbose_file_reads",
+            "clippy::wildcard_enum_match_arm",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "clippy::style",
+            description: r##"lint group for: clippy::assertions_on_constants, clippy::assign_op_pattern, clippy::blacklisted_name, clippy::blocks_in_if_conditions, clippy::bool_assert_comparison, clippy::borrow_interior_mutable_const, clippy::builtin_type_shadow, clippy::bytes_nth, clippy::chars_last_cmp, clippy::chars_next_cmp, clippy::cmp_null, clippy::collapsible_else_if, clippy::collapsible_if, clippy::collapsible_match, clippy::comparison_chain, clippy::comparison_to_empty, clippy::declare_interior_mutable_const, clippy::double_must_use, clippy::double_neg, clippy::duplicate_underscore_argument, clippy::enum_variant_names, clippy::excessive_precision, clippy::field_reassign_with_default, clippy::fn_to_numeric_cast, clippy::fn_to_numeric_cast_with_truncation, clippy::for_kv_map, clippy::from_over_into, clippy::from_str_radix_10, clippy::inconsistent_digit_grouping, clippy::infallible_destructuring_match, clippy::inherent_to_string, clippy::into_iter_on_ref, clippy::iter_cloned_collect, clippy::iter_next_slice, clippy::iter_nth_zero, clippy::iter_skip_next, clippy::just_underscores_and_digits, clippy::len_without_is_empty, clippy::len_zero, clippy::let_and_return, clippy::main_recursion, clippy::manual_async_fn, clippy::manual_map, clippy::manual_non_exhaustive, clippy::manual_range_contains, clippy::manual_saturating_arithmetic, clippy::map_clone, clippy::map_collect_result_unit, clippy::match_like_matches_macro, clippy::match_overlapping_arm, clippy::match_ref_pats, clippy::match_result_ok, clippy::mem_replace_option_with_none, clippy::mem_replace_with_default, clippy::missing_safety_doc, clippy::mixed_case_hex_literals, clippy::module_inception, clippy::must_use_unit, clippy::mut_mutex_lock, clippy::needless_borrow, clippy::needless_doctest_main, clippy::needless_range_loop, clippy::needless_return, clippy::neg_multiply, clippy::new_ret_no_self, clippy::new_without_default, clippy::ok_expect, clippy::op_ref, clippy::option_map_or_none, clippy::print_literal, clippy::print_with_newline, clippy::println_empty_string, clippy::ptr_arg, clippy::ptr_eq, clippy::question_mark, clippy::redundant_closure, clippy::redundant_field_names, clippy::redundant_pattern, clippy::redundant_pattern_matching, clippy::redundant_static_lifetimes, clippy::result_map_or_into_option, clippy::result_unit_err, clippy::same_item_push, clippy::self_named_constructors, clippy::should_implement_trait, clippy::single_char_add_str, clippy::single_component_path_imports, clippy::single_match, clippy::string_extend_chars, clippy::tabs_in_doc_comments, clippy::to_digit_is_some, clippy::toplevel_ref_arg, clippy::try_err, clippy::unnecessary_fold, clippy::unnecessary_lazy_evaluations, clippy::unnecessary_mut_passed, clippy::unsafe_removed_from_name, clippy::unused_unit, clippy::unusual_byte_groupings, clippy::unwrap_or_else_default, clippy::upper_case_acronyms, clippy::while_let_on_iterator, clippy::write_literal, clippy::write_with_newline, clippy::writeln_empty_string, clippy::wrong_self_convention, clippy::zero_ptr"##,
+        },
+        children: &[
+            "clippy::assertions_on_constants",
+            "clippy::assign_op_pattern",
+            "clippy::blacklisted_name",
+            "clippy::blocks_in_if_conditions",
+            "clippy::bool_assert_comparison",
+            "clippy::borrow_interior_mutable_const",
+            "clippy::builtin_type_shadow",
+            "clippy::bytes_nth",
+            "clippy::chars_last_cmp",
+            "clippy::chars_next_cmp",
+            "clippy::cmp_null",
+            "clippy::collapsible_else_if",
+            "clippy::collapsible_if",
+            "clippy::collapsible_match",
+            "clippy::comparison_chain",
+            "clippy::comparison_to_empty",
+            "clippy::declare_interior_mutable_const",
+            "clippy::double_must_use",
+            "clippy::double_neg",
+            "clippy::duplicate_underscore_argument",
+            "clippy::enum_variant_names",
+            "clippy::excessive_precision",
+            "clippy::field_reassign_with_default",
+            "clippy::fn_to_numeric_cast",
+            "clippy::fn_to_numeric_cast_with_truncation",
+            "clippy::for_kv_map",
+            "clippy::from_over_into",
+            "clippy::from_str_radix_10",
+            "clippy::inconsistent_digit_grouping",
+            "clippy::infallible_destructuring_match",
+            "clippy::inherent_to_string",
+            "clippy::into_iter_on_ref",
+            "clippy::iter_cloned_collect",
+            "clippy::iter_next_slice",
+            "clippy::iter_nth_zero",
+            "clippy::iter_skip_next",
+            "clippy::just_underscores_and_digits",
+            "clippy::len_without_is_empty",
+            "clippy::len_zero",
+            "clippy::let_and_return",
+            "clippy::main_recursion",
+            "clippy::manual_async_fn",
+            "clippy::manual_map",
+            "clippy::manual_non_exhaustive",
+            "clippy::manual_range_contains",
+            "clippy::manual_saturating_arithmetic",
+            "clippy::map_clone",
+            "clippy::map_collect_result_unit",
+            "clippy::match_like_matches_macro",
+            "clippy::match_overlapping_arm",
+            "clippy::match_ref_pats",
+            "clippy::match_result_ok",
+            "clippy::mem_replace_option_with_none",
+            "clippy::mem_replace_with_default",
+            "clippy::missing_safety_doc",
+            "clippy::mixed_case_hex_literals",
+            "clippy::module_inception",
+            "clippy::must_use_unit",
+            "clippy::mut_mutex_lock",
+            "clippy::needless_borrow",
+            "clippy::needless_doctest_main",
+            "clippy::needless_range_loop",
+            "clippy::needless_return",
+            "clippy::neg_multiply",
+            "clippy::new_ret_no_self",
+            "clippy::new_without_default",
+            "clippy::ok_expect",
+            "clippy::op_ref",
+            "clippy::option_map_or_none",
+            "clippy::print_literal",
+            "clippy::print_with_newline",
+            "clippy::println_empty_string",
+            "clippy::ptr_arg",
+            "clippy::ptr_eq",
+            "clippy::question_mark",
+            "clippy::redundant_closure",
+            "clippy::redundant_field_names",
+            "clippy::redundant_pattern",
+            "clippy::redundant_pattern_matching",
+            "clippy::redundant_static_lifetimes",
+            "clippy::result_map_or_into_option",
+            "clippy::result_unit_err",
+            "clippy::same_item_push",
+            "clippy::self_named_constructors",
+            "clippy::should_implement_trait",
+            "clippy::single_char_add_str",
+            "clippy::single_component_path_imports",
+            "clippy::single_match",
+            "clippy::string_extend_chars",
+            "clippy::tabs_in_doc_comments",
+            "clippy::to_digit_is_some",
+            "clippy::toplevel_ref_arg",
+            "clippy::try_err",
+            "clippy::unnecessary_fold",
+            "clippy::unnecessary_lazy_evaluations",
+            "clippy::unnecessary_mut_passed",
+            "clippy::unsafe_removed_from_name",
+            "clippy::unused_unit",
+            "clippy::unusual_byte_groupings",
+            "clippy::unwrap_or_else_default",
+            "clippy::upper_case_acronyms",
+            "clippy::while_let_on_iterator",
+            "clippy::write_literal",
+            "clippy::write_with_newline",
+            "clippy::writeln_empty_string",
+            "clippy::wrong_self_convention",
+            "clippy::zero_ptr",
+        ],
+    },
+    LintGroup {
+        lint: Lint {
+            label: "clippy::suspicious",
+            description: r##"lint group for: clippy::blanket_clippy_restriction_lints, clippy::empty_loop, clippy::eval_order_dependence, clippy::float_equality_without_abs, clippy::for_loops_over_fallibles, clippy::misrefactored_assign_op, clippy::mut_range_bound, clippy::mutable_key_type, clippy::non_send_fields_in_send_ty, clippy::suspicious_arithmetic_impl, clippy::suspicious_assignment_formatting, clippy::suspicious_else_formatting, clippy::suspicious_map, clippy::suspicious_op_assign_impl, clippy::suspicious_unary_op_formatting"##,
+        },
+        children: &[
+            "clippy::blanket_clippy_restriction_lints",
+            "clippy::empty_loop",
+            "clippy::eval_order_dependence",
+            "clippy::float_equality_without_abs",
+            "clippy::for_loops_over_fallibles",
+            "clippy::misrefactored_assign_op",
+            "clippy::mut_range_bound",
+            "clippy::mutable_key_type",
+            "clippy::non_send_fields_in_send_ty",
+            "clippy::suspicious_arithmetic_impl",
+            "clippy::suspicious_assignment_formatting",
+            "clippy::suspicious_else_formatting",
+            "clippy::suspicious_map",
+            "clippy::suspicious_op_assign_impl",
+            "clippy::suspicious_unary_op_formatting",
+        ],
+    },
+];
diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs
index b58b86b38d..498a1b3323 100644
--- a/crates/mbe/src/lib.rs
+++ b/crates/mbe/src/lib.rs
@@ -120,15 +120,12 @@ impl Shift {
                             _ => tree_id,
                         }
                     }
-                    tt::TokenTree::Leaf(leaf) => {
-                        let id = match leaf {
-                            tt::Leaf::Literal(it) => it.id,
-                            tt::Leaf::Punct(it) => it.id,
-                            tt::Leaf::Ident(it) => it.id,
-                        };
-
-                        (id != tt::TokenId::unspecified()).then(|| id.0)
+                    tt::TokenTree::Leaf(tt::Leaf::Ident(ident))
+                        if ident.id != tt::TokenId::unspecified() =>
+                    {
+                        Some(ident.id.0)
                     }
+                    _ => None,
                 })
                 .max()
         }
@@ -248,10 +245,6 @@ impl DeclarativeMacro {
             None => (id, Origin::Def),
         }
     }
-
-    pub fn shift(&self) -> Shift {
-        self.shift
-    }
 }
 
 impl Rule {
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index b9b8f3a1b1..bdc56b3196 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -21,10 +21,20 @@ fn diagnostic_severity(
         DiagnosticLevel::Ice => lsp_types::DiagnosticSeverity::ERROR,
         DiagnosticLevel::Error => lsp_types::DiagnosticSeverity::ERROR,
         DiagnosticLevel::Warning => match &code {
-            Some(code) if config.warnings_as_hint.contains(&code.code) => {
+            // HACK: special case for `warnings` rustc lint.
+            Some(code)
+                if config.warnings_as_hint.iter().any(|lint| {
+                    lint == "warnings" || ide_db::helpers::lint_eq_or_in_group(&code.code, &lint)
+                }) =>
+            {
                 lsp_types::DiagnosticSeverity::HINT
             }
-            Some(code) if config.warnings_as_info.contains(&code.code) => {
+            // HACK: special case for `warnings` rustc lint.
+            Some(code)
+                if config.warnings_as_info.iter().any(|lint| {
+                    lint == "warnings" || ide_db::helpers::lint_eq_or_in_group(&code.code, &lint)
+                }) =>
+            {
                 lsp_types::DiagnosticSeverity::INFORMATION
             }
             _ => lsp_types::DiagnosticSeverity::WARNING,


Test patch:
diff --git a/crates/hir_def/src/macro_expansion_tests.rs b/crates/hir_def/src/macro_expansion_tests.rs
index c3116edc88..036d9147a0 100644
--- a/crates/hir_def/src/macro_expansion_tests.rs
+++ b/crates/hir_def/src/macro_expansion_tests.rs
@@ -16,21 +16,16 @@ mod proc_macros;
 
 use std::{iter, ops::Range};
 
-use ::mbe::TokenMap;
 use base_db::{fixture::WithFixture, SourceDatabase};
 use expect_test::Expect;
-use hir_expand::{
-    db::{AstDatabase, TokenExpander},
-    AstId, InFile, MacroDefId, MacroDefKind, MacroFile,
-};
+use hir_expand::{db::AstDatabase, InFile, MacroFile};
 use stdx::format_to;
 use syntax::{
     ast::{self, edit::IndentLevel},
-    AstNode, SyntaxElement,
-    SyntaxKind::{self, COMMENT, EOF, IDENT, LIFETIME_IDENT},
-    SyntaxNode, TextRange, T,
+    AstNode,
+    SyntaxKind::{COMMENT, EOF, IDENT, LIFETIME_IDENT},
+    SyntaxNode, T,
 };
-use tt::{Subtree, TokenId};
 
 use crate::{
     db::DefDatabase, nameres::ModuleSource, resolver::HasResolver, src::HasSource, test_db::TestDB,
@@ -66,47 +61,7 @@ fn check(ra_fixture: &str, mut expect: Expect) {
     // in the module and assume that, if impls's source is a different
     // `HirFileId`, than it came from macro expansion.
 
-    let mut text_edits = Vec::new();
     let mut expansions = Vec::new();
-
-    for macro_ in source_file.syntax().descendants().filter_map(ast::Macro::cast) {
-        let mut show_token_ids = false;
-        for comment in macro_.syntax().children_with_tokens().filter(|it| it.kind() == COMMENT) {
-            show_token_ids |= comment.to_string().contains("+tokenids");
-        }
-        if !show_token_ids {
-            continue;
-        }
-
-        let call_offset = macro_.syntax().text_range().start().into();
-        let file_ast_id = db.ast_id_map(source.file_id).ast_id(&macro_);
-        let ast_id = AstId::new(source.file_id, file_ast_id.upcast());
-        let kind = MacroDefKind::Declarative(ast_id);
-
-        let macro_def = db.macro_def(MacroDefId { krate, kind, local_inner: false }).unwrap();
-        if let TokenExpander::DeclarativeMacro { mac, def_site_token_map } = &*macro_def {
-            let tt = match &macro_ {
-                ast::Macro::MacroRules(mac) => mac.token_tree().unwrap(),
-                ast::Macro::MacroDef(_) => unimplemented!(""),
-            };
-
-            let tt_start = tt.syntax().text_range().start();
-            tt.syntax().descendants_with_tokens().filter_map(SyntaxElement::into_token).for_each(
-                |token| {
-                    let range = token.text_range().checked_sub(tt_start).unwrap();
-                    if let Some(id) = def_site_token_map.token_by_range(range) {
-                        let offset = (range.end() + tt_start).into();
-                        text_edits.push((offset..offset, format!("#{}", id.0)));
-                    }
-                },
-            );
-            text_edits.push((
-                call_offset..call_offset,
-                format!("// call ids will be shifted by {:?}\n", mac.shift()),
-            ));
-        }
-    }
-
     for macro_call in source_file.syntax().descendants().filter_map(ast::MacroCall::cast) {
         let macro_call = InFile::new(source.file_id, &macro_call);
         let mut error = None;
@@ -122,24 +77,24 @@ fn check(ra_fixture: &str, mut expect: Expect) {
         let macro_file = MacroFile { macro_call_id };
         let mut expansion_result = db.parse_macro_expansion(macro_file);
         expansion_result.err = expansion_result.err.or(error);
-        expansions.push((macro_call.value.clone(), expansion_result, db.macro_arg(macro_call_id)));
+        expansions.push((macro_call.value.clone(), expansion_result));
     }
 
-    for (call, exp, arg) in expansions.into_iter().rev() {
+    let mut expanded_text = source_file.to_string();
+
+    for (call, exp) in expansions.into_iter().rev() {
         let mut tree = false;
         let mut expect_errors = false;
-        let mut show_token_ids = false;
         for comment in call.syntax().children_with_tokens().filter(|it| it.kind() == COMMENT) {
             tree |= comment.to_string().contains("+tree");
             expect_errors |= comment.to_string().contains("+errors");
-            show_token_ids |= comment.to_string().contains("+tokenids");
         }
 
         let mut expn_text = String::new();
         if let Some(err) = exp.err {
             format_to!(expn_text, "/* error: {} */", err);
         }
-        if let Some((parse, token_map)) = exp.value {
+        if let Some((parse, _token_map)) = exp.value {
             if expect_errors {
                 assert!(!parse.errors().is_empty(), "no parse errors in expansion");
                 for e in parse.errors() {
@@ -152,10 +107,7 @@ fn check(ra_fixture: &str, mut expect: Expect) {
                     parse.errors()
                 );
             }
-            let pp = pretty_print_macro_expansion(
-                parse.syntax_node(),
-                show_token_ids.then(|| &*token_map),
-            );
+            let pp = pretty_print_macro_expansion(parse.syntax_node());
             let indent = IndentLevel::from_node(call.syntax());
             let pp = reindent(indent, pp);
             format_to!(expn_text, "{}", pp);
@@ -170,41 +122,14 @@ fn check(ra_fixture: &str, mut expect: Expect) {
         }
         let range = call.syntax().text_range();
         let range: Range<usize> = range.into();
-
-        if show_token_ids {
-            if let Some((tree, map)) = arg.as_deref() {
-                let tt_range = call.token_tree().unwrap().syntax().text_range();
-                let mut ranges = Vec::new();
-                extract_id_ranges(&mut ranges, &map, &tree);
-                for (range, id) in ranges {
-                    let idx = (tt_range.start() + range.end()).into();
-                    text_edits.push((idx..idx, format!("#{}", id.0)));
-                }
-            }
-            text_edits.push((range.start..range.start, "// ".into()));
-            call.to_string().match_indices('\n').for_each(|(offset, _)| {
-                let offset = offset + 1 + range.start;
-                text_edits.push((offset..offset, "// ".into()));
-            });
-            text_edits.push((range.end..range.end, "\n".into()));
-            text_edits.push((range.end..range.end, expn_text));
-        } else {
-            text_edits.push((range, expn_text));
-        }
-    }
-
-    text_edits.sort_by_key(|(range, _)| range.start);
-    text_edits.reverse();
-    let mut expanded_text = source_file.to_string();
-    for (range, text) in text_edits {
-        expanded_text.replace_range(range, &text);
+        expanded_text.replace_range(range, &expn_text)
     }
 
     for decl_id in def_map[local_id].scope.declarations() {
         if let ModuleDefId::AdtId(AdtId::StructId(struct_id)) = decl_id {
             let src = struct_id.lookup(&db).source(&db);
             if src.file_id.is_attr_macro(&db) || src.file_id.is_custom_derive(&db) {
-                let pp = pretty_print_macro_expansion(src.value.syntax().clone(), None);
+                let pp = pretty_print_macro_expansion(src.value.syntax().clone());
                 format_to!(expanded_text, "\n{}", pp)
             }
         }
@@ -213,7 +138,7 @@ fn check(ra_fixture: &str, mut expect: Expect) {
     for impl_id in def_map[local_id].scope.impls() {
         let src = impl_id.lookup(&db).source(&db);
         if src.file_id.is_builtin_derive(&db).is_some() {
-            let pp = pretty_print_macro_expansion(src.value.syntax().clone(), None);
+            let pp = pretty_print_macro_expansion(src.value.syntax().clone());
             format_to!(expanded_text, "\n{}", pp)
         }
     }
@@ -222,20 +147,6 @@ fn check(ra_fixture: &str, mut expect: Expect) {
     expect.assert_eq(&expanded_text);
 }
 
-fn extract_id_ranges(ranges: &mut Vec<(TextRange, TokenId)>, map: &TokenMap, tree: &Subtree) {
-    tree.token_trees.iter().for_each(|tree| match tree {
-        tt::TokenTree::Leaf(leaf) => {
-            let id = match leaf {
-                tt::Leaf::Literal(it) => it.id,
-                tt::Leaf::Punct(it) => it.id,
-                tt::Leaf::Ident(it) => it.id,
-            };
-            ranges.extend(map.ranges_by_token(id, SyntaxKind::ERROR).map(|range| (range, id)));
-        }
-        tt::TokenTree::Subtree(tree) => extract_id_ranges(ranges, map, tree),
-    });
-}
-
 fn reindent(indent: IndentLevel, pp: String) -> String {
     if !pp.contains('\n') {
         return pp;
@@ -252,7 +163,7 @@ fn reindent(indent: IndentLevel, pp: String) -> String {
     res
 }
 
-fn pretty_print_macro_expansion(expn: SyntaxNode, map: Option<&TokenMap>) -> String {
+fn pretty_print_macro_expansion(expn: SyntaxNode) -> String {
     let mut res = String::new();
     let mut prev_kind = EOF;
     let mut indent_level = 0;
@@ -295,12 +206,7 @@ fn pretty_print_macro_expansion(expn: SyntaxNode, map: Option<&TokenMap>) -> Str
             res.push_str(&"    ".repeat(level));
         }
         prev_kind = curr_kind;
-        format_to!(res, "{}", token);
-        if let Some(map) = map {
-            if let Some(id) = map.token_by_range(token.text_range()) {
-                format_to!(res, "#{}", id.0);
-            }
-        }
+        format_to!(res, "{}", token)
     }
     res
 }
diff --git a/crates/hir_def/src/macro_expansion_tests/mbe.rs b/crates/hir_def/src/macro_expansion_tests/mbe.rs
index 466c85fc5b..953750d360 100644
--- a/crates/hir_def/src/macro_expansion_tests/mbe.rs
+++ b/crates/hir_def/src/macro_expansion_tests/mbe.rs
@@ -10,42 +10,6 @@ use expect_test::expect;
 
 use crate::macro_expansion_tests::check;
 
-#[test]
-fn token_mapping_smoke_test() {
-    check(
-        r#"
-// +tokenids
-macro_rules! f {
-    ( struct $ident:ident ) => {
-        struct $ident {
-            map: ::std::collections::HashSet<()>,
-        }
-    };
-}
-
-// +tokenids
-f!(struct MyTraitMap2);
-"#,
-        expect![[r##"
-// call ids will be shifted by Shift(30)
-// +tokenids
-macro_rules! f {#0
-    (#1 struct#2 $#3ident#4:#5ident#6 )#1 =#7>#8 {#9
-        struct#10 $#11ident#12 {#13
-            map#14:#15 :#16:#17std#18:#19:#20collections#21:#22:#23HashSet#24<#25(#26)#26>#27,#28
-        }#13
-    }#9;#29
-}#0
-
-// // +tokenids
-// f!(struct#1 MyTraitMap2#2);
-struct#10 MyTraitMap2#32 {#13
-    map#14:#15 ::std#18::collections#21::HashSet#24<#25(#26)#26>#27,#28
-}#13
-"##]],
-    );
-}
-
 #[test]
 fn mbe_smoke_test() {
     check(
diff --git a/crates/ide_db/src/tests/sourcegen_lints.rs b/crates/ide_db/src/tests/sourcegen_lints.rs
index 3a44fab0f8..3c7587635f 100644
--- a/crates/ide_db/src/tests/sourcegen_lints.rs
+++ b/crates/ide_db/src/tests/sourcegen_lints.rs
@@ -22,6 +22,10 @@ pub struct Lint {
     pub label: &'static str,
     pub description: &'static str,
 }
+pub struct LintGroup {
+    pub lint: Lint,
+    pub children: &'static [&'static str],
+}
 ",
     );
 
@@ -60,17 +64,41 @@ fn generate_lint_descriptor(buf: &mut String) {
     let lints = stdout[start_lints..].lines().skip(1).take_while(|l| !l.is_empty()).map(|line| {
         let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
         let (_default_level, description) = rest.trim().split_once(char::is_whitespace).unwrap();
-        (name.trim(), Cow::Borrowed(description.trim()))
+        (name.trim(), Cow::Borrowed(description.trim()), vec![])
     });
     let lint_groups =
         stdout[start_lint_groups..].lines().skip(1).take_while(|l| !l.is_empty()).map(|line| {
             let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
-            (name.trim(), format!("lint group for: {}", lints.trim()).into())
+            (
+                name.trim(),
+                format!("lint group for: {}", lints.trim()).into(),
+                lints
+                    .split_ascii_whitespace()
+                    .map(|s| s.trim().trim_matches(',').replace("-", "_"))
+                    .collect(),
+            )
         });
 
-    lints.chain(lint_groups).sorted_by(|(ident, _), (ident2, _)| ident.cmp(ident2)).for_each(
-        |(name, description)| push_lint_completion(buf, &name.replace("-", "_"), &description),
-    );
+    let lints = lints
+        .chain(lint_groups)
+        .sorted_by(|(ident, ..), (ident2, ..)| ident.cmp(ident2))
+        .collect::<Vec<_>>();
+    for (name, description, ..) in &lints {
+        push_lint_completion(buf, &name.replace("-", "_"), &description);
+    }
+    buf.push_str("];\n");
+    buf.push_str(r#"pub const DEFAULT_LINT_GROUPS: &[LintGroup] = &["#);
+    for (name, description, children) in &lints {
+        if !children.is_empty() {
+            // HACK: warnings is emitted with a general description, not with its members
+            if name == &"warnings" {
+                push_lint_group(buf, &name, &description, &Vec::new());
+                continue;
+            }
+            push_lint_group(buf, &name.replace("-", "_"), &description, children);
+        }
+    }
+    buf.push('\n');
     buf.push_str("];\n");
 
     // rustdoc
@@ -84,22 +112,40 @@ fn generate_lint_descriptor(buf: &mut String) {
             let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
             let (_default_level, description) =
                 rest.trim().split_once(char::is_whitespace).unwrap();
-            (name.trim(), Cow::Borrowed(description.trim()))
+            (name.trim(), Cow::Borrowed(description.trim()), vec![])
         });
     let lint_groups_rustdoc =
         stdout[start_lint_groups_rustdoc..].lines().skip(2).take_while(|l| !l.is_empty()).map(
             |line| {
                 let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
-                (name.trim(), format!("lint group for: {}", lints.trim()).into())
+                (
+                    name.trim(),
+                    format!("lint group for: {}", lints.trim()).into(),
+                    lints
+                        .split_ascii_whitespace()
+                        .map(|s| s.trim().trim_matches(',').replace("-", "_"))
+                        .collect(),
+                )
             },
         );
 
-    lints_rustdoc
+    let lints_rustdoc = lints_rustdoc
         .chain(lint_groups_rustdoc)
-        .sorted_by(|(ident, _), (ident2, _)| ident.cmp(ident2))
-        .for_each(|(name, description)| {
-            push_lint_completion(buf, &name.replace("-", "_"), &description)
-        });
+        .sorted_by(|(ident, ..), (ident2, ..)| ident.cmp(ident2))
+        .collect::<Vec<_>>();
+
+    for (name, description, ..) in &lints_rustdoc {
+        push_lint_completion(buf, &name.replace("-", "_"), &description)
+    }
+    buf.push_str("];\n");
+
+    buf.push_str(r#"pub const RUSTDOC_LINT_GROUPS: &[LintGroup] = &["#);
+    for (name, description, children) in &lints_rustdoc {
+        if !children.is_empty() {
+            push_lint_group(buf, &name.replace("-", "_"), &description, children);
+        }
+    }
+    buf.push('\n');
     buf.push_str("];\n");
 }
 
@@ -140,6 +186,7 @@ fn unescape(s: &str) -> String {
 fn generate_descriptor_clippy(buf: &mut String, path: &Path) {
     let file_content = std::fs::read_to_string(path).unwrap();
     let mut clippy_lints: Vec<ClippyLint> = Vec::new();
+    let mut clippy_groups: std::collections::BTreeMap<String, Vec<String>> = Default::default();
 
     for line in file_content.lines().map(|line| line.trim()) {
         if let Some(line) = line.strip_prefix(r#""id": ""#) {
@@ -148,6 +195,13 @@ fn generate_descriptor_clippy(buf: &mut String, path: &Path) {
                 help: String::new(),
             };
             clippy_lints.push(clippy_lint)
+        } else if let Some(line) = line.strip_prefix(r#""group": ""#) {
+            if let Some(group) = line.strip_suffix("\",") {
+                clippy_groups
+                    .entry(group.to_owned())
+                    .or_default()
+                    .push(clippy_lints.last().unwrap().id.clone());
+            }
         } else if let Some(line) = line.strip_prefix(r#""docs": ""#) {
             let prefix_to_strip = r#" ### What it does"#;
             let line = match line.strip_prefix(prefix_to_strip) {
@@ -176,6 +230,18 @@ fn generate_descriptor_clippy(buf: &mut String, path: &Path) {
         push_lint_completion(buf, &lint_ident, &doc);
     }
     buf.push_str("];\n");
+
+    buf.push_str(r#"pub const CLIPPY_LINT_GROUPS: &[LintGroup] = &["#);
+    for (id, children) in clippy_groups {
+        let children = children.iter().map(|id| format!("clippy::{}", id)).collect::<Vec<_>>();
+        if !children.is_empty() {
+            let lint_ident = format!("clippy::{}", id);
+            let description = format!("lint group for: {}", children.iter().join(", "));
+            push_lint_group(buf, &lint_ident, &description, &children);
+        }
+    }
+    buf.push('\n');
+    buf.push_str("];\n");
 }
 
 fn push_lint_completion(buf: &mut String, label: &str, description: &str) {
@@ -183,9 +249,28 @@ fn push_lint_completion(buf: &mut String, label: &str, description: &str) {
         buf,
         r###"    Lint {{
         label: "{}",
-        description: r##"{}"##
+        description: r##"{}"##,
     }},"###,
         label,
-        description
+        description,
+    );
+}
+
+fn push_lint_group<'a>(buf: &mut String, label: &str, description: &str, children: &[String]) {
+    buf.push_str(
+        r###"    LintGroup {
+        lint:
+        "###,
+    );
+
+    push_lint_completion(buf, label, description);
+
+    let children = format!("&[{}]", children.iter().map(|it| format!("\"{}\"", it)).join(", "));
+    format_to!(
+        buf,
+        r###"
+        children: {},
+        }},"###,
+        children,
     );
 }