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 {
33 type Language: Language;
34 type Lint: Lint;
35
36 fn init(&self) -> Session;
37 fn early_lint<'sess>(&self, input: &[PathBuf], pcx: ParsingContext<'sess>);
38 fn late_lint<'sess>(&self, input: &[PathBuf], pcx: ParsingContext<'sess>);
39}
40
41pub trait Lint {
42 fn id(&self) -> &'static str;
43 fn severity(&self) -> Severity;
44 fn description(&self) -> &'static str;
45 fn help(&self) -> &'static str;
46}
47
48pub struct LintContext<'s> {
49 sess: &'s Session,
50 with_description: bool,
51 pub inline_config: InlineConfig,
52}
53
54impl<'s> LintContext<'s> {
55 pub fn new(sess: &'s Session, with_description: bool, config: InlineConfig) -> Self {
56 Self { sess, with_description, inline_config: config }
57 }
58
59 pub fn session(&self) -> &'s Session {
60 self.sess
61 }
62
63 pub fn emit<L: Lint>(&self, lint: &'static L, span: Span) {
65 if self.inline_config.is_disabled(span, lint.id()) {
66 return;
67 }
68
69 let desc = if self.with_description { lint.description() } else { "" };
70 let diag: DiagBuilder<'_, ()> = self
71 .sess
72 .dcx
73 .diag(lint.severity().into(), desc)
74 .code(DiagId::new_str(lint.id()))
75 .span(MultiSpan::from_span(span))
76 .help(lint.help());
77
78 diag.emit();
79 }
80
81 pub fn emit_with_fix<L: Lint>(&self, lint: &'static L, span: Span, snippet: Snippet) {
86 if self.inline_config.is_disabled(span, lint.id()) {
87 return;
88 }
89
90 let snippet = match snippet {
92 Snippet::Diff { desc, span: diff_span, add } => {
93 let target_span = diff_span.unwrap_or(span);
95
96 if self.span_to_snippet(target_span).is_some() {
98 Snippet::Diff { desc, span: Some(target_span), add }
99 } else {
100 Snippet::Block { desc, code: add }
102 }
103 }
104 block => block,
106 };
107
108 let desc = if self.with_description { lint.description() } else { "" };
109 let diag: DiagBuilder<'_, ()> = self
110 .sess
111 .dcx
112 .diag(lint.severity().into(), desc)
113 .code(DiagId::new_str(lint.id()))
114 .span(MultiSpan::from_span(span))
115 .highlighted_note(snippet.to_note(self))
116 .help(lint.help());
117
118 diag.emit();
119 }
120
121 pub fn span_to_snippet(&self, span: Span) -> Option<String> {
122 self.sess.source_map().span_to_snippet(span).ok()
123 }
124}
125
126#[derive(Debug, Clone, Eq, PartialEq)]
127pub enum Snippet {
128 Block { desc: Option<&'static str>, code: String },
130 Diff { desc: Option<&'static str>, span: Option<Span>, add: String },
132}
133
134impl Snippet {
135 pub fn to_note(self, ctx: &LintContext<'_>) -> Vec<(DiagMsg, Style)> {
136 let mut output = Vec::new();
137 match self.desc() {
138 Some(desc) => {
139 output.push((DiagMsg::from(desc), Style::NoStyle));
140 output.push((DiagMsg::from("\n\n"), Style::NoStyle));
141 }
142 None => output.push((DiagMsg::from(" \n"), Style::NoStyle)),
143 }
144 match self {
145 Self::Diff { span, add, .. } => {
146 if let Some(span) = span
148 && let Some(rmv) = ctx.span_to_snippet(span)
149 {
150 for line in rmv.lines() {
151 output.push((DiagMsg::from(format!("- {line}\n")), Style::Removal));
152 }
153 }
154 for line in add.lines() {
155 output.push((DiagMsg::from(format!("+ {line}\n")), Style::Addition));
156 }
157 }
158 Self::Block { code, .. } => {
159 for line in code.lines() {
160 output.push((DiagMsg::from(format!("- {line}\n")), Style::NoStyle));
161 }
162 }
163 }
164 output.push((DiagMsg::from("\n"), Style::NoStyle));
165 output
166 }
167
168 pub fn desc(&self) -> Option<&'static str> {
169 match self {
170 Self::Diff { desc, .. } => *desc,
171 Self::Block { desc, .. } => *desc,
172 }
173 }
174}