22//! file and then used to generate the [clippy lint list](https://rust-lang.github.io/rust-clippy/master/index.html)
33//!
44//! This module and therefor the entire lint is guarded by a feature flag called
5- //! `internal_metadata_lint`
5+ //! `metadata-collector-lint`
6+ //!
7+ //! The module transforms all lint names to ascii lowercase to ensure that we don't have mismatches
8+ //! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such
9+ //! a simple mistake)
610//!
711//! The metadata currently contains:
8- //! - [ ] TODO The lint declaration line for [#1303](https://github.com/rust-lang/rust-clippy/issues/1303)
12+ //! - [x ] TODO The lint declaration line for [#1303](https://github.com/rust-lang/rust-clippy/issues/1303)
913//! and [#6492](https://github.com/rust-lang/rust-clippy/issues/6492)
1014//! - [ ] TODO The Applicability for each lint for [#4310](https://github.com/rust-lang/rust-clippy/issues/4310)
1115
1721// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames
1822
1923use if_chain:: if_chain;
20- use rustc_hir:: { ExprKind , Item , ItemKind , Mutability } ;
24+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
25+ use rustc_hir:: { self as hir, ExprKind , Item , ItemKind , Mutability } ;
2126use rustc_lint:: { CheckLintNameResult , LateContext , LateLintPass , LintContext , LintId } ;
2227use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
23- use rustc_span:: { sym, Loc , Span } ;
28+ use rustc_span:: { sym, Loc , Span , Symbol } ;
2429use serde:: Serialize ;
2530use std:: fs:: OpenOptions ;
2631use std:: io:: prelude:: * ;
2732
2833use crate :: utils:: internal_lints:: is_lint_ref_type;
29- use crate :: utils:: span_lint;
34+ use crate :: utils:: { last_path_segment , match_function_call , match_type , paths , span_lint, walk_ptrs_ty_depth } ;
3035
36+ /// This is the output file of the lint collector.
3137const OUTPUT_FILE : & str = "metadata_collection.json" ;
38+ /// These lints are excluded from the export.
3239const BLACK_LISTED_LINTS : [ & str ; 2 ] = [ "lint_author" , "deep_code_inspection" ] ;
3340
41+ // TODO xFrednet 2021-02-15: `span_lint_and_then` & `span_lint_hir_and_then` requires special
42+ // handling
43+ #[ rustfmt:: skip]
44+ const LINT_EMISSION_FUNCTIONS : [ & [ & str ] ; 5 ] = [
45+ & [ "clippy_lints" , "utils" , "diagnostics" , "span_lint" ] ,
46+ & [ "clippy_lints" , "utils" , "diagnostics" , "span_lint_and_help" ] ,
47+ & [ "clippy_lints" , "utils" , "diagnostics" , "span_lint_and_note" ] ,
48+ & [ "clippy_lints" , "utils" , "diagnostics" , "span_lint_hir" ] ,
49+ & [ "clippy_lints" , "utils" , "diagnostics" , "span_lint_and_sugg" ] ,
50+ ] ;
51+
3452declare_clippy_lint ! {
3553 /// **What it does:** Collects metadata about clippy lints for the website.
3654 ///
@@ -66,12 +84,21 @@ impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]);
6684#[ derive( Debug , Clone , Default ) ]
6785pub struct MetadataCollector {
6886 lints : Vec < LintMetadata > ,
87+ applicability_into : FxHashMap < String , ApplicabilityInfo > ,
6988}
7089
7190impl Drop for MetadataCollector {
91+ /// You might ask: How hacky is this?
92+ /// My answer: YES
7293 fn drop ( & mut self ) {
73- // You might ask: How hacky is this?
74- // My answer: YES
94+ let mut applicability_info = std:: mem:: take ( & mut self . applicability_into ) ;
95+
96+ // Mapping the final data
97+ self . lints
98+ . iter_mut ( )
99+ . for_each ( |x| x. applicability = applicability_info. remove ( & x. id ) ) ;
100+
101+ // Outputting
75102 let mut file = OpenOptions :: new ( ) . write ( true ) . create ( true ) . open ( OUTPUT_FILE ) . unwrap ( ) ;
76103 writeln ! ( file, "{}" , serde_json:: to_string_pretty( & self . lints) . unwrap( ) ) . unwrap ( ) ;
77104 }
@@ -83,6 +110,21 @@ struct LintMetadata {
83110 id_span : SerializableSpan ,
84111 group : String ,
85112 docs : String ,
113+ /// This field is only used in the output and will only be
114+ /// mapped shortly before the actual output.
115+ applicability : Option < ApplicabilityInfo > ,
116+ }
117+
118+ impl LintMetadata {
119+ fn new ( id : String , id_span : SerializableSpan , group : String , docs : String ) -> Self {
120+ Self {
121+ id,
122+ id_span,
123+ group,
124+ docs,
125+ applicability : None ,
126+ }
127+ }
86128}
87129
88130#[ derive( Debug , Clone , Serialize ) ]
@@ -101,12 +143,31 @@ impl SerializableSpan {
101143
102144 Self {
103145 path : format ! ( "{}" , loc. file. name) ,
104- line : 1 ,
146+ line : loc . line ,
105147 }
106148 }
107149}
108150
151+ #[ derive( Debug , Clone , Default , Serialize ) ]
152+ struct ApplicabilityInfo {
153+ /// Indicates if any of the lint emissions uses multiple spans. This is related to
154+ /// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
155+ /// currently not be applied automatically.
156+ has_multi_suggestion : bool ,
157+ /// These are all the available applicability values for the lint suggestions
158+ applicabilities : FxHashSet < String > ,
159+ }
160+
109161impl < ' tcx > LateLintPass < ' tcx > for MetadataCollector {
162+ /// Collecting lint declarations like:
163+ /// ```rust, ignore
164+ /// declare_clippy_lint! {
165+ /// /// **What it does:** Something IDK.
166+ /// pub SOME_LINT,
167+ /// internal,
168+ /// "Who am I?"
169+ /// }
170+ /// ```
110171 fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' _ > ) {
111172 if_chain ! {
112173 if let ItemKind :: Static ( ref ty, Mutability :: Not , body_id) = item. kind;
@@ -115,7 +176,7 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
115176 if let ExprKind :: AddrOf ( _, _, ref inner_exp) = expr. kind;
116177 if let ExprKind :: Struct ( _, _, _) = inner_exp. kind;
117178 then {
118- let lint_name = item. ident. name. as_str ( ) . to_string ( ) . to_ascii_lowercase( ) ;
179+ let lint_name = sym_to_string ( item. ident. name) . to_ascii_lowercase( ) ;
119180 if BLACK_LISTED_LINTS . contains( & lint_name. as_str( ) ) {
120181 return ;
121182 }
@@ -126,33 +187,50 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
126187 if let Some ( group_some) = get_lint_group( cx, lint_lst[ 0 ] ) {
127188 group = group_some;
128189 } else {
129- lint_collection_error ( cx, item, "Unable to determine lint group" ) ;
190+ lint_collection_error_item ( cx, item, "Unable to determine lint group" ) ;
130191 return ;
131192 }
132193 } else {
133- lint_collection_error ( cx, item, "Unable to find lint in lint_store" ) ;
194+ lint_collection_error_item ( cx, item, "Unable to find lint in lint_store" ) ;
134195 return ;
135196 }
136197
137198 let docs: String ;
138199 if let Some ( docs_some) = extract_attr_docs( item) {
139200 docs = docs_some;
140201 } else {
141- lint_collection_error ( cx, item, "could not collect the lint documentation" ) ;
202+ lint_collection_error_item ( cx, item, "could not collect the lint documentation" ) ;
142203 return ;
143204 } ;
144205
145- self . lints. push( LintMetadata {
146- id : lint_name,
147- id_span : SerializableSpan :: from_item( cx, item) ,
206+ self . lints. push( LintMetadata :: new (
207+ lint_name,
208+ SerializableSpan :: from_item( cx, item) ,
148209 group,
149210 docs,
211+ ) ) ;
212+ }
213+ }
214+ }
215+
216+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx hir:: Expr < ' _ > ) {
217+ if let Some ( args) = match_simple_lint_emission ( cx, expr) {
218+ if let Some ( ( lint_name, mut applicability) ) = extract_emission_info ( cx, args) {
219+ let app_info = self . applicability_into . entry ( lint_name) . or_default ( ) ;
220+ applicability. drain ( ..) . for_each ( |x| {
221+ app_info. applicabilities . insert ( x) ;
150222 } ) ;
223+ } else {
224+ lint_collection_error_span ( cx, expr. span , "I found this but I can't get the lint or applicability" ) ;
151225 }
152226 }
153227 }
154228}
155229
230+ fn sym_to_string ( sym : Symbol ) -> String {
231+ sym. as_str ( ) . to_string ( )
232+ }
233+
156234/// This function collects all documentation that has been added to an item using
157235/// `#[doc = r""]` attributes. Several attributes are aggravated using line breaks
158236///
@@ -166,23 +244,11 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
166244fn extract_attr_docs ( item : & Item < ' _ > ) -> Option < String > {
167245 item. attrs
168246 . 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- // })
247+ . filter_map ( |ref x| x. doc_str ( ) . map ( |sym| sym. as_str ( ) . to_string ( ) ) )
248+ . reduce ( |mut acc, sym| {
249+ acc. push_str ( & sym) ;
250+ acc. push ( '\n' ) ;
251+ acc
186252 } )
187253}
188254
@@ -196,11 +262,57 @@ fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
196262 None
197263}
198264
199- fn lint_collection_error ( cx : & LateContext < ' _ > , item : & Item < ' _ > , message : & str ) {
265+ fn lint_collection_error_item ( cx : & LateContext < ' _ > , item : & Item < ' _ > , message : & str ) {
200266 span_lint (
201267 cx,
202268 INTERNAL_METADATA_COLLECTOR ,
203269 item. ident . span ,
204270 & format ! ( "Metadata collection error for `{}`: {}" , item. ident. name, message) ,
205271 ) ;
206272}
273+
274+ fn lint_collection_error_span ( cx : & LateContext < ' _ > , span : Span , message : & str ) {
275+ span_lint (
276+ cx,
277+ INTERNAL_METADATA_COLLECTOR ,
278+ span,
279+ & format ! ( "Metadata collection error: {}" , message) ,
280+ ) ;
281+ }
282+
283+ fn match_simple_lint_emission < ' tcx > (
284+ cx : & LateContext < ' tcx > ,
285+ expr : & ' tcx hir:: Expr < ' _ > ,
286+ ) -> Option < & ' tcx [ hir:: Expr < ' tcx > ] > {
287+ LINT_EMISSION_FUNCTIONS
288+ . iter ( )
289+ . find_map ( |emission_fn| match_function_call ( cx, expr, emission_fn) . map ( |args| args) )
290+ }
291+
292+ /// This returns the lint name and the possible applicability of this emission
293+ fn extract_emission_info < ' tcx > ( cx : & LateContext < ' tcx > , args : & [ hir:: Expr < ' _ > ] ) -> Option < ( String , Vec < String > ) > {
294+ let mut lint_name = None ;
295+ let mut applicability = None ;
296+
297+ for arg in args {
298+ let ( arg_ty, _) = walk_ptrs_ty_depth ( cx. typeck_results ( ) . expr_ty ( & arg) ) ;
299+
300+ if match_type ( cx, arg_ty, & paths:: LINT ) {
301+ // If we found the lint arg, extract the lint name
302+ if let ExprKind :: Path ( ref lint_path) = arg. kind {
303+ lint_name = Some ( last_path_segment ( lint_path) . ident . name )
304+ }
305+ } else if match_type ( cx, arg_ty, & paths:: APPLICABILITY ) {
306+ if let ExprKind :: Path ( ref path) = arg. kind {
307+ applicability = Some ( last_path_segment ( path) . ident . name )
308+ }
309+ }
310+ }
311+
312+ lint_name. map ( |lint_name| {
313+ (
314+ sym_to_string ( lint_name) . to_ascii_lowercase ( ) ,
315+ applicability. map ( sym_to_string) . map_or_else ( Vec :: new, |x| vec ! [ x] ) ,
316+ )
317+ } )
318+ }
0 commit comments