use crate::code_lint::ast::ParsedFile;
use crate::code_lint::contract::{CodeRule, RuleTarget};
use crate::diagnostic::{Diagnostic, Language, RuleName, ViolationTemplate, violation_template};
use crate::rule_declaration::{
Classification, Consensus, Declaration, EnforcementMode, Example, FilterListDefaults,
ImpactedQuality, LanguageDefaults, ListKind, ListOption, Precision, Reference, RuleDoc,
RuleOptions, Topic,
};
use std::collections::HashSet;
use std::path::Path;
const BANNED: ListOption = ListOption {
kind: ListKind::Deny,
doc: "Exception suppression calls flagged when called.",
default: FilterListDefaults {
base: &["suppress", "contextlib.suppress"],
extend: &[],
remove: &[],
},
};
const TEMPLATE: ViolationTemplate = violation_template! {
summary: "An exception is silenced with `{callee}()`.",
rationale: "Silencing an exception without a recorded reason hides unexpected failures and leaves the next reader unable to tell intentional ignoring from accidental masking.",
suggestion: "Replace the block with an explicit `except` handler.",
};
pub const RULE: CodeRule<ListOption> = CodeRule {
declaration: Declaration {
name: RuleName("suppressed-exception"),
template: &TEMPLATE,
languages: &[Language::Python],
options: RuleOptions {
enforcement_mode: Some(LanguageDefaults::new(
EnforcementMode::RequireExplanation,
&[],
)),
options: BANNED,
},
classification: Classification {
topics: &[Topic::ERROR_HANDLING],
precision: Precision::Exact,
consensus: Consensus::Opinionated,
impacted_quality: ImpactedQuality::Maintainability,
},
doc: RuleDoc {
summary: "Flags `contextlib.suppress` blocks.",
what_it_does: indoc::indoc! {r"
Flags `contextlib.suppress(...)` used as a context manager in a `with` statement."},
why_is_this_bad: indoc::indoc! {r"
`suppress` silently discards an exception. The code does not say why that failure is
harmless, so a reader cannot tell an intentional ignore from a bug being hidden, and
a later change that makes the exception meaningful goes unnoticed.
An explicit `except` handler keeps the ignored case visible and can record it, for
example with a debug log."},
known_problems: None,
references: &[
Reference {
title: "Python docs: contextlib.suppress",
url: "https://docs.python.org/3/library/contextlib.html#contextlib.suppress",
},
Reference::NAME_RESOLUTION,
],
examples: &[Example {
language: Language::Python,
flagged: indoc::indoc! {r"
with suppress(FileNotFoundError):
os.remove(lock_path)
"},
flagged_span: "suppress(FileNotFoundError)",
fixed: indoc::indoc! {r#"
try:
os.remove(lock_path)
except FileNotFoundError:
logger.debug("Lock file %s was already removed by the cleanup job.", lock_path)
"#},
}],
},
},
target: RuleTarget::All,
check: check_file,
};
fn check_file(
rule: &CodeRule<ListOption>,
path: &Path,
file: &ParsedFile,
banned_calls: &HashSet<String>,
) -> Vec<Diagnostic> {
rule.check_banned_calls_where(path, file, banned_calls, |matched| {
matched.is_with_context_manager
})
}
#[cfg(test)]
crate::test_utils::rule_test!(
RULE,
{
Python => {
pass: [
suppress_call_outside_with_ignored => r#"
mgr = suppress(FileNotFoundError)
"#,
],
fail: [
bare_suppress => r#"
with suppress(FileNotFoundError):
os.remove("tmp.txt")
"# => "suppress(FileNotFoundError)",
contextlib_qualified => r#"
with contextlib.suppress(KeyError):
data = cache["missing"]
"# => "contextlib.suppress(KeyError)",
],
},
}
);