Skip to content

Commit

Permalink
Diagnostics: Add helper to declaratively generate a diagnostic if a f…
Browse files Browse the repository at this point in the history
…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
alanz authored and facebook-github-bot committed Apr 3, 2024
1 parent 4d9df03 commit 2060ade
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/ide/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ mod eqwalizer_assists;
mod expression_can_be_simplified;
mod from_config;
mod head_mismatch;
#[allow(dead_code)] // Temporary until next diff
mod helpers;
mod meck;
// @fb-only: mod meta_only;
mod missing_compile_warn_missing_spec;
Expand Down
144 changes: 144 additions & 0 deletions crates/ide/src/diagnostics/helpers.rs
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
"#,
)
}
}

0 comments on commit 2060ade

Please sign in to comment.