Skip to main content

forge_lint/linter/
project.rs

1use super::{Lint, LintContext, LinterConfig};
2use foundry_common::comments::inline_config::InlineConfig;
3use foundry_config::lint::LintSpecificConfig;
4use solar::{
5    ast,
6    interface::{Session, Span, diagnostics::DiagMsg, source_map::SourceFile},
7    sema::Gcx,
8};
9use std::{path::PathBuf, sync::Arc};
10
11/// A single source unit visible to a project-wide lint pass, pre-loaded with its inline config so
12/// emits respect `// forge-lint: disable-*` markers without rebuilding it per emit.
13pub struct ProjectSource<'ast> {
14    pub path: PathBuf,
15    pub file: Arc<SourceFile>,
16    pub ast: &'ast ast::SourceUnit<'ast>,
17    pub inline_config: InlineConfig<Vec<String>>,
18}
19
20/// Trait for lints that need to inspect every input source at once (e.g. cross-file checks).
21///
22/// `check_project` runs once after all per-file [`super::EarlyLintPass`] /
23/// [`super::LateLintPass`] passes have completed. Passes that need HIR or type-check info access
24/// it through [`ProjectLintEmitter::gcx`].
25pub trait ProjectLintPass<'ast>: Send + Sync {
26    fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]);
27}
28
29/// Helper passed to [`ProjectLintPass::check_project`] for emitting diagnostics against a specific
30/// source and accessing the global compilation context.
31pub struct ProjectLintEmitter<'s, 'c> {
32    sess: &'s Session,
33    gcx: Gcx<'s>,
34    with_description: bool,
35    with_json_emitter: bool,
36    lint_specific: &'c LintSpecificConfig,
37    active_lints: Vec<&'static str>,
38}
39
40impl<'s, 'c> ProjectLintEmitter<'s, 'c> {
41    pub const fn new(
42        sess: &'s Session,
43        gcx: Gcx<'s>,
44        with_description: bool,
45        with_json_emitter: bool,
46        lint_specific: &'c LintSpecificConfig,
47        active_lints: Vec<&'static str>,
48    ) -> Self {
49        Self { sess, gcx, with_description, with_json_emitter, lint_specific, active_lints }
50    }
51
52    /// Returns `true` if the given lint id is enabled for this run. Project passes that perform
53    /// expensive analysis should guard their work behind this check.
54    pub fn is_lint_enabled(&self, id: &'static str) -> bool {
55        self.active_lints.contains(&id)
56    }
57
58    /// Returns the global compilation context, for passes that need HIR or semantic info.
59    pub const fn gcx(&self) -> Gcx<'s> {
60        self.gcx
61    }
62
63    /// Emits a diagnostic with the lint's default description as the message.
64    pub fn emit<'a, 'ast, L: Lint>(
65        &'a self,
66        source: &'a ProjectSource<'ast>,
67        lint: &'static L,
68        span: Span,
69    ) where
70        'c: 'a,
71    {
72        self.build_ctx(source).emit(lint, span);
73    }
74
75    /// Emits a diagnostic with a caller-provided message.
76    pub fn emit_with_msg<'a, 'ast, L: Lint>(
77        &'a self,
78        source: &'a ProjectSource<'ast>,
79        lint: &'static L,
80        span: Span,
81        msg: impl Into<DiagMsg>,
82    ) where
83        'c: 'a,
84    {
85        self.build_ctx(source).emit_with_msg(lint, span, msg);
86    }
87
88    fn build_ctx<'a, 'ast>(&'a self, source: &'a ProjectSource<'ast>) -> LintContext<'s, 'a>
89    where
90        'c: 'a,
91    {
92        LintContext::new(
93            self.sess,
94            self.with_description,
95            self.with_json_emitter,
96            LinterConfig { inline: &source.inline_config, lint_specific: self.lint_specific },
97            self.active_lints.clone(),
98            Some(source.file.clone()),
99        )
100    }
101}