Skip to content

Commit 9e9435f

Browse files
committedSep 11, 2024·
refactor(linter): add LintFilter (#5685)
Re-creation of #5329
1 parent 731ffaa commit 9e9435f

File tree

8 files changed

+452
-81
lines changed

8 files changed

+452
-81
lines changed
 

‎apps/oxlint/src/lint/mod.rs

+44-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use std::{env, io::BufWriter, time::Instant};
33
use ignore::gitignore::Gitignore;
44
use oxc_diagnostics::{DiagnosticService, GraphicalReportHandler};
55
use oxc_linter::{
6-
partial_loader::LINT_PARTIAL_LOADER_EXT, LintService, LintServiceOptions, Linter, OxlintOptions,
6+
partial_loader::LINT_PARTIAL_LOADER_EXT, AllowWarnDeny, InvalidFilterKind, LintFilter,
7+
LintService, LintServiceOptions, Linter, OxlintOptions,
78
};
89
use oxc_span::VALID_EXTENSIONS;
910

@@ -80,6 +81,11 @@ impl Runner for LintRunner {
8081
}
8182
}
8283

84+
let filter = match Self::get_filters(filter) {
85+
Ok(filter) => filter,
86+
Err(e) => return e,
87+
};
88+
8389
let extensions = VALID_EXTENSIONS
8490
.iter()
8591
.chain(LINT_PARTIAL_LOADER_EXT.iter())
@@ -184,6 +190,43 @@ impl LintRunner {
184190
}
185191
diagnostic_service
186192
}
193+
194+
// moved into a separate function for readability, but it's only ever used
195+
// in one place.
196+
fn get_filters(
197+
filters_arg: Vec<(AllowWarnDeny, String)>,
198+
) -> Result<Vec<LintFilter>, CliRunResult> {
199+
let mut filters = Vec::with_capacity(filters_arg.len());
200+
201+
for (severity, filter_arg) in filters_arg {
202+
match LintFilter::new(severity, filter_arg) {
203+
Ok(filter) => {
204+
filters.push(filter);
205+
}
206+
Err(InvalidFilterKind::Empty) => {
207+
return Err(CliRunResult::InvalidOptions {
208+
message: format!("Cannot {severity} an empty filter."),
209+
});
210+
}
211+
Err(InvalidFilterKind::PluginMissing(filter)) => {
212+
return Err(CliRunResult::InvalidOptions {
213+
message: format!(
214+
"Failed to {severity} filter {filter}: Plugin name is missing. Expected <plugin>/<rule>"
215+
),
216+
});
217+
}
218+
Err(InvalidFilterKind::RuleMissing(filter)) => {
219+
return Err(CliRunResult::InvalidOptions {
220+
message: format!(
221+
"Failed to {severity} filter {filter}: Rule name is missing. Expected <plugin>/<rule>"
222+
),
223+
});
224+
}
225+
}
226+
}
227+
228+
Ok(filters)
229+
}
187230
}
188231

189232
#[cfg(all(test, not(target_os = "windows")))]

‎crates/oxc_linter/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub use crate::{
3232
context::LintContext,
3333
fixer::FixKind,
3434
frameworks::FrameworkFlags,
35-
options::{AllowWarnDeny, OxlintOptions},
35+
options::{AllowWarnDeny, InvalidFilterKind, LintFilter, OxlintOptions},
3636
rule::{RuleCategory, RuleFixMeta, RuleMeta, RuleWithSeverity},
3737
service::{LintService, LintServiceOptions},
3838
};

‎crates/oxc_linter/src/options/allow_warn_deny.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::convert::From;
1+
use std::{convert::From, fmt};
22

33
use oxc_diagnostics::{OxcDiagnostic, Severity};
44
use schemars::{schema::SchemaObject, JsonSchema};
@@ -29,6 +29,13 @@ impl AllowWarnDeny {
2929
}
3030
}
3131

32+
impl fmt::Display for AllowWarnDeny {
33+
#[inline]
34+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35+
self.as_str().fmt(f)
36+
}
37+
}
38+
3239
impl TryFrom<&str> for AllowWarnDeny {
3340
type Error = OxcDiagnostic;
3441

+280
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
use crate::RuleCategory;
2+
3+
use super::{plugins::LintPlugins, AllowWarnDeny};
4+
use std::{borrow::Cow, fmt};
5+
6+
/// Enables, disables, and sets the severity of lint rules.
7+
///
8+
/// Filters come in 3 forms:
9+
/// 1. Filter by rule name and/or plugin: `no-const-assign`, `eslint/no-const-assign`
10+
/// 2. Filter an entire category: `correctness`
11+
/// 3. Some unknow filter. This is a fallback used when parsing a filter string,
12+
/// and is interpreted uniquely by the linter.
13+
#[derive(Debug, Clone)]
14+
#[cfg_attr(test, derive(PartialEq))]
15+
pub struct LintFilter {
16+
severity: AllowWarnDeny,
17+
kind: LintFilterKind,
18+
}
19+
20+
impl LintFilter {
21+
/// # Errors
22+
///
23+
/// If `kind` is an empty string, or is a `<plugin>/<rule>` filter but is missing either the
24+
/// plugin or the rule.
25+
pub fn new<F: TryInto<LintFilterKind>>(
26+
severity: AllowWarnDeny,
27+
kind: F,
28+
) -> Result<Self, <F as TryInto<LintFilterKind>>::Error> {
29+
Ok(Self { severity, kind: kind.try_into()? })
30+
}
31+
32+
#[must_use]
33+
pub fn allow<F: Into<LintFilterKind>>(kind: F) -> Self {
34+
Self { severity: AllowWarnDeny::Allow, kind: kind.into() }
35+
}
36+
37+
#[must_use]
38+
pub fn warn<F: Into<LintFilterKind>>(kind: F) -> Self {
39+
Self { severity: AllowWarnDeny::Warn, kind: kind.into() }
40+
}
41+
42+
#[must_use]
43+
pub fn deny<F: Into<LintFilterKind>>(kind: F) -> Self {
44+
Self { severity: AllowWarnDeny::Deny, kind: kind.into() }
45+
}
46+
47+
#[inline]
48+
pub fn severity(&self) -> AllowWarnDeny {
49+
self.severity
50+
}
51+
52+
#[inline]
53+
pub fn kind(&self) -> &LintFilterKind {
54+
&self.kind
55+
}
56+
}
57+
58+
impl Default for LintFilter {
59+
fn default() -> Self {
60+
Self {
61+
severity: AllowWarnDeny::Warn,
62+
kind: LintFilterKind::Category(RuleCategory::Correctness),
63+
}
64+
}
65+
}
66+
67+
impl From<LintFilter> for (AllowWarnDeny, LintFilterKind) {
68+
fn from(val: LintFilter) -> Self {
69+
(val.severity, val.kind)
70+
}
71+
}
72+
73+
impl<'a> From<&'a LintFilter> for (AllowWarnDeny, &'a LintFilterKind) {
74+
fn from(val: &'a LintFilter) -> Self {
75+
(val.severity, &val.kind)
76+
}
77+
}
78+
79+
#[derive(Debug, Clone)]
80+
#[cfg_attr(test, derive(PartialEq))]
81+
pub enum LintFilterKind {
82+
Generic(Cow<'static, str>),
83+
/// e.g. `no-const-assign` or `eslint/no-const-assign`
84+
Rule(LintPlugins, Cow<'static, str>),
85+
/// e.g. `correctness`
86+
Category(RuleCategory),
87+
// TODO: plugin + category? e.g `-A react:correctness`
88+
}
89+
90+
impl LintFilterKind {
91+
/// # Errors
92+
///
93+
/// If `filter` is an empty string, or is a `<plugin>/<rule>` filter but is missing either the
94+
/// plugin or the rule.
95+
pub fn parse(filter: Cow<'static, str>) -> Result<Self, InvalidFilterKind> {
96+
if filter.is_empty() {
97+
return Err(InvalidFilterKind::Empty);
98+
}
99+
100+
if filter.contains('/') {
101+
// this is an unfortunate amount of code duplication, but it needs to be done for
102+
// `filter` to live long enough to avoid a String allocation for &'static str
103+
let (plugin, rule) = match filter {
104+
Cow::Borrowed(filter) => {
105+
let mut parts = filter.splitn(2, '/');
106+
107+
let plugin = parts
108+
.next()
109+
.ok_or(InvalidFilterKind::PluginMissing(Cow::Borrowed(filter)))?;
110+
if plugin.is_empty() {
111+
return Err(InvalidFilterKind::PluginMissing(Cow::Borrowed(filter)));
112+
}
113+
114+
let rule = parts
115+
.next()
116+
.ok_or(InvalidFilterKind::RuleMissing(Cow::Borrowed(filter)))?;
117+
if rule.is_empty() {
118+
return Err(InvalidFilterKind::RuleMissing(Cow::Borrowed(filter)));
119+
}
120+
121+
(LintPlugins::from(plugin), Cow::Borrowed(rule))
122+
}
123+
Cow::Owned(filter) => {
124+
let mut parts = filter.splitn(2, '/');
125+
126+
let plugin = parts
127+
.next()
128+
.ok_or_else(|| InvalidFilterKind::PluginMissing(filter.clone().into()))?;
129+
if plugin.is_empty() {
130+
return Err(InvalidFilterKind::PluginMissing(filter.into()));
131+
}
132+
133+
let rule = parts
134+
.next()
135+
.ok_or_else(|| InvalidFilterKind::RuleMissing(filter.clone().into()))?;
136+
if rule.is_empty() {
137+
return Err(InvalidFilterKind::RuleMissing(filter.into()));
138+
}
139+
140+
(LintPlugins::from(plugin), Cow::Owned(rule.to_string()))
141+
}
142+
};
143+
Ok(LintFilterKind::Rule(plugin, rule))
144+
} else {
145+
match RuleCategory::try_from(filter.as_ref()) {
146+
Ok(category) => Ok(LintFilterKind::Category(category)),
147+
Err(()) => Ok(LintFilterKind::Generic(filter)),
148+
}
149+
}
150+
}
151+
}
152+
153+
impl TryFrom<String> for LintFilterKind {
154+
type Error = InvalidFilterKind;
155+
156+
#[inline]
157+
fn try_from(filter: String) -> Result<Self, Self::Error> {
158+
Self::parse(Cow::Owned(filter))
159+
}
160+
}
161+
162+
impl TryFrom<&'static str> for LintFilterKind {
163+
type Error = InvalidFilterKind;
164+
165+
#[inline]
166+
fn try_from(filter: &'static str) -> Result<Self, Self::Error> {
167+
Self::parse(Cow::Borrowed(filter))
168+
}
169+
}
170+
171+
impl TryFrom<Cow<'static, str>> for LintFilterKind {
172+
type Error = InvalidFilterKind;
173+
174+
#[inline]
175+
fn try_from(filter: Cow<'static, str>) -> Result<Self, Self::Error> {
176+
Self::parse(filter)
177+
}
178+
}
179+
180+
impl From<RuleCategory> for LintFilterKind {
181+
#[inline]
182+
fn from(category: RuleCategory) -> Self {
183+
LintFilterKind::Category(category)
184+
}
185+
}
186+
187+
#[derive(Debug, Clone, PartialEq, Eq)]
188+
pub enum InvalidFilterKind {
189+
Empty,
190+
PluginMissing(Cow<'static, str>),
191+
RuleMissing(Cow<'static, str>),
192+
}
193+
194+
impl fmt::Display for InvalidFilterKind {
195+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
196+
match self {
197+
Self::Empty => "Filter cannot be empty.".fmt(f),
198+
Self::PluginMissing(filter) => {
199+
write!(
200+
f,
201+
"Filter '{filter}' must match <plugin>/<rule> but is missing a plugin name."
202+
)
203+
}
204+
Self::RuleMissing(filter) => {
205+
write!(
206+
f,
207+
"Filter '{filter}' must match <plugin>/<rule> but is missing a rule name."
208+
)
209+
}
210+
}
211+
}
212+
}
213+
214+
impl std::error::Error for InvalidFilterKind {}
215+
216+
#[cfg(test)]
217+
mod test {
218+
use super::*;
219+
220+
#[test]
221+
fn test_from_category() {
222+
let correctness: LintFilter = LintFilter::new(AllowWarnDeny::Warn, "correctness").unwrap();
223+
assert_eq!(correctness.severity(), AllowWarnDeny::Warn);
224+
assert!(
225+
matches!(correctness.kind(), LintFilterKind::Category(RuleCategory::Correctness)),
226+
"{:?}",
227+
correctness.kind()
228+
);
229+
}
230+
231+
#[test]
232+
fn test_eslint_deny() {
233+
let filter = LintFilter::deny(LintFilterKind::try_from("no-const-assign").unwrap());
234+
assert_eq!(filter.severity(), AllowWarnDeny::Deny);
235+
assert_eq!(filter.kind(), &LintFilterKind::Generic("no-const-assign".into()));
236+
237+
let filter = LintFilter::deny(LintFilterKind::try_from("eslint/no-const-assign").unwrap());
238+
assert_eq!(filter.severity(), AllowWarnDeny::Deny);
239+
assert_eq!(
240+
filter.kind(),
241+
&LintFilterKind::Rule(LintPlugins::from("eslint"), "no-const-assign".into())
242+
);
243+
assert!(matches!(filter.kind(), LintFilterKind::Rule(_, _)));
244+
}
245+
246+
#[test]
247+
fn test_parse() {
248+
let test_cases: Vec<(&'static str, LintFilterKind)> = vec![
249+
("import/namespace", LintFilterKind::Rule(LintPlugins::IMPORT, "namespace".into())),
250+
(
251+
"react-hooks/exhaustive-deps",
252+
LintFilterKind::Rule(LintPlugins::REACT, "exhaustive-deps".into()),
253+
),
254+
// categories
255+
("nursery", LintFilterKind::Category("nursery".try_into().unwrap())),
256+
("perf", LintFilterKind::Category("perf".try_into().unwrap())),
257+
// misc
258+
("not-a-valid-filter", LintFilterKind::Generic("not-a-valid-filter".into())),
259+
];
260+
261+
for (input, expected) in test_cases {
262+
let actual = LintFilterKind::try_from(input).unwrap();
263+
assert_eq!(actual, expected, "input: {input}");
264+
}
265+
}
266+
267+
#[test]
268+
fn test_parse_invalid() {
269+
let test_cases = vec!["/rules-of-hooks", "import/", "", "/", "//"];
270+
271+
for input in test_cases {
272+
let actual = LintFilterKind::parse(Cow::Borrowed(input));
273+
assert!(
274+
actual.is_err(),
275+
"input '{input}' produced filter '{:?}' but it should have errored",
276+
actual.unwrap()
277+
);
278+
}
279+
}
280+
}

‎crates/oxc_linter/src/options/mod.rs

+56-42
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
mod allow_warn_deny;
2+
mod filter;
23
mod plugins;
34

45
use std::{convert::From, path::PathBuf};
56

6-
pub use allow_warn_deny::AllowWarnDeny;
7+
use filter::LintFilterKind;
78
use oxc_diagnostics::Error;
8-
pub use plugins::LintPluginOptions;
99
use plugins::LintPlugins;
1010
use rustc_hash::FxHashSet;
1111

12+
pub use allow_warn_deny::AllowWarnDeny;
13+
pub use filter::{InvalidFilterKind, LintFilter};
14+
pub use plugins::LintPluginOptions;
15+
1216
use crate::{
1317
config::{LintConfig, OxlintConfig},
1418
fixer::FixKind,
@@ -44,7 +48,7 @@ impl From<OxlintOptions> for LintOptions {
4448
pub struct OxlintOptions {
4549
/// Allow / Deny rules in order. [("allow" / "deny", rule name)]
4650
/// Defaults to [("deny", "correctness")]
47-
pub filter: Vec<(AllowWarnDeny, String)>,
51+
pub filter: Vec<LintFilter>,
4852
pub config_path: Option<PathBuf>,
4953
/// Enable automatic code fixes. Set to [`None`] to disable.
5054
///
@@ -59,7 +63,7 @@ pub struct OxlintOptions {
5963
impl Default for OxlintOptions {
6064
fn default() -> Self {
6165
Self {
62-
filter: vec![(AllowWarnDeny::Warn, String::from("correctness"))],
66+
filter: vec![LintFilter::warn(RuleCategory::Correctness)],
6367
config_path: None,
6468
fix: FixKind::None,
6569
plugins: LintPluginOptions::default(),
@@ -70,7 +74,7 @@ impl Default for OxlintOptions {
7074

7175
impl OxlintOptions {
7276
#[must_use]
73-
pub fn with_filter(mut self, filter: Vec<(AllowWarnDeny, String)>) -> Self {
77+
pub fn with_filter(mut self, filter: Vec<LintFilter>) -> Self {
7478
if !filter.is_empty() {
7579
self.filter = filter;
7680
}
@@ -191,48 +195,58 @@ impl OxlintOptions {
191195
let mut rules: FxHashSet<RuleWithSeverity> = FxHashSet::default();
192196
let all_rules = self.get_filtered_rules();
193197

194-
for (severity, name_or_category) in &self.filter {
195-
let maybe_category = RuleCategory::from(name_or_category.as_str());
198+
for (severity, filter) in self.filter.iter().map(Into::into) {
196199
match severity {
197-
AllowWarnDeny::Deny | AllowWarnDeny::Warn => {
198-
match maybe_category {
199-
Some(category) => rules.extend(
200+
AllowWarnDeny::Deny | AllowWarnDeny::Warn => match filter {
201+
LintFilterKind::Category(category) => {
202+
rules.extend(
203+
all_rules
204+
.iter()
205+
.filter(|rule| rule.category() == *category)
206+
.map(|rule| RuleWithSeverity::new(rule.clone(), severity)),
207+
);
208+
}
209+
LintFilterKind::Rule(_, name) => {
210+
rules.extend(
200211
all_rules
201212
.iter()
202-
.filter(|rule| rule.category() == category)
203-
.map(|rule| RuleWithSeverity::new(rule.clone(), *severity)),
204-
),
205-
None => {
206-
if name_or_category == "all" {
207-
rules.extend(
208-
all_rules
209-
.iter()
210-
.filter(|rule| rule.category() != RuleCategory::Nursery)
211-
.map(|rule| RuleWithSeverity::new(rule.clone(), *severity)),
212-
);
213-
} else {
214-
rules.extend(
215-
all_rules
216-
.iter()
217-
.filter(|rule| rule.name() == name_or_category)
218-
.map(|rule| RuleWithSeverity::new(rule.clone(), *severity)),
219-
);
220-
}
213+
.filter(|rule| rule.name() == name)
214+
.map(|rule| RuleWithSeverity::new(rule.clone(), severity)),
215+
);
216+
}
217+
LintFilterKind::Generic(name_or_category) => {
218+
if name_or_category == "all" {
219+
rules.extend(
220+
all_rules
221+
.iter()
222+
.filter(|rule| rule.category() != RuleCategory::Nursery)
223+
.map(|rule| RuleWithSeverity::new(rule.clone(), severity)),
224+
);
225+
} else {
226+
rules.extend(
227+
all_rules
228+
.iter()
229+
.filter(|rule| rule.name() == name_or_category)
230+
.map(|rule| RuleWithSeverity::new(rule.clone(), severity)),
231+
);
221232
}
222-
};
223-
}
224-
AllowWarnDeny::Allow => {
225-
match maybe_category {
226-
Some(category) => rules.retain(|rule| rule.category() != category),
227-
None => {
228-
if name_or_category == "all" {
229-
rules.clear();
230-
} else {
231-
rules.retain(|rule| rule.name() != name_or_category);
232-
}
233+
}
234+
},
235+
AllowWarnDeny::Allow => match filter {
236+
LintFilterKind::Category(category) => {
237+
rules.retain(|rule| rule.category() != *category);
238+
}
239+
LintFilterKind::Rule(_, name) => {
240+
rules.retain(|rule| rule.name() != name);
241+
}
242+
LintFilterKind::Generic(name_or_category) => {
243+
if name_or_category == "all" {
244+
rules.clear();
245+
} else {
246+
rules.retain(|rule| rule.name() != name_or_category);
233247
}
234-
};
235-
}
248+
}
249+
},
236250
}
237251
}
238252

‎crates/oxc_linter/src/options/plugins.rs

+44-20
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use bitflags::bitflags;
33
bitflags! {
44
// NOTE: may be increased to a u32 if needed
55
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
6-
pub(crate) struct LintPlugins: u16 {
6+
pub struct LintPlugins: u16 {
77
/// `eslint-plugin-react`, plus `eslint-plugin-react-hooks`
88
const REACT = 1 << 0;
99
/// `eslint-plugin-unicorn`
@@ -79,6 +79,32 @@ impl LintPlugins {
7979
}
8080
}
8181

82+
impl From<&str> for LintPlugins {
83+
fn from(value: &str) -> Self {
84+
match value {
85+
"react" | "react-hooks" | "react_hooks" => LintPlugins::REACT,
86+
"unicorn" => LintPlugins::UNICORN,
87+
"typescript" | "typescript-eslint" | "typescript_eslint" | "@typescript-eslint" => {
88+
LintPlugins::TYPESCRIPT
89+
}
90+
// deepscan for backwards compatibility. Those rules have been moved into oxc
91+
"oxc" | "deepscan" => LintPlugins::OXC,
92+
"import" => LintPlugins::IMPORT,
93+
"jsdoc" => LintPlugins::JSDOC,
94+
"jest" => LintPlugins::JEST,
95+
"vitest" => LintPlugins::VITEST,
96+
"jsx-a11y" | "jsx_a11y" => LintPlugins::JSX_A11Y,
97+
"nextjs" => LintPlugins::NEXTJS,
98+
"react-perf" | "react_perf" => LintPlugins::REACT_PERF,
99+
"promise" => LintPlugins::PROMISE,
100+
"node" => LintPlugins::NODE,
101+
// "eslint" is not really a plugin, so it's 'empty'. This has the added benefit of
102+
// making it the default value.
103+
_ => LintPlugins::empty(),
104+
}
105+
}
106+
}
107+
82108
#[derive(Debug)]
83109
#[non_exhaustive]
84110
pub struct LintPluginOptions {
@@ -167,27 +193,25 @@ impl<S: AsRef<str>> FromIterator<(S, bool)> for LintPluginOptions {
167193
fn from_iter<I: IntoIterator<Item = (S, bool)>>(iter: I) -> Self {
168194
let mut options = Self::default();
169195
for (s, enabled) in iter {
170-
match s.as_ref() {
171-
"react" | "react-hooks" => options.react = enabled,
172-
"unicorn" => options.unicorn = enabled,
173-
"typescript" | "typescript-eslint" | "@typescript-eslint" => {
174-
options.typescript = enabled;
175-
}
176-
// deepscan for backwards compatibility. Those rules have been
177-
// moved into oxc
178-
"oxc" | "deepscan" => options.oxc = enabled,
179-
"import" => options.import = enabled,
180-
"jsdoc" => options.jsdoc = enabled,
181-
"jest" => options.jest = enabled,
182-
"vitest" => options.vitest = enabled,
183-
"jsx-a11y" => options.jsx_a11y = enabled,
184-
"nextjs" => options.nextjs = enabled,
185-
"react-perf" => options.react_perf = enabled,
186-
"promise" => options.promise = enabled,
187-
"node" => options.node = enabled,
188-
_ => { /* ignored */ }
196+
let flags = LintPlugins::from(s.as_ref());
197+
match flags {
198+
LintPlugins::REACT => options.react = enabled,
199+
LintPlugins::UNICORN => options.unicorn = enabled,
200+
LintPlugins::TYPESCRIPT => options.typescript = enabled,
201+
LintPlugins::OXC => options.oxc = enabled,
202+
LintPlugins::IMPORT => options.import = enabled,
203+
LintPlugins::JSDOC => options.jsdoc = enabled,
204+
LintPlugins::JEST => options.jest = enabled,
205+
LintPlugins::VITEST => options.vitest = enabled,
206+
LintPlugins::JSX_A11Y => options.jsx_a11y = enabled,
207+
LintPlugins::NEXTJS => options.nextjs = enabled,
208+
LintPlugins::REACT_PERF => options.react_perf = enabled,
209+
LintPlugins::PROMISE => options.promise = enabled,
210+
LintPlugins::NODE => options.node = enabled,
211+
_ => {} // ignored
189212
}
190213
}
214+
191215
options
192216
}
193217
}

‎crates/oxc_linter/src/rule.rs

+16-13
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,6 @@ pub enum RuleCategory {
8181
}
8282

8383
impl RuleCategory {
84-
pub fn from(input: &str) -> Option<Self> {
85-
match input {
86-
"correctness" => Some(Self::Correctness),
87-
"suspicious" => Some(Self::Suspicious),
88-
"pedantic" => Some(Self::Pedantic),
89-
"perf" => Some(Self::Perf),
90-
"style" => Some(Self::Style),
91-
"restriction" => Some(Self::Restriction),
92-
"nursery" => Some(Self::Nursery),
93-
_ => None,
94-
}
95-
}
96-
9784
pub fn description(self) -> &'static str {
9885
match self {
9986
Self::Correctness => "Code that is outright wrong or useless.",
@@ -109,6 +96,22 @@ impl RuleCategory {
10996
}
11097
}
11198

99+
impl TryFrom<&str> for RuleCategory {
100+
type Error = ();
101+
fn try_from(value: &str) -> Result<Self, Self::Error> {
102+
match value {
103+
"correctness" => Ok(Self::Correctness),
104+
"suspicious" => Ok(Self::Suspicious),
105+
"pedantic" => Ok(Self::Pedantic),
106+
"perf" => Ok(Self::Perf),
107+
"style" => Ok(Self::Style),
108+
"restriction" => Ok(Self::Restriction),
109+
"nursery" => Ok(Self::Nursery),
110+
_ => Err(()),
111+
}
112+
}
113+
}
114+
112115
impl fmt::Display for RuleCategory {
113116
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114117
match self {

‎tasks/benchmark/benches/linter.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{env, path::Path, rc::Rc};
22

33
use oxc_allocator::Allocator;
44
use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion};
5-
use oxc_linter::{AllowWarnDeny, FixKind, Linter, OxlintOptions};
5+
use oxc_linter::{AllowWarnDeny, FixKind, LintFilter, Linter, OxlintOptions};
66
use oxc_parser::Parser;
77
use oxc_semantic::SemanticBuilder;
88
use oxc_span::SourceType;
@@ -34,8 +34,8 @@ fn bench_linter(criterion: &mut Criterion) {
3434
.build_module_record(Path::new(""), program)
3535
.build(program);
3636
let filter = vec![
37-
(AllowWarnDeny::Deny, "all".into()),
38-
(AllowWarnDeny::Deny, "nursery".into()),
37+
LintFilter::new(AllowWarnDeny::Deny, "all").unwrap(),
38+
LintFilter::new(AllowWarnDeny::Deny, "nursery").unwrap(),
3939
];
4040
let lint_options = OxlintOptions::default()
4141
.with_filter(filter)

0 commit comments

Comments
 (0)
Please sign in to comment.