use crate::ast::policy::{DiagnosticPolicy, PolicyVerb};
use crate::diag_catalog;
use crate::diagnostics::{Diagnostic, Severity};
pub fn apply_policy(diagnostics: Vec<Diagnostic>, policy: &DiagnosticPolicy) -> Vec<Diagnostic> {
if policy.entries.is_empty() {
return diagnostics;
}
let mut out: Vec<Diagnostic> = Vec::with_capacity(diagnostics.len());
for mut diag in diagnostics {
match policy.verb_for(&diag.code, diag.subject_id.as_deref()) {
None => out.push(diag),
Some(verb) => match verb {
PolicyVerb::Allow => {
match diag.severity {
Severity::Error => out.push(diag),
Severity::Warning | Severity::Advisory => {
}
}
}
PolicyVerb::Deny => {
match diag.severity {
Severity::Error => {}
Severity::Warning | Severity::Advisory => {
diag.severity = Severity::Error;
}
}
out.push(diag);
}
PolicyVerb::Warn => {
match diag.severity {
Severity::Error => {}
Severity::Warning | Severity::Advisory => {
diag.severity = Severity::Warning;
}
}
out.push(diag);
}
},
}
}
out
}
pub(super) fn check_policy_entries(policy: &DiagnosticPolicy, diagnostics: &mut Vec<Diagnostic>) {
for entry in &policy.entries {
match diag_catalog::lookup(&entry.code) {
None => {
diagnostics.push(Diagnostic::warning(
"policy.unknown_code",
format!(
"diagnostics policy names '{}', which is not a diagnostic code this \
engine emits; the entry has no effect",
entry.code
),
entry.source_span,
Some(entry.code.clone()),
));
}
Some(catalog_entry) => {
if !catalog_entry.is_governable() {
let verb_name = match entry.verb {
PolicyVerb::Allow => "allow",
PolicyVerb::Warn => "warn",
PolicyVerb::Deny => continue,
};
diagnostics.push(Diagnostic::warning(
"policy.ineffective_on_error",
format!(
"diagnostics policy `{verb_name} \"{}\"` has no effect: '{}' is an \
integrity Error and Error severity cannot be suppressed or \
weakened",
entry.code, entry.code
),
entry.source_span,
Some(entry.code.clone()),
));
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::policy::PolicyEntry;
fn policy(entries: Vec<(PolicyVerb, &str)>) -> DiagnosticPolicy {
DiagnosticPolicy {
entries: entries
.into_iter()
.map(|(verb, code)| PolicyEntry {
verb,
code: code.to_owned(),
subjects: Vec::new(),
source_span: None,
})
.collect(),
}
}
fn scoped_policy(entries: Vec<(PolicyVerb, &str, Vec<&str>)>) -> DiagnosticPolicy {
DiagnosticPolicy {
entries: entries
.into_iter()
.map(|(verb, code, subjects)| PolicyEntry {
verb,
code: code.to_owned(),
subjects: subjects.into_iter().map(str::to_owned).collect(),
source_span: None,
})
.collect(),
}
}
fn diag(code: &str, severity: Severity) -> Diagnostic {
Diagnostic::new(code, severity, "msg", None, None)
}
fn subject_diag(code: &str, severity: Severity, subject: &str) -> Diagnostic {
Diagnostic::new(code, severity, "msg", None, Some(subject.to_owned()))
}
#[test]
fn empty_policy_is_identity() {
let input = vec![diag("layout.off_canvas", Severity::Advisory)];
let out = apply_policy(input.clone(), &DiagnosticPolicy::default());
assert_eq!(out, input);
}
#[test]
fn allow_drops_advisory_but_not_error() {
let input = vec![
diag("layout.off_canvas", Severity::Advisory),
diag("id.duplicate", Severity::Error),
];
let p = policy(vec![
(PolicyVerb::Allow, "layout.off_canvas"),
(PolicyVerb::Allow, "id.duplicate"),
]);
let out = apply_policy(input, &p);
assert_eq!(out.len(), 1);
assert_eq!(out[0].code, "id.duplicate");
assert_eq!(out[0].severity, Severity::Error);
}
#[test]
fn deny_elevates_to_error() {
let input = vec![diag("token.unused", Severity::Advisory)];
let p = policy(vec![(PolicyVerb::Deny, "token.unused")]);
let out = apply_policy(input, &p);
assert_eq!(out.len(), 1);
assert_eq!(out[0].severity, Severity::Error);
}
#[test]
fn warn_forces_warning_and_last_wins_over_deny() {
let input = vec![diag("node.unknown_property", Severity::Warning)];
let p = policy(vec![
(PolicyVerb::Deny, "node.unknown_property"),
(PolicyVerb::Warn, "node.unknown_property"),
]);
let out = apply_policy(input, &p);
assert_eq!(out[0].severity, Severity::Warning);
}
#[test]
fn scoped_allow_drops_only_matching_subject() {
let input = vec![
subject_diag("layout.off_canvas", Severity::Advisory, "bg.glow"),
subject_diag("layout.off_canvas", Severity::Advisory, "shape.1"),
];
let p = scoped_policy(vec![(
PolicyVerb::Allow,
"layout.off_canvas",
vec!["bg.glow"],
)]);
let out = apply_policy(input, &p);
assert_eq!(out.len(), 1);
assert_eq!(out[0].subject_id.as_deref(), Some("shape.1"));
}
#[test]
fn unscoped_later_entry_overrides_scoped_entry_for_same_subject() {
let input = vec![subject_diag(
"layout.off_canvas",
Severity::Advisory,
"bg.glow",
)];
let p = DiagnosticPolicy {
entries: vec![
PolicyEntry {
verb: PolicyVerb::Deny,
code: "layout.off_canvas".to_owned(),
subjects: vec!["bg.glow".to_owned()],
source_span: None,
},
PolicyEntry {
verb: PolicyVerb::Warn,
code: "layout.off_canvas".to_owned(),
subjects: Vec::new(),
source_span: None,
},
],
};
let out = apply_policy(input, &p);
assert_eq!(out[0].severity, Severity::Warning);
}
#[test]
fn unknown_code_is_flagged() {
let p = policy(vec![(PolicyVerb::Allow, "not.a_real_code")]);
let mut out = Vec::new();
check_policy_entries(&p, &mut out);
assert_eq!(out.len(), 1);
assert_eq!(out[0].code, "policy.unknown_code");
}
#[test]
fn allow_on_error_code_is_flagged_but_deny_is_silent() {
let p = policy(vec![
(PolicyVerb::Allow, "id.duplicate"),
(PolicyVerb::Deny, "id.duplicate"),
]);
let mut out = Vec::new();
check_policy_entries(&p, &mut out);
assert_eq!(out.len(), 1);
assert_eq!(out[0].code, "policy.ineffective_on_error");
}
}