-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Diagnostics: Add helper to declaratively generate a diagnostic if a f…
…unction is used Summary: This brings in a `DiagnosticTemplate` for a (static) diagnostic, giving message, code, severity, whether it should include a quick fix, and a structure to capture function matches. This allows us to generate a given diagnostic for a function being used by just calling a function with the appropriate data structure. It is made use of in the next diff. Reviewed By: robertoaloi Differential Revision: D55691840 fbshipit-source-id: 1666e06972051750f475f9702f005264598e3e1a
- Loading branch information
1 parent
4d9df03
commit 2060ade
Showing
2 changed files
with
146 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under both the MIT license found in the | ||
* LICENSE-MIT file in the root directory of this source tree and the Apache | ||
* License, Version 2.0 found in the LICENSE-APACHE file in the root directory | ||
* of this source tree. | ||
*/ | ||
|
||
//! Helpers for writing diagnostics | ||
|
||
use elp_ide_db::elp_base_db::FileId; | ||
use elp_ide_db::DiagnosticCode; | ||
use hir::FunctionDef; | ||
use hir::Semantic; | ||
|
||
use super::Diagnostic; | ||
use super::Severity; | ||
use crate::codemod_helpers::find_call_in_function; | ||
use crate::codemod_helpers::MakeDiagCtx; | ||
use crate::FunctionMatch; | ||
|
||
// --------------------------------------------------------------------- | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct DiagnosticTemplate { | ||
pub(crate) code: DiagnosticCode, | ||
pub(crate) message: String, | ||
pub(crate) severity: Severity, | ||
pub(crate) with_ignore_fix: bool, | ||
} | ||
|
||
/// Define a checker for a function that should not be used. Generate | ||
/// a diagnostic according to the template if it is found. | ||
#[derive(Debug)] | ||
pub(crate) struct FunctionCallDiagnostic { | ||
pub(crate) diagnostic_template: DiagnosticTemplate, | ||
pub(crate) matches: Vec<FunctionMatch>, | ||
} | ||
|
||
pub(crate) fn check_used_functions( | ||
sema: &Semantic, | ||
file_id: FileId, | ||
used_functions: &[FunctionCallDiagnostic], | ||
diags: &mut Vec<Diagnostic>, | ||
) { | ||
let mfas: Vec<(&FunctionMatch, &DiagnosticTemplate)> = used_functions | ||
.iter() | ||
.flat_map(|u| u.matches.iter().map(|m| (m, &u.diagnostic_template))) | ||
.collect(); | ||
sema.def_map(file_id) | ||
.get_functions() | ||
.for_each(|(_, def)| check_function_with_diagnostic_template(diags, sema, def, &mfas)); | ||
} | ||
|
||
pub(crate) fn check_function_with_diagnostic_template( | ||
diags: &mut Vec<Diagnostic>, | ||
sema: &Semantic, | ||
def: &FunctionDef, | ||
mfas: &[(&FunctionMatch, &DiagnosticTemplate)], | ||
) { | ||
find_call_in_function( | ||
diags, | ||
sema, | ||
def, | ||
mfas, | ||
&move |ctx| Some(*ctx.t), | ||
&move |MakeDiagCtx { | ||
sema, | ||
def_fb, | ||
extra, | ||
range, | ||
.. | ||
}: MakeDiagCtx<'_, &DiagnosticTemplate>| { | ||
let diag = Diagnostic::new(extra.code.clone(), extra.message.clone(), range) | ||
.with_severity(extra.severity); | ||
let diag = if extra.with_ignore_fix { | ||
diag.with_ignore_fix(sema, def_fb.file_id()) | ||
} else { | ||
diag | ||
}; | ||
Some(diag) | ||
}, | ||
); | ||
} | ||
|
||
// --------------------------------------------------------------------- | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use elp_ide_db::DiagnosticCode; | ||
|
||
use super::check_used_functions; | ||
use super::DiagnosticTemplate; | ||
use super::FunctionCallDiagnostic; | ||
use crate::diagnostics::AdhocSemanticDiagnostics; | ||
use crate::diagnostics::DiagnosticsConfig; | ||
use crate::diagnostics::Severity; | ||
use crate::tests::check_diagnostics_with_config; | ||
use crate::FunctionMatch; | ||
|
||
#[track_caller] | ||
pub(crate) fn check_diagnostics_with_ad_hoc_semantics<'a>( | ||
ad_hoc_semantic_diagnostics: Vec<&'a dyn AdhocSemanticDiagnostics>, | ||
fixture: &str, | ||
) { | ||
let config = DiagnosticsConfig::default() | ||
.set_experimental(true) | ||
.disable(DiagnosticCode::UndefinedFunction) | ||
.set_ad_hoc_semantic_diagnostics(ad_hoc_semantic_diagnostics); | ||
check_diagnostics_with_config(config, fixture) | ||
} | ||
|
||
#[test] | ||
fn unused_function() { | ||
check_diagnostics_with_ad_hoc_semantics( | ||
vec![&|acc, sema, file_id, _ext| { | ||
check_used_functions( | ||
sema, | ||
file_id, | ||
&vec![FunctionCallDiagnostic { | ||
diagnostic_template: DiagnosticTemplate { | ||
code: DiagnosticCode::AdHoc("a code".to_string()), | ||
message: "diagnostic message".to_string(), | ||
severity: Severity::Warning, | ||
with_ignore_fix: true, | ||
}, | ||
matches: vec![FunctionMatch::mfas("main", "foo", vec![0])] | ||
.into_iter() | ||
.flatten() | ||
.collect(), | ||
}], | ||
acc, | ||
); | ||
}], | ||
r#" | ||
-module(main). | ||
foo() -> main:foo(). | ||
%% ^^^^^^^^^^ 💡 warning: diagnostic message | ||
"#, | ||
) | ||
} | ||
} |