diff --git a/Cargo.lock b/Cargo.lock index 568afa2838f06..d75c1ab115032 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1510,6 +1510,7 @@ dependencies = [ name = "oxc_linter" version = "0.6.1" dependencies = [ + "bitflags 2.6.0", "convert_case", "dashmap 6.0.1", "insta", diff --git a/apps/oxlint/src/command/lint.rs b/apps/oxlint/src/command/lint.rs index 672e5a77fcf93..69afcce3a91a9 100644 --- a/apps/oxlint/src/command/lint.rs +++ b/apps/oxlint/src/command/lint.rs @@ -1,7 +1,7 @@ use std::{path::PathBuf, str::FromStr}; use bpaf::Bpaf; -use oxc_linter::AllowWarnDeny; +use oxc_linter::{AllowWarnDeny, FixKind}; use super::{ expand_glob, @@ -122,6 +122,40 @@ pub struct FixOptions { /// Fix as many issues as possible. Only unfixed issues are reported in the output #[bpaf(switch)] pub fix: bool, + /// Apply auto-fixable suggestions. May change program behavior. + #[bpaf(switch)] + pub fix_suggestions: bool, + + /// Apply dangerous fixes and suggestions. + #[bpaf(switch)] + pub fix_dangerously: bool, +} + +impl FixOptions { + pub fn fix_kind(&self) -> FixKind { + let mut kind = FixKind::None; + + if self.fix { + kind.set(FixKind::SafeFix, true); + } + + if self.fix_suggestions { + kind.set(FixKind::Suggestion, true); + } + + if self.fix_dangerously { + if kind.is_none() { + kind.set(FixKind::Fix, true); + } + kind.set(FixKind::Dangerous, true); + } + + kind + } + + pub fn is_enabled(&self) -> bool { + self.fix || self.fix_suggestions || self.fix_dangerously + } } /// Handle Warnings diff --git a/apps/oxlint/src/lint/mod.rs b/apps/oxlint/src/lint/mod.rs index 609239796c192..dd78c771e3e02 100644 --- a/apps/oxlint/src/lint/mod.rs +++ b/apps/oxlint/src/lint/mod.rs @@ -93,7 +93,7 @@ impl Runner for LintRunner { let lint_options = LintOptions::default() .with_filter(filter) .with_config_path(basic_options.config) - .with_fix(fix_options.fix) + .with_fix(fix_options.fix_kind()) .with_react_plugin(enable_plugins.react_plugin) .with_unicorn_plugin(enable_plugins.unicorn_plugin) .with_typescript_plugin(enable_plugins.typescript_plugin) diff --git a/crates/oxc_language_server/src/linter.rs b/crates/oxc_language_server/src/linter.rs index 6df7c2f98071a..be86e2a5d57f7 100644 --- a/crates/oxc_language_server/src/linter.rs +++ b/crates/oxc_language_server/src/linter.rs @@ -13,7 +13,7 @@ use oxc_linter::{ AstroPartialLoader, JavaScriptSource, SveltePartialLoader, VuePartialLoader, LINT_PARTIAL_LOADER_EXT, }, - LintContext, Linter, + FixKind, LintContext, Linter, }; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; @@ -388,7 +388,7 @@ pub struct ServerLinter { impl ServerLinter { pub fn new() -> Self { - let linter = Linter::default().with_fix(true); + let linter = Linter::default().with_fix(FixKind::SafeFix); Self { linter: Arc::new(linter) } } diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index 2e4512213361a..4a830be3a9806 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -7,7 +7,7 @@ use futures::future::join_all; use globset::Glob; use ignore::gitignore::Gitignore; use log::{debug, error, info}; -use oxc_linter::{LintOptions, Linter}; +use oxc_linter::{FixKind, LintOptions, Linter}; use serde::{Deserialize, Serialize}; use tokio::sync::{Mutex, OnceCell, RwLock, SetError}; use tower_lsp::{ @@ -345,7 +345,9 @@ impl Backend { let mut linter = self.server_linter.write().await; *linter = ServerLinter::new_with_linter( Linter::from_options( - LintOptions::default().with_fix(true).with_config_path(Some(config_path)), + LintOptions::default() + .with_fix(FixKind::SafeFix) + .with_config_path(Some(config_path)), ) .expect("should have initialized linter with new options"), ); diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index 40e5ea0f327d6..263e0d9c9e2cb 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -33,6 +33,7 @@ oxc_codegen = { workspace = true } oxc_resolver = { workspace = true } rayon = { workspace = true } +bitflags = { workspace = true } lazy_static = { workspace = true } serde_json = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/crates/oxc_linter/src/context.rs b/crates/oxc_linter/src/context.rs index 19268430fce85..8aa469a042b52 100644 --- a/crates/oxc_linter/src/context.rs +++ b/crates/oxc_linter/src/context.rs @@ -10,7 +10,7 @@ use oxc_syntax::module_record::ModuleRecord; use crate::{ config::OxlintRules, disable_directives::{DisableDirectives, DisableDirectivesBuilder}, - fixer::{CompositeFix, Message, RuleFixer}, + fixer::{FixKind, Message, RuleFix, RuleFixer}, javascript_globals::GLOBALS, AllowWarnDeny, OxlintConfig, OxlintEnv, OxlintGlobals, OxlintSettings, }; @@ -26,10 +26,12 @@ pub struct LintContext<'a> { disable_directives: Rc>, - /// Whether or not to apply code fixes during linting. Defaults to `false`. + /// Whether or not to apply code fixes during linting. Defaults to + /// [`FixKind::None`] (no fixing). /// - /// Set via the `--fix` CLI flag. - fix: bool, + /// Set via the `--fix`, `--fix-suggestions`, and `--fix-dangerously` CLI + /// flags. + fix: FixKind, file_path: Rc, @@ -69,7 +71,7 @@ impl<'a> LintContext<'a> { semantic, diagnostics: RefCell::new(Vec::with_capacity(DIAGNOSTICS_INITIAL_CAPACITY)), disable_directives: Rc::new(disable_directives), - fix: false, + fix: FixKind::None, file_path: file_path.into(), eslint_config: Arc::new(OxlintConfig::default()), current_rule_name: "", @@ -79,7 +81,7 @@ impl<'a> LintContext<'a> { /// Enable/disable automatic code fixes. #[must_use] - pub fn with_fix(mut self, fix: bool) -> Self { + pub fn with_fix(mut self, fix: FixKind) -> Self { self.fix = fix; self } @@ -190,6 +192,7 @@ impl<'a> LintContext<'a> { /// Report a lint rule violation. /// /// Use [`LintContext::diagnostic_with_fix`] to provide an automatic fix. + #[inline] pub fn diagnostic(&self, diagnostic: OxcDiagnostic) { self.add_diagnostic(Message::new(diagnostic, None)); } @@ -197,18 +200,106 @@ impl<'a> LintContext<'a> { /// Report a lint rule violation and provide an automatic fix. /// /// The second argument is a [closure] that takes a [`RuleFixer`] and - /// returns something that can turn into a [`CompositeFix`]. + /// returns something that can turn into a `CompositeFix`. + /// + /// Fixes created this way should not create parse errors or change the + /// semantics of the linted code. If your fix may change the code's + /// semantics, use [`LintContext::diagnostic_with_suggestion`] instead. If + /// your fix has the potential to create parse errors, use + /// [`LintContext::diagnostic_with_dangerous_fix`]. /// /// [closure]: + #[inline] pub fn diagnostic_with_fix(&self, diagnostic: OxcDiagnostic, fix: F) where - C: Into>, + C: Into>, + F: FnOnce(RuleFixer<'_, 'a>) -> C, + { + self.diagnostic_with_fix_of_kind(diagnostic, FixKind::SafeFix, fix); + } + + /// Report a lint rule violation and provide a suggestion for fixing it. + /// + /// The second argument is a [closure] that takes a [`RuleFixer`] and + /// returns something that can turn into a `CompositeFix`. + /// + /// Fixes created this way should not create parse errors, but have the + /// potential to change the code's semantics. If your fix is completely safe + /// and definitely does not change semantics, use [`LintContext::diagnostic_with_fix`]. + /// If your fix has the potential to create parse errors, use + /// [`LintContext::diagnostic_with_dangerous_fix`]. + /// + /// [closure]: + #[inline] + pub fn diagnostic_with_suggestion(&self, diagnostic: OxcDiagnostic, fix: F) + where + C: Into>, + F: FnOnce(RuleFixer<'_, 'a>) -> C, + { + self.diagnostic_with_fix_of_kind(diagnostic, FixKind::Suggestion, fix); + } + + /// Report a lint rule violation and provide a potentially dangerous + /// automatic fix for it. + /// + /// The second argument is a [closure] that takes a [`RuleFixer`] and + /// returns something that can turn into a `CompositeFix`. + /// + /// Dangerous fixes should be avoided and are not applied by default with + /// `--fix`. Use this method if: + /// - Your fix is experimental and you want to test it out in the wild + /// before marking it as safe. + /// - Your fix is extremely aggressive and risky, but you want to provide + /// it as an option to users. + /// + /// When possible, prefer [`LintContext::diagnostic_with_fix`]. If the only + /// risk your fix poses is minor(ish) changes to code semantics, use + /// [`LintContext::diagnostic_with_suggestion`] instead. + /// + /// [closure]: + /// + #[inline] + pub fn diagnostic_with_dangerous_fix(&self, diagnostic: OxcDiagnostic, fix: F) + where + C: Into>, + F: FnOnce(RuleFixer<'_, 'a>) -> C, + { + self.diagnostic_with_fix_of_kind(diagnostic, FixKind::DangerousFix, fix); + } + + pub fn diagnostic_with_fix_of_kind( + &self, + diagnostic: OxcDiagnostic, + fix_kind: FixKind, + fix: F, + ) where + C: Into>, F: FnOnce(RuleFixer<'_, 'a>) -> C, { - if self.fix { - let fixer = RuleFixer::new(self); - let composite_fix: CompositeFix = fix(fixer).into(); - let fix = composite_fix.normalize_fixes(self.source_text()); + // if let Some(accepted_fix_kind) = self.fix { + // let fixer = RuleFixer::new(fix_kind, self); + // let rule_fix: RuleFix<'a> = fix(fixer).into(); + // let diagnostic = match (rule_fix.message(), &diagnostic.help) { + // (Some(message), None) => diagnostic.with_help(message.to_owned()), + // _ => diagnostic, + // }; + // if rule_fix.kind() <= accepted_fix_kind { + // let fix = rule_fix.into_fix(self.source_text()); + // self.add_diagnostic(Message::new(diagnostic, Some(fix))); + // } else { + // self.diagnostic(diagnostic); + // } + // } else { + // self.diagnostic(diagnostic); + // } + let fixer = RuleFixer::new(fix_kind, self); + let rule_fix: RuleFix<'a> = fix(fixer).into(); + let diagnostic = match (rule_fix.message(), &diagnostic.help) { + (Some(message), None) => diagnostic.with_help(message.to_owned()), + _ => diagnostic, + }; + if self.fix.can_apply(rule_fix.kind()) { + let fix = rule_fix.into_fix(self.source_text()); self.add_diagnostic(Message::new(diagnostic, Some(fix))); } else { self.diagnostic(diagnostic); diff --git a/crates/oxc_linter/src/fixer/fix.rs b/crates/oxc_linter/src/fixer/fix.rs index 8213921f596bb..7b5e7c775c2b4 100644 --- a/crates/oxc_linter/src/fixer/fix.rs +++ b/crates/oxc_linter/src/fixer/fix.rs @@ -1,7 +1,234 @@ -use std::borrow::Cow; +use std::{borrow::Cow, ops::Deref}; -use oxc_span::{Span, SPAN}; +use bitflags::bitflags; +use oxc_span::{GetSpan, Span, SPAN}; +bitflags! { + /// Flags describing an automatic code fix. + /// + /// These are used by lint rules when they provide a code fix or suggestion. + /// These are also used by the `LintService` to decide which kinds of + /// changes to apply. + /// + /// [`FixKind`] is designed to be interopable with [`bool`]. `true` turns + /// into [`FixKind::Fix`] (applies only safe fixes) and `false` turns into + /// `FixKind::None` (do not apply any fixes or suggestions). + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct FixKind: u8 { + /// An automatic code fix. Most of these are applied with `--fix` + /// + const Fix = 1 << 0; + /// A recommendation about how to fix a rule violation. These are usually + /// safe to apply, in that they shouldn't cause parse or runtime errors, + /// but may change the meaning of the code. + const Suggestion = 1 << 1; + /// Marks a fix or suggestion as dangerous. Dangerous fixes/suggestions + /// may break the code. Covers cases that are + /// - Aggressive (e.g. some code removal) + /// - Are under development. Think of this as similar to the `nursery` + /// rule category. + const Dangerous = 1 << 2; + + /// Used to specify that no fixes should be applied. + const None = 0; + const SafeFix = Self::Fix.bits(); + const DangerousFix = Self::Dangerous.bits() | Self::Fix.bits(); + } +} + +// explicit definition for clarity +impl Default for FixKind { + #[inline] + fn default() -> Self { + Self::None + } +} + +impl FixKind { + #[inline] + pub fn is_none(self) -> bool { + self.is_empty() + } + + #[inline] + pub fn is_some(self) -> bool { + self.bits() > 0 + } + + #[inline] + pub fn is_dangerous(self) -> bool { + self.contains(Self::Dangerous) + } + + /// Check if a fix produced by a lint rule is allowed to be applied + /// to the source code. + /// + /// Here, `self` is the set of allowed [`FixKind`]s, and `rule_fix` is the + /// kind of fixed produced by the rule. + /// + /// # Example + /// ``` + /// use oxc_linter::FixKind; + /// + /// // `none` means no fixes will be applied at all + /// assert!(!FixKind::None.can_apply(FixKind::SafeFix)); + /// + /// // allow safe fixes + /// assert!(FixKind::SafeFix.can_apply(FixKind::SafeFix)); + /// assert!(!FixKind::SafeFix.can_apply(FixKind::DangerousFix)); // not safe + /// assert!(!FixKind::SafeFix.can_apply(FixKind::Suggestion)); // not a fix + /// ``` + #[inline] + pub fn can_apply(self, rule_fix: Self) -> bool { + self.contains(rule_fix) + } +} + +// TODO: rename +#[derive(Debug, Default)] +#[must_use = "Fixes must be used. If you don't need a fix, use `LintContext::diagnostic`, or create an empty fix using `RuleFixer::noop`."] +pub struct RuleFix<'a> { + kind: FixKind, + /// A suggestion message. Will be shown in editors via code actions. + /// + /// NOTE: code actions aren't implemented yet. + message: Option>, + /// The actual that will be applied to the source code. + /// + /// See: [`Fix`] + fix: CompositeFix<'a>, +} + +macro_rules! impl_from { + ($($ty:ty),*) => { + $( + impl<'a> From<$ty> for RuleFix<'a> { + fn from(fix: $ty) -> Self { + Self { kind: FixKind::SafeFix, message: None, fix: fix.into() } + } + } + )* + }; +} +// I'd like to use +// impl<'a, F: Into>> From for RuleFix<'a> b +// but this breaks when implementing `From> for CompositeFix<'a>`. +impl_from!(CompositeFix<'a>, Fix<'a>, Option>, Vec>); + +impl<'a> From> for CompositeFix<'a> { + #[inline] + fn from(val: RuleFix<'a>) -> Self { + val.fix + } +} + +impl<'a> RuleFix<'a> { + #[inline] + pub(super) fn new(kind: FixKind, message: Option>, fix: CompositeFix<'a>) -> Self { + Self { kind, message, fix } + } + + /// Create a new safe fix. + #[inline] + pub fn fix(fix: CompositeFix<'a>) -> Self { + Self { kind: FixKind::Fix, message: None, fix } + } + + /// Create a new suggestion + #[inline] + pub const fn suggestion(fix: CompositeFix<'a>, message: Cow<'a, str>) -> Self { + Self { kind: FixKind::Suggestion, message: Some(message), fix } + } + + /// Create a dangerous fix. + #[inline] + pub fn dangerous(fix: CompositeFix<'a>) -> Self { + Self { kind: FixKind::DangerousFix, message: None, fix } + } + + /// Mark this [`RuleFix`] as dangerous. + /// + /// This is especially useful for fixer functions that are safe in some + /// cases but not in others. + /// + /// # Example + /// + /// ```ignore + /// use oxc_linter::fixer::{RuleFix, RuleFixer}; + /// use oxc_ast::ast::Expression; + /// + /// fn my_fixer<'a>(fixer: RuleFixer<'a>, bad_node: &Expression<'a>) -> RuleFix<'a> { + /// fixer.delete(bad_node).dangerously() + /// } + /// + /// is_dangerous(bad_node: &Expression<'_>) -> bool { + /// // some check on bad_node + /// # true + /// } + /// + /// fn maybe_dangerous_fixer<'a>(fixer: RuleFixer<'a>, bad_node: &Expression<'a>) -> RuleFix<'a> { + /// let fix = fixer.delete(bad_node); + /// if is_dangerous() { + /// fix.dangerously() + /// } else { + /// fix + /// } + /// } + /// ``` + pub fn dangerously(mut self) -> Self { + self.kind.set(FixKind::Dangerous, true); + self + } + + #[inline] + pub fn with_message>>(mut self, message: S) -> Self { + self.message = Some(message.into()); + self + } + + #[inline] + pub fn kind(&self) -> FixKind { + self.kind + } + + #[inline] + pub fn message(&self) -> Option<&str> { + self.message.as_deref() + } + + #[inline] + pub fn into_fix(self, source_text: &str) -> Fix<'a> { + self.fix.normalize_fixes(source_text) + } + + #[inline] + pub fn extend>>(mut self, fix: F) -> Self { + self.fix = self.fix.concat(fix.into()); + self + } + + #[inline] + pub fn push>>(&mut self, fix: F) { + self.fix.push(fix.into()); + } +} + +impl GetSpan for RuleFix<'_> { + fn span(&self) -> Span { + self.fix.span() + } +} + +impl<'a> Deref for RuleFix<'a> { + type Target = CompositeFix<'a>; + fn deref(&self) -> &Self::Target { + &self.fix + } +} + +/// A completed, normalized fix ready to be applied to the source code. +/// +/// Used internally by this module. Lint rules should use [`RuleFix`]. #[derive(Debug, Clone)] #[non_exhaustive] pub struct Fix<'a> { @@ -31,6 +258,10 @@ impl<'a> Fix<'a> { } } +// NOTE (@DonIsaac): having these variants is effectively the same as interning +// single or 0-element Vecs. I experimented with using smallvec here, but the +// resulting struct size was larger (40 bytes vs 32). So, we're sticking with +// this (at least for now). #[derive(Debug, Default)] pub enum CompositeFix<'a> { /// No fixes @@ -57,16 +288,129 @@ impl<'a> From>> for CompositeFix<'a> { } impl<'a> From>> for CompositeFix<'a> { - fn from(fixes: Vec>) -> Self { - if fixes.is_empty() { - CompositeFix::None - } else { - CompositeFix::Multiple(fixes) + fn from(mut fixes: Vec>) -> Self { + match fixes.len() { + 0 => CompositeFix::None, + // fixes[0] doesn't correctly move the vec's entry + 1 => CompositeFix::Single(fixes.pop().unwrap()), + _ => CompositeFix::Multiple(fixes), + } + } +} + +impl<'a> From>> for CompositeFix<'a> { + fn from(fixes: Vec) -> Self { + fixes.into_iter().reduce(Self::concat).unwrap_or_default() + } +} + +impl GetSpan for CompositeFix<'_> { + fn span(&self) -> Span { + match self { + CompositeFix::Single(fix) => fix.span, + CompositeFix::Multiple(fixes) => { + fixes.iter().map(|fix| fix.span).reduce(|a, b| a.merge(&b)).unwrap_or(SPAN) + } + CompositeFix::None => SPAN, } } } impl<'a> CompositeFix<'a> { + pub fn push(&mut self, fix: CompositeFix<'a>) { + match self { + Self::None => *self = fix, + Self::Single(fix1) => match fix { + Self::None => {} + Self::Single(other_fix) => { + *self = Self::Multiple(vec![std::mem::take(fix1), other_fix]); + } + Self::Multiple(mut fixes) => { + fixes.insert(0, std::mem::take(fix1)); + *self = Self::Multiple(fixes); + } + }, + Self::Multiple(fixes) => match fix { + Self::None => {} + Self::Single(fix) => { + fixes.push(fix); + } + Self::Multiple(other_fixes) => fixes.extend(other_fixes), + }, + } + } + + #[cold] + #[must_use] + pub fn concat(self, fix: CompositeFix<'a>) -> Self { + match (self, fix) { + (Self::None, f) | (f, Self::None) => f, + (Self::Single(fix1), Self::Single(fix2)) => Self::Multiple(vec![fix1, fix2]), + (Self::Single(fix), Self::Multiple(mut fixes)) => { + fixes.insert(0, fix); + Self::Multiple(fixes) + } + (Self::Multiple(mut fixes), Self::Single(fix)) => { + fixes.push(fix); + Self::Multiple(fixes) + } + (Self::Multiple(mut fixes1), Self::Multiple(fixes2)) => { + fixes1.extend(fixes2); + Self::Multiple(fixes1) + } + } + } + + /// Gets the number of [`Fix`]es contained in this [`CompositeFix`]. + pub fn len(&self) -> usize { + match self { + Self::None => 0, + Self::Single(_) => 1, + Self::Multiple(fs) => { + debug_assert!(fs.len() > 1, "Single-element or empty composite fix vecs should have been turned into CompositeFix::None or CompositeFix::Single"); + fs.len() + } + } + } + + /// Returns `true` if this [`CompositeFix`] contains no [`Fix`]es + pub fn is_empty(&self) -> bool { + match self { + Self::None => true, + Self::Single(_) => false, + Self::Multiple(fs) => { + debug_assert!( + !fs.is_empty(), + "Empty CompositeFix vecs should have been turned into CompositeFix::None" + ); + fs.is_empty() + } + } + } + // TODO: do we want this? + // pub fn extend(&mut self, fix: CompositeFix<'a>) { + // match self { + // Self::None => *self = fix, + // Self::Single(fix1) => { + // match fix { + // Self::None => {}, + // Self::Single(fix2) => *self = Self::Multiple(vec![fix1.clone(), fix2]), + // Self::Multiple(mut fixes) => { + // fixes.insert(0, fix1.clone()); + // *self = Self::Multiple(fixes); + // } + // } + // } + // Self::Multiple(fixes) => { + // match fix { + // Self::None => {}, + // Self::Single(fix2) => fixes.push(fix2), + // Self::Multiple(fixes2) => fixes.extend(fixes2), + // } + // } + // } + // } + /// Gets one fix from the fixes. If we retrieve multiple fixes, this merges those into one. /// pub fn normalize_fixes(self, source_text: &str) -> Fix<'a> { @@ -76,7 +420,7 @@ impl<'a> CompositeFix<'a> { CompositeFix::None => Fix::empty(), } } - /// Merges multiple fixes to one, returns an `Fix::default`(which will not fix anything) if: + /// Merges multiple fixes to one, returns an [`Fix::empty`] (which will not fix anything) if: /// /// 1. `fixes` is empty /// 2. contains overlapped ranges @@ -138,3 +482,139 @@ impl<'a> CompositeFix<'a> { Fix::new(output, Span::new(start, end)) } } + +#[cfg(test)] +mod test { + use super::*; + + impl<'a> PartialEq for Fix<'a> { + fn eq(&self, other: &Self) -> bool { + self.span == other.span && self.content == other.content + } + } + + impl<'a> Clone for CompositeFix<'a> { + fn clone(&self) -> Self { + match self { + Self::None => Self::None, + Self::Single(f) => Self::Single(f.clone()), + Self::Multiple(fs) => Self::Multiple(fs.clone()), + } + } + } + + impl<'a> PartialEq for CompositeFix<'a> { + fn eq(&self, other: &Self) -> bool { + match self { + Self::None => matches!(other, CompositeFix::None), + Self::Single(fix) => { + let Self::Single(other) = other else { + return false; + }; + fix == other + } + Self::Multiple(fixes) => { + let Self::Multiple(other) = other else { + return false; + }; + if fixes.len() != other.len() { + return false; + } + fixes.iter().zip(other.iter()).all(|(a, b)| a == b) + } + } + } + } + + #[test] + fn test_none() { + assert!(FixKind::None.is_none()); + assert!(!FixKind::SafeFix.is_none()); + assert_eq!(FixKind::default(), FixKind::None); + } + + #[test] + fn test_can_apply() { + assert!(FixKind::SafeFix.can_apply(FixKind::SafeFix)); + assert!(!FixKind::SafeFix.can_apply(FixKind::Suggestion)); + assert!(!FixKind::SafeFix.can_apply(FixKind::DangerousFix)); + + assert!(FixKind::DangerousFix.can_apply(FixKind::SafeFix)); + assert!(FixKind::DangerousFix.can_apply(FixKind::DangerousFix)); + assert!(!FixKind::DangerousFix.can_apply(FixKind::Suggestion)); + + assert!(!FixKind::None.can_apply(FixKind::SafeFix)); + assert!(!FixKind::None.can_apply(FixKind::Suggestion)); + assert!(!FixKind::None.can_apply(FixKind::DangerousFix)); + } + + #[test] + fn test_composite_push_on_none() { + let f: CompositeFix = Fix::new("foo", Span::empty(4)).into(); + + let mut none = CompositeFix::None; + none.push(CompositeFix::None); + assert_eq!(none, CompositeFix::None); + + none.push(f.clone()); + assert_eq!(&none, &f); + + let mut none = CompositeFix::None; + let fixes = CompositeFix::from(vec![f.clone(), f]); + none.push(fixes.clone()); + assert_eq!(none.len(), 2); + assert_eq!(none, fixes); + } + + #[test] + fn test_composite_push_on_single() { + let f1 = Fix::new("foo", Span::empty(4)); + let f2 = Fix::new("bar", Span::empty(5)); + let f3 = Fix::new("baz", Span::empty(6)); + let single = || CompositeFix::Single(f1.clone()); + + // None.push(single) == single + let mut f = single(); + f.push(CompositeFix::None); + assert_eq!(f, single()); + + // single1.push(single2) == [single1, single2] + f.push(CompositeFix::Single(f2.clone())); + assert_eq!( + f, + CompositeFix::Multiple(vec![ + Fix::new("foo", Span::empty(4)), + Fix::new("bar", Span::empty(5)) + ]) + ); + + // single.push([f1, f2]) == [single, f1, f2] + let mut f = single(); + f.push(vec![f2.clone(), f3.clone()].into()); + + assert_eq!(f, CompositeFix::Multiple(vec![f1.clone(), f2.clone(), f3.clone()])); + } + + #[test] + fn test_composite_push_on_multiple() { + let f1 = Fix::new("foo", Span::empty(4)); + let f2 = Fix::new("bar", Span::empty(5)); + let f3 = Fix::new("baz", Span::empty(6)); + let multiple = || CompositeFix::Multiple(vec![f1.clone(), f2.clone()]); + + // None.push(multiple) == multiple + let mut f = multiple(); + f.push(CompositeFix::None); + assert_eq!(f, multiple()); + + // [f1, f2].push(f3) == [f1, f2, f3] + let mut f = multiple(); + f.push(CompositeFix::Single(f3.clone())); + assert_eq!(f, CompositeFix::Multiple(vec![f1.clone(), f2.clone(), f3.clone()])); + + // [f1, f2].push([f3, f3]) == [f1, f2, f3, f3] + let mut f = multiple(); + f.push(vec![f3.clone(), f3.clone()].into()); + assert_eq!(f, CompositeFix::Multiple(vec![f1, f2, f3.clone(), f3])); + } +} diff --git a/crates/oxc_linter/src/fixer/mod.rs b/crates/oxc_linter/src/fixer/mod.rs index b62d20dae8f98..ed2bd0cbd085f 100644 --- a/crates/oxc_linter/src/fixer/mod.rs +++ b/crates/oxc_linter/src/fixer/mod.rs @@ -8,82 +8,159 @@ use oxc_span::{GetSpan, Span}; use crate::LintContext; -pub use fix::{CompositeFix, Fix}; +pub use fix::{CompositeFix, Fix, FixKind, RuleFix}; -/// Inspired by ESLint's [`RuleFixer`]. +/// Produces [`RuleFix`] instances. Inspired by ESLint's [`RuleFixer`]. /// /// [`RuleFixer`]: https://github.com/eslint/eslint/blob/main/lib/linter/rule-fixer.js #[derive(Clone, Copy)] +#[must_use] pub struct RuleFixer<'c, 'a: 'c> { + /// What kind of fixes will factory methods produce? + /// + /// Controlled via `diagnostic_with_fix`, `diagnostic_with_suggestion`, and + /// `diagnostic_with_dangerous_fix` methods on [`LintContext`] + kind: FixKind, + /// Enable/disable automatic creation of suggestion messages. + /// + /// Auto-messaging is useful for single fixes, but not so much when we know + /// multiple fixes will be applied. Some [`RuleFix`] factory methods + /// allocate strings on the heap, which would then just get thrown away. + /// Turning this off prevents unneeded allocations. + /// + /// Defaults to `true` + auto_message: bool, ctx: &'c LintContext<'a>, } impl<'c, 'a: 'c> RuleFixer<'c, 'a> { - pub fn new(ctx: &'c LintContext<'a>) -> Self { - Self { ctx } + /// Maximum length code snippets can be inside auto-created messages before + /// they get truncated. Prevents the terminal from getting flooded when a + /// replacement covers a large span. + const MAX_SNIPPET_LEN: usize = 256; + + pub(super) fn new(kind: FixKind, ctx: &'c LintContext<'a>) -> Self { + Self { kind, auto_message: true, ctx } + } + + /// Hint to the [`RuleFixer`] that it will be creating [`CompositeFix`]es + /// containing more than one [`Fix`]. + /// + /// Calling this method in such cases is _highly recommended_ as it has a + /// sizeable performance impact, but is not _strictly_ necessary. + pub fn for_multifix(mut self) -> Self { + self.auto_message = false; + self + } + + // NOTE(@DonIsaac): Internal methods shouldn't use `T: Into` generics to optimize binary + // size. Only use such generics in public APIs. + fn new_fix(&self, fix: CompositeFix<'a>, message: Option>) -> RuleFix<'a> { + RuleFix::new(self.kind, message, fix) + } + + /// Create a new [`RuleFix`] with pre-allocated memory for multiple fixes. + pub fn new_fix_with_capacity(&self, capacity: usize) -> RuleFix<'a> { + RuleFix::new(self.kind, None, CompositeFix::Multiple(Vec::with_capacity(capacity))) } /// Get a snippet of source text covered by the given [`Span`]. For details, /// see [`Span::source_text`]. - pub fn source_range(self, span: Span) -> &'a str { + #[inline] + pub fn source_range(&self, span: Span) -> &'a str { self.ctx.source_range(span) } - /// Create a [`Fix`] that deletes the text covered by the given [`Span`] or - /// AST node. - pub fn delete(self, spanned: &S) -> Fix<'a> { + /// Create a [`RuleFix`] that deletes the text covered by the given [`Span`] + /// or AST node. + #[inline] + pub fn delete(&self, spanned: &S) -> RuleFix<'a> { self.delete_range(spanned.span()) } + /// Delete text covered by a [`Span`] #[allow(clippy::unused_self)] - pub fn delete_range(self, span: Span) -> Fix<'a> { - Fix::delete(span) + pub fn delete_range(&self, span: Span) -> RuleFix<'a> { + self.new_fix( + CompositeFix::Single(Fix::delete(span)), + self.auto_message.then_some(Cow::Borrowed("Delete this code.")), + ) } /// Replace a `target` AST node with the source code of a `replacement` node.. - pub fn replace_with(self, target: &T, replacement: &S) -> Fix<'a> { + pub fn replace_with(&self, target: &T, replacement: &S) -> RuleFix<'a> { let replacement_text = self.ctx.source_range(replacement.span()); - Fix::new(replacement_text, target.span()) + let fix = Fix::new(replacement_text, target.span()); + let message = self.auto_message.then(|| { + let target_text = self.possibly_truncate_range(target.span()); + let borrowed_replacement = Cow::Borrowed(replacement_text); + let replacement_text = self.possibly_truncate_snippet(&borrowed_replacement); + Cow::Owned(format!("Replace `{target_text}` with `{replacement_text}`.")) + }); + + self.new_fix(CompositeFix::Single(fix), message) } /// Replace a `target` AST node with a `replacement` string. #[allow(clippy::unused_self)] - pub fn replace>>(self, target: Span, replacement: S) -> Fix<'a> { - Fix::new(replacement, target) + pub fn replace>>(&self, target: Span, replacement: S) -> RuleFix<'a> { + let fix = Fix::new(replacement, target); + let target_text = self.possibly_truncate_range(target); + let content = self.possibly_truncate_snippet(&fix.content); + let message = self + .auto_message + .then(|| Cow::Owned(format!("Replace `{target_text}` with `{content}`."))); + + self.new_fix(CompositeFix::Single(fix), message) } /// Creates a fix command that inserts text before the given node. + #[inline] pub fn insert_text_before>>( - self, + &self, target: &T, text: S, - ) -> Fix<'a> { + ) -> RuleFix<'a> { self.insert_text_before_range(target.span(), text) } /// Creates a fix command that inserts text before the specified range in the source text. - pub fn insert_text_before_range>>(self, span: Span, text: S) -> Fix<'a> { + #[inline] + pub fn insert_text_before_range>>( + &self, + span: Span, + text: S, + ) -> RuleFix<'a> { self.insert_text_at(span.start, text) } /// Creates a fix command that inserts text after the given node. + #[inline] pub fn insert_text_after>>( - self, + &self, target: &T, text: S, - ) -> Fix<'a> { + ) -> RuleFix<'a> { self.insert_text_after_range(target.span(), text) } /// Creates a fix command that inserts text after the specified range in the source text. - pub fn insert_text_after_range>>(self, span: Span, text: S) -> Fix<'a> { + #[inline] + pub fn insert_text_after_range>>( + &self, + span: Span, + text: S, + ) -> RuleFix<'a> { self.insert_text_at(span.end, text) } /// Creates a fix command that inserts text at the specified index in the source text. #[allow(clippy::unused_self)] - fn insert_text_at>>(self, index: u32, text: S) -> Fix<'a> { - Fix::new(text, Span::new(index, index)) + fn insert_text_at>>(&self, index: u32, text: S) -> RuleFix<'a> { + let fix = Fix::new(text, Span::new(index, index)); + let content = self.possibly_truncate_snippet(&fix.content); + let message = self.auto_message.then(|| Cow::Owned(format!("Insert `{content}`"))); + self.new_fix(CompositeFix::Single(fix), message) } #[allow(clippy::unused_self)] @@ -92,8 +169,31 @@ impl<'c, 'a: 'c> RuleFixer<'c, 'a> { } #[allow(clippy::unused_self)] - pub fn noop(self) -> Fix<'a> { - Fix::empty() + #[inline] + pub fn noop(&self) -> RuleFix<'a> { + self.new_fix(CompositeFix::None, None) + } + + fn possibly_truncate_range(&self, span: Span) -> Cow<'a, str> { + let snippet = self.ctx.source_range(span); + self.possibly_truncate_snippet(snippet) + } + + #[allow(clippy::unused_self)] + fn possibly_truncate_snippet<'s>(&self, snippet: &'s str) -> Cow<'s, str> + where + 'a: 's, + { + if snippet.len() > Self::MAX_SNIPPET_LEN { + let substring = match snippet.char_indices().nth(Self::MAX_SNIPPET_LEN) { + Some((pos, _)) => Cow::Borrowed(&snippet[..pos]), + // slow path when MAX_SNIPPET_LEN is on a UTF-8 character boundary + None => Cow::Owned(snippet.chars().take(Self::MAX_SNIPPET_LEN).collect()), + }; + substring + "..." + } else { + Cow::Borrowed(snippet) + } } } diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index 35eebc4e99d6a..372d799ced51b 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -27,13 +27,14 @@ use oxc_semantic::AstNode; pub use crate::{ config::OxlintConfig, context::LintContext, + fixer::FixKind, options::{AllowWarnDeny, LintOptions}, rule::{RuleCategory, RuleMeta, RuleWithSeverity}, service::{LintService, LintServiceOptions}, }; use crate::{ config::{OxlintEnv, OxlintGlobals, OxlintSettings}, - fixer::{Fix, Fixer, Message}, + fixer::{Fixer, Message}, rules::RuleEnum, table::RuleTable, }; @@ -84,10 +85,19 @@ impl Linter { self } - /// Enable/disable automatic code fixes. + /// Set the kind of auto fixes to apply. + /// + /// # Example + /// + /// ``` + /// use oxc_linter::{Linter, FixKind}; + /// + /// // turn off all auto fixes. This is default behavior. + /// Linter::default().with_fix(FixKind::None); + /// ``` #[must_use] - pub fn with_fix(mut self, yes: bool) -> Self { - self.options.fix = yes; + pub fn with_fix(mut self, kind: FixKind) -> Self { + self.options.fix = kind; self } diff --git a/crates/oxc_linter/src/options.rs b/crates/oxc_linter/src/options.rs index 3bb9c4c0bc62f..d30d917ea088c 100644 --- a/crates/oxc_linter/src/options.rs +++ b/crates/oxc_linter/src/options.rs @@ -6,8 +6,8 @@ use schemars::{schema::SchemaObject, JsonSchema}; use serde_json::{Number, Value}; use crate::{ - config::OxlintConfig, rules::RULES, utils::is_jest_rule_adapted_to_vitest, RuleCategory, - RuleEnum, RuleWithSeverity, + config::OxlintConfig, fixer::FixKind, rules::RULES, utils::is_jest_rule_adapted_to_vitest, + RuleCategory, RuleEnum, RuleWithSeverity, }; #[derive(Debug)] @@ -16,7 +16,10 @@ pub struct LintOptions { /// Defaults to [("deny", "correctness")] pub filter: Vec<(AllowWarnDeny, String)>, pub config_path: Option, - pub fix: bool, + /// Enable automatic code fixes. Set to [`None`] to disable. + /// + /// The kind represents the riskiest fix that the linter can apply. + pub fix: FixKind, pub react_plugin: bool, pub unicorn_plugin: bool, @@ -37,7 +40,7 @@ impl Default for LintOptions { Self { filter: vec![(AllowWarnDeny::Warn, String::from("correctness"))], config_path: None, - fix: false, + fix: FixKind::None, react_plugin: true, unicorn_plugin: true, typescript_plugin: true, @@ -69,9 +72,19 @@ impl LintOptions { self } + /// Set the kind of auto fixes to apply. + /// + /// # Example + /// + /// ``` + /// use oxc_linter::{LintOptions, FixKind}; + /// + /// // turn off all auto fixes. This is default behavior. + /// LintOptions::default().with_fix(FixKind::None); + /// ``` #[must_use] - pub fn with_fix(mut self, yes: bool) -> Self { - self.fix = yes; + pub fn with_fix(mut self, kind: FixKind) -> Self { + self.fix = kind; self } diff --git a/crates/oxc_linter/src/rules/jest/prefer_expect_resolves.rs b/crates/oxc_linter/src/rules/jest/prefer_expect_resolves.rs index 1289c3ce2b5c9..be9c781522907 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_expect_resolves.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_expect_resolves.rs @@ -8,7 +8,7 @@ use oxc_span::Span; use crate::{ context::LintContext, - fixer::{Fix, RuleFixer}, + fixer::{RuleFix, RuleFixer}, rule::Rule, utils::{ collect_possible_jest_call_node, parse_expect_jest_fn_call, ParsedExpectFnCall, @@ -116,7 +116,7 @@ impl PreferExpectResolves { jest_expect_fn_call: &ParsedExpectFnCall<'a>, call_expr: &CallExpression<'a>, ident_span: Span, - ) -> Fix<'a> { + ) -> RuleFix<'a> { let mut formatter = fixer.codegen(); let first = call_expr.arguments.first().unwrap(); let Argument::AwaitExpression(await_expr) = first else { diff --git a/crates/oxc_linter/src/rules/jest/prefer_todo.rs b/crates/oxc_linter/src/rules/jest/prefer_todo.rs index f08b8f3b152a0..01c7824b72f6c 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_todo.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_todo.rs @@ -8,7 +8,7 @@ use oxc_span::{GetSpan, Span}; use crate::{ context::LintContext, - fixer::{Fix, RuleFixer}, + fixer::{RuleFix, RuleFixer}, rule::Rule, utils::{ collect_possible_jest_call_node, is_type_of_jest_fn_call, JestFnKind, JestGeneralFnKind, @@ -137,7 +137,7 @@ fn is_empty_function(expr: &CallExpression) -> bool { } } -fn build_code<'a>(fixer: RuleFixer<'_, 'a>, expr: &CallExpression<'a>) -> Fix<'a> { +fn build_code<'a>(fixer: RuleFixer<'_, 'a>, expr: &CallExpression<'a>) -> RuleFix<'a> { let mut formatter = fixer.codegen(); match &expr.callee { diff --git a/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs b/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs index 9cf66fa38d045..5474c6da2d1e0 100644 --- a/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs +++ b/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs @@ -15,7 +15,7 @@ use oxc_span::{CompactStr, GetSpan, Span}; use crate::{ context::LintContext, - fixer::{Fix, RuleFixer}, + fixer::{RuleFix, RuleFixer}, rule::Rule, AstNode, }; @@ -256,7 +256,7 @@ impl Rule for ConsistentTypeImports { Ok(fixes) => fixes, Err(err) => { debug_assert!(false, "Failed to fix: {err}"); - vec![] + fixer.noop() } } }; @@ -329,8 +329,9 @@ fn fixer_error, T>(message: S) -> FixerResult { // import { Foo, Bar } from 'foo' => import type { Foo, Bar } from 'foo' #[allow(clippy::unnecessary_cast, clippy::cast_possible_truncation)] -fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResult>> { +fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResult> { let FixOptions { fixer, import_decl, type_names, fix_style, ctx } = options; + let fixer = fixer.for_multifix(); let GroupedSpecifiers { namespace_specifier, named_specifiers, default_specifier } = classify_specifier(import_decl); @@ -389,8 +390,8 @@ fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResu let fixes_named_specifiers = get_fixes_named_specifiers(options, &type_names_specifiers, &named_specifiers)?; - let mut fixes = vec![]; - let mut after_fixes = vec![]; + let mut rule_fixes = fixer.new_fix_with_capacity(4); + let mut after_fixes = fixer.new_fix_with_capacity(4); if !type_names_specifiers.is_empty() { // definitely all type references: `import type { A, B } from 'foo'` @@ -409,7 +410,7 @@ fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResu &fixes_named_specifiers.type_named_specifiers_text, )?; if type_only_named_import.span.end <= import_decl.span.start { - fixes.push(fix); + rule_fixes.push(fix); } else { after_fixes.push(fix); } @@ -427,9 +428,9 @@ fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResu .join(", "), ctx.source_range(import_decl.source.span), ); - fixes.push(fixer.insert_text_before(*import_decl, text)); + rule_fixes.push(fixer.insert_text_before(*import_decl, text)); } else { - fixes.push(fixer.insert_text_before( + rule_fixes.push(fixer.insert_text_before( *import_decl, format!( "import type {{{}}} from {};\n", @@ -441,7 +442,7 @@ fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResu } } - let mut fixes_remove_type_namespace_specifier = vec![]; + let mut fixes_remove_type_namespace_specifier = fixer.new_fix_with_capacity(0); if let Some(namespace_specifier) = namespace_specifier { if type_names.iter().contains(&namespace_specifier.local.name.as_str()) { @@ -459,7 +460,7 @@ fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResu // import type * as Ns from 'foo' // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ insert - fixes.push(fixer.insert_text_before( + rule_fixes.push(fixer.insert_text_before( *import_decl, format!( "import type {} from {};\n", @@ -475,7 +476,7 @@ fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResu if type_names.len() == import_decl.specifiers.as_ref().map_or(0, |s| s.len()) { // import type Type from 'foo' // ^^^^^ insert - fixes.push(fixer.insert_text_after( + rule_fixes.push(fixer.insert_text_after( &Span::new(import_decl.span().start, import_decl.span().start + 6), " type", )); @@ -490,7 +491,7 @@ fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResu default_specifier.span.start, import_decl.span().start + comma, )); - fixes.push(fixer.insert_text_before( + rule_fixes.push(fixer.insert_text_before( *import_decl, format!( "import type {default_text} from {};\n", @@ -502,7 +503,7 @@ fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResu find_first_non_white_space(&import_text[(comma + 1) as usize..]) { let after_token = comma as usize + 1 + after_token.0; - fixes.push(fixer.delete_range(Span::new( + rule_fixes.push(fixer.delete_range(Span::new( default_specifier.span.start, import_decl.span().start + after_token as u32, ))); @@ -511,16 +512,16 @@ fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResu } } - fixes.extend(fixes_named_specifiers.remove_type_name_specifiers); - fixes.extend(fixes_remove_type_namespace_specifier); - fixes.extend(after_fixes); - Ok(fixes) + Ok(rule_fixes + .extend(fixes_named_specifiers.remove_type_name_specifiers) + .extend(fixes_remove_type_namespace_specifier) + .extend(after_fixes)) } fn fix_insert_named_specifiers_in_named_specifier_list<'a>( options: &FixOptions<'a, '_>, insert_text: &str, -) -> FixerResult> { +) -> FixerResult> { let FixOptions { fixer, import_decl, ctx, .. } = options; let import_text = ctx.source_range(import_decl.span); let close_brace = try_find_char(import_text, '}')?; @@ -541,7 +542,7 @@ fn fix_insert_named_specifiers_in_named_specifier_list<'a>( #[derive(Default, Debug)] struct FixNamedSpecifiers<'a> { type_named_specifiers_text: String, - remove_type_name_specifiers: Vec>, + remove_type_name_specifiers: RuleFix<'a>, } // get the type-only named import declaration with same source @@ -578,15 +579,16 @@ fn get_fixes_named_specifiers<'a>( options: &FixOptions<'a, '_>, subset_named_specifiers: &[&ImportSpecifier<'a>], all_named_specifiers: &[&ImportSpecifier<'a>], -) -> Result, Box> { +) -> FixerResult> { let FixOptions { fixer, import_decl, ctx, .. } = options; + let fixer = fixer.for_multifix(); if all_named_specifiers.is_empty() { return Ok(FixNamedSpecifiers::default()); } let mut type_named_specifiers_text: Vec<&str> = vec![]; - let mut remove_type_named_specifiers: Vec = vec![]; + let mut remove_type_named_specifiers = fixer.new_fix_with_capacity(1); if subset_named_specifiers.len() == all_named_specifiers.len() { // import Foo, {Type1, Type2} from 'foo' @@ -736,10 +738,11 @@ fn try_find_char(text: &str, c: char) -> Result> { fn fix_inline_type_import_declaration<'a>( options: &FixOptions<'a, '_>, -) -> FixerResult>> { +) -> FixerResult> { let FixOptions { fixer, import_decl, type_names, ctx, .. } = options; + let fixer = fixer.for_multifix(); - let mut fixes = vec![]; + let mut rule_fixes = fixer.new_fix_with_capacity(0); let Some(specifiers) = &import_decl.specifiers else { return fixer_error("Missing specifiers in import declaration"); @@ -748,7 +751,7 @@ fn fix_inline_type_import_declaration<'a>( for specifier in specifiers { if let ImportDeclarationSpecifier::ImportSpecifier(specifier) = specifier { if type_names.iter().contains(&specifier.local.name.as_str()) { - fixes.push( + rule_fixes.push( fixer.replace( specifier.span, format!("type {}", ctx.source_range(specifier.span)), @@ -758,20 +761,21 @@ fn fix_inline_type_import_declaration<'a>( } } - Ok(fixes) + Ok(rule_fixes) } fn fix_insert_type_specifier_for_import_declaration<'a>( options: &FixOptions<'a, '_>, is_default_import: bool, -) -> FixerResult>> { +) -> FixerResult> { let FixOptions { fixer, import_decl, ctx, .. } = options; + let fixer = fixer.for_multifix(); let import_source = ctx.source_range(import_decl.span); - let mut fixes = vec![]; + let mut rule_fixes = fixer.new_fix_with_capacity(1); // "import { Foo, Bar } from 'foo'" => "import type { Foo, Bar } from 'foo'" // ^^^^ add - fixes.push( + rule_fixes.push( fixer.replace(Span::new(import_decl.span.start, import_decl.span.start + 6), "import type"), ); @@ -784,7 +788,7 @@ fn fix_insert_type_specifier_for_import_declaration<'a>( let base = import_decl.span.start; // import foo, {} from 'foo' // ^^^^ delete - fixes.push( + rule_fixes.push( fixer.delete(&Span::new(base + comma_token, base + (closing_brace_token + 1))), ); if import_decl.specifiers.as_ref().is_some_and(|specifiers| specifiers.len() > 1) { @@ -800,7 +804,7 @@ fn fix_insert_type_specifier_for_import_declaration<'a>( )); }; - fixes.push(fixer.insert_text_after( + rule_fixes.push(fixer.insert_text_after( *import_decl, format!( "\nimport type {} from {}", @@ -818,7 +822,7 @@ fn fix_insert_type_specifier_for_import_declaration<'a>( if specifier.import_kind.is_type() { // import { type A } from 'foo.js' // ^^^^^^^^ delete - fixes.push( + rule_fixes.push( fixer.delete(&Span::new( specifier.span.start, specifier.imported.span().start, @@ -829,7 +833,7 @@ fn fix_insert_type_specifier_for_import_declaration<'a>( } } - Ok(fixes) + Ok(rule_fixes) } struct GroupedSpecifiers<'a, 'b> { @@ -866,11 +870,12 @@ fn classify_specifier<'a, 'b>(import_decl: &'b ImportDeclaration<'a>) -> Grouped // import type Foo from 'foo' // ^^^^ remove +// note:(don): RuleFix added fn fix_remove_type_specifier_from_import_declaration<'a>( fixer: RuleFixer<'_, 'a>, import_decl_span: Span, ctx: &LintContext<'a>, -) -> Fix<'a> { +) -> RuleFix<'a> { let import_source = ctx.source_range(import_decl_span); let new_import_source = import_source // ` type Foo from 'foo'` @@ -896,7 +901,7 @@ fn fix_remove_type_specifier_from_import_specifier<'a>( fixer: RuleFixer<'_, 'a>, specifier_span: Span, ctx: &LintContext<'a>, -) -> Fix<'a> { +) -> RuleFix<'a> { let specifier_source = ctx.source_range(specifier_span); let new_specifier_source = specifier_source.strip_prefix("type "); diff --git a/crates/oxc_linter/src/rules/unicorn/no_null.rs b/crates/oxc_linter/src/rules/unicorn/no_null.rs index 6b25f7b29aa25..2dfa16ce204b0 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_null.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_null.rs @@ -12,7 +12,7 @@ use oxc_syntax::operator::BinaryOperator; use crate::{ ast_util::is_method_call, context::LintContext, - fixer::{Fix, RuleFixer}, + fixer::{RuleFix, RuleFixer}, rule::Rule, AstNode, }; @@ -220,7 +220,7 @@ impl Rule for NoNull { } } -fn fix_null<'a>(fixer: RuleFixer<'_, 'a>, null: &NullLiteral) -> Fix<'a> { +fn fix_null<'a>(fixer: RuleFixer<'_, 'a>, null: &NullLiteral) -> RuleFix<'a> { fixer.replace(null.span, "undefined") } #[test] diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs index 3f6e5dd2fb309..fa0ace02fe1b2 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs @@ -9,7 +9,7 @@ use oxc_span::{GetSpan, Span}; use crate::{ ast_util::outermost_paren_parent, context::LintContext, - fixer::{Fix, RuleFixer}, + fixer::{RuleFix, RuleFixer}, rule::Rule, AstNode, }; @@ -223,7 +223,7 @@ fn generate_fix<'a>( fixer: RuleFixer<'_, 'a>, ctx: &LintContext<'a>, node: &AstNode<'a>, -) -> Fix<'a> { +) -> RuleFix<'a> { if call_expr.arguments.len() > 1 { return fixer.noop(); } diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_spread.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_spread.rs index e19f9cc2b426c..95b5fb847cdde 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_spread.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_spread.rs @@ -14,7 +14,7 @@ use crate::{ get_new_expr_ident_name, is_method_call, is_new_expression, outermost_paren_parent, }, context::LintContext, - fixer::{Fix, RuleFixer}, + fixer::{RuleFix, RuleFixer}, rule::Rule, AstNode, }; @@ -447,7 +447,7 @@ fn fix_replace<'a, T: GetSpan, U: GetSpan>( fixer: RuleFixer<'_, 'a>, target: &T, replacement: &U, -) -> Fix<'a> { +) -> RuleFix<'a> { let replacement = fixer.source_range(replacement.span()); fixer.replace(target.span(), replacement) } @@ -457,7 +457,7 @@ fn fix_by_removing_spread<'a, S: GetSpan>( fixer: RuleFixer<'_, 'a>, iterable: &S, spread: &SpreadElement<'a>, -) -> Fix<'a> { +) -> RuleFix<'a> { fixer.replace(iterable.span(), fixer.source_range(spread.argument.span())) } diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_query_selector.rs b/crates/oxc_linter/src/rules/unicorn/prefer_query_selector.rs index 006e2d2dd121b..5fd38e4aa990c 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_query_selector.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_query_selector.rs @@ -4,7 +4,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; use phf::phf_map; -use crate::{context::LintContext, rule::Rule, utils::is_node_value_not_dom_node, AstNode, Fix}; +use crate::{context::LintContext, rule::Rule, utils::is_node_value_not_dom_node, AstNode}; fn prefer_query_selector_diagnostic(x0: &str, x1: &str, span2: Span) -> OxcDiagnostic { OxcDiagnostic::warn(format!("eslint-plugin-unicorn(prefer-query-selector): Prefer `.{x0}()` over `.{x1}()`.")) @@ -107,7 +107,7 @@ impl Rule for PreferQuerySelector { if let Some(literal_value) = literal_value { return ctx.diagnostic_with_fix(diagnostic, |fixer| { if literal_value.is_empty() { - return Fix::new(*preferred_selector, property_span); + return fixer.replace(property_span, *preferred_selector); } // let source_text = diff --git a/crates/oxc_linter/src/service.rs b/crates/oxc_linter/src/service.rs index 36704600284bd..c01f70e0d3e41 100644 --- a/crates/oxc_linter/src/service.rs +++ b/crates/oxc_linter/src/service.rs @@ -227,7 +227,7 @@ impl Runtime { self.process_source(path, &allocator, source_text, source_type, true, tx_error); // TODO: Span is wrong, ban this feature for file process by `PartialLoader`. - if !is_processed_by_partial_loader && self.linter.options().fix { + if !is_processed_by_partial_loader && self.linter.options().fix.is_some() { let fix_result = Fixer::new(source_text, messages).fix(); fs::write(path, fix_result.fixed_code.as_bytes()).unwrap(); messages = fix_result.messages; diff --git a/crates/oxc_linter/src/snapshots/array_type.snap b/crates/oxc_linter/src/snapshots/array_type.snap index 5e9fea2bc8a20..13be26fb6c1f6 100644 --- a/crates/oxc_linter/src/snapshots/array_type.snap +++ b/crates/oxc_linter/src/snapshots/array_type.snap @@ -6,354 +6,413 @@ source: crates/oxc_linter/src/tester.rs 1 │ let a: Array = []; · ───────────── ╰──── + help: Replace `Array` with `number[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'T[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: Array = []; · ────────────────────── ╰──── + help: Replace `Array` with `(string | number)[]`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden. Use 'readonly number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ───────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly number[]`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden. Use 'readonly T[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ────────────────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly (string | number)[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: Array = []; · ───────────── ╰──── + help: Replace `Array` with `number[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'T[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: Array = []; · ────────────────────── ╰──── + help: Replace `Array` with `(string | number)[]`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden. Use 'readonly number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ───────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly number[]`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden. Use 'readonly T[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ────────────────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly (string | number)[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: Array = []; · ───────────── ╰──── + help: Replace `Array` with `number[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'T[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: Array = []; · ────────────────────── ╰──── + help: Replace `Array` with `(string | number)[]`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden for simple types. Use 'readonly number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ───────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly number[]`. ⚠ typescript-eslint(array-type): Array type using 'readonly T[]' is forbidden for non-simple types. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly (string | number)[] = []; · ──────────────────────────── ╰──── + help: Replace `readonly (string | number)[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: Array = []; · ───────────── ╰──── + help: Replace `Array` with `number[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'T[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: Array = []; · ────────────────────── ╰──── + help: Replace `Array` with `(string | number)[]`. ⚠ typescript-eslint(array-type): Array type using 'readonly number[]' is forbidden. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly number[] = []; · ───────────────── ╰──── + help: Replace `readonly number[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'readonly T[]' is forbidden. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly (string | number)[] = []; · ──────────────────────────── ╰──── + help: Replace `readonly (string | number)[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden for simple types. Use 'number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: Array = []; · ───────────── ╰──── + help: Replace `Array` with `number[]`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden for non-simple types. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: (string | number)[] = []; · ─────────────────── ╰──── + help: Replace `(string | number)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden for simple types. Use 'readonly number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ───────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly number[]`. ⚠ typescript-eslint(array-type): Array type using 'readonly T[]' is forbidden for non-simple types. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly (string | number)[] = []; · ──────────────────────────── ╰──── + help: Replace `readonly (string | number)[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden for simple types. Use 'number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: Array = []; · ───────────── ╰──── + help: Replace `Array` with `number[]`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden for non-simple types. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: (string | number)[] = []; · ─────────────────── ╰──── + help: Replace `(string | number)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden. Use 'readonly number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ───────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly number[]`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden. Use 'readonly T[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ────────────────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly (string | number)[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden for simple types. Use 'number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: Array = []; · ───────────── ╰──── + help: Replace `Array` with `number[]`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden for non-simple types. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: (string | number)[] = []; · ─────────────────── ╰──── + help: Replace `(string | number)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden for simple types. Use 'readonly number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ───────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly number[]`. ⚠ typescript-eslint(array-type): Array type using 'readonly T[]' is forbidden for non-simple types. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly (string | number)[] = []; · ──────────────────────────── ╰──── + help: Replace `readonly (string | number)[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden for simple types. Use 'number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: Array = []; · ───────────── ╰──── + help: Replace `Array` with `number[]`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden for non-simple types. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: (string | number)[] = []; · ─────────────────── ╰──── + help: Replace `(string | number)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'readonly number[]' is forbidden. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly number[] = []; · ───────────────── ╰──── + help: Replace `readonly number[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'readonly T[]' is forbidden. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly (string | number)[] = []; · ──────────────────────────── ╰──── + help: Replace `readonly (string | number)[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'number[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: number[] = []; · ──────── ╰──── + help: Replace `number[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: (string | number)[] = []; · ─────────────────── ╰──── + help: Replace `(string | number)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'readonly number[]' is forbidden. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly number[] = []; · ───────────────── ╰──── + help: Replace `readonly number[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'readonly T[]' is forbidden. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly (string | number)[] = []; · ──────────────────────────── ╰──── + help: Replace `readonly (string | number)[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'number[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: number[] = []; · ──────── ╰──── + help: Replace `number[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: (string | number)[] = []; · ─────────────────── ╰──── + help: Replace `(string | number)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden. Use 'readonly number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ───────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly number[]`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden. Use 'readonly T[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ────────────────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly (string | number)[]`. ⚠ typescript-eslint(array-type): Array type using 'number[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: number[] = []; · ──────── ╰──── + help: Replace `number[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: (string | number)[] = []; · ─────────────────── ╰──── + help: Replace `(string | number)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden for simple types. Use 'readonly number[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ───────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly number[]`. ⚠ typescript-eslint(array-type): Array type using 'readonly T[]' is forbidden for non-simple types. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly (string | number)[] = []; · ──────────────────────────── ╰──── + help: Replace `readonly (string | number)[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'number[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: number[] = []; · ──────── ╰──── + help: Replace `number[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: (string | number)[] = []; · ─────────────────── ╰──── + help: Replace `(string | number)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'readonly number[]' is forbidden. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly number[] = []; · ───────────────── ╰──── + help: Replace `readonly number[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'readonly T[]' is forbidden. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly (string | number)[] = []; · ──────────────────────────── ╰──── + help: Replace `readonly (string | number)[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'bigint[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: bigint[] = []; · ──────── ╰──── + help: Replace `bigint[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: (string | bigint)[] = []; · ─────────────────── ╰──── + help: Replace `(string | bigint)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden for simple types. Use 'readonly bigint[]' instead. ╭─[array_type.tsx:1:8] 1 │ let a: ReadonlyArray = []; · ───────────────────── ╰──── + help: Replace `ReadonlyArray` with `readonly bigint[]`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:8] 1 │ let a: (string | bigint)[] = []; · ─────────────────── ╰──── + help: Replace `(string | bigint)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'readonly bigint[]' is forbidden. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly bigint[] = []; · ───────────────── ╰──── + help: Replace `readonly bigint[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'readonly T[]' is forbidden. Use 'ReadonlyArray' instead. ╭─[array_type.tsx:1:8] 1 │ let a: readonly (string | bigint)[] = []; · ──────────────────────────── ╰──── + help: Replace `readonly (string | bigint)[]` with `ReadonlyArray`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'Bar[]' instead. ╭─[array_type.tsx:1:15] 1 │ let a: { foo: Array }[] = []; · ────────── ╰──── + help: Replace `Array` with `Bar[]`. ⚠ typescript-eslint(array-type): Array type using 'Bar[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:21] 1 │ let a: Array<{ foo: Bar[] }> = []; · ───── ╰──── + help: Replace `Bar[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'Bar[]' instead. ╭─[array_type.tsx:1:17] 1 │ function foo(a: Array): Array {} · ────────── ╰──── + help: Replace `Array` with `Bar[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'Bar[]' instead. ╭─[array_type.tsx:1:30] 1 │ function foo(a: Array): Array {} · ────────── ╰──── + help: Replace `Array` with `Bar[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden for simple types. Use 'undefined[]' instead. ╭─[array_type.tsx:1:8] 1 │ let x: Array = [undefined] as undefined[]; · ──────────────── ╰──── + help: Replace `Array` with `undefined[]`. × Expected `<` but found `EOF` ╭─[array_type.tsx:1:40] @@ -365,18 +424,21 @@ source: crates/oxc_linter/src/tester.rs 1 │ let z: Array = [3, '4']; · ───── ╰──── + help: Replace `Array` with `any[]`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden for non-simple types. Use 'Array' instead. ╭─[array_type.tsx:1:24] 1 │ let ya = [[1, '2']] as [number, string][]; · ────────────────── ╰──── + help: Replace `[number, string][]` with `Array<[number, string]>`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden for simple types. Use 'T[]' instead. ╭─[array_type.tsx:1:15] 1 │ type Arr = Array; · ──────── ╰──── + help: Replace `Array` with `T[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden for simple types. Use 'T[]' instead. ╭─[array_type.tsx:3:14] @@ -385,6 +447,7 @@ source: crates/oxc_linter/src/tester.rs · ──────── 4 │ bar: T[]; ╰──── + help: Replace `Array` with `T[]`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden for non-simple types. Use 'Array' instead. ╭─[array_type.tsx:2:35] @@ -393,30 +456,35 @@ source: crates/oxc_linter/src/tester.rs · ──────────────────── 3 │ return bar.map(e => e.bar); ╰──── + help: Replace `ArrayClass[]` with `Array>`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden for non-simple types. Use 'Array' instead. ╭─[array_type.tsx:1:13] 1 │ let barVar: ((c: number) => number)[]; · ───────────────────────── ╰──── + help: Replace `((c: number) => number)[]` with `Array<(c: number) => number>`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden for non-simple types. Use 'Array' instead. ╭─[array_type.tsx:1:17] 1 │ type barUnion = (string | number | boolean)[]; · ───────────────────────────── ╰──── + help: Replace `(string | number | boolean)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden for non-simple types. Use 'Array' instead. ╭─[array_type.tsx:1:24] 1 │ type barIntersection = (string & number)[]; · ─────────────────── ╰──── + help: Replace `(string & number)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'undefined[]' instead. ╭─[array_type.tsx:1:8] 1 │ let x: Array = [undefined] as undefined[]; · ──────────────── ╰──── + help: Replace `Array` with `undefined[]`. × Expected `<` but found `EOF` ╭─[array_type.tsx:1:40] @@ -428,12 +496,14 @@ source: crates/oxc_linter/src/tester.rs 1 │ let z: Array = [3, '4']; · ───── ╰──── + help: Replace `Array` with `any[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'T[]' instead. ╭─[array_type.tsx:1:15] 1 │ type Arr = Array; · ──────── ╰──── + help: Replace `Array` with `T[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'T[]' instead. ╭─[array_type.tsx:3:14] @@ -442,6 +512,7 @@ source: crates/oxc_linter/src/tester.rs · ──────── 4 │ bar: T[]; ╰──── + help: Replace `Array` with `T[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'T[]' instead. ╭─[array_type.tsx:2:35] @@ -450,54 +521,63 @@ source: crates/oxc_linter/src/tester.rs · ───────────────────────── 3 │ return foo.map(e => e.foo); ╰──── + help: Replace `Array>` with `ArrayClass[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'T[]' instead. ╭─[array_type.tsx:1:13] 1 │ let fooVar: Array<(c: number) => number>; · ──────────────────────────── ╰──── + help: Replace `Array<(c: number) => number>` with `((c: number) => number)[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'T[]' instead. ╭─[array_type.tsx:1:17] 1 │ type fooUnion = Array; · ──────────────────────────────── ╰──── + help: Replace `Array` with `(string | number | boolean)[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'T[]' instead. ╭─[array_type.tsx:1:24] 1 │ type fooIntersection = Array; · ────────────────────── ╰──── + help: Replace `Array` with `(string & number)[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'any[]' instead. ╭─[array_type.tsx:1:8] 1 │ let x: Array; · ───── ╰──── + help: Replace `Array` with `any[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'any[]' instead. ╭─[array_type.tsx:1:8] 1 │ let x: Array<>; · ─────── ╰──── + help: Replace `Array<>` with `any[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden for simple types. Use 'any[]' instead. ╭─[array_type.tsx:1:8] 1 │ let x: Array; · ───── ╰──── + help: Replace `Array` with `any[]`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden for simple types. Use 'any[]' instead. ╭─[array_type.tsx:1:8] 1 │ let x: Array<>; · ─────── ╰──── + help: Replace `Array<>` with `any[]`. ⚠ typescript-eslint(array-type): Array type using 'number[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:31] 1 │ let x: Array = [1] as number[]; · ──────── ╰──── + help: Replace `number[]` with `Array`. × Expected `<` but found `EOF` ╭─[array_type.tsx:1:40] @@ -509,6 +589,7 @@ source: crates/oxc_linter/src/tester.rs 1 │ let ya = [[1, '2']] as [number, string][]; · ────────────────── ╰──── + help: Replace `[number, string][]` with `Array<[number, string]>`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:4:14] @@ -517,6 +598,7 @@ source: crates/oxc_linter/src/tester.rs · ─── 5 │ baz: Arr; ╰──── + help: Replace `T[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:2:35] @@ -525,24 +607,28 @@ source: crates/oxc_linter/src/tester.rs · ──────────────────── 3 │ return bar.map(e => e.bar); ╰──── + help: Replace `ArrayClass[]` with `Array>`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:13] 1 │ let barVar: ((c: number) => number)[]; · ───────────────────────── ╰──── + help: Replace `((c: number) => number)[]` with `Array<(c: number) => number>`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:17] 1 │ type barUnion = (string | number | boolean)[]; · ───────────────────────────── ╰──── + help: Replace `(string | number | boolean)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:1:24] 1 │ type barIntersection = (string & number)[]; · ─────────────────── ╰──── + help: Replace `(string & number)[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'string[]' is forbidden. Use 'Array' instead. ╭─[array_type.tsx:3:24] @@ -551,15 +637,18 @@ source: crates/oxc_linter/src/tester.rs · ──────── 4 │ } ╰──── + help: Replace `string[]` with `Array`. ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'T[]' instead. ╭─[array_type.tsx:1:12] 1 │ const foo: Array void> = []; · ─────────────────────────────────── ╰──── + help: Replace `Array void>` with `(new (...args: any[]) => void)[]`. ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden. Use 'readonly T[]' instead. ╭─[array_type.tsx:1:12] 1 │ const foo: ReadonlyArray void> = []; · ─────────────────────────────────────────── ╰──── + help: Replace `ReadonlyArray void>` with `readonly (new (...args: any[]) => void)[]`. diff --git a/crates/oxc_linter/src/snapshots/ban_tslint_comment.snap b/crates/oxc_linter/src/snapshots/ban_tslint_comment.snap index 15505d38f7056..a0f87008fe826 100644 --- a/crates/oxc_linter/src/snapshots/ban_tslint_comment.snap +++ b/crates/oxc_linter/src/snapshots/ban_tslint_comment.snap @@ -6,42 +6,49 @@ source: crates/oxc_linter/src/tester.rs 1 │ /* tslint:disable */ · ──────────────────── ╰──── + help: Delete this code. ⚠ typescript-eslint(ban-tslint-comment): tslint comment detected: "tslint:enable" ╭─[ban_tslint_comment.tsx:1:1] 1 │ /* tslint:enable */ · ─────────────────── ╰──── + help: Delete this code. ⚠ typescript-eslint(ban-tslint-comment): tslint comment detected: "tslint:disable:rule1 rule2 rule3..." ╭─[ban_tslint_comment.tsx:1:1] 1 │ /* tslint:disable:rule1 rule2 rule3... */ · ───────────────────────────────────────── ╰──── + help: Delete this code. ⚠ typescript-eslint(ban-tslint-comment): tslint comment detected: "tslint:enable:rule1 rule2 rule3..." ╭─[ban_tslint_comment.tsx:1:1] 1 │ /* tslint:enable:rule1 rule2 rule3... */ · ──────────────────────────────────────── ╰──── + help: Delete this code. ⚠ typescript-eslint(ban-tslint-comment): tslint comment detected: "tslint:disable-next-line" ╭─[ban_tslint_comment.tsx:1:1] 1 │ // tslint:disable-next-line · ─────────────────────────── ╰──── + help: Delete this code. ⚠ typescript-eslint(ban-tslint-comment): tslint comment detected: "tslint:disable-line" ╭─[ban_tslint_comment.tsx:1:13] 1 │ someCode(); // tslint:disable-line · ────────────────────── ╰──── + help: Delete this code. ⚠ typescript-eslint(ban-tslint-comment): tslint comment detected: "tslint:disable-next-line:rule1 rule2 rule3..." ╭─[ban_tslint_comment.tsx:1:1] 1 │ // tslint:disable-next-line:rule1 rule2 rule3... · ──────────────────────────────────────────────── ╰──── + help: Delete this code. ⚠ typescript-eslint(ban-tslint-comment): tslint comment detected: "tslint:disable-line" ╭─[ban_tslint_comment.tsx:2:9] @@ -51,3 +58,4 @@ source: crates/oxc_linter/src/tester.rs 3 │ console.log(woah); 4 │ ╰──── + help: Delete this code. diff --git a/crates/oxc_linter/src/snapshots/consistent_type_imports.snap b/crates/oxc_linter/src/snapshots/consistent_type_imports.snap index cd8247b007eee..d3333cd4f9d8b 100644 --- a/crates/oxc_linter/src/snapshots/consistent_type_imports.snap +++ b/crates/oxc_linter/src/snapshots/consistent_type_imports.snap @@ -296,6 +296,7 @@ source: crates/oxc_linter/src/tester.rs · ─────────────────────────── 3 │ let foo: Foo; ╰──── + help: Replace `import type Foo from 'foo';` with `import Foo from 'foo';`. ⚠ typescript-eslint(consistent-type-imports): Use an `import` instead of an `import type`. ╭─[consistent_type_imports.tsx:2:15] @@ -304,6 +305,7 @@ source: crates/oxc_linter/src/tester.rs · ─────────────────────────────── 3 │ let foo: Foo; ╰──── + help: Replace `import type { Foo } from 'foo';` with `import { Foo } from 'foo';`. ⚠ typescript-eslint(consistent-type-imports): All imports in the declaration are only used as types. Use `import type`. ╭─[consistent_type_imports.tsx:2:15] @@ -336,6 +338,7 @@ source: crates/oxc_linter/src/tester.rs · ──────────────────────────── 3 │ ╰──── + help: Replace `import type Type from 'foo';` with `import Type from 'foo';`. ⚠ typescript-eslint(consistent-type-imports): Use an `import` instead of an `import type`. ╭─[consistent_type_imports.tsx:2:15] @@ -344,6 +347,7 @@ source: crates/oxc_linter/src/tester.rs · ──────────────────────────────── 3 │ ╰──── + help: Replace `import type { Type } from 'foo';` with `import { Type } from 'foo';`. ⚠ typescript-eslint(consistent-type-imports): Use an `import` instead of an `import type`. ╭─[consistent_type_imports.tsx:2:15] @@ -352,6 +356,7 @@ source: crates/oxc_linter/src/tester.rs · ───────────────────────────────── 3 │ ╰──── + help: Replace `import type * as Type from 'foo';` with `import * as Type from 'foo';`. ⚠ typescript-eslint(consistent-type-imports): Use an `import` instead of an `import type`. ╭─[consistent_type_imports.tsx:2:15] @@ -360,6 +365,7 @@ source: crates/oxc_linter/src/tester.rs · ──────────────────────────────────────────────── 3 │ import type // comment ╰──── + help: Replace `import type /*comment*/ * as AllType from 'foo';` with `import /*comment*/ * as AllType from 'foo';`. ⚠ typescript-eslint(consistent-type-imports): Use an `import` instead of an `import type`. ╭─[consistent_type_imports.tsx:3:15] @@ -368,6 +374,9 @@ source: crates/oxc_linter/src/tester.rs 4 │ ╰─▶ DefType from 'foo'; 5 │ import type /*comment*/ { Type } from 'foo'; ╰──── + help: Replace `import type // comment + DefType from 'foo';` with `import // comment + DefType from 'foo';`. ⚠ typescript-eslint(consistent-type-imports): Use an `import` instead of an `import type`. ╭─[consistent_type_imports.tsx:5:15] @@ -376,6 +385,7 @@ source: crates/oxc_linter/src/tester.rs · ──────────────────────────────────────────── 6 │ ╰──── + help: Replace `import type /*comment*/ { Type } from 'foo';` with `import /*comment*/ { Type } from 'foo';`. ⚠ typescript-eslint(consistent-type-imports): Imports Rest are only used as type. ╭─[consistent_type_imports.tsx:2:15] @@ -424,6 +434,7 @@ source: crates/oxc_linter/src/tester.rs · ────── 3 │ type T = A; ╰──── + help: Replace `type A` with `A`. ⚠ typescript-eslint(consistent-type-imports): Imports A are only used as type. ╭─[consistent_type_imports.tsx:2:15] diff --git a/crates/oxc_linter/src/snapshots/escape_case.snap b/crates/oxc_linter/src/snapshots/escape_case.snap index e4d3764de19c5..2d57ac5467eb4 100644 --- a/crates/oxc_linter/src/snapshots/escape_case.snap +++ b/crates/oxc_linter/src/snapshots/escape_case.snap @@ -6,45 +6,53 @@ source: crates/oxc_linter/src/tester.rs 1 │ const foo = "\xAab\xaab\xAAb\uAaAab\uaaaab\uAAAAb\u{AaAa}b\u{aaaa}b\u{AAAA}"; · ──────────────────────────────────────────────────────────────── ╰──── + help: Replace `"\xAab\xaab\xAAb\uAaAab\uaaaab\uAAAAb\u{AaAa}b\u{aaaa}b\u{AAAA}"` with `"\xAAb\xAAb\xAAb\uAAAAb\uAAAAb\uAAAAb\u{AAAA}b\u{AAAA}b\u{AAAA}"`. ⚠ eslint-plugin-unicorn(escape-case): Use uppercase characters for the value of the escape sequence. ╭─[escape_case.tsx:1:14] 1 │ const foo = `\xAab\xaab\xAA${foo}\uAaAab\uaaaab\uAAAAb\u{AaAa}${foo}\u{aaaa}b\u{AAAA}`; · ────────────── ╰──── + help: Replace `\xAab\xaab\xAA` with `\xAAb\xAAb\xAA`. ⚠ eslint-plugin-unicorn(escape-case): Use uppercase characters for the value of the escape sequence. ╭─[escape_case.tsx:1:34] 1 │ const foo = `\xAab\xaab\xAA${foo}\uAaAab\uaaaab\uAAAAb\u{AaAa}${foo}\u{aaaa}b\u{AAAA}`; · ───────────────────────────── ╰──── + help: Replace `\uAaAab\uaaaab\uAAAAb\u{AaAa}` with `\uAAAAb\uAAAAb\uAAAAb\u{AAAA}`. ⚠ eslint-plugin-unicorn(escape-case): Use uppercase characters for the value of the escape sequence. ╭─[escape_case.tsx:1:69] 1 │ const foo = `\xAab\xaab\xAA${foo}\uAaAab\uaaaab\uAAAAb\u{AaAa}${foo}\u{aaaa}b\u{AAAA}`; · ───────────────── ╰──── + help: Replace `\u{aaaa}b\u{AAAA}` with `\u{AAAA}b\u{AAAA}`. ⚠ eslint-plugin-unicorn(escape-case): Use uppercase characters for the value of the escape sequence. ╭─[escape_case.tsx:1:14] 1 │ const foo = `\ud834${foo}\ud834${foo}\ud834`; · ────── ╰──── + help: Replace `\ud834` with `\uD834`. ⚠ eslint-plugin-unicorn(escape-case): Use uppercase characters for the value of the escape sequence. ╭─[escape_case.tsx:1:26] 1 │ const foo = `\ud834${foo}\ud834${foo}\ud834`; · ────── ╰──── + help: Replace `\ud834` with `\uD834`. ⚠ eslint-plugin-unicorn(escape-case): Use uppercase characters for the value of the escape sequence. ╭─[escape_case.tsx:1:38] 1 │ const foo = `\ud834${foo}\ud834${foo}\ud834`; · ────── ╰──── + help: Replace `\ud834` with `\uD834`. ⚠ eslint-plugin-unicorn(escape-case): Use uppercase characters for the value of the escape sequence. ╭─[escape_case.tsx:1:24] 1 │ const foo = new RegExp("/\u{1d306}/", "u") · ───────────── ╰──── + help: Replace `"/\u{1d306}/"` with `"/\u{1D306}/"`. diff --git a/crates/oxc_linter/src/snapshots/explicit_length_check.snap b/crates/oxc_linter/src/snapshots/explicit_length_check.snap index 308a06cc9450c..e3fb1306b7791 100644 --- a/crates/oxc_linter/src/snapshots/explicit_length_check.snap +++ b/crates/oxc_linter/src/snapshots/explicit_length_check.snap @@ -20,6 +20,7 @@ source: crates/oxc_linter/src/tester.rs 1 │ bar(!foo.length || foo.length) · ─────────── ╰──── + help: Replace `!foo.length` with `foo.length === 0`. ⚠ eslint-plugin-unicorn(explicit-length-check): Use `.length > 0` when checking length is not zero. ╭─[explicit_length_check.tsx:1:20] @@ -68,3 +69,4 @@ source: crates/oxc_linter/src/tester.rs 1 │ let foo = arr.length ? 'non-empty' : 'empty' · ────────── ╰──── + help: Replace `arr.length` with `arr.length > 0`. diff --git a/crates/oxc_linter/src/snapshots/no_debugger.snap b/crates/oxc_linter/src/snapshots/no_debugger.snap index 2a2410922a3e6..103c585aa8d05 100644 --- a/crates/oxc_linter/src/snapshots/no_debugger.snap +++ b/crates/oxc_linter/src/snapshots/no_debugger.snap @@ -6,3 +6,4 @@ source: crates/oxc_linter/src/tester.rs 1 │ if (foo) debugger · ──────── ╰──── + help: Replace `debugger` with `{}`. diff --git a/crates/oxc_linter/src/snapshots/no_focused_tests.snap b/crates/oxc_linter/src/snapshots/no_focused_tests.snap index 92a25a26cbfc4..cad6eace87d0b 100644 --- a/crates/oxc_linter/src/snapshots/no_focused_tests.snap +++ b/crates/oxc_linter/src/snapshots/no_focused_tests.snap @@ -134,6 +134,7 @@ source: crates/oxc_linter/src/tester.rs · ──── 4 │ ╰──── + help: Delete this code. ⚠ eslint-plugin-jest(no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:10] diff --git a/crates/oxc_linter/src/snapshots/no_hex_escape.snap b/crates/oxc_linter/src/snapshots/no_hex_escape.snap index f2ac87079440f..e607ab2b13a21 100644 --- a/crates/oxc_linter/src/snapshots/no_hex_escape.snap +++ b/crates/oxc_linter/src/snapshots/no_hex_escape.snap @@ -6,9 +6,12 @@ source: crates/oxc_linter/src/tester.rs 1 │ const foo = "\xb1" · ────── ╰──── + help: Replace `"\xb1"` with `'\u00b1'`. ⚠ eslint-plugin-unicorn(no-hex-escape): Use Unicode escapes instead of hexadecimal escapes. ╭─[no_hex_escape.tsx:1:8] 1 │ wrapId(/(^|[])(?:алг|арг(?:\x20*рез)?|ввод|ВКЛЮЧИТЬ|вс[её]|выбор|вывод|выход|дано|для|до|дс|если|иначе|исп|использовать|кон(?:(?:\x20+|_)исп)?|кц(?:(?:\x20+|_)при)?|надо|нач|нс|нц|от|пауза|пока|при|раза?|рез|стоп|таб|то|утв|шаг)(?=[]|$)/.source) · ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ╰──── + help: Replace `/(^|[])(?:алг|арг(?:\x20*рез)?|ввод|ВКЛЮЧИТЬ|вс[её]|выбор|вывод|выход|дано|для|до|дс|если|иначе|исп|использовать|кон(?:(?:\x20+|_)исп)?|кц(?:(?:\x20+|_)при)?|надо|нач|нс|нц|от|пауза|пока|при|раза?|рез|стоп|таб|то|утв|шаг)(?=[]|$)/...` with `/(^|[])(?:алг|арг(?:\u0020*рез)?|ввод|ВКЛЮЧИТЬ|вс[её]|выбор|вывод|выход|дано|для|до|дс|если|иначе|исп|использовать| + кон(?:(?:\u0020+|_)исп)?|кц(?:(?:\u0020+|_)при)?|надо|нач|нс|нц|от|пауза|пока|при|раза?|рез|стоп|таб|то|утв|шаг)(?=[]|$)/...`. diff --git a/crates/oxc_linter/src/snapshots/no_new_statics.snap b/crates/oxc_linter/src/snapshots/no_new_statics.snap index 86ebd818940c9..62c87d560a0e8 100644 --- a/crates/oxc_linter/src/snapshots/no_new_statics.snap +++ b/crates/oxc_linter/src/snapshots/no_new_statics.snap @@ -6,36 +6,42 @@ source: crates/oxc_linter/src/tester.rs 1 │ new Promise.resolve() · ─── ╰──── + help: Delete this code. ⚠ eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.reject` ╭─[no_new_statics.tsx:1:1] 1 │ new Promise.reject() · ─── ╰──── + help: Delete this code. ⚠ eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.all` ╭─[no_new_statics.tsx:1:1] 1 │ new Promise.all() · ─── ╰──── + help: Delete this code. ⚠ eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.allSettled` ╭─[no_new_statics.tsx:1:1] 1 │ new Promise.allSettled() · ─── ╰──── + help: Delete this code. ⚠ eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.any` ╭─[no_new_statics.tsx:1:1] 1 │ new Promise.any() · ─── ╰──── + help: Delete this code. ⚠ eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.race` ╭─[no_new_statics.tsx:1:1] 1 │ new Promise.race() · ─── ╰──── + help: Delete this code. ⚠ eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.resolve` ╭─[no_new_statics.tsx:3:13] @@ -44,3 +50,4 @@ source: crates/oxc_linter/src/tester.rs · ─── 4 │ } ╰──── + help: Delete this code. diff --git a/crates/oxc_linter/src/snapshots/no_test_prefixes.snap b/crates/oxc_linter/src/snapshots/no_test_prefixes.snap index f2369626b8078..34ce5497251b0 100644 --- a/crates/oxc_linter/src/snapshots/no_test_prefixes.snap +++ b/crates/oxc_linter/src/snapshots/no_test_prefixes.snap @@ -1,66 +1,75 @@ --- source: crates/oxc_linter/src/tester.rs -assertion_line: 216 --- ⚠ eslint-plugin-jest(no-test-prefixes): Use "describe.only" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ fdescribe('foo', function () {}) · ───────── ╰──── + help: Replace `fdescribe` with `describe.only`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "describe.skip.each" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xdescribe.each([])('foo', function () {}) · ────────────── ╰──── + help: Replace `xdescribe.each` with `describe.skip.each`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "it.only" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ fit('foo', function () {}) · ─── ╰──── + help: Replace `fit` with `it.only`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "describe.skip" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xdescribe('foo', function () {}) · ───────── ╰──── + help: Replace `xdescribe` with `describe.skip`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "it.skip" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xit('foo', function () {}) · ─── ╰──── + help: Replace `xit` with `it.skip`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "test.skip" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xtest('foo', function () {}) · ───── ╰──── + help: Replace `xtest` with `test.skip`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "it.skip.each" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xit.each``('foo', function () {}) · ──────── ╰──── + help: Replace `xit.each` with `it.skip.each`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "test.skip.each" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xtest.each``('foo', function () {}) · ────────── ╰──── + help: Replace `xtest.each` with `test.skip.each`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "it.skip.each" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xit.each([])('foo', function () {}) · ──────── ╰──── + help: Replace `xit.each` with `it.skip.each`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "test.skip.each" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xtest.each([])('foo', function () {}) · ────────── ╰──── + help: Replace `xtest.each` with `test.skip.each`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "it.skip" instead. ╭─[no_test_prefixes.tsx:3:17] @@ -69,6 +78,7 @@ assertion_line: 216 · ─── 4 │ ╰──── + help: Replace `xit` with `it.skip`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "it.skip" instead. ╭─[no_test_prefixes.tsx:3:17] @@ -77,6 +87,7 @@ assertion_line: 216 · ──────── 4 │ ╰──── + help: Replace `skipThis` with `it.skip`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "it.only" instead. ╭─[no_test_prefixes.tsx:3:17] @@ -85,63 +96,74 @@ assertion_line: 216 · ──────── 4 │ ╰──── + help: Replace `onlyThis` with `it.only`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "describe.only" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ fdescribe("foo", function () {}) · ───────── ╰──── + help: Replace `fdescribe` with `describe.only`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "describe.skip.each" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xdescribe.each([])("foo", function () {}) · ────────────── ╰──── + help: Replace `xdescribe.each` with `describe.skip.each`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "it.only" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ fit("foo", function () {}) · ─── ╰──── + help: Replace `fit` with `it.only`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "describe.skip" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xdescribe("foo", function () {}) · ───────── ╰──── + help: Replace `xdescribe` with `describe.skip`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "it.skip" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xit("foo", function () {}) · ─── ╰──── + help: Replace `xit` with `it.skip`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "test.skip" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xtest("foo", function () {}) · ───── ╰──── + help: Replace `xtest` with `test.skip`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "it.skip.each" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xit.each``("foo", function () {}) · ──────── ╰──── + help: Replace `xit.each` with `it.skip.each`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "test.skip.each" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xtest.each``("foo", function () {}) · ────────── ╰──── + help: Replace `xtest.each` with `test.skip.each`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "it.skip.each" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xit.each([])("foo", function () {}) · ──────── ╰──── + help: Replace `xit.each` with `it.skip.each`. ⚠ eslint-plugin-jest(no-test-prefixes): Use "test.skip.each" instead. ╭─[no_test_prefixes.tsx:1:1] 1 │ xtest.each([])("foo", function () {}) · ────────── ╰──── + help: Replace `xtest.each` with `test.skip.each`. diff --git a/crates/oxc_linter/src/snapshots/no_useless_escape.snap b/crates/oxc_linter/src/snapshots/no_useless_escape.snap index 5bc1093baa3e1..d21f0bd2b9c82 100644 --- a/crates/oxc_linter/src/snapshots/no_useless_escape.snap +++ b/crates/oxc_linter/src/snapshots/no_useless_escape.snap @@ -6,282 +6,329 @@ source: crates/oxc_linter/src/tester.rs 1 │ var foo = /\#/; · ── ╰──── + help: Replace `\#` with `#`. ⚠ eslint(no-useless-escape): Unnecessary escape character ';' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = /\;/; · ── ╰──── + help: Replace `\;` with `;`. ⚠ eslint(no-useless-escape): Unnecessary escape character '\'' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = "\'"; · ── ╰──── + help: Replace `\'` with `'`. ⚠ eslint(no-useless-escape): Unnecessary escape character '#' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = "\#/"; · ── ╰──── + help: Replace `\#` with `#`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'a' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = "\a" · ── ╰──── + help: Replace `\a` with `a`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'B' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = "\B"; · ── ╰──── + help: Replace `\B` with `B`. ⚠ eslint(no-useless-escape): Unnecessary escape character '@' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = "\@"; · ── ╰──── + help: Replace `\@` with `@`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'a' ╭─[no_useless_escape.tsx:1:16] 1 │ var foo = "foo \a bar"; · ── ╰──── + help: Replace `\a` with `a`. ⚠ eslint(no-useless-escape): Unnecessary escape character '"' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = '\"'; · ── ╰──── + help: Replace `\"` with `"`. ⚠ eslint(no-useless-escape): Unnecessary escape character '#' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = '\#'; · ── ╰──── + help: Replace `\#` with `#`. ⚠ eslint(no-useless-escape): Unnecessary escape character '$' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = '\$'; · ── ╰──── + help: Replace `\$` with `$`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'p' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = '\p'; · ── ╰──── + help: Replace `\p` with `p`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'p' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = '\p\a\@'; · ── ╰──── + help: Replace `\p` with `p`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'a' ╭─[no_useless_escape.tsx:1:14] 1 │ var foo = '\p\a\@'; · ── ╰──── + help: Replace `\a` with `a`. ⚠ eslint(no-useless-escape): Unnecessary escape character '@' ╭─[no_useless_escape.tsx:1:16] 1 │ var foo = '\p\a\@'; · ── ╰──── + help: Replace `\@` with `@`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'd' ╭─[no_useless_escape.tsx:1:13] 1 │ · ── ╰──── + help: Replace `\d` with `d`. ⚠ eslint(no-useless-escape): Unnecessary escape character '`' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = '\`'; · ── ╰──── + help: Replace `\`` with ```. ⚠ eslint(no-useless-escape): Unnecessary escape character '"' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = `\"`; · ── ╰──── + help: Replace `\"` with `"`. ⚠ eslint(no-useless-escape): Unnecessary escape character '\'' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = `\'`; · ── ╰──── + help: Replace `\'` with `'`. ⚠ eslint(no-useless-escape): Unnecessary escape character '#' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = `\#`; · ── ╰──── + help: Replace `\#` with `#`. ⚠ eslint(no-useless-escape): Unnecessary escape character '`' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = '\`foo\`'; · ── ╰──── + help: Replace `\`` with ```. ⚠ eslint(no-useless-escape): Unnecessary escape character '`' ╭─[no_useless_escape.tsx:1:17] 1 │ var foo = '\`foo\`'; · ── ╰──── + help: Replace `\`` with ```. ⚠ eslint(no-useless-escape): Unnecessary escape character '"' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = `\"${foo}\"`; · ── ╰──── + help: Replace `\"` with `"`. ⚠ eslint(no-useless-escape): Unnecessary escape character '"' ╭─[no_useless_escape.tsx:1:20] 1 │ var foo = `\"${foo}\"`; · ── ╰──── + help: Replace `\"` with `"`. ⚠ eslint(no-useless-escape): Unnecessary escape character '\'' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = `\'${foo}\'`; · ── ╰──── + help: Replace `\'` with `'`. ⚠ eslint(no-useless-escape): Unnecessary escape character '\'' ╭─[no_useless_escape.tsx:1:20] 1 │ var foo = `\'${foo}\'`; · ── ╰──── + help: Replace `\'` with `'`. ⚠ eslint(no-useless-escape): Unnecessary escape character '#' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = `\#${foo}`; · ── ╰──── + help: Replace `\#` with `#`. ⚠ eslint(no-useless-escape): Unnecessary escape character ' ' ╭─[no_useless_escape.tsx:1:12] 1 │ let foo = '\ '; · ── ╰──── + help: Replace `\ ` with ` `. ⚠ eslint(no-useless-escape): Unnecessary escape character ' ' ╭─[no_useless_escape.tsx:1:12] 1 │ let foo = /\ /; · ── ╰──── + help: Replace `\ ` with ` `. ⚠ eslint(no-useless-escape): Unnecessary escape character '$' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = `\$\{{${foo}`; · ── ╰──── + help: Replace `\$` with `$`. ⚠ eslint(no-useless-escape): Unnecessary escape character '$' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = `\$a${foo}`; · ── ╰──── + help: Replace `\$` with `$`. ⚠ eslint(no-useless-escape): Unnecessary escape character '{' ╭─[no_useless_escape.tsx:1:13] 1 │ var foo = `a\{{${foo}`; · ── ╰──── + help: Replace `\{` with `{`. ⚠ eslint(no-useless-escape): Unnecessary escape character '-' ╭─[no_useless_escape.tsx:1:15] 1 │ var foo = /[ab\-]/ · ── ╰──── + help: Replace `\-` with `-`. ⚠ eslint(no-useless-escape): Unnecessary escape character '-' ╭─[no_useless_escape.tsx:1:13] 1 │ var foo = /[\-ab]/ · ── ╰──── + help: Replace `\-` with `-`. ⚠ eslint(no-useless-escape): Unnecessary escape character '?' ╭─[no_useless_escape.tsx:1:15] 1 │ var foo = /[ab\?]/ · ── ╰──── + help: Replace `\?` with `?`. ⚠ eslint(no-useless-escape): Unnecessary escape character '.' ╭─[no_useless_escape.tsx:1:15] 1 │ var foo = /[ab\.]/ · ── ╰──── + help: Replace `\.` with `.`. ⚠ eslint(no-useless-escape): Unnecessary escape character '|' ╭─[no_useless_escape.tsx:1:14] 1 │ var foo = /[a\|b]/ · ── ╰──── + help: Replace `\|` with `|`. ⚠ eslint(no-useless-escape): Unnecessary escape character '-' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = /\-/ · ── ╰──── + help: Replace `\-` with `-`. ⚠ eslint(no-useless-escape): Unnecessary escape character '-' ╭─[no_useless_escape.tsx:1:13] 1 │ var foo = /[\-]/ · ── ╰──── + help: Replace `\-` with `-`. ⚠ eslint(no-useless-escape): Unnecessary escape character '$' ╭─[no_useless_escape.tsx:1:15] 1 │ var foo = /[ab\$]/ · ── ╰──── + help: Replace `\$` with `$`. ⚠ eslint(no-useless-escape): Unnecessary escape character '(' ╭─[no_useless_escape.tsx:1:13] 1 │ var foo = /[\(paren]/ · ── ╰──── + help: Replace `\(` with `(`. ⚠ eslint(no-useless-escape): Unnecessary escape character '[' ╭─[no_useless_escape.tsx:1:13] 1 │ var foo = /[\[]/ · ── ╰──── + help: Replace `\[` with `[`. ⚠ eslint(no-useless-escape): Unnecessary escape character '/' ╭─[no_useless_escape.tsx:1:13] 1 │ var foo = /[\/]/ · ── ╰──── + help: Replace `\/` with `/`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'B' ╭─[no_useless_escape.tsx:1:13] 1 │ var foo = /[\B]/ · ── ╰──── + help: Replace `\B` with `B`. ⚠ eslint(no-useless-escape): Unnecessary escape character '-' ╭─[no_useless_escape.tsx:1:16] 1 │ var foo = /[a][\-b]/ · ── ╰──── + help: Replace `\-` with `-`. ⚠ eslint(no-useless-escape): Unnecessary escape character '-' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = /\-[]/ · ── ╰──── + help: Replace `\-` with `-`. ⚠ eslint(no-useless-escape): Unnecessary escape character '^' ╭─[no_useless_escape.tsx:1:14] 1 │ var foo = /[a\^]/ · ── ╰──── + help: Replace `\^` with `^`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'e' ╭─[no_useless_escape.tsx:2:22] @@ -289,6 +336,7 @@ source: crates/oxc_linter/src/tester.rs 2 │ literal with useless \escape` · ── ╰──── + help: Replace `\e` with `e`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'e' ╭─[no_useless_escape.tsx:2:22] @@ -296,6 +344,7 @@ source: crates/oxc_linter/src/tester.rs 2 │ literal with useless \escape` · ── ╰──── + help: Replace `\e` with `e`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'e' ╭─[no_useless_escape.tsx:2:13] @@ -303,6 +352,7 @@ source: crates/oxc_linter/src/tester.rs 2 │ and useless \escape` · ── ╰──── + help: Replace `\e` with `e`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'e' ╭─[no_useless_escape.tsx:2:13] @@ -310,6 +360,7 @@ source: crates/oxc_linter/src/tester.rs 2 │ and useless \escape` · ── ╰──── + help: Replace `\e` with `e`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'a' ╭─[no_useless_escape.tsx:4:1] @@ -317,6 +368,7 @@ source: crates/oxc_linter/src/tester.rs 4 │ \and useless escape` · ── ╰──── + help: Replace `\a` with `a`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'a' ╭─[no_useless_escape.tsx:4:1] @@ -324,57 +376,67 @@ source: crates/oxc_linter/src/tester.rs 4 │ \and useless escape` · ── ╰──── + help: Replace `\a` with `a`. ⚠ eslint(no-useless-escape): Unnecessary escape character 'a' ╭─[no_useless_escape.tsx:1:2] 1 │ `\a``` · ── ╰──── + help: Replace `\a` with `a`. ⚠ eslint(no-useless-escape): Unnecessary escape character '(' ╭─[no_useless_escape.tsx:1:12] 1 │ var foo = /\(([^\)\(]+)\)$|\(([^\)\)]+)\)$/; · ─── ╰──── + help: Replace `\(` with `(`. ⚠ eslint(no-useless-escape): Unnecessary escape character ')' ╭─[no_useless_escape.tsx:1:19] 1 │ var foo = /\(([^\)\(]+)\)$|\(([^\)\)]+)\)$/; · ─── ╰──── + help: Replace `\)` with `)`. ⚠ eslint(no-useless-escape): Unnecessary escape character '(' ╭─[no_useless_escape.tsx:1:23] 1 │ var foo = /\(([^\)\(]+)\)$|\(([^\)\)]+)\)$/; · ─── ╰──── + help: Replace `\(` with `(`. ⚠ eslint(no-useless-escape): Unnecessary escape character ')' ╭─[no_useless_escape.tsx:1:30] 1 │ var foo = /\(([^\)\(]+)\)$|\(([^\)\)]+)\)$/; · ─── ╰──── + help: Replace `\)` with `)`. ⚠ eslint(no-useless-escape): Unnecessary escape character ')' ╭─[no_useless_escape.tsx:1:41] 1 │ var foo = /\(([^\)\(]+)\)$|\(([^\)\)]+)\)$/; · ── ╰──── + help: Replace `\)` with `)`. ⚠ eslint(no-useless-escape): Unnecessary escape character ')' ╭─[no_useless_escape.tsx:1:43] 1 │ var foo = /\(([^\)\(]+)\)$|\(([^\)\)]+)\)$/; · ── ╰──── + help: Replace `\)` with `)`. ⚠ eslint(no-useless-escape): Unnecessary escape character '\u{85}' ╭─[no_useless_escape.tsx:1:40] 1 │ var stringLiteralWithNextLine = "line 1\…line 2"; · ─ ╰──── + help: Replace `\…` with `…`. ⚠ eslint(no-useless-escape): Unnecessary escape character '\u{85}' ╭─[no_useless_escape.tsx:1:40] 1 │ var stringLiteralWithNextLine = `line 1\…line 2`; · ─ ╰──── + help: Replace `\…` with `…`. diff --git a/crates/oxc_linter/src/snapshots/prefer_prototype_methods.snap b/crates/oxc_linter/src/snapshots/prefer_prototype_methods.snap index b415bff521a23..acfdc0d70ae84 100644 --- a/crates/oxc_linter/src/snapshots/prefer_prototype_methods.snap +++ b/crates/oxc_linter/src/snapshots/prefer_prototype_methods.snap @@ -6,135 +6,158 @@ source: crates/oxc_linter/src/tester.rs 1 │ const foo = [].push.apply(bar, elements); · ─────── ╰──── + help: Replace `[]` with `Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Array.prototype.slice`. ╭─[prefer_prototype_methods.tsx:1:13] 1 │ const foo = [].slice.call(bar); · ──────── ╰──── + help: Replace `[]` with `Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Object.prototype.toString`. ╭─[prefer_prototype_methods.tsx:1:13] 1 │ const foo = {}.toString.call(bar); · ─────────── ╰──── + help: Replace `{}` with `Object.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Object.prototype.hasOwnProperty`. ╭─[prefer_prototype_methods.tsx:1:13] 1 │ const foo = {}.hasOwnProperty.call(bar, "property"); · ───────────────── ╰──── + help: Replace `{}` with `Object.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Object.prototype.propertyIsEnumerable`. ╭─[prefer_prototype_methods.tsx:1:13] 1 │ const foo = {}.propertyIsEnumerable.call(bar, "property"); · ─────────────────────── ╰──── + help: Replace `{}` with `Object.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Array.prototype.forEach`. ╭─[prefer_prototype_methods.tsx:1:1] 1 │ [].forEach.call(foo, () => {}) · ────────── ╰──── + help: Replace `[]` with `Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Array.prototype.push`. ╭─[prefer_prototype_methods.tsx:1:14] 1 │ const push = [].push.bind(foo) · ─────── ╰──── + help: Replace `[]` with `Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using method from `Array.prototype`. ╭─[prefer_prototype_methods.tsx:1:13] 1 │ const foo = [][method].call(foo) · ────────── ╰──── + help: Replace `[]` with `Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using method from `Array.prototype`. ╭─[prefer_prototype_methods.tsx:1:45] 1 │ const method = "realMethodName";const foo = [][method].call(foo) · ────────── ╰──── + help: Replace `[]` with `Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Array.prototype.slice`. ╭─[prefer_prototype_methods.tsx:1:29] 1 │ const array = Reflect.apply([].slice, foo, []) · ──────── ╰──── + help: Replace `[]` with `Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Array.prototype.bar`. ╭─[prefer_prototype_methods.tsx:1:15] 1 │ Reflect.apply([].bar, baz, []) · ────── ╰──── + help: Replace `[]` with `Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Object.prototype.toString`. ╭─[prefer_prototype_methods.tsx:1:13] 1 │ const foo = ({}).toString.call(bar); · ───────────── ╰──── + help: Replace `{}` with `Object.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Object.prototype.toString`. ╭─[prefer_prototype_methods.tsx:1:14] 1 │ const foo = ({}.toString).call(bar); · ─────────── ╰──── + help: Replace `{}` with `Object.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Object.prototype.toString`. ╭─[prefer_prototype_methods.tsx:1:14] 1 │ const foo = ({}.toString.call)(bar); · ─────────── ╰──── + help: Replace `{}` with `Object.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Array.prototype.slice`. ╭─[prefer_prototype_methods.tsx:1:22] 1 │ function foo(){return[].slice.call(bar);} · ──────── ╰──── + help: Replace `[]` with ` Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Object.prototype.toString`. ╭─[prefer_prototype_methods.tsx:1:22] 1 │ function foo(){return{}.toString.call(bar)} · ─────────── ╰──── + help: Replace `{}` with ` Object.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using method from `Object.prototype`. ╭─[prefer_prototype_methods.tsx:1:15] 1 │ Reflect.apply({}[Symbol()], baz, []) · ──────────── ╰──── + help: Replace `{}` with `Object.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using method from `Object.prototype`. ╭─[prefer_prototype_methods.tsx:1:15] 1 │ Reflect.apply({}[Symbol("symbol description")], baz, []) · ──────────────────────────────── ╰──── + help: Replace `{}` with `Object.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using method from `Array.prototype`. ╭─[prefer_prototype_methods.tsx:1:15] 1 │ Reflect.apply([][Symbol()], baz, []) · ──────────── ╰──── + help: Replace `[]` with `Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using method from `Object.prototype`. ╭─[prefer_prototype_methods.tsx:1:15] 1 │ Reflect.apply({}[Symbol("symbol description")], baz, []) · ──────────────────────────────── ╰──── + help: Replace `{}` with `Object.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using method from `Array.prototype`. ╭─[prefer_prototype_methods.tsx:1:1] 1 │ [][Symbol.iterator].call(foo) · ─────────────────── ╰──── + help: Replace `[]` with `Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Array.prototype.at`. ╭─[prefer_prototype_methods.tsx:1:13] 1 │ const foo = [].at.call(bar) · ───── ╰──── + help: Replace `[]` with `Array.prototype`. ⚠ eslint-plugin-unicorn(prefer-prototype-methods): Prefer using `Array.prototype.findLast`. ╭─[prefer_prototype_methods.tsx:1:13] 1 │ const foo = [].findLast.call(bar) · ─────────── ╰──── + help: Replace `[]` with `Array.prototype`. diff --git a/crates/oxc_linter/src/snapshots/prefer_to_be.snap b/crates/oxc_linter/src/snapshots/prefer_to_be.snap index 44d0b76fd228e..b3034a0dd457a 100644 --- a/crates/oxc_linter/src/snapshots/prefer_to_be.snap +++ b/crates/oxc_linter/src/snapshots/prefer_to_be.snap @@ -6,291 +6,340 @@ source: crates/oxc_linter/src/tester.rs 1 │ expect(value).toEqual("my string"); · ─────── ╰──── + help: Replace `toEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:15] 1 │ expect(value).toStrictEqual("my string"); · ───────────── ╰──── + help: Replace `toStrictEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:15] 1 │ expect(value).toStrictEqual(1); · ───────────── ╰──── + help: Replace `toStrictEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:15] 1 │ expect(value).toStrictEqual(1,); · ───────────── ╰──── + help: Replace `toStrictEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:15] 1 │ expect(value).toStrictEqual(-1); · ───────────── ╰──── + help: Replace `toStrictEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:15] 1 │ expect(value).toEqual(`my string`); · ─────── ╰──── + help: Replace `toEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:15] 1 │ expect(value)["toEqual"](`my string`); · ───────── ╰──── + help: Replace `"toEqual"` with `"toBe"`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:15] 1 │ expect(value).toStrictEqual(`my ${string}`); · ───────────── ╰──── + help: Replace `toStrictEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:32] 1 │ expect(loadMessage()).resolves.toStrictEqual("hello world"); · ───────────── ╰──── + help: Replace `toStrictEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:32] 1 │ expect(loadMessage()).resolves["toStrictEqual"]("hello world"); · ─────────────── ╰──── + help: Replace `"toStrictEqual"` with `"toBe"`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:35] 1 │ expect(loadMessage())["resolves"].toStrictEqual("hello world"); · ───────────── ╰──── + help: Replace `toStrictEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:32] 1 │ expect(loadMessage()).resolves.toStrictEqual(false); · ───────────── ╰──── + help: Replace `toStrictEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNull` instead. ╭─[prefer_to_be.tsx:1:14] 1 │ expect(null).toBe(null); · ──── ╰──── + help: Replace `toBe(null)` with `toBeNull()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNull` instead. ╭─[prefer_to_be.tsx:1:14] 1 │ expect(null).toEqual(null); · ─────── ╰──── + help: Replace `toEqual(null)` with `toBeNull()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNull` instead. ╭─[prefer_to_be.tsx:1:14] 1 │ expect(null).toEqual(null,); · ─────── ╰──── + help: Replace `toEqual(null,)` with `toBeNull()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNull` instead. ╭─[prefer_to_be.tsx:1:14] 1 │ expect(null).toStrictEqual(null); · ───────────── ╰──── + help: Replace `toStrictEqual(null)` with `toBeNull()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNull` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toBe(null); · ──── ╰──── + help: Replace `toBe(null)` with `toBeNull()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNull` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not["toBe"](null); · ────── ╰──── + help: Replace `"toBe"](null)` with `"toBeNull"]()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNull` instead. ╭─[prefer_to_be.tsx:1:27] 1 │ expect("a string")["not"]["toBe"](null); · ────── ╰──── + help: Replace `"toBe"](null)` with `"toBeNull"]()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNull` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toEqual(null); · ─────── ╰──── + help: Replace `toEqual(null)` with `toBeNull()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNull` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toStrictEqual(null); · ───────────── ╰──── + help: Replace `toStrictEqual(null)` with `toBeNull()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeUndefined` instead. ╭─[prefer_to_be.tsx:1:19] 1 │ expect(undefined).toBe(undefined); · ──── ╰──── + help: Replace `toBe(undefined)` with `toBeUndefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeUndefined` instead. ╭─[prefer_to_be.tsx:1:19] 1 │ expect(undefined).toEqual(undefined); · ─────── ╰──── + help: Replace `toEqual(undefined)` with `toBeUndefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeUndefined` instead. ╭─[prefer_to_be.tsx:1:19] 1 │ expect(undefined).toStrictEqual(undefined); · ───────────── ╰──── + help: Replace `toStrictEqual(undefined)` with `toBeUndefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeDefined` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toBe(undefined); · ──── ╰──── + help: Replace `not.toBe(undefined)` with `toBeDefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeDefined` instead. ╭─[prefer_to_be.tsx:1:32] 1 │ expect("a string").rejects.not.toBe(undefined); · ──── ╰──── + help: Replace `not.toBe(undefined)` with `toBeDefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeDefined` instead. ╭─[prefer_to_be.tsx:1:32] 1 │ expect("a string").rejects.not["toBe"](undefined); · ────── ╰──── + help: Replace `.not["toBe"](undefined)` with `["toBeDefined"]()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeDefined` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toEqual(undefined); · ─────── ╰──── + help: Replace `not.toEqual(undefined)` with `toBeDefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeDefined` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toStrictEqual(undefined); · ───────────── ╰──── + help: Replace `not.toStrictEqual(undefined)` with `toBeDefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNaN` instead. ╭─[prefer_to_be.tsx:1:13] 1 │ expect(NaN).toBe(NaN); · ──── ╰──── + help: Replace `toBe(NaN)` with `toBeNaN()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNaN` instead. ╭─[prefer_to_be.tsx:1:13] 1 │ expect(NaN).toEqual(NaN); · ─────── ╰──── + help: Replace `toEqual(NaN)` with `toBeNaN()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNaN` instead. ╭─[prefer_to_be.tsx:1:13] 1 │ expect(NaN).toStrictEqual(NaN); · ───────────── ╰──── + help: Replace `toStrictEqual(NaN)` with `toBeNaN()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNaN` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toBe(NaN); · ──── ╰──── + help: Replace `toBe(NaN)` with `toBeNaN()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNaN` instead. ╭─[prefer_to_be.tsx:1:32] 1 │ expect("a string").rejects.not.toBe(NaN); · ──── ╰──── + help: Replace `toBe(NaN)` with `toBeNaN()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNaN` instead. ╭─[prefer_to_be.tsx:1:35] 1 │ expect("a string")["rejects"].not.toBe(NaN); · ──── ╰──── + help: Replace `toBe(NaN)` with `toBeNaN()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNaN` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toEqual(NaN); · ─────── ╰──── + help: Replace `toEqual(NaN)` with `toBeNaN()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNaN` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toStrictEqual(NaN); · ───────────── ╰──── + help: Replace `toStrictEqual(NaN)` with `toBeNaN()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeUndefined` instead. ╭─[prefer_to_be.tsx:1:23] 1 │ expect(undefined).not.toBeDefined(); · ─────────── ╰──── + help: Replace `not.toBeDefined()` with `toBeUndefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeUndefined` instead. ╭─[prefer_to_be.tsx:1:32] 1 │ expect(undefined).resolves.not.toBeDefined(); · ─────────── ╰──── + help: Replace `not.toBeDefined()` with `toBeUndefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeUndefined` instead. ╭─[prefer_to_be.tsx:1:28] 1 │ expect(undefined).resolves.toBe(undefined); · ──── ╰──── + help: Replace `toBe(undefined)` with `toBeUndefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeDefined` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toBeUndefined(); · ───────────── ╰──── + help: Replace `not.toBeUndefined()` with `toBeDefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeDefined` instead. ╭─[prefer_to_be.tsx:1:32] 1 │ expect("a string").rejects.not.toBeUndefined(); · ───────────── ╰──── + help: Replace `not.toBeUndefined()` with `toBeDefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:14] 1 │ expect(null).toEqual(1 as unknown as string as unknown as any); · ─────── ╰──── + help: Replace `toEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:14] 1 │ expect(null).toEqual(-1 as unknown as string as unknown as any); · ─────── ╰──── + help: Replace `toEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBe` when expecting primitive literals. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toStrictEqual("string" as number); · ───────────── ╰──── + help: Replace `toStrictEqual` with `toBe`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNull` instead. ╭─[prefer_to_be.tsx:1:14] 1 │ expect(null).toBe(null as unknown as string as unknown as any); · ──── ╰──── + help: Replace `toBe(null as unknown as string as unknown as any)` with `toBeNull()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeNull` instead. ╭─[prefer_to_be.tsx:1:24] 1 │ expect("a string").not.toEqual(null as number); · ─────── ╰──── + help: Replace `toEqual(null as number)` with `toBeNull()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeUndefined` instead. ╭─[prefer_to_be.tsx:1:19] 1 │ expect(undefined).toBe(undefined as unknown as string as any); · ──── ╰──── + help: Replace `toBe(undefined as unknown as string as any)` with `toBeUndefined()`. ⚠ eslint-plugin-jest(prefer-to-be): Use `toBeUndefined` instead. ╭─[prefer_to_be.tsx:1:20] 1 │ expect("a string").toEqual(undefined as number); · ─────── ╰──── + help: Replace `toEqual(undefined as number)` with `toBeUndefined()`. diff --git a/crates/oxc_linter/src/snapshots/prefer_to_have_length.snap b/crates/oxc_linter/src/snapshots/prefer_to_have_length.snap index b34f78c1fe6bd..f6cb16d8e4a10 100644 --- a/crates/oxc_linter/src/snapshots/prefer_to_have_length.snap +++ b/crates/oxc_linter/src/snapshots/prefer_to_have_length.snap @@ -6,57 +6,67 @@ source: crates/oxc_linter/src/tester.rs 1 │ expect(files["length"]).toBe(1); · ──── ╰──── + help: Replace `expect(files["length"]).toBe` with `expect(files).toHaveLength`. ⚠ eslint-plugin-jest(prefer-to-have-length): Suggest using `toHaveLength()`. ╭─[prefer_to_have_length.tsx:1:25] 1 │ expect(files["length"]).toBe(1,); · ──── ╰──── + help: Replace `expect(files["length"]).toBe` with `expect(files).toHaveLength`. ⚠ eslint-plugin-jest(prefer-to-have-length): Suggest using `toHaveLength()`. ╭─[prefer_to_have_length.tsx:1:32] 1 │ expect(files["length"])["not"].toBe(1); · ──── ╰──── + help: Replace `expect(files["length"])["not"].toBe` with `expect(files)["not"].toHaveLength`. ⚠ eslint-plugin-jest(prefer-to-have-length): Suggest using `toHaveLength()`. ╭─[prefer_to_have_length.tsx:1:25] 1 │ expect(files["length"])["toBe"](1); · ────── ╰──── + help: Replace `expect(files["length"])["toBe"]` with `expect(files).toHaveLength`. ⚠ eslint-plugin-jest(prefer-to-have-length): Suggest using `toHaveLength()`. ╭─[prefer_to_have_length.tsx:1:29] 1 │ expect(files["length"]).not["toBe"](1); · ────── ╰──── + help: Replace `expect(files["length"]).not["toBe"]` with `expect(files).not.toHaveLength`. ⚠ eslint-plugin-jest(prefer-to-have-length): Suggest using `toHaveLength()`. ╭─[prefer_to_have_length.tsx:1:32] 1 │ expect(files["length"])["not"]["toBe"](1); · ────── ╰──── + help: Replace `expect(files["length"])["not"]["toBe"]` with `expect(files)["not"].toHaveLength`. ⚠ eslint-plugin-jest(prefer-to-have-length): Suggest using `toHaveLength()`. ╭─[prefer_to_have_length.tsx:1:22] 1 │ expect(files.length).toBe(1); · ──── ╰──── + help: Replace `expect(files.length).toBe` with `expect(files).toHaveLength`. ⚠ eslint-plugin-jest(prefer-to-have-length): Suggest using `toHaveLength()`. ╭─[prefer_to_have_length.tsx:1:22] 1 │ expect(files.length).toEqual(1); · ─────── ╰──── + help: Replace `expect(files.length).toEqual` with `expect(files).toHaveLength`. ⚠ eslint-plugin-jest(prefer-to-have-length): Suggest using `toHaveLength()`. ╭─[prefer_to_have_length.tsx:1:22] 1 │ expect(files.length).toStrictEqual(1); · ───────────── ╰──── + help: Replace `expect(files.length).toStrictEqual` with `expect(files).toHaveLength`. ⚠ eslint-plugin-jest(prefer-to-have-length): Suggest using `toHaveLength()`. ╭─[prefer_to_have_length.tsx:1:26] 1 │ expect(files.length).not.toStrictEqual(1); · ───────────── ╰──── + help: Replace `expect(files.length).not.toStrictEqual` with `expect(files).not.toHaveLength`. diff --git a/crates/oxc_linter/src/snapshots/prefer_todo.snap b/crates/oxc_linter/src/snapshots/prefer_todo.snap index 5a78c08655324..c05d1a19f18b0 100644 --- a/crates/oxc_linter/src/snapshots/prefer_todo.snap +++ b/crates/oxc_linter/src/snapshots/prefer_todo.snap @@ -6,51 +6,60 @@ source: crates/oxc_linter/src/tester.rs 1 │ test('i need to write this test'); · ──── ╰──── + help: Replace `` with `.todo`. ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. ╭─[prefer_todo.tsx:1:1] 1 │ test('i need to write this test',); · ──── ╰──── + help: Replace `` with `.todo`. ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. ╭─[prefer_todo.tsx:1:1] 1 │ test(`i need to write this test`); · ──── ╰──── + help: Replace `` with `.todo`. ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. ╭─[prefer_todo.tsx:1:1] 1 │ it('foo', function () {}) · ───────────────────────── ╰──── + help: Replace `it('foo', function () {})` with `it.todo('foo')`. ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. ╭─[prefer_todo.tsx:1:1] 1 │ it('foo', () => {}) · ─────────────────── ╰──── + help: Replace `it('foo', () => {})` with `it.todo('foo')`. ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. ╭─[prefer_todo.tsx:1:1] 1 │ test.skip('i need to write this test', () => {}); · ──────────────────────────────────────────────── ╰──── + help: Replace `test.skip('i need to write this test', () => {})` with `test.todo('i need to write this test')`. ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. ╭─[prefer_todo.tsx:1:1] 1 │ test.skip('i need to write this test', function() {}); · ───────────────────────────────────────────────────── ╰──── + help: Replace `test.skip('i need to write this test', function() {})` with `test.todo('i need to write this test')`. ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. ╭─[prefer_todo.tsx:1:1] 1 │ test[`skip`]('i need to write this test', function() {}); · ──────────────────────────────────────────────────────── ╰──── + help: Replace `test[`skip`]('i need to write this test', function() {})` with `test['todo']('i need to write this test')`. ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. ╭─[prefer_todo.tsx:1:1] 1 │ test[`skip`]('i need to write this test', function() {}); · ──────────────────────────────────────────────────────── ╰──── + help: Replace `test[`skip`]('i need to write this test', function() {})` with `test['todo']('i need to write this test')`. diff --git a/crates/oxc_linter/src/snapshots/sort_imports.snap b/crates/oxc_linter/src/snapshots/sort_imports.snap index f60b093834d50..24dfd53125dc5 100644 --- a/crates/oxc_linter/src/snapshots/sort_imports.snap +++ b/crates/oxc_linter/src/snapshots/sort_imports.snap @@ -62,6 +62,7 @@ source: crates/oxc_linter/src/tester.rs 1 │ import {b, a, d, c} from 'foo.js'; · ─ ╰──── + help: Replace `b, a, d, c` with `a, b, c, d`. ⚠ eslint(sort-imports): Member 'a' of the import declaration should be sorted alphabetically. ╭─[sort_imports.tsx:1:12] @@ -69,12 +70,14 @@ source: crates/oxc_linter/src/tester.rs · ─ 2 │ import {e, f, g, h} from 'bar.js'; ╰──── + help: Replace `b, a, d, c` with `a, b, c, d`. ⚠ eslint(sort-imports): Member 'B' of the import declaration should be sorted alphabetically. ╭─[sort_imports.tsx:1:12] 1 │ import {a, B, c, D} from 'foo.js'; · ─ ╰──── + help: Replace `a, B, c, D` with `B, D, a, c`. ⚠ eslint(sort-imports): Member 'aaaaa' of the import declaration should be sorted alphabetically. ╭─[sort_imports.tsx:1:30] @@ -107,6 +110,17 @@ source: crates/oxc_linter/src/tester.rs · ────────── 7 │ bar, ╰──── + help: Replace `boop, + foo, + zoo, + baz as qux, + bar, + beep` with `bar, + beep, + boop, + foo, + baz as qux, + zoo`. ⚠ eslint(sort-imports): Imports should be sorted alphabetically. ╭─[sort_imports.tsx:2:13] @@ -196,3 +210,4 @@ source: crates/oxc_linter/src/tester.rs 3 │ import { c, a } from 'c'; · ─ ╰──── + help: Replace `c, a` with `a, c`. diff --git a/crates/oxc_linter/src/tester.rs b/crates/oxc_linter/src/tester.rs index 09742d8fb3957..1ad9c32e66934 100644 --- a/crates/oxc_linter/src/tester.rs +++ b/crates/oxc_linter/src/tester.rs @@ -9,8 +9,8 @@ use serde::Deserialize; use serde_json::Value; use crate::{ - rules::RULES, AllowWarnDeny, Fixer, LintOptions, LintService, LintServiceOptions, Linter, - OxlintConfig, RuleEnum, RuleWithSeverity, + fixer::FixKind, rules::RULES, AllowWarnDeny, Fixer, LintOptions, LintService, + LintServiceOptions, Linter, OxlintConfig, RuleEnum, RuleWithSeverity, }; #[derive(Eq, PartialEq)] @@ -259,7 +259,7 @@ impl Tester { let allocator = Allocator::default(); let rule = self.find_rule().read_json(rule_config.unwrap_or_default()); let options = LintOptions::default() - .with_fix(is_fix) + .with_fix(is_fix.then_some(FixKind::DangerousFix).unwrap_or_default()) .with_import_plugin(self.import_plugin) .with_jest_plugin(self.jest_plugin) .with_vitest_plugin(self.vitest_plugin) diff --git a/tasks/website/src/linter/snapshots/cli.snap b/tasks/website/src/linter/snapshots/cli.snap index cab0aa8056c8e..7df968dd6a60a 100644 --- a/tasks/website/src/linter/snapshots/cli.snap +++ b/tasks/website/src/linter/snapshots/cli.snap @@ -6,7 +6,7 @@ expression: snapshot ## Usage - **`oxlint`** \[**`-c`**=_`<./oxlintrc.json>`_\] \[**`--fix`**\] \[_`PATH`_\]... + **`oxlint`** \[**`-c`**=_`<./oxlintrc.json>`_\] \[**`--fix`**\] \[**`--fix-suggestions`**\] \[**`--fix-dangerously`**\] \[_`PATH`_\]... ## Basic Configuration - **`-c`**, **`--config`**=_`<./oxlintrc.json>`_ — @@ -74,6 +74,10 @@ Arguments: ## Fix Problems - **` --fix`** — Fix as many issues as possible. Only unfixed issues are reported in the output +- **` --fix-suggestions`** — + Apply auto-fixable suggestions. May change program behavior. +- **` --fix-dangerously`** — + Apply dangerous fixes and suggestions. diff --git a/tasks/website/src/linter/snapshots/cli_terminal.snap b/tasks/website/src/linter/snapshots/cli_terminal.snap index c8102ba9f7474..842d4cd679242 100644 --- a/tasks/website/src/linter/snapshots/cli_terminal.snap +++ b/tasks/website/src/linter/snapshots/cli_terminal.snap @@ -2,7 +2,7 @@ source: tasks/website/src/linter/cli.rs expression: snapshot --- -Usage: [-c=<./oxlintrc.json>] [--fix] [PATH]... +Usage: [-c=<./oxlintrc.json>] [--fix] [--fix-suggestions] [--fix-dangerously] [PATH]... Basic Configuration -c, --config=<./oxlintrc.json> Oxlint configuration file (experimental) @@ -45,6 +45,8 @@ Enable Plugins Fix Problems --fix Fix as many issues as possible. Only unfixed issues are reported in the output + --fix-suggestions Apply auto-fixable suggestions. May change program behavior. + --fix-dangerously Apply dangerous fixes and suggestions. Ignore Files --ignore-path=PATH Specify the file to use as your .eslintignore