mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(linter): support conditional fix capabilities (#4559)
This commit is contained in:
parent
16c7b98962
commit
ddd8b27977
3 changed files with 81 additions and 27 deletions
|
|
@ -125,6 +125,8 @@ pub enum RuleFixMeta {
|
|||
None,
|
||||
/// An auto-fix could be implemented, but it has not been yet.
|
||||
FixPending,
|
||||
/// An auto-fix is available for some violations, but not all.
|
||||
Conditional(FixKind),
|
||||
/// An auto-fix is available.
|
||||
Fixable(FixKind),
|
||||
}
|
||||
|
|
@ -135,17 +137,22 @@ impl RuleFixMeta {
|
|||
/// Also returns `true` for suggestions.
|
||||
#[inline]
|
||||
pub fn has_fix(self) -> bool {
|
||||
matches!(self, Self::Fixable(_))
|
||||
matches!(self, Self::Fixable(_) | Self::Conditional(_))
|
||||
}
|
||||
|
||||
pub fn supports_fix(self, kind: FixKind) -> bool {
|
||||
matches!(self, Self::Fixable(fix_kind) | Self::Conditional(fix_kind) if fix_kind.can_apply(kind))
|
||||
}
|
||||
|
||||
pub fn description(self) -> Cow<'static, str> {
|
||||
match self {
|
||||
Self::None => Cow::Borrowed("No auto-fix is available for this rule."),
|
||||
Self::FixPending => Cow::Borrowed("An auto-fix is still under development."),
|
||||
Self::Fixable(kind) => {
|
||||
Self::Fixable(kind) | Self::Conditional(kind) => {
|
||||
// e.g. an auto-fix is available for this rule
|
||||
// e.g. a suggestion is available for this rule
|
||||
// e.g. a dangerous auto-fix is available for this rule
|
||||
// e.g. an auto-fix is available for this rule for some violations
|
||||
// e.g. an auto-fix and a suggestion are available for this rule
|
||||
let noun = match (kind.contains(FixKind::Fix), kind.contains(FixKind::Suggestion)) {
|
||||
(true, true) => "auto-fix and a suggestion are available for this rule",
|
||||
|
|
@ -153,7 +160,7 @@ impl RuleFixMeta {
|
|||
(false, true) => "suggestion is available for this rule",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let message =
|
||||
let mut message =
|
||||
if kind.is_dangerous() { format!("dangerous {noun}") } else { noun.into() };
|
||||
|
||||
let article = match message.chars().next().unwrap() {
|
||||
|
|
@ -161,23 +168,21 @@ impl RuleFixMeta {
|
|||
_ => "A",
|
||||
};
|
||||
|
||||
Cow::Owned(format!("{article} {message}"))
|
||||
if matches!(self, Self::Conditional(_)) {
|
||||
message += " for some violations";
|
||||
}
|
||||
|
||||
Cow::Owned(format!("{article} {message}."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for RuleFixMeta {
|
||||
type Error = ();
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
impl From<RuleFixMeta> for FixKind {
|
||||
fn from(value: RuleFixMeta) -> Self {
|
||||
match value {
|
||||
"none" => Ok(Self::None),
|
||||
"pending" => Ok(Self::FixPending),
|
||||
"fix" => Ok(Self::Fixable(FixKind::Fix)),
|
||||
"fix-dangerous" => Ok(Self::Fixable(FixKind::DangerousFix)),
|
||||
"suggestion" => Ok(Self::Fixable(FixKind::Suggestion)),
|
||||
"suggestion-dangerous" => Ok(Self::Fixable(FixKind::Suggestion | FixKind::Dangerous)),
|
||||
_ => Err(()),
|
||||
RuleFixMeta::None | RuleFixMeta::FixPending => FixKind::None,
|
||||
RuleFixMeta::Fixable(kind) | RuleFixMeta::Conditional(kind) => kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ pub fn declare_all_lint_rules(metadata: AllLintRulesMeta) -> TokenStream {
|
|||
let expanded = quote! {
|
||||
#(pub use self::#use_stmts::#struct_names;)*
|
||||
|
||||
use crate::{context::LintContext, rule::{Rule, RuleCategory, RuleMeta}, AstNode};
|
||||
use crate::{context::LintContext, rule::{Rule, RuleCategory, RuleFixMeta, RuleMeta}, AstNode};
|
||||
use oxc_semantic::SymbolId;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -81,6 +81,13 @@ pub fn declare_all_lint_rules(metadata: AllLintRulesMeta) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
/// This [`Rule`]'s auto-fix capabilities.
|
||||
pub fn fix(&self) -> RuleFixMeta {
|
||||
match self {
|
||||
#(Self::#struct_names(_) => #struct_names::FIX),*
|
||||
}
|
||||
}
|
||||
|
||||
pub fn documentation(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
#(Self::#struct_names(_) => #struct_names::documentation()),*
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use convert_case::{Boundary, Case, Converter};
|
||||
use itertools::Itertools as _;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
|
|
@ -79,7 +80,7 @@ pub fn declare_oxc_lint(metadata: LintRuleMeta) -> TokenStream {
|
|||
let import_statement = if used_in_test {
|
||||
None
|
||||
} else {
|
||||
Some(quote! { use crate::rule::{RuleCategory, RuleMeta, RuleFixMeta}; })
|
||||
Some(quote! { use crate::{rule::{RuleCategory, RuleMeta, RuleFixMeta}, fixer::FixKind}; })
|
||||
};
|
||||
|
||||
let output = quote! {
|
||||
|
|
@ -119,17 +120,58 @@ fn parse_attr<'a, const LEN: usize>(
|
|||
}
|
||||
|
||||
fn parse_fix(s: &str) -> proc_macro2::TokenStream {
|
||||
const SEP: char = '_';
|
||||
|
||||
match s {
|
||||
"none" => quote! { RuleFixMeta::None },
|
||||
"pending" => quote! { RuleFixMeta::FixPending },
|
||||
"fix" => quote! { RuleFixMeta::Fixable(FixKind::Fix) },
|
||||
"fix-dangerous" => quote! { RuleFixMeta::Fixable(FixKind::Fix.union(FixKind::Dangerous)) },
|
||||
"suggestion" => quote! { RuleFixMeta::Fixable(FixKind::Suggestion) },
|
||||
"suggestion-dangerous" => quote! { RuleFixMeta::Fixable(FixKind::Suggestion.union(FixKind::Dangerous)) },
|
||||
"None" => panic!("Invalid fix kind. Did you mean 'none'?"),
|
||||
"Pending" => panic!("Invalid fix kind. Did you mean 'pending'?"),
|
||||
"Fix" => panic!("Invalid fix kind. Did you mean 'fix'?"),
|
||||
"Suggestion" => panic!("Invalid fix kind. Did you mean 'suggestion'?"),
|
||||
invalid => panic!("invalid fix kind: {invalid}. Valid kinds are none, pending, fix, fix-dangerous, suggestion, and suggestion-dangerous"),
|
||||
"none" => {
|
||||
return quote! { RuleFixMeta::None };
|
||||
}
|
||||
"pending" => { return quote! { RuleFixMeta::FixPending }; }
|
||||
"fix" => {
|
||||
return quote! { RuleFixMeta::Fixable(FixKind::SafeFix) }
|
||||
},
|
||||
"suggestion" => {
|
||||
return quote! { RuleFixMeta::Fixable(FixKind::Suggestion) }
|
||||
},
|
||||
// "fix-dangerous" => quote! { RuleFixMeta::Fixable(FixKind::Fix.union(FixKind::Dangerous)) },
|
||||
// "suggestion" => quote! { RuleFixMeta::Fixable(FixKind::Suggestion) },
|
||||
// "suggestion-dangerous" => quote! { RuleFixMeta::Fixable(FixKind::Suggestion.union(FixKind::Dangerous)) },
|
||||
"conditional" => panic!("Invalid fix capabilities: missing a fix kind. Did you mean 'fix-conditional'?"),
|
||||
"None" => panic!("Invalid fix capabilities. Did you mean 'none'?"),
|
||||
"Pending" => panic!("Invalid fix capabilities. Did you mean 'pending'?"),
|
||||
"Fix" => panic!("Invalid fix capabilities. Did you mean 'fix'?"),
|
||||
"Suggestion" => panic!("Invalid fix capabilities. Did you mean 'suggestion'?"),
|
||||
invalid if !invalid.contains(SEP) => panic!("invalid fix capabilities: {invalid}. Valid capabilities are none, pending, fix, suggestion, or [fix|suggestion]_[conditional?]_[dangerous?]."),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
assert!(s.contains(SEP));
|
||||
|
||||
let mut is_conditional = false;
|
||||
let fix_kinds = s
|
||||
.split(SEP)
|
||||
.filter(|seg| {
|
||||
let conditional = *seg == "conditional";
|
||||
is_conditional = is_conditional || conditional;
|
||||
!conditional
|
||||
})
|
||||
.unique()
|
||||
.map(parse_fix_kind)
|
||||
.reduce(|acc, kind| quote! { #acc.union(#kind) })
|
||||
.expect("No fix kinds were found during parsing, but at least one is required.");
|
||||
|
||||
if is_conditional {
|
||||
quote! { RuleFixMeta::Conditional(#fix_kinds) }
|
||||
} else {
|
||||
quote! { RuleFixMeta::Fixable(#fix_kinds) }
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_fix_kind(s: &str) -> proc_macro2::TokenStream {
|
||||
match s {
|
||||
"fix" => quote! { FixKind::Fix },
|
||||
"suggestion" => quote! { FixKind::Suggestion },
|
||||
"dangerous" => quote! { FixKind::Dangerous },
|
||||
_ => panic!("invalid fix kind: {s}. Valid fix kinds are fix, suggestion, or dangerous."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue