]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs
Rollup merge of #93385 - CraftSpider:rustdoc-ty-fixes, r=camelid
[rust.git] / src / tools / clippy / clippy_lints / src / utils / internal_lints / metadata_collector.rs
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 therefore the entire lint is guarded by a feature flag called `internal`
5 //!
6 //! The module transforms all lint names to ascii lowercase to ensure that we don't have mismatches
7 //! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such
8 //! a simple mistake)
9
10 use crate::utils::internal_lints::{extract_clippy_version_value, is_lint_ref_type};
11
12 use clippy_utils::diagnostics::span_lint;
13 use clippy_utils::ty::{match_type, walk_ptrs_ty_depth};
14 use clippy_utils::{last_path_segment, match_def_path, match_function_call, match_path, paths};
15 use if_chain::if_chain;
16 use rustc_ast as ast;
17 use rustc_data_structures::fx::FxHashMap;
18 use rustc_hir::{
19     self as hir, def::DefKind, intravisit, intravisit::Visitor, ExprKind, Item, ItemKind, Mutability, QPath,
20 };
21 use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
22 use rustc_middle::hir::nested_filter;
23 use rustc_session::{declare_tool_lint, impl_lint_pass};
24 use rustc_span::symbol::Ident;
25 use rustc_span::{sym, Loc, Span, Symbol};
26 use serde::{ser::SerializeStruct, Serialize, Serializer};
27 use std::collections::BinaryHeap;
28 use std::fmt;
29 use std::fs::{self, OpenOptions};
30 use std::io::prelude::*;
31 use std::path::Path;
32
33 /// This is the output file of the lint collector.
34 const OUTPUT_FILE: &str = "../util/gh-pages/lints.json";
35 /// These lints are excluded from the export.
36 const BLACK_LISTED_LINTS: [&str; 3] = ["lint_author", "deep_code_inspection", "internal_metadata_collector"];
37 /// These groups will be ignored by the lint group matcher. This is useful for collections like
38 /// `clippy::all`
39 const IGNORED_LINT_GROUPS: [&str; 1] = ["clippy::all"];
40 /// Lints within this group will be excluded from the collection. These groups
41 /// have to be defined without the `clippy::` prefix.
42 const EXCLUDED_LINT_GROUPS: [&str; 1] = ["internal"];
43 /// Collected deprecated lint will be assigned to this group in the JSON output
44 const DEPRECATED_LINT_GROUP_STR: &str = "deprecated";
45 /// This is the lint level for deprecated lints that will be displayed in the lint list
46 const DEPRECATED_LINT_LEVEL: &str = "none";
47 /// This array holds Clippy's lint groups with their corresponding default lint level. The
48 /// lint level for deprecated lints is set in `DEPRECATED_LINT_LEVEL`.
49 const DEFAULT_LINT_LEVELS: &[(&str, &str)] = &[
50     ("correctness", "deny"),
51     ("suspicious", "warn"),
52     ("restriction", "allow"),
53     ("style", "warn"),
54     ("pedantic", "allow"),
55     ("complexity", "warn"),
56     ("perf", "warn"),
57     ("cargo", "allow"),
58     ("nursery", "allow"),
59 ];
60 /// This prefix is in front of the lint groups in the lint store. The prefix will be trimmed
61 /// to only keep the actual lint group in the output.
62 const CLIPPY_LINT_GROUP_PREFIX: &str = "clippy::";
63
64 /// This template will be used to format the configuration section in the lint documentation.
65 /// The `configurations` parameter will be replaced with one or multiple formatted
66 /// `ClippyConfiguration` instances. See `CONFIGURATION_VALUE_TEMPLATE` for further customizations
67 macro_rules! CONFIGURATION_SECTION_TEMPLATE {
68     () => {
69         r#"
70 ### Configuration
71 This lint has the following configuration variables:
72
73 {configurations}
74 "#
75     };
76 }
77 /// This template will be used to format an individual `ClippyConfiguration` instance in the
78 /// lint documentation.
79 ///
80 /// The format function will provide strings for the following parameters: `name`, `ty`, `doc` and
81 /// `default`
82 macro_rules! CONFIGURATION_VALUE_TEMPLATE {
83     () => {
84         "* `{name}`: `{ty}`: {doc} (defaults to `{default}`)\n"
85     };
86 }
87
88 const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [
89     &["clippy_utils", "diagnostics", "span_lint"],
90     &["clippy_utils", "diagnostics", "span_lint_and_help"],
91     &["clippy_utils", "diagnostics", "span_lint_and_note"],
92     &["clippy_utils", "diagnostics", "span_lint_hir"],
93     &["clippy_utils", "diagnostics", "span_lint_and_sugg"],
94     &["clippy_utils", "diagnostics", "span_lint_and_then"],
95     &["clippy_utils", "diagnostics", "span_lint_hir_and_then"],
96 ];
97 const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [
98     ("span_suggestion", false),
99     ("span_suggestion_short", false),
100     ("span_suggestion_verbose", false),
101     ("span_suggestion_hidden", false),
102     ("tool_only_span_suggestion", false),
103     ("multipart_suggestion", true),
104     ("multipart_suggestions", true),
105     ("tool_only_multipart_suggestion", true),
106     ("span_suggestions", true),
107 ];
108 const SUGGESTION_FUNCTIONS: [&[&str]; 2] = [
109     &["clippy_utils", "diagnostics", "multispan_sugg"],
110     &["clippy_utils", "diagnostics", "multispan_sugg_with_applicability"],
111 ];
112 const DEPRECATED_LINT_TYPE: [&str; 3] = ["clippy_lints", "deprecated_lints", "ClippyDeprecatedLint"];
113
114 /// The index of the applicability name of `paths::APPLICABILITY_VALUES`
115 const APPLICABILITY_NAME_INDEX: usize = 2;
116 /// This applicability will be set for unresolved applicability values.
117 const APPLICABILITY_UNRESOLVED_STR: &str = "Unresolved";
118 /// The version that will be displayed if none has been defined
119 const VERION_DEFAULT_STR: &str = "Unknown";
120
121 declare_clippy_lint! {
122     /// ### What it does
123     /// Collects metadata about clippy lints for the website.
124     ///
125     /// This lint will be used to report problems of syntax parsing. You should hopefully never
126     /// see this but never say never I guess ^^
127     ///
128     /// ### Why is this bad?
129     /// This is not a bad thing but definitely a hacky way to do it. See
130     /// issue [#4310](https://github.com/rust-lang/rust-clippy/issues/4310) for a discussion
131     /// about the implementation.
132     ///
133     /// ### Known problems
134     /// Hopefully none. It would be pretty uncool to have a problem here :)
135     ///
136     /// ### Example output
137     /// ```json,ignore
138     /// {
139     ///     "id": "internal_metadata_collector",
140     ///     "id_span": {
141     ///         "path": "clippy_lints/src/utils/internal_lints/metadata_collector.rs",
142     ///         "line": 1
143     ///     },
144     ///     "group": "clippy::internal",
145     ///     "docs": " ### What it does\nCollects metadata about clippy lints for the website. [...] "
146     /// }
147     /// ```
148     #[clippy::version = "1.56.0"]
149     pub INTERNAL_METADATA_COLLECTOR,
150     internal_warn,
151     "A busy bee collection metadata about lints"
152 }
153
154 impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]);
155
156 #[allow(clippy::module_name_repetitions)]
157 #[derive(Debug, Clone)]
158 pub struct MetadataCollector {
159     /// All collected lints
160     ///
161     /// We use a Heap here to have the lints added in alphabetic order in the export
162     lints: BinaryHeap<LintMetadata>,
163     applicability_info: FxHashMap<String, ApplicabilityInfo>,
164     config: Vec<ClippyConfiguration>,
165 }
166
167 impl MetadataCollector {
168     pub fn new() -> Self {
169         Self {
170             lints: BinaryHeap::<LintMetadata>::default(),
171             applicability_info: FxHashMap::<String, ApplicabilityInfo>::default(),
172             config: collect_configs(),
173         }
174     }
175
176     fn get_lint_configs(&self, lint_name: &str) -> Option<String> {
177         self.config
178             .iter()
179             .filter(|config| config.lints.iter().any(|lint| lint == lint_name))
180             .map(ToString::to_string)
181             .reduce(|acc, x| acc + &x)
182             .map(|configurations| format!(CONFIGURATION_SECTION_TEMPLATE!(), configurations = configurations))
183     }
184 }
185
186 impl Drop for MetadataCollector {
187     /// You might ask: How hacky is this?
188     /// My answer:     YES
189     fn drop(&mut self) {
190         // The metadata collector gets dropped twice, this makes sure that we only write
191         // when the list is full
192         if self.lints.is_empty() {
193             return;
194         }
195
196         let mut applicability_info = std::mem::take(&mut self.applicability_info);
197
198         // Mapping the final data
199         let mut lints = std::mem::take(&mut self.lints).into_sorted_vec();
200         lints
201             .iter_mut()
202             .for_each(|x| x.applicability = Some(applicability_info.remove(&x.id).unwrap_or_default()));
203
204         // Outputting
205         if Path::new(OUTPUT_FILE).exists() {
206             fs::remove_file(OUTPUT_FILE).unwrap();
207         }
208         let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap();
209         writeln!(file, "{}", serde_json::to_string_pretty(&lints).unwrap()).unwrap();
210     }
211 }
212
213 #[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
214 struct LintMetadata {
215     id: String,
216     id_span: SerializableSpan,
217     group: String,
218     level: String,
219     docs: String,
220     version: String,
221     /// This field is only used in the output and will only be
222     /// mapped shortly before the actual output.
223     applicability: Option<ApplicabilityInfo>,
224 }
225
226 impl LintMetadata {
227     fn new(
228         id: String,
229         id_span: SerializableSpan,
230         group: String,
231         level: &'static str,
232         version: String,
233         docs: String,
234     ) -> Self {
235         Self {
236             id,
237             id_span,
238             group,
239             level: level.to_string(),
240             version,
241             docs,
242             applicability: None,
243         }
244     }
245 }
246
247 #[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
248 struct SerializableSpan {
249     path: String,
250     line: usize,
251 }
252
253 impl std::fmt::Display for SerializableSpan {
254     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255         write!(f, "{}:{}", self.path.rsplit('/').next().unwrap_or_default(), self.line)
256     }
257 }
258
259 impl SerializableSpan {
260     fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Self {
261         Self::from_span(cx, item.ident.span)
262     }
263
264     fn from_span(cx: &LateContext<'_>, span: Span) -> Self {
265         let loc: Loc = cx.sess().source_map().lookup_char_pos(span.lo());
266
267         Self {
268             path: format!("{}", loc.file.name.prefer_remapped()),
269             line: loc.line,
270         }
271     }
272 }
273
274 #[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
275 struct ApplicabilityInfo {
276     /// Indicates if any of the lint emissions uses multiple spans. This is related to
277     /// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
278     /// currently not be applied automatically.
279     is_multi_part_suggestion: bool,
280     applicability: Option<usize>,
281 }
282
283 impl Serialize for ApplicabilityInfo {
284     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
285     where
286         S: Serializer,
287     {
288         let mut s = serializer.serialize_struct("ApplicabilityInfo", 2)?;
289         s.serialize_field("is_multi_part_suggestion", &self.is_multi_part_suggestion)?;
290         if let Some(index) = self.applicability {
291             s.serialize_field(
292                 "applicability",
293                 &paths::APPLICABILITY_VALUES[index][APPLICABILITY_NAME_INDEX],
294             )?;
295         } else {
296             s.serialize_field("applicability", APPLICABILITY_UNRESOLVED_STR)?;
297         }
298         s.end()
299     }
300 }
301
302 // ==================================================================
303 // Configuration
304 // ==================================================================
305 #[derive(Debug, Clone, Default)]
306 pub struct ClippyConfiguration {
307     name: String,
308     config_type: &'static str,
309     default: String,
310     lints: Vec<String>,
311     doc: String,
312     #[allow(dead_code)]
313     deprecation_reason: Option<&'static str>,
314 }
315
316 impl ClippyConfiguration {
317     pub fn new(
318         name: &'static str,
319         config_type: &'static str,
320         default: String,
321         doc_comment: &'static str,
322         deprecation_reason: Option<&'static str>,
323     ) -> Self {
324         let (lints, doc) = parse_config_field_doc(doc_comment)
325             .unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string()));
326
327         Self {
328             name: to_kebab(name),
329             lints,
330             doc,
331             config_type,
332             default,
333             deprecation_reason,
334         }
335     }
336 }
337
338 fn collect_configs() -> Vec<ClippyConfiguration> {
339     crate::utils::conf::metadata::get_configuration_metadata()
340 }
341
342 /// This parses the field documentation of the config struct.
343 ///
344 /// ```rust, ignore
345 /// parse_config_field_doc(cx, "Lint: LINT_NAME_1, LINT_NAME_2. Papa penguin, papa penguin")
346 /// ```
347 ///
348 /// Would yield:
349 /// ```rust, ignore
350 /// Some(["lint_name_1", "lint_name_2"], "Papa penguin, papa penguin")
351 /// ```
352 fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec<String>, String)> {
353     const DOC_START: &str = " Lint: ";
354     if_chain! {
355         if doc_comment.starts_with(DOC_START);
356         if let Some(split_pos) = doc_comment.find('.');
357         then {
358             let mut doc_comment = doc_comment.to_string();
359             let mut documentation = doc_comment.split_off(split_pos);
360
361             // Extract lints
362             doc_comment.make_ascii_lowercase();
363             let lints: Vec<String> = doc_comment.split_off(DOC_START.len()).split(", ").map(str::to_string).collect();
364
365             // Format documentation correctly
366             // split off leading `.` from lint name list and indent for correct formatting
367             documentation = documentation.trim_start_matches('.').trim().replace("\n ", "\n    ");
368
369             Some((lints, documentation))
370         } else {
371             None
372         }
373     }
374 }
375
376 /// Transforms a given `snake_case_string` to a tasty `kebab-case-string`
377 fn to_kebab(config_name: &str) -> String {
378     config_name.replace('_', "-")
379 }
380
381 impl fmt::Display for ClippyConfiguration {
382     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
383         write!(
384             f,
385             CONFIGURATION_VALUE_TEMPLATE!(),
386             name = self.name,
387             ty = self.config_type,
388             doc = self.doc,
389             default = self.default
390         )
391     }
392 }
393
394 // ==================================================================
395 // Lint pass
396 // ==================================================================
397 impl<'hir> LateLintPass<'hir> for MetadataCollector {
398     /// Collecting lint declarations like:
399     /// ```rust, ignore
400     /// declare_clippy_lint! {
401     ///     /// ### What it does
402     ///     /// Something IDK.
403     ///     pub SOME_LINT,
404     ///     internal,
405     ///     "Who am I?"
406     /// }
407     /// ```
408     fn check_item(&mut self, cx: &LateContext<'hir>, item: &'hir Item<'_>) {
409         if let ItemKind::Static(ty, Mutability::Not, _) = item.kind {
410             // Normal lint
411             if_chain! {
412                 // item validation
413                 if is_lint_ref_type(cx, ty);
414                 // blacklist check
415                 let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
416                 if !BLACK_LISTED_LINTS.contains(&lint_name.as_str());
417                 // metadata extraction
418                 if let Some((group, level)) = get_lint_group_and_level_or_lint(cx, &lint_name, item);
419                 if let Some(mut docs) = extract_attr_docs_or_lint(cx, item);
420                 then {
421                     if let Some(configuration_section) = self.get_lint_configs(&lint_name) {
422                         docs.push_str(&configuration_section);
423                     }
424                     let version = get_lint_version(cx, item);
425
426                     self.lints.push(LintMetadata::new(
427                         lint_name,
428                         SerializableSpan::from_item(cx, item),
429                         group,
430                         level,
431                         version,
432                         docs,
433                     ));
434                 }
435             }
436
437             if_chain! {
438                 if is_deprecated_lint(cx, ty);
439                 // blacklist check
440                 let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
441                 if !BLACK_LISTED_LINTS.contains(&lint_name.as_str());
442                 // Metadata the little we can get from a deprecated lint
443                 if let Some(docs) = extract_attr_docs_or_lint(cx, item);
444                 then {
445                     let version = get_lint_version(cx, item);
446
447                     self.lints.push(LintMetadata::new(
448                         lint_name,
449                         SerializableSpan::from_item(cx, item),
450                         DEPRECATED_LINT_GROUP_STR.to_string(),
451                         DEPRECATED_LINT_LEVEL,
452                         version,
453                         docs,
454                     ));
455                 }
456             }
457         }
458     }
459
460     /// Collecting constant applicability from the actual lint emissions
461     ///
462     /// Example:
463     /// ```rust, ignore
464     /// span_lint_and_sugg(
465     ///     cx,
466     ///     SOME_LINT,
467     ///     item.span,
468     ///     "Le lint message",
469     ///     "Here comes help:",
470     ///     "#![allow(clippy::all)]",
471     ///     Applicability::MachineApplicable, // <-- Extracts this constant value
472     /// );
473     /// ```
474     fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
475         if let Some(args) = match_lint_emission(cx, expr) {
476             let mut emission_info = extract_emission_info(cx, args);
477             if emission_info.is_empty() {
478                 // See:
479                 // - src/misc.rs:734:9
480                 // - src/methods/mod.rs:3545:13
481                 // - src/methods/mod.rs:3496:13
482                 // We are basically unable to resolve the lint name itself.
483                 return;
484             }
485
486             for (lint_name, applicability, is_multi_part) in emission_info.drain(..) {
487                 let app_info = self.applicability_info.entry(lint_name).or_default();
488                 app_info.applicability = applicability;
489                 app_info.is_multi_part_suggestion = is_multi_part;
490             }
491         }
492     }
493 }
494
495 // ==================================================================
496 // Lint definition extraction
497 // ==================================================================
498 fn sym_to_string(sym: Symbol) -> String {
499     sym.as_str().to_string()
500 }
501
502 fn extract_attr_docs_or_lint(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
503     extract_attr_docs(cx, item).or_else(|| {
504         lint_collection_error_item(cx, item, "could not collect the lint documentation");
505         None
506     })
507 }
508
509 /// This function collects all documentation that has been added to an item using
510 /// `#[doc = r""]` attributes. Several attributes are aggravated using line breaks
511 ///
512 /// ```ignore
513 /// #[doc = r"Hello world!"]
514 /// #[doc = r"=^.^="]
515 /// struct SomeItem {}
516 /// ```
517 ///
518 /// Would result in `Hello world!\n=^.^=\n`
519 ///
520 /// ---
521 ///
522 /// This function may modify the doc comment to ensure that the string can be displayed using a
523 /// markdown viewer in Clippy's lint list. The following modifications could be applied:
524 /// * Removal of leading space after a new line. (Important to display tables)
525 /// * Ensures that code blocks only contain language information
526 fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
527     let attrs = cx.tcx.hir().attrs(item.hir_id());
528     let mut lines = attrs.iter().filter_map(ast::Attribute::doc_str);
529     let mut docs = String::from(&*lines.next()?.as_str());
530     let mut in_code_block = false;
531     let mut is_code_block_rust = false;
532     for line in lines {
533         let line = line.as_str();
534         let line = &*line;
535
536         // Rustdoc hides code lines starting with `# ` and this removes them from Clippy's lint list :)
537         if is_code_block_rust && line.trim_start().starts_with("# ") {
538             continue;
539         }
540
541         // The line should be represented in the lint list, even if it's just an empty line
542         docs.push('\n');
543         if let Some(info) = line.trim_start().strip_prefix("```") {
544             in_code_block = !in_code_block;
545             is_code_block_rust = false;
546             if in_code_block {
547                 let lang = info
548                     .trim()
549                     .split(',')
550                     // remove rustdoc directives
551                     .find(|&s| !matches!(s, "" | "ignore" | "no_run" | "should_panic"))
552                     // if no language is present, fill in "rust"
553                     .unwrap_or("rust");
554                 docs.push_str("```");
555                 docs.push_str(lang);
556
557                 is_code_block_rust = lang == "rust";
558                 continue;
559             }
560         }
561         // This removes the leading space that the macro translation introduces
562         if let Some(stripped_doc) = line.strip_prefix(' ') {
563             docs.push_str(stripped_doc);
564         } else if !line.is_empty() {
565             docs.push_str(line);
566         }
567     }
568     Some(docs)
569 }
570
571 fn get_lint_version(cx: &LateContext<'_>, item: &Item<'_>) -> String {
572     extract_clippy_version_value(cx, item).map_or_else(
573         || VERION_DEFAULT_STR.to_string(),
574         |version| version.as_str().to_string(),
575     )
576 }
577
578 fn get_lint_group_and_level_or_lint(
579     cx: &LateContext<'_>,
580     lint_name: &str,
581     item: &Item<'_>,
582 ) -> Option<(String, &'static str)> {
583     let result = cx.lint_store.check_lint_name(
584         lint_name,
585         Some(sym::clippy),
586         &[Ident::with_dummy_span(sym::clippy)].into_iter().collect(),
587     );
588     if let CheckLintNameResult::Tool(Ok(lint_lst)) = result {
589         if let Some(group) = get_lint_group(cx, lint_lst[0]) {
590             if EXCLUDED_LINT_GROUPS.contains(&group.as_str()) {
591                 return None;
592             }
593
594             if let Some(level) = get_lint_level_from_group(&group) {
595                 Some((group, level))
596             } else {
597                 lint_collection_error_item(
598                     cx,
599                     item,
600                     &format!("Unable to determine lint level for found group `{}`", group),
601                 );
602                 None
603             }
604         } else {
605             lint_collection_error_item(cx, item, "Unable to determine lint group");
606             None
607         }
608     } else {
609         lint_collection_error_item(cx, item, "Unable to find lint in lint_store");
610         None
611     }
612 }
613
614 fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
615     for (group_name, lints, _) in cx.lint_store.get_lint_groups() {
616         if IGNORED_LINT_GROUPS.contains(&group_name) {
617             continue;
618         }
619
620         if lints.iter().any(|group_lint| *group_lint == lint_id) {
621             let group = group_name.strip_prefix(CLIPPY_LINT_GROUP_PREFIX).unwrap_or(group_name);
622             return Some((*group).to_string());
623         }
624     }
625
626     None
627 }
628
629 fn get_lint_level_from_group(lint_group: &str) -> Option<&'static str> {
630     DEFAULT_LINT_LEVELS
631         .iter()
632         .find_map(|(group_name, group_level)| (*group_name == lint_group).then(|| *group_level))
633 }
634
635 fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
636     if let hir::TyKind::Path(ref path) = ty.kind {
637         if let hir::def::Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, ty.hir_id) {
638             return match_def_path(cx, def_id, &DEPRECATED_LINT_TYPE);
639         }
640     }
641
642     false
643 }
644
645 // ==================================================================
646 // Lint emission
647 // ==================================================================
648 fn lint_collection_error_item(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
649     span_lint(
650         cx,
651         INTERNAL_METADATA_COLLECTOR,
652         item.ident.span,
653         &format!("metadata collection error for `{}`: {}", item.ident.name, message),
654     );
655 }
656
657 // ==================================================================
658 // Applicability
659 // ==================================================================
660 /// This function checks if a given expression is equal to a simple lint emission function call.
661 /// It will return the function arguments if the emission matched any function.
662 fn match_lint_emission<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) -> Option<&'hir [hir::Expr<'hir>]> {
663     LINT_EMISSION_FUNCTIONS
664         .iter()
665         .find_map(|emission_fn| match_function_call(cx, expr, emission_fn))
666 }
667
668 fn take_higher_applicability(a: Option<usize>, b: Option<usize>) -> Option<usize> {
669     a.map_or(b, |a| a.max(b.unwrap_or_default()).into())
670 }
671
672 fn extract_emission_info<'hir>(
673     cx: &LateContext<'hir>,
674     args: &'hir [hir::Expr<'hir>],
675 ) -> Vec<(String, Option<usize>, bool)> {
676     let mut lints = Vec::new();
677     let mut applicability = None;
678     let mut multi_part = false;
679
680     for arg in args {
681         let (arg_ty, _) = walk_ptrs_ty_depth(cx.typeck_results().expr_ty(arg));
682
683         if match_type(cx, arg_ty, &paths::LINT) {
684             // If we found the lint arg, extract the lint name
685             let mut resolved_lints = resolve_lints(cx, arg);
686             lints.append(&mut resolved_lints);
687         } else if match_type(cx, arg_ty, &paths::APPLICABILITY) {
688             applicability = resolve_applicability(cx, arg);
689         } else if arg_ty.is_closure() {
690             multi_part |= check_is_multi_part(cx, arg);
691             applicability = applicability.or_else(|| resolve_applicability(cx, arg));
692         }
693     }
694
695     lints
696         .drain(..)
697         .map(|lint_name| (lint_name, applicability, multi_part))
698         .collect()
699 }
700
701 /// Resolves the possible lints that this expression could reference
702 fn resolve_lints<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Vec<String> {
703     let mut resolver = LintResolver::new(cx);
704     resolver.visit_expr(expr);
705     resolver.lints
706 }
707
708 /// This function tries to resolve the linked applicability to the given expression.
709 fn resolve_applicability<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<usize> {
710     let mut resolver = ApplicabilityResolver::new(cx);
711     resolver.visit_expr(expr);
712     resolver.complete()
713 }
714
715 fn check_is_multi_part<'hir>(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool {
716     if let ExprKind::Closure(_, _, body_id, _, _) = closure_expr.kind {
717         let mut scanner = IsMultiSpanScanner::new(cx);
718         intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body_id));
719         return scanner.is_multi_part();
720     } else if let Some(local) = get_parent_local(cx, closure_expr) {
721         if let Some(local_init) = local.init {
722             return check_is_multi_part(cx, local_init);
723         }
724     }
725
726     false
727 }
728
729 struct LintResolver<'a, 'hir> {
730     cx: &'a LateContext<'hir>,
731     lints: Vec<String>,
732 }
733
734 impl<'a, 'hir> LintResolver<'a, 'hir> {
735     fn new(cx: &'a LateContext<'hir>) -> Self {
736         Self {
737             cx,
738             lints: Vec::<String>::default(),
739         }
740     }
741 }
742
743 impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> {
744     type NestedFilter = nested_filter::All;
745
746     fn nested_visit_map(&mut self) -> Self::Map {
747         self.cx.tcx.hir()
748     }
749
750     fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
751         if_chain! {
752             if let ExprKind::Path(qpath) = &expr.kind;
753             if let QPath::Resolved(_, path) = qpath;
754
755             let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr));
756             if match_type(self.cx, expr_ty, &paths::LINT);
757             then {
758                 if let hir::def::Res::Def(DefKind::Static, _) = path.res {
759                     let lint_name = last_path_segment(qpath).ident.name;
760                     self.lints.push(sym_to_string(lint_name).to_ascii_lowercase());
761                 } else if let Some(local) = get_parent_local(self.cx, expr) {
762                     if let Some(local_init) = local.init {
763                         intravisit::walk_expr(self, local_init);
764                     }
765                 }
766             }
767         }
768
769         intravisit::walk_expr(self, expr);
770     }
771 }
772
773 /// This visitor finds the highest applicability value in the visited expressions
774 struct ApplicabilityResolver<'a, 'hir> {
775     cx: &'a LateContext<'hir>,
776     /// This is the index of hightest `Applicability` for `paths::APPLICABILITY_VALUES`
777     applicability_index: Option<usize>,
778 }
779
780 impl<'a, 'hir> ApplicabilityResolver<'a, 'hir> {
781     fn new(cx: &'a LateContext<'hir>) -> Self {
782         Self {
783             cx,
784             applicability_index: None,
785         }
786     }
787
788     fn add_new_index(&mut self, new_index: usize) {
789         self.applicability_index = take_higher_applicability(self.applicability_index, Some(new_index));
790     }
791
792     fn complete(self) -> Option<usize> {
793         self.applicability_index
794     }
795 }
796
797 impl<'a, 'hir> intravisit::Visitor<'hir> for ApplicabilityResolver<'a, 'hir> {
798     type NestedFilter = nested_filter::All;
799
800     fn nested_visit_map(&mut self) -> Self::Map {
801         self.cx.tcx.hir()
802     }
803
804     fn visit_path(&mut self, path: &'hir hir::Path<'hir>, _id: hir::HirId) {
805         for (index, enum_value) in paths::APPLICABILITY_VALUES.iter().enumerate() {
806             if match_path(path, enum_value) {
807                 self.add_new_index(index);
808                 return;
809             }
810         }
811     }
812
813     fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
814         let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr));
815
816         if_chain! {
817             if match_type(self.cx, expr_ty, &paths::APPLICABILITY);
818             if let Some(local) = get_parent_local(self.cx, expr);
819             if let Some(local_init) = local.init;
820             then {
821                 intravisit::walk_expr(self, local_init);
822             }
823         };
824
825         intravisit::walk_expr(self, expr);
826     }
827 }
828
829 /// This returns the parent local node if the expression is a reference one
830 fn get_parent_local<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Local<'hir>> {
831     if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind {
832         if let hir::def::Res::Local(local_hir) = path.res {
833             return get_parent_local_hir_id(cx, local_hir);
834         }
835     }
836
837     None
838 }
839
840 fn get_parent_local_hir_id<'hir>(cx: &LateContext<'hir>, hir_id: hir::HirId) -> Option<&'hir hir::Local<'hir>> {
841     let map = cx.tcx.hir();
842
843     match map.find(map.get_parent_node(hir_id)) {
844         Some(hir::Node::Local(local)) => Some(local),
845         Some(hir::Node::Pat(pattern)) => get_parent_local_hir_id(cx, pattern.hir_id),
846         _ => None,
847     }
848 }
849
850 /// This visitor finds the highest applicability value in the visited expressions
851 struct IsMultiSpanScanner<'a, 'hir> {
852     cx: &'a LateContext<'hir>,
853     suggestion_count: usize,
854 }
855
856 impl<'a, 'hir> IsMultiSpanScanner<'a, 'hir> {
857     fn new(cx: &'a LateContext<'hir>) -> Self {
858         Self {
859             cx,
860             suggestion_count: 0,
861         }
862     }
863
864     /// Add a new single expression suggestion to the counter
865     fn add_single_span_suggestion(&mut self) {
866         self.suggestion_count += 1;
867     }
868
869     /// Signals that a suggestion with possible multiple spans was found
870     fn add_multi_part_suggestion(&mut self) {
871         self.suggestion_count += 2;
872     }
873
874     /// Checks if the suggestions include multiple spanns
875     fn is_multi_part(&self) -> bool {
876         self.suggestion_count > 1
877     }
878 }
879
880 impl<'a, 'hir> intravisit::Visitor<'hir> for IsMultiSpanScanner<'a, 'hir> {
881     type NestedFilter = nested_filter::All;
882
883     fn nested_visit_map(&mut self) -> Self::Map {
884         self.cx.tcx.hir()
885     }
886
887     fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
888         // Early return if the lint is already multi span
889         if self.is_multi_part() {
890             return;
891         }
892
893         match &expr.kind {
894             ExprKind::Call(fn_expr, _args) => {
895                 let found_function = SUGGESTION_FUNCTIONS
896                     .iter()
897                     .any(|func_path| match_function_call(self.cx, fn_expr, func_path).is_some());
898                 if found_function {
899                     // These functions are all multi part suggestions
900                     self.add_single_span_suggestion();
901                 }
902             },
903             ExprKind::MethodCall(path, arg, _arg_span) => {
904                 let (self_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&arg[0]));
905                 if match_type(self.cx, self_ty, &paths::DIAGNOSTIC_BUILDER) {
906                     let called_method = path.ident.name.as_str().to_string();
907                     for (method_name, is_multi_part) in &SUGGESTION_DIAGNOSTIC_BUILDER_METHODS {
908                         if *method_name == called_method {
909                             if *is_multi_part {
910                                 self.add_multi_part_suggestion();
911                             } else {
912                                 self.add_single_span_suggestion();
913                             }
914                             break;
915                         }
916                     }
917                 }
918             },
919             _ => {},
920         }
921
922         intravisit::walk_expr(self, expr);
923     }
924 }