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