]> git.lizzy.rs Git - rust.git/blob - crates/ide-diagnostics/src/lib.rs
Auto merge of #12514 - Veykril:proc-mac-err, r=Veykril
[rust.git] / crates / ide-diagnostics / src / lib.rs
1 //! Diagnostics rendering and fixits.
2 //!
3 //! Most of the diagnostics originate from the dark depth of the compiler, and
4 //! are originally expressed in term of IR. When we emit the diagnostic, we are
5 //! usually not in the position to decide how to best "render" it in terms of
6 //! user-authored source code. We are especially not in the position to offer
7 //! fixits, as the compiler completely lacks the infrastructure to edit the
8 //! source code.
9 //!
10 //! Instead, we "bubble up" raw, structured diagnostics until the `hir` crate,
11 //! where we "cook" them so that each diagnostic is formulated in terms of `hir`
12 //! types. Well, at least that's the aspiration, the "cooking" is somewhat
13 //! ad-hoc at the moment. Anyways, we get a bunch of ide-friendly diagnostic
14 //! structs from hir, and we want to render them to unified serializable
15 //! representation (span, level, message) here. If we can, we also provide
16 //! fixits. By the way, that's why we want to keep diagnostics structured
17 //! internally -- so that we have all the info to make fixes.
18 //!
19 //! We have one "handler" module per diagnostic code. Such a module contains
20 //! rendering, optional fixes and tests. It's OK if some low-level compiler
21 //! functionality ends up being tested via a diagnostic.
22 //!
23 //! There are also a couple of ad-hoc diagnostics implemented directly here, we
24 //! don't yet have a great pattern for how to do them properly.
25
26 mod handlers {
27     pub(crate) mod break_outside_of_loop;
28     pub(crate) mod inactive_code;
29     pub(crate) mod incorrect_case;
30     pub(crate) mod invalid_derive_target;
31     pub(crate) mod macro_error;
32     pub(crate) mod malformed_derive;
33     pub(crate) mod mismatched_arg_count;
34     pub(crate) mod missing_fields;
35     pub(crate) mod missing_match_arms;
36     pub(crate) mod missing_unsafe;
37     pub(crate) mod no_such_field;
38     pub(crate) mod replace_filter_map_next_with_find_map;
39     pub(crate) mod type_mismatch;
40     pub(crate) mod unimplemented_builtin_macro;
41     pub(crate) mod unresolved_extern_crate;
42     pub(crate) mod unresolved_import;
43     pub(crate) mod unresolved_macro_call;
44     pub(crate) mod unresolved_module;
45     pub(crate) mod unresolved_proc_macro;
46
47     // The handlers below are unusual, the implement the diagnostics as well.
48     pub(crate) mod field_shorthand;
49     pub(crate) mod useless_braces;
50     pub(crate) mod unlinked_file;
51 }
52
53 #[cfg(test)]
54 mod tests;
55
56 use hir::{diagnostics::AnyDiagnostic, Semantics};
57 use ide_db::{
58     assists::{Assist, AssistId, AssistKind, AssistResolveStrategy},
59     base_db::{FileId, SourceDatabase},
60     label::Label,
61     source_change::SourceChange,
62     FxHashSet, RootDatabase,
63 };
64 use syntax::{ast::AstNode, TextRange};
65
66 #[derive(Copy, Clone, Debug, PartialEq)]
67 pub struct DiagnosticCode(pub &'static str);
68
69 impl DiagnosticCode {
70     pub fn as_str(&self) -> &str {
71         self.0
72     }
73 }
74
75 #[derive(Debug)]
76 pub struct Diagnostic {
77     pub code: DiagnosticCode,
78     pub message: String,
79     pub range: TextRange,
80     pub severity: Severity,
81     pub unused: bool,
82     pub experimental: bool,
83     pub fixes: Option<Vec<Assist>>,
84 }
85
86 impl Diagnostic {
87     fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic {
88         let message = message.into();
89         Diagnostic {
90             code: DiagnosticCode(code),
91             message,
92             range,
93             severity: Severity::Error,
94             unused: false,
95             experimental: false,
96             fixes: None,
97         }
98     }
99
100     fn experimental(mut self) -> Diagnostic {
101         self.experimental = true;
102         self
103     }
104
105     fn severity(mut self, severity: Severity) -> Diagnostic {
106         self.severity = severity;
107         self
108     }
109
110     fn with_fixes(mut self, fixes: Option<Vec<Assist>>) -> Diagnostic {
111         self.fixes = fixes;
112         self
113     }
114
115     fn with_unused(mut self, unused: bool) -> Diagnostic {
116         self.unused = unused;
117         self
118     }
119 }
120
121 #[derive(Debug, Copy, Clone)]
122 pub enum Severity {
123     Error,
124     // We don't actually emit this one yet, but we should at some point.
125     // Warning,
126     WeakWarning,
127 }
128
129 #[derive(Clone, Debug, PartialEq, Eq)]
130 pub enum ExprFillDefaultMode {
131     Todo,
132     Default,
133 }
134 impl Default for ExprFillDefaultMode {
135     fn default() -> Self {
136         Self::Todo
137     }
138 }
139
140 #[derive(Default, Debug, Clone)]
141 pub struct DiagnosticsConfig {
142     pub attr_proc_macros_enabled: bool,
143     pub disable_experimental: bool,
144     pub disabled: FxHashSet<String>,
145     pub expr_fill_default: ExprFillDefaultMode,
146 }
147
148 struct DiagnosticsContext<'a> {
149     config: &'a DiagnosticsConfig,
150     sema: Semantics<'a, RootDatabase>,
151     resolve: &'a AssistResolveStrategy,
152 }
153
154 pub fn diagnostics(
155     db: &RootDatabase,
156     config: &DiagnosticsConfig,
157     resolve: &AssistResolveStrategy,
158     file_id: FileId,
159 ) -> Vec<Diagnostic> {
160     let _p = profile::span("diagnostics");
161     let sema = Semantics::new(db);
162     let parse = db.parse(file_id);
163     let mut res = Vec::new();
164
165     // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
166     res.extend(
167         parse.errors().iter().take(128).map(|err| {
168             Diagnostic::new("syntax-error", format!("Syntax Error: {}", err), err.range())
169         }),
170     );
171
172     for node in parse.tree().syntax().descendants() {
173         handlers::useless_braces::useless_braces(&mut res, file_id, &node);
174         handlers::field_shorthand::field_shorthand(&mut res, file_id, &node);
175     }
176
177     let module = sema.to_module_def(file_id);
178
179     let ctx = DiagnosticsContext { config, sema, resolve };
180     if module.is_none() {
181         handlers::unlinked_file::unlinked_file(&ctx, &mut res, file_id);
182     }
183
184     let mut diags = Vec::new();
185     if let Some(m) = module {
186         m.diagnostics(db, &mut diags)
187     }
188
189     for diag in diags {
190         #[rustfmt::skip]
191         let d = match diag {
192             AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d),
193             AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d),
194             AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d),
195             AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d),
196             AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d),
197             AnyDiagnostic::MissingFields(d) => handlers::missing_fields::missing_fields(&ctx, &d),
198             AnyDiagnostic::MissingMatchArms(d) => handlers::missing_match_arms::missing_match_arms(&ctx, &d),
199             AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d),
200             AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d),
201             AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d),
202             AnyDiagnostic::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d),
203             AnyDiagnostic::UnimplementedBuiltinMacro(d) => handlers::unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d),
204             AnyDiagnostic::UnresolvedExternCrate(d) => handlers::unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
205             AnyDiagnostic::UnresolvedImport(d) => handlers::unresolved_import::unresolved_import(&ctx, &d),
206             AnyDiagnostic::UnresolvedMacroCall(d) => handlers::unresolved_macro_call::unresolved_macro_call(&ctx, &d),
207             AnyDiagnostic::UnresolvedModule(d) => handlers::unresolved_module::unresolved_module(&ctx, &d),
208             AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d, config.attr_proc_macros_enabled),
209             AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d),
210
211             AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) {
212                 Some(it) => it,
213                 None => continue,
214             }
215         };
216         res.push(d)
217     }
218
219     res.retain(|d| {
220         !ctx.config.disabled.contains(d.code.as_str())
221             && !(ctx.config.disable_experimental && d.experimental)
222     });
223
224     res
225 }
226
227 fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist {
228     let mut res = unresolved_fix(id, label, target);
229     res.source_change = Some(source_change);
230     res
231 }
232
233 fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
234     assert!(!id.contains(' '));
235     Assist {
236         id: AssistId(id, AssistKind::QuickFix),
237         label: Label::new(label.to_string()),
238         group: None,
239         target,
240         source_change: None,
241         trigger_signature_help: false,
242     }
243 }