1mod early;
2mod late;
3
4pub use early::{EarlyLintPass, EarlyLintVisitor};
5pub use late::{LateLintPass, LateLintVisitor};
6
7use foundry_compilers::Language;
8use foundry_config::lint::Severity;
9use solar_interface::{
10 Session, Span,
11 diagnostics::{DiagBuilder, DiagId, DiagMsg, MultiSpan, Style},
12};
13use solar_sema::ParsingContext;
14use std::path::PathBuf;
15
16use crate::inline_config::InlineConfig;
17
18pub trait Linter: Send + Sync + Clone {
37 type Language: Language;
38 type Lint: Lint;
39
40 fn init(&self) -> Session;
41 fn early_lint<'sess>(&self, input: &[PathBuf], pcx: ParsingContext<'sess>);
42 fn late_lint<'sess>(&self, input: &[PathBuf], pcx: ParsingContext<'sess>);
43}
44
45pub trait Lint {
46 fn id(&self) -> &'static str;
47 fn severity(&self) -> Severity;
48 fn description(&self) -> &'static str;
49 fn help(&self) -> &'static str;
50}
51
52pub struct LintContext<'s> {
53 sess: &'s Session,
54 with_description: bool,
55 pub inline_config: InlineConfig,
56 active_lints: Vec<&'static str>,
57}
58
59impl<'s> LintContext<'s> {
60 pub fn new(
61 sess: &'s Session,
62 with_description: bool,
63 config: InlineConfig,
64 active_lints: Vec<&'static str>,
65 ) -> Self {
66 Self { sess, with_description, inline_config: config, active_lints }
67 }
68
69 pub fn session(&self) -> &'s Session {
70 self.sess
71 }
72
73 pub fn is_lint_enabled(&self, id: &'static str) -> bool {
78 self.active_lints.contains(&id)
79 }
80
81 pub fn emit<L: Lint>(&self, lint: &'static L, span: Span) {
83 if self.inline_config.is_disabled(span, lint.id()) || !self.is_lint_enabled(lint.id()) {
84 return;
85 }
86
87 let desc = if self.with_description { lint.description() } else { "" };
88 let diag: DiagBuilder<'_, ()> = self
89 .sess
90 .dcx
91 .diag(lint.severity().into(), desc)
92 .code(DiagId::new_str(lint.id()))
93 .span(MultiSpan::from_span(span))
94 .help(lint.help());
95
96 diag.emit();
97 }
98
99 pub fn emit_with_fix<L: Lint>(&self, lint: &'static L, span: Span, snippet: Snippet) {
104 if self.inline_config.is_disabled(span, lint.id()) || !self.is_lint_enabled(lint.id()) {
105 return;
106 }
107
108 let snippet = match snippet {
110 Snippet::Diff { desc, span: diff_span, add } => {
111 let target_span = diff_span.unwrap_or(span);
113
114 if self.span_to_snippet(target_span).is_some() {
116 Snippet::Diff { desc, span: Some(target_span), add }
117 } else {
118 Snippet::Block { desc, code: add }
120 }
121 }
122 block => block,
124 };
125
126 let desc = if self.with_description { lint.description() } else { "" };
127 let diag: DiagBuilder<'_, ()> = self
128 .sess
129 .dcx
130 .diag(lint.severity().into(), desc)
131 .code(DiagId::new_str(lint.id()))
132 .span(MultiSpan::from_span(span))
133 .highlighted_note(snippet.to_note(self))
134 .help(lint.help());
135
136 diag.emit();
137 }
138
139 pub fn span_to_snippet(&self, span: Span) -> Option<String> {
140 self.sess.source_map().span_to_snippet(span).ok()
141 }
142}
143
144#[derive(Debug, Clone, Eq, PartialEq)]
145pub enum Snippet {
146 Block { desc: Option<&'static str>, code: String },
148 Diff { desc: Option<&'static str>, span: Option<Span>, add: String },
150}
151
152impl Snippet {
153 pub fn to_note(self, ctx: &LintContext<'_>) -> Vec<(DiagMsg, Style)> {
154 let mut output = Vec::new();
155 match self.desc() {
156 Some(desc) => {
157 output.push((DiagMsg::from(desc), Style::NoStyle));
158 output.push((DiagMsg::from("\n\n"), Style::NoStyle));
159 }
160 None => output.push((DiagMsg::from(" \n"), Style::NoStyle)),
161 }
162 match self {
163 Self::Diff { span, add, .. } => {
164 if let Some(span) = span
166 && let Some(rmv) = ctx.span_to_snippet(span)
167 {
168 for line in rmv.lines() {
169 output.push((DiagMsg::from(format!("- {line}\n")), Style::Removal));
170 }
171 }
172 for line in add.lines() {
173 output.push((DiagMsg::from(format!("+ {line}\n")), Style::Addition));
174 }
175 }
176 Self::Block { code, .. } => {
177 for line in code.lines() {
178 output.push((DiagMsg::from(format!("- {line}\n")), Style::NoStyle));
179 }
180 }
181 }
182 output.push((DiagMsg::from("\n"), Style::NoStyle));
183 output
184 }
185
186 pub fn desc(&self) -> Option<&'static str> {
187 match self {
188 Self::Diff { desc, .. } => *desc,
189 Self::Block { desc, .. } => *desc,
190 }
191 }
192}