|
| 1 | +//! This lint is used to collect metadata about clippy lints. This metadata is exported as a json |
| 2 | +//! file and then used to generate the [clippy lint list](https://rust-lang.github.io/rust-clippy/master/index.html) |
| 3 | +//! |
| 4 | +//! This module and therefor the entire lint is guarded by a feature flag called |
| 5 | +//! `internal_metadata_lint` |
| 6 | +//! |
| 7 | +//! The metadata currently contains: |
| 8 | +//! - [ ] TODO The lint declaration line for [#1303](https://github.com/rust-lang/rust-clippy/issues/1303) |
| 9 | +//! and [#6492](https://github.com/rust-lang/rust-clippy/issues/6492) |
| 10 | +//! - [ ] TODO The Applicability for each lint for [#4310](https://github.com/rust-lang/rust-clippy/issues/4310) |
| 11 | +
|
| 12 | +// # Applicability |
| 13 | +// - TODO xFrednet 2021-01-17: Find all methods that take and modify applicability or predefine |
| 14 | +// them? |
| 15 | +// - TODO xFrednet 2021-01-17: Find lint emit and collect applicability |
| 16 | +// # NITs |
| 17 | +// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames |
| 18 | + |
| 19 | +use if_chain::if_chain; |
| 20 | +use rustc_hir::{ExprKind, Item, ItemKind, Mutability}; |
| 21 | +use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId}; |
| 22 | +use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 23 | +use rustc_span::{sym, Loc, Span}; |
| 24 | +use serde::Serialize; |
| 25 | +use std::fs::OpenOptions; |
| 26 | +use std::io::prelude::*; |
| 27 | + |
| 28 | +use crate::utils::internal_lints::is_lint_ref_type; |
| 29 | +use crate::utils::span_lint; |
| 30 | + |
| 31 | +const OUTPUT_FILE: &str = "metadata_collection.json"; |
| 32 | +const BLACK_LISTED_LINTS: [&str; 2] = ["lint_author", "deep_code_inspection"]; |
| 33 | + |
| 34 | +declare_clippy_lint! { |
| 35 | + /// **What it does:** Collects metadata about clippy lints for the website. |
| 36 | + /// |
| 37 | + /// This lint will be used to report problems of syntax parsing. You should hopefully never |
| 38 | + /// see this but never say never I guess ^^ |
| 39 | + /// |
| 40 | + /// **Why is this bad?** This is not a bad thing but definitely a hacky way to do it. See |
| 41 | + /// issue [#4310](https://github.com/rust-lang/rust-clippy/issues/4310) for a discussion |
| 42 | + /// about the implementation. |
| 43 | + /// |
| 44 | + /// **Known problems:** Hopefully none. It would be pretty uncool to have a problem here :) |
| 45 | + /// |
| 46 | + /// **Example output:** |
| 47 | + /// ```json,ignore |
| 48 | + /// { |
| 49 | + /// "id": "internal_metadata_collector", |
| 50 | + /// "id_span": { |
| 51 | + /// "path": "clippy_lints/src/utils/internal_lints/metadata_collector.rs", |
| 52 | + /// "line": 1 |
| 53 | + /// }, |
| 54 | + /// "group": "clippy::internal", |
| 55 | + /// "docs": " **What it does:** Collects metadata about clippy lints for the website. [...] " |
| 56 | + /// } |
| 57 | + /// ``` |
| 58 | + pub INTERNAL_METADATA_COLLECTOR, |
| 59 | + internal, |
| 60 | + "A busy bee collection metadata about lints" |
| 61 | +} |
| 62 | + |
| 63 | +impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]); |
| 64 | + |
| 65 | +#[allow(clippy::module_name_repetitions)] |
| 66 | +#[derive(Debug, Clone, Default)] |
| 67 | +pub struct MetadataCollector { |
| 68 | + lints: Vec<LintMetadata>, |
| 69 | +} |
| 70 | + |
| 71 | +impl Drop for MetadataCollector { |
| 72 | + fn drop(&mut self) { |
| 73 | + // You might ask: How hacky is this? |
| 74 | + // My answer: YES |
| 75 | + let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap(); |
| 76 | + writeln!(file, "{}", serde_json::to_string_pretty(&self.lints).unwrap()).unwrap(); |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +#[derive(Debug, Clone, Serialize)] |
| 81 | +struct LintMetadata { |
| 82 | + id: String, |
| 83 | + id_span: SerializableSpan, |
| 84 | + group: String, |
| 85 | + docs: String, |
| 86 | +} |
| 87 | + |
| 88 | +#[derive(Debug, Clone, Serialize)] |
| 89 | +struct SerializableSpan { |
| 90 | + path: String, |
| 91 | + line: usize, |
| 92 | +} |
| 93 | + |
| 94 | +impl SerializableSpan { |
| 95 | + fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Self { |
| 96 | + Self::from_span(cx, item.ident.span) |
| 97 | + } |
| 98 | + |
| 99 | + fn from_span(cx: &LateContext<'_>, span: Span) -> Self { |
| 100 | + let loc: Loc = cx.sess().source_map().lookup_char_pos(span.lo()); |
| 101 | + |
| 102 | + Self { |
| 103 | + path: format!("{}", loc.file.name), |
| 104 | + line: 1, |
| 105 | + } |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +impl<'tcx> LateLintPass<'tcx> for MetadataCollector { |
| 110 | + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { |
| 111 | + if_chain! { |
| 112 | + if let ItemKind::Static(ref ty, Mutability::Not, body_id) = item.kind; |
| 113 | + if is_lint_ref_type(cx, ty); |
| 114 | + let expr = &cx.tcx.hir().body(body_id).value; |
| 115 | + if let ExprKind::AddrOf(_, _, ref inner_exp) = expr.kind; |
| 116 | + if let ExprKind::Struct(_, _, _) = inner_exp.kind; |
| 117 | + then { |
| 118 | + let lint_name = item.ident.name.as_str().to_string().to_ascii_lowercase(); |
| 119 | + if BLACK_LISTED_LINTS.contains(&lint_name.as_str()) { |
| 120 | + return; |
| 121 | + } |
| 122 | + |
| 123 | + let group: String; |
| 124 | + let result = cx.lint_store.check_lint_name(lint_name.as_str(), Some(sym::clippy)); |
| 125 | + if let CheckLintNameResult::Tool(Ok(lint_lst)) = result { |
| 126 | + if let Some(group_some) = get_lint_group(cx, lint_lst[0]) { |
| 127 | + group = group_some; |
| 128 | + } else { |
| 129 | + lint_collection_error(cx, item, "Unable to determine lint group"); |
| 130 | + return; |
| 131 | + } |
| 132 | + } else { |
| 133 | + lint_collection_error(cx, item, "Unable to find lint in lint_store"); |
| 134 | + return; |
| 135 | + } |
| 136 | + |
| 137 | + let docs: String; |
| 138 | + if let Some(docs_some) = extract_attr_docs(item) { |
| 139 | + docs = docs_some; |
| 140 | + } else { |
| 141 | + lint_collection_error(cx, item, "could not collect the lint documentation"); |
| 142 | + return; |
| 143 | + }; |
| 144 | + |
| 145 | + self.lints.push(LintMetadata { |
| 146 | + id: lint_name, |
| 147 | + id_span: SerializableSpan::from_item(cx, item), |
| 148 | + group, |
| 149 | + docs, |
| 150 | + }); |
| 151 | + } |
| 152 | + } |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +/// This function collects all documentation that has been added to an item using |
| 157 | +/// `#[doc = r""]` attributes. Several attributes are aggravated using line breaks |
| 158 | +/// |
| 159 | +/// ```ignore |
| 160 | +/// #[doc = r"Hello world!"] |
| 161 | +/// #[doc = r"=^.^="] |
| 162 | +/// struct SomeItem {} |
| 163 | +/// ``` |
| 164 | +/// |
| 165 | +/// Would result in `Hello world!\n=^.^=\n` |
| 166 | +fn extract_attr_docs(item: &Item<'_>) -> Option<String> { |
| 167 | + item.attrs |
| 168 | + .iter() |
| 169 | + .filter_map(|ref x| x.doc_str()) |
| 170 | + .fold(None, |acc, sym| { |
| 171 | + let mut doc_str = sym.as_str().to_string(); |
| 172 | + doc_str.push('\n'); |
| 173 | + |
| 174 | + #[allow(clippy::option_if_let_else)] // See clippy#6737 |
| 175 | + if let Some(mut x) = acc { |
| 176 | + x.push_str(&doc_str); |
| 177 | + Some(x) |
| 178 | + } else { |
| 179 | + Some(doc_str) |
| 180 | + } |
| 181 | + |
| 182 | + // acc.map_or(Some(doc_str), |mut x| { |
| 183 | + // x.push_str(&doc_str); |
| 184 | + // Some(x) |
| 185 | + // }) |
| 186 | + }) |
| 187 | +} |
| 188 | + |
| 189 | +fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> { |
| 190 | + for (group_name, lints, _) in &cx.lint_store.get_lint_groups() { |
| 191 | + if lints.iter().any(|x| *x == lint_id) { |
| 192 | + return Some((*group_name).to_string()); |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + None |
| 197 | +} |
| 198 | + |
| 199 | +fn lint_collection_error(cx: &LateContext<'_>, item: &Item<'_>, message: &str) { |
| 200 | + span_lint( |
| 201 | + cx, |
| 202 | + INTERNAL_METADATA_COLLECTOR, |
| 203 | + item.ident.span, |
| 204 | + &format!("Metadata collection error for `{}`: {}", item.ident.name, message), |
| 205 | + ); |
| 206 | +} |
0 commit comments