-
-
Notifications
You must be signed in to change notification settings - Fork 350
/
lint.rs
290 lines (244 loc) · 8.3 KB
/
lint.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
use std::path::PathBuf;
use bpaf::Bpaf;
use oxc_linter::AllowWarnDeny;
use super::{
ignore::{ignore_options, IgnoreOptions},
misc_options, CliCommand, MiscOptions, VERSION,
};
// To add a header or footer, see
// <https://docs.rs/bpaf/latest/bpaf/struct.OptionParser.html#method.descr>
/// Linter for the JavaScript Oxidation Compiler
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options, version(VERSION))]
pub struct LintCommand {
#[bpaf(external(lint_options))]
pub lint_options: LintOptions,
}
impl LintCommand {
pub fn handle_threads(&self) {
CliCommand::set_rayon_threads(self.lint_options.misc_options.threads);
}
}
#[derive(Debug, Clone, Bpaf)]
pub struct LintOptions {
#[bpaf(external(lint_filter), map(LintFilter::into_tuple), many)]
pub filter: Vec<(AllowWarnDeny, String)>,
#[bpaf(external)]
pub enable_plugins: EnablePlugins,
#[bpaf(external)]
pub fix_options: FixOptions,
#[bpaf(external)]
pub ignore_options: IgnoreOptions,
#[bpaf(external)]
pub warning_options: WarningOptions,
#[bpaf(external)]
pub output_options: OutputOptions,
/// list all the rules that are currently registered
#[bpaf(long("rules"), switch, hide_usage)]
pub list_rules: bool,
#[bpaf(external)]
pub misc_options: MiscOptions,
/// ESLint configuration file (experimental)
///
/// * only `.json` extension is supported
#[bpaf(long, short, argument("PATH"))]
pub config: Option<PathBuf>,
/// TypeScript `tsconfig.json` path for reading path alias and project references for import plugin
#[bpaf(argument("PATH"))]
pub tsconfig: Option<PathBuf>,
/// Single file, single path or list of paths
#[bpaf(positional("PATH"), many, guard(validate_paths, PATHS_ERROR_MESSAGE))]
pub paths: Vec<PathBuf>,
}
#[allow(clippy::ptr_arg)]
fn validate_paths(paths: &Vec<PathBuf>) -> bool {
if paths.is_empty() {
true
} else {
paths.iter().all(|p| p.components().next() != Some(std::path::Component::ParentDir))
}
}
const PATHS_ERROR_MESSAGE: &str = "PATH cannot start with \"..\"";
// This is formatted according to
// <https://docs.rs/bpaf/latest/bpaf/params/struct.NamedArg.html#method.help>
/// Allowing / Denying Multiple Lints
/// For example `-D correctness -A no-debugger` or `-A all -D no-debugger`.
/// ㅤ
/// The default category is "-D correctness".
/// Use "--rules" for rule names.
/// Use "--help --help" for rule categories.
///
/// The categories are:
/// * correctness - code that is outright wrong or useless
/// * suspicious - code that is most likely wrong or useless
/// * pedantic - lints which are rather strict or have occasional false positives
/// * style - code that should be written in a more idiomatic way
/// * nursery - new lints that are still under development
/// * restriction - lints which prevent the use of language and library features
/// * all - all the categories listed above
#[derive(Debug, Clone, Bpaf)]
pub enum LintFilter {
Allow(
/// Allow the rule or category (suppress the lint)
#[bpaf(short('A'), long("allow"), argument("NAME"))]
String,
),
Deny(
/// Deny the rule or category (emit an error)
#[bpaf(short('D'), long("deny"), argument("NAME"))]
String,
),
}
impl LintFilter {
fn into_tuple(self) -> (AllowWarnDeny, String) {
match self {
Self::Allow(s) => (AllowWarnDeny::Allow, s),
Self::Deny(s) => (AllowWarnDeny::Deny, s),
}
}
}
/// Fix Problems
#[derive(Debug, Clone, Bpaf)]
pub struct FixOptions {
/// Fix as many issues as possible. Only unfixed issues are reported in the output
#[bpaf(switch)]
pub fix: bool,
}
/// Handle Warnings
#[derive(Debug, Clone, Bpaf)]
pub struct WarningOptions {
/// Disable reporting on warnings, only errors are reported
#[bpaf(switch, hide_usage)]
pub quiet: bool,
/// Ensure warnings produce a non-zero exit code
#[bpaf(switch, hide_usage)]
pub deny_warnings: bool,
/// Specify a warning threshold,
/// which can be used to force exit with an error status if there are too many warning-level rule violations in your project
#[bpaf(argument("INT"), hide_usage)]
pub max_warnings: Option<usize>,
}
/// Output
#[derive(Debug, Clone, Bpaf)]
pub struct OutputOptions {
/// Use a specific output format (default, json)
// last flag is the default
#[bpaf(long, short, flag(OutputFormat::Json, OutputFormat::Default))]
pub format: OutputFormat,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum OutputFormat {
Default,
Json,
}
/// Enable Plugins
#[allow(clippy::struct_field_names)]
#[derive(Debug, Clone, Bpaf)]
pub struct EnablePlugins {
/// Enable the experimental import plugin and detect ESM problems
#[bpaf(switch, hide_usage)]
pub import_plugin: bool,
/// Enable the Jest plugin and detect test problems
#[bpaf(switch, hide_usage)]
pub jest_plugin: bool,
/// Enable the JSX-a11y plugin and detect accessibility problems
#[bpaf(switch, hide_usage)]
pub jsx_a11y_plugin: bool,
/// Enable the Next.js plugin and detect Next.js problems
#[bpaf(switch, hide_usage)]
pub nextjs_plugin: bool,
/// Enable the React performance plugin and detect rendering performance problems
#[bpaf(switch, hide_usage)]
pub react_perf_plugin: bool,
}
#[cfg(test)]
mod warning_options {
use super::{lint_command, WarningOptions};
fn get_warning_options(arg: &str) -> WarningOptions {
let args = arg.split(' ').map(std::string::ToString::to_string).collect::<Vec<_>>();
lint_command().run_inner(args.as_slice()).unwrap().lint_options.warning_options
}
#[test]
fn default() {
let options = get_warning_options(".");
assert!(!options.quiet);
assert_eq!(options.max_warnings, None);
}
#[test]
fn quiet() {
let options = get_warning_options("--quiet .");
assert!(options.quiet);
}
#[test]
fn max_warnings() {
let options = get_warning_options("--max-warnings 10 .");
assert_eq!(options.max_warnings, Some(10));
}
}
#[cfg(test)]
mod lint_options {
use std::path::PathBuf;
use oxc_linter::AllowWarnDeny;
use super::{lint_command, LintOptions, OutputFormat};
fn get_lint_options(arg: &str) -> LintOptions {
let args = arg.split(' ').map(std::string::ToString::to_string).collect::<Vec<_>>();
lint_command().run_inner(args.as_slice()).unwrap().lint_options
}
#[test]
fn default() {
let options = get_lint_options(".");
assert_eq!(options.paths, vec![PathBuf::from(".")]);
assert!(!options.fix_options.fix);
assert!(!options.list_rules);
assert_eq!(options.output_options.format, OutputFormat::Default);
}
#[test]
fn multiple_paths() {
let options = get_lint_options("foo bar baz");
assert_eq!(
options.paths,
[PathBuf::from("foo"), PathBuf::from("bar"), PathBuf::from("baz")]
);
}
#[test]
fn no_parent_path() {
match lint_command().run_inner(&["../parent_dir"]) {
Ok(_) => panic!("Should not allow parent dir"),
Err(err) => match err {
bpaf::ParseFailure::Stderr(doc) => {
assert_eq!("`../parent_dir`: PATH cannot start with \"..\"", format!("{doc}"));
}
_ => unreachable!(),
},
}
}
#[test]
fn fix() {
let options = get_lint_options("--fix test.js");
assert!(options.fix_options.fix);
}
#[test]
fn filter() {
let options =
get_lint_options("-D suspicious --deny pedantic -A no-debugger --allow no-var src");
assert_eq!(
options.filter,
[
(AllowWarnDeny::Deny, "suspicious".into()),
(AllowWarnDeny::Deny, "pedantic".into()),
(AllowWarnDeny::Allow, "no-debugger".into()),
(AllowWarnDeny::Allow, "no-var".into())
]
);
}
#[test]
fn format() {
let options = get_lint_options("-f json");
assert_eq!(options.output_options.format, OutputFormat::Json);
}
#[test]
fn list_rules() {
let options = get_lint_options("--rules");
assert!(options.list_rules);
}
}