1pub mod content;
10pub mod file_path;
11pub mod package;
12pub mod project;
13pub mod runner;
14
15use camino::Utf8Path;
16use guppy::PackageId;
17use std::{borrow::Cow, fmt};
18
19pub trait Linter: Send + Sync + fmt::Debug {
21 fn name(&self) -> &'static str;
23}
24
25trait LintContext<'l> {
27 fn kind(&self) -> LintKind<'l>;
29
30 fn source(&self, name: &'static str) -> LintSource<'l> {
32 LintSource::new(name, self.kind())
33 }
34}
35
36pub struct LintFormatter<'l, 'a> {
40 source: LintSource<'l>,
41 messages: &'a mut Vec<(LintSource<'l>, LintMessage)>,
42}
43
44impl<'l, 'a> LintFormatter<'l, 'a> {
45 pub fn new(
46 source: LintSource<'l>,
47 messages: &'a mut Vec<(LintSource<'l>, LintMessage)>,
48 ) -> Self {
49 Self { source, messages }
50 }
51
52 pub fn write(&mut self, level: LintLevel, message: impl Into<Cow<'static, str>>) {
54 self.messages
55 .push((self.source, LintMessage::new(level, message)));
56 }
57
58 pub fn write_kind(
60 &mut self,
61 kind: LintKind<'l>,
62 level: LintLevel,
63 message: impl Into<Cow<'static, str>>,
64 ) {
65 self.messages.push((
66 LintSource::new(self.source.name(), kind),
67 LintMessage::new(level, message),
68 ));
69 }
70}
71
72#[derive(Clone, Debug, Eq, PartialEq)]
74pub enum RunStatus<'l> {
75 Executed,
77 Skipped(SkipReason<'l>),
79}
80
81#[derive(Clone, Debug, Eq, PartialEq)]
83#[non_exhaustive]
84pub enum SkipReason<'l> {
85 NonUtf8Content,
87 UnsupportedExtension(Option<&'l str>),
89 UnsupportedFile(&'l Utf8Path),
91 UnsupportedPackage(&'l PackageId),
93 GlobExemption(&'l str),
95 }
97
98#[derive(Debug)]
100pub struct LintMessage {
101 level: LintLevel,
102 message: Cow<'static, str>,
103}
104
105impl LintMessage {
106 pub fn new(level: LintLevel, message: impl Into<Cow<'static, str>>) -> Self {
107 Self {
108 level,
109 message: message.into(),
110 }
111 }
112
113 pub fn level(&self) -> LintLevel {
114 self.level
115 }
116
117 pub fn message(&self) -> &str {
118 &self.message
119 }
120}
121
122#[derive(Copy, Clone, Debug, Eq, PartialEq)]
123#[allow(dead_code)]
124#[non_exhaustive]
125pub enum LintLevel {
126 Error,
127 Warning,
128 }
130
131impl fmt::Display for LintLevel {
132 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133 match self {
134 LintLevel::Error => write!(f, "ERROR"),
135 LintLevel::Warning => write!(f, "WARNING"),
136 }
137 }
138}
139
140#[derive(Copy, Clone, Debug)]
142pub struct LintSource<'l> {
143 name: &'static str,
144 kind: LintKind<'l>,
145}
146
147impl<'l> LintSource<'l> {
148 fn new(name: &'static str, kind: LintKind<'l>) -> Self {
149 Self { name, kind }
150 }
151
152 pub fn name(&self) -> &'static str {
153 self.name
154 }
155
156 pub fn kind(&self) -> LintKind<'l> {
157 self.kind
158 }
159}
160
161#[derive(Copy, Clone, Debug, Eq, PartialEq)]
162pub enum LintKind<'l> {
163 Project,
164 Package {
165 name: &'l str,
166 workspace_path: &'l Utf8Path,
167 },
168 FilePath(&'l Utf8Path),
169 Content(&'l Utf8Path),
170}
171
172impl<'l> fmt::Display for LintKind<'l> {
173 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
174 match self {
175 LintKind::Project => write!(f, "project"),
176 LintKind::Package {
177 name,
178 workspace_path,
179 } => write!(f, "package '{}' (at {})", name, workspace_path),
180 LintKind::FilePath(path) => write!(f, "file path {}", path),
181 LintKind::Content(path) => write!(f, "content {}", path),
182 }
183 }
184}
185
186pub mod prelude {
187 pub use super::{
188 content::{ContentContext, ContentLinter},
189 file_path::{FilePathContext, FilePathLinter},
190 package::{PackageContext, PackageLinter},
191 project::{ProjectContext, ProjectLinter},
192 runner::{LintEngine, LintEngineConfig, LintResults},
193 LintFormatter, LintKind, LintLevel, LintMessage, LintSource, Linter, RunStatus, SkipReason,
194 };
195 pub use x_core::{Result, SystemError};
196}