use std::{
    env,
    ffi::OsStr,
    fmt::Debug,
    io::{ErrorKind, Write},
    path::{Path, PathBuf, absolute},
    sync::Arc,
    time::Instant,
};

use cow_utils::CowUtils;
use ignore::{gitignore::Gitignore, overrides::OverrideBuilder};

use oxc_diagnostics::{DiagnosticSender, DiagnosticService, GraphicalReportHandler, OxcDiagnostic};
use oxc_linter::{
    AllowWarnDeny, ConfigBuilderError, ConfigStore, ConfigStoreBuilder, ExternalLinter,
    ExternalPluginStore, InvalidFilterKind, LintFilter, LintOptions, LintRunner,
    LintServiceOptions, Linter, OxlintSuppressionFileAction, RuleTimingStore, SuppressionManager,
};

#[cfg(feature = "napi")]
use crate::js_config::JsConfigLoaderCb;
use crate::{
    cli::{
        CliRunResult, DebugOption, LintCommand, MiscOptions, ReportUnusedDirectives, WarningOptions,
    },
    config_loader::{
        CliConfigLoadError, ConfigLoadError, ConfigLoader, materialize_default_plugins,
    },
    output_formatter::{LintCommandInfo, OutputFormatter},
    walk::Walk,
};
use oxc_linter::LintIgnoreMatcher;

pub struct CliRunner {
    options: LintCommand,
    cwd: PathBuf,
    external_linter: Option<ExternalLinter>,
    /// Callback for loading JavaScript/TypeScript config files (experimental).
    /// This is only available when running via Node.js with NAPI.
    #[cfg(feature = "napi")]
    js_config_loader: Option<JsConfigLoaderCb>,
}

impl Debug for CliRunner {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut s = f.debug_struct("CliRunner");
        s.field("options", &self.options).field("cwd", &self.cwd).field(
            "external_linter",
            if self.external_linter.is_some() { &"Some(ExternalLinter)" } else { &"None" },
        );
        #[cfg(feature = "napi")]
        s.field(
            "js_config_loader",
            if self.js_config_loader.is_some() { &"Some(JsConfigLoaderCb)" } else { &"None" },
        );
        s.finish()
    }
}

impl CliRunner {
    /// # Panics
    pub fn new(options: LintCommand, external_linter: Option<ExternalLinter>) -> Self {
        Self {
            options,
            cwd: env::current_dir().expect("Failed to get current working directory"),
            external_linter,
            #[cfg(feature = "napi")]
            js_config_loader: None,
        }
    }

    /// # Panics
    pub fn run(self, stdout: &mut dyn Write) -> CliRunResult {
        let format_str = self.options.output_options.format;
        let debug_files = self.options.output_options.debug.contains(DebugOption::Files);
        let debug_timings = self.options.output_options.debug.contains(DebugOption::Timings);
        let output_formatter = OutputFormatter::new(format_str);

        let LintCommand {
            paths,
            filter,
            basic_options,
            warning_options,
            ignore_options,
            fix_options,
            enable_plugins,
            misc_options,
            disable_nested_config,
            inline_config_options,
            suppression_options,
            ..
        } = self.options;

        if basic_options.init {
            return crate::mode::run_init(&self.cwd, stdout);
        }

        let external_linter = self.external_linter.as_ref();

        let mut paths = paths;
        let provided_path_count = paths.len();
        let now = Instant::now();

        let filters = match Self::get_filters(filter) {
            Ok(filters) => filters,
            Err((result, message)) => {
                print_and_flush_stdout(stdout, &message);
                return result;
            }
        };

        let handler = if cfg!(any(test, feature = "testing")) {
            GraphicalReportHandler::new_themed(miette::GraphicalTheme::none())
        } else {
            GraphicalReportHandler::new()
        };

        let mut override_builder = None;

        if !ignore_options.no_ignore {
            let mut builder = OverrideBuilder::new(&self.cwd);

            if !ignore_options.ignore_pattern.is_empty() {
                for pattern in &ignore_options.ignore_pattern {
                    // Meaning of ignore pattern is reversed
                    // <https://docs.rs/ignore/latest/ignore/overrides/struct.OverrideBuilder.html#method.add>
                    let pattern = format!("!{pattern}");
                    builder.add(&pattern).unwrap();
                }
            }

            let builder = builder.build().unwrap();

            // The ignore crate whitelists explicit paths, but priority
            // should be given to the ignore file. Many users lint
            // automatically and pass a list of changed files explicitly.
            // To accommodate this, unless `--no-ignore` is passed,
            // pre-filter the paths.
            if !paths.is_empty() {
                let (ignore, _err) = Gitignore::new(&ignore_options.ignore_path);

                paths.retain_mut(|p| {
                    // Try to prepend cwd to all paths
                    let Ok(mut path) = absolute(self.cwd.join(&p)) else {
                        return false;
                    };

                    std::mem::swap(p, &mut path);

                    if path.is_dir() {
                        true
                    } else {
                        !(builder.matched(p, false).is_ignore()
                            || ignore.matched(path, false).is_ignore())
                    }
                });
            }

            override_builder = Some(builder);
        }

        if paths.is_empty() {
            // If explicit paths were provided, but all have been
            // filtered, return early.
            if provided_path_count > 0 {
                if debug_files {
                    return crate::mode::run_debug_files(
                        std::iter::empty::<&Path>(),
                        &self.cwd,
                        stdout,
                    );
                }

                return Self::handle_no_files_found(
                    stdout,
                    &output_formatter,
                    now,
                    None,
                    misc_options.no_error_on_unmatched_pattern,
                );
            }

            paths.push(self.cwd.clone());
        }

        let walker = Walk::new(&paths, &self.cwd, &ignore_options, override_builder);
        let mut paths = walker.paths();

        // NAPI tests build `oxlint` with `testing` feature enabled.
        // In NAPI tests, sort file paths if oxlint is run with `--threads 1`.
        // This guarantees files are linted in a deterministic order.
        //
        // Note: Sorting paths would not be sufficient to guarantee deterministic linting order unless
        // `--threads 1` is also used, because otherwise linting happens in parallel on multiple threads,
        // which also produces non-determinism.
        if cfg!(feature = "testing") && misc_options.threads == Some(1) {
            paths.sort_unstable();
        }

        let mut external_plugin_store = ExternalPluginStore::new(self.external_linter.is_some());

        // Setup JS workspace before loading any configs (config parsing can load JS plugins).
        if let Some(external_linter) = &external_linter {
            let res = (external_linter.create_workspace)(self.cwd.to_string_lossy().into_owned());

            if let Err(err) = res {
                print_and_flush_stdout(stdout, &format!("Failed to setup JS workspace:\n{err}\n"));
                return CliRunResult::JsPluginWorkspaceSetupFailed;
            }
        }

        let search_for_nested_configs = !disable_nested_config &&
            // If the `--config` option is explicitly passed, we should not search for nested config files
            // as the passed config file takes absolute precedence.
            basic_options.config.is_none() &&
            !misc_options.print_config &&
            !self.options.list_rules;

        let config_result = {
            let mut config_loader =
                ConfigLoader::new(external_linter, &mut external_plugin_store, &filters, None);
            #[cfg(feature = "napi")]
            {
                config_loader = config_loader.with_js_config_loader(self.js_config_loader.as_ref());
            }
            config_loader.load_root_and_nested(
                &self.cwd,
                basic_options.config.as_ref(),
                &paths,
                search_for_nested_configs,
            )
        };

        let (mut root_config, nested_configs, nested_ignore_patterns) = match config_result {
            Ok(loaded) => (loaded.root, loaded.nested, loaded.nested_ignore_patterns),
            Err(error) => {
                match error {
                    CliConfigLoadError::RootConfig(error) => {
                        print_and_flush_stdout(
                            stdout,
                            &format!(
                                "Failed to parse oxlint configuration file.\n{}\n",
                                render_report(&handler, &error)
                            ),
                        );
                    }
                    CliConfigLoadError::NestedConfigs(errors) => {
                        if let Some(error) = errors.into_iter().next() {
                            let message = match &error {
                                ConfigLoadError::Parse { path, error } => {
                                    format!(
                                        "Failed to parse oxlint configuration file at {}.\n{}\n",
                                        path.to_string_lossy().cow_replace('\\', "/"),
                                        render_report(&handler, error)
                                    )
                                }
                                ConfigLoadError::Build { path, error } => {
                                    format!(
                                        "Failed to build configuration from {}.\n{}\n",
                                        path.to_string_lossy().cow_replace('\\', "/"),
                                        render_report(
                                            &handler,
                                            &OxcDiagnostic::error(error.clone())
                                        )
                                    )
                                }
                                ConfigLoadError::JsConfigFileFoundButJsRuntimeNotAvailable => {
                                    "Error: JavaScript/TypeScript config files found but JS runtime not available.\n\
                                     This is an experimental feature that requires running oxlint via Node.js.\n\
                                     Please use JSON config files (.oxlintrc.json or .oxlintrc.jsonc) instead, or run oxlint via the npm package.\n".to_string()
                                }
                                ConfigLoadError::Diagnostic(error) => {
                                    let report = render_report(&handler, error);
                                    format!("Failed to parse oxlint configuration file.\n{report}\n")
                                }
                            };
                            print_and_flush_stdout(stdout, &message);
                        }
                    }
                }

                return CliRunResult::InvalidOptionConfig;
            }
        };

        materialize_default_plugins(&mut root_config);
        let mut plugins = root_config.plugins.unwrap_or_default();
        enable_plugins.apply_overrides(&mut plugins);
        root_config.plugins = Some(plugins);

        let config_builder = match ConfigStoreBuilder::from_oxlintrc(
            false,
            root_config.clone(),
            external_linter,
            &mut external_plugin_store,
            None,
        ) {
            Ok(builder) => builder,
            Err(e) => {
                print_and_flush_stdout(
                    stdout,
                    &format!(
                        "Failed to parse oxlint configuration file.\n{}\n",
                        render_config_builder_error(&handler, e)
                    ),
                );
                return CliRunResult::InvalidOptionConfig;
            }
        }
        .with_filters(&filters);

        if misc_options.print_config {
            return crate::mode::run_print_config(&config_builder, root_config, stdout);
        }

        let lint_config = match config_builder.build(&mut external_plugin_store) {
            Ok(config) => config,
            Err(e) => {
                print_and_flush_stdout(
                    stdout,
                    &format!(
                        "Failed to build configuration.\n{}\n",
                        render_config_builder_error(&handler, e)
                    ),
                );
                return CliRunResult::InvalidOptionConfig;
            }
        };

        if self.options.list_rules {
            return crate::mode::run_rules(&lint_config, &output_formatter, stdout);
        }

        let ignore_matcher = LintIgnoreMatcher::new(
            &root_config.ignore_patterns,
            // Without a config file there are no patterns and the root is never consulted,
            // so the CWD fallback is an arbitrary placeholder.
            root_config.dir().unwrap_or(&self.cwd),
            nested_ignore_patterns,
        );

        let files_to_lint = paths
            .into_iter()
            .filter(|path| !ignore_matcher.should_ignore(Path::new(path)))
            .collect::<Vec<Arc<OsStr>>>();

        if debug_files {
            return crate::mode::run_debug_files(
                files_to_lint.iter().map(|path| Path::new(path.as_ref())),
                &self.cwd,
                stdout,
            );
        }

        // If no external rules, discard `ExternalLinter`
        let mut external_linter = self.external_linter;
        if external_plugin_store.is_empty() {
            external_linter = None;
        }

        // TODO(refactor): pull this into a shared function, so that the language server can use
        // the same functionality.
        let use_cross_module = lint_config.plugins().has_import()
            || nested_configs.values().any(|config| config.plugins().has_import());
        let mut options =
            LintServiceOptions::new(self.cwd.clone()).with_cross_module(use_cross_module);

        let mut suppression_manager = SuppressionManager::load(
            options.cwd(),
            "oxlint-suppressions.json",
            suppression_options.suppress_all,
            suppression_options.prune_suppressions || fix_options.is_enabled(),
        );

        let config_store = ConfigStore::new(lint_config, nested_configs, external_plugin_store);
        let type_check_only = self.options.type_check_only;
        let type_aware =
            type_check_only || self.options.type_aware || config_store.type_aware_enabled();
        let type_check =
            type_check_only || self.options.type_check || config_store.type_check_enabled();
        if type_check && !type_aware {
            print_and_flush_stdout(
                stdout,
                "The `--type-check` option requires type-aware linting.\nUse `--type-aware --type-check` or enable `options.typeAware` in your config.\n",
            );
            return CliRunResult::InvalidOptionTypeCheckWithoutTypeAware;
        }
        if type_check_only && fix_options.is_enabled() {
            print_and_flush_stdout(
                stdout,
                "The `--type-check-only` option cannot be used with fix flags.\nRemove `--fix`, `--fix-suggestions`, and `--fix-dangerously`.\n",
            );
            return CliRunResult::InvalidOptionTypeCheckOnlyWithFix;
        }
        if type_check_only
            && (suppression_options.suppress_all || suppression_options.prune_suppressions)
        {
            print_and_flush_stdout(
                stdout,
                "The `--type-check-only` option cannot be used with suppression update flags.\nRemove `--suppress-all` and `--prune-suppressions`.\n",
            );
            return CliRunResult::InvalidOptionTypeCheckOnlyWithSuppressionUpdate;
        }
        let deny_warnings = warning_options.deny_warnings || config_store.deny_warnings();
        let max_warnings = warning_options.max_warnings.or(config_store.max_warnings());

        // Only propagate Warn/Deny; treat Allow (off) as disabling reports.
        let report_unused_directives = if type_check_only {
            None
        } else {
            match inline_config_options.report_unused_directives {
                ReportUnusedDirectives::WithoutSeverity(true) => Some(AllowWarnDeny::Warn),
                ReportUnusedDirectives::WithSeverity(Some(severity)) if severity.is_warn_deny() => {
                    Some(severity)
                }
                ReportUnusedDirectives::WithSeverity(Some(_)) => None,
                _ => match config_store.report_unused_disable_directives() {
                    Some(severity) if severity.is_warn_deny() => Some(severity),
                    _ => None,
                },
            }
        };
        let (mut diagnostic_service, tx_error) = Self::get_diagnostic_service(
            &output_formatter,
            &warning_options,
            &misc_options,
            max_warnings,
        );

        // Send JS plugins config to JS side
        if let Some(external_linter) = &external_linter {
            let res = config_store.external_plugin_store().setup_rule_configs(
                self.cwd.to_string_lossy().into_owned(),
                None,
                external_linter,
            );
            if let Err(err) = res {
                print_and_flush_stdout(
                    stdout,
                    &format!("Failed to setup JS plugin options:\n{err}\n"),
                );
                return CliRunResult::InvalidOptionConfig;
            }
        }

        let linter = Linter::new(LintOptions::default(), config_store, external_linter)
            .with_fix(fix_options.fix_kind())
            .with_report_unused_directives(report_unused_directives);

        let number_of_files = files_to_lint.len();
        let tsconfig = basic_options.tsconfig;
        if let Some(path) = tsconfig.as_ref() {
            if path.is_file() {
                options = options.with_tsconfig(path);
            } else {
                let path = if path.is_relative() { options.cwd().join(path) } else { path.clone() };

                print_and_flush_stdout(
                    stdout,
                    &format!(
                        "The tsconfig file {:?} does not exist, Please provide a valid tsconfig file.\n",
                        path.to_string_lossy().cow_replace('\\', "/")
                    ),
                );

                return CliRunResult::InvalidOptionTsConfig;
            }
        }

        let number_of_rules =
            if type_check_only { None } else { linter.number_of_rules(type_aware) };

        if number_of_files == 0 {
            return Self::handle_no_files_found(
                stdout,
                &output_formatter,
                now,
                number_of_rules,
                misc_options.no_error_on_unmatched_pattern,
            );
        }

        let cwd = options.cwd().to_path_buf();

        // Create the LintRunner
        // TODO: Add a warning message if `tsgolint` cannot be found, but type-aware rules are enabled
        let lint_runner = match LintRunner::builder(options, linter)
            .with_type_aware(type_aware)
            .with_type_check(type_check)
            .with_silent(misc_options.silent)
            .with_fix_kind(fix_options.fix_kind())
            .with_type_check_only(type_check_only)
            .with_timings(debug_timings)
            .build()
        {
            Ok(runner) => runner,
            Err(err) => {
                print_and_flush_stdout(stdout, &format!("{err}\n"));
                return CliRunResult::TsGoLintError;
            }
        };

        let diff_manager = suppression_manager.build_diff();

        let rule_timing_store = debug_timings.then(RuleTimingStore::new);
        let lint_result = if let Some(rule_timing_store) = &rule_timing_store {
            lint_runner.lint_files::<true>(
                &files_to_lint,
                tx_error.clone(),
                &diff_manager,
                Some(rule_timing_store),
            )
        } else {
            lint_runner.lint_files::<false>(&files_to_lint, tx_error.clone(), &diff_manager, None)
        };

        match lint_result {
            Ok(lint_runner) => {
                lint_runner.report_unused_directives(report_unused_directives, &tx_error);
            }
            Err(err) => {
                print_and_flush_stdout(stdout, &format!("{err}\n"));
                return CliRunResult::TsGoLintError;
            }
        }

        // A suppression file can contain regular lint rules that were not run in type-check-only
        // mode, so its runtime diff is incomplete and cannot be used to validate the baseline.
        let result = if type_check_only {
            Ok(())
        } else {
            suppression_manager.finalize(diff_manager, &tx_error, &cwd)
        };
        let suppress_all_succeeded = suppression_options.suppress_all && result.is_ok();

        drop(tx_error);

        let diagnostic_result = diagnostic_service.run(stdout);

        let oxlint_suppression_file_action = if let Err(report_suppression_error) = result {
            OxlintSuppressionFileAction::UnableToPerformFsOperation(report_suppression_error)
        } else {
            suppression_manager.file_action
        };

        let has_unpruned_suppressions = matches!(
            oxlint_suppression_file_action,
            OxlintSuppressionFileAction::HasUnprunedSuppressions
        );

        if let Some(end) = output_formatter.lint_command_info(&LintCommandInfo {
            number_of_files,
            number_of_rules,
            threads_count: rayon::current_num_threads(),
            start_time: now.elapsed(),
            oxlint_suppression_file_action,
            rule_timings: rule_timing_store.as_ref().map(RuleTimingStore::collect),
        }) {
            print_and_flush_stdout(stdout, &end);
        }

        // When --suppress-all is used and the file was written successfully,
        // exit with success (matching ESLint behavior: suppressing is a success action).
        if suppress_all_succeeded {
            return CliRunResult::LintSucceeded;
        }

        if has_unpruned_suppressions {
            return CliRunResult::LintUnprunedSuppressions;
        }

        if diagnostic_result.errors_count() > 0 {
            CliRunResult::LintFoundErrors
        } else if deny_warnings && diagnostic_result.warnings_count() > 0 {
            CliRunResult::LintNoWarningsAllowed
        } else if diagnostic_result.max_warnings_exceeded() {
            CliRunResult::LintMaxWarningsExceeded
        } else {
            CliRunResult::LintSucceeded
        }
    }
}

impl CliRunner {
    #[must_use]
    pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
        self.cwd = cwd;
        self
    }

    #[cfg(feature = "napi")]
    #[must_use]
    pub fn with_config_loader(mut self, config_loader: Option<JsConfigLoaderCb>) -> Self {
        self.js_config_loader = config_loader;
        self
    }

    fn get_diagnostic_service(
        reporter: &OutputFormatter,
        warning_options: &WarningOptions,
        misc_options: &MiscOptions,
        max_warnings: Option<usize>,
    ) -> (DiagnosticService, DiagnosticSender) {
        let (service, sender) = DiagnosticService::new(reporter.get_diagnostic_reporter());
        (
            service
                .with_quiet(warning_options.quiet)
                .with_silent(misc_options.silent)
                .with_max_warnings(max_warnings),
            sender,
        )
    }

    fn handle_no_files_found(
        stdout: &mut dyn Write,
        output_formatter: &OutputFormatter,
        now: Instant,
        number_of_rules: Option<usize>,
        no_error_on_unmatched_pattern: bool,
    ) -> CliRunResult {
        if !no_error_on_unmatched_pattern {
            print_and_flush_stdout(
                stdout,
                "No files found to lint. Please check your paths and ignore patterns.\n",
            );
        }

        if let Some(end) = output_formatter.lint_command_info(&LintCommandInfo {
            number_of_files: 0,
            number_of_rules,
            threads_count: rayon::current_num_threads(),
            start_time: now.elapsed(),
            oxlint_suppression_file_action: OxlintSuppressionFileAction::None,
            rule_timings: None,
        }) {
            print_and_flush_stdout(stdout, &end);
        }

        if no_error_on_unmatched_pattern {
            CliRunResult::LintSucceeded
        } else {
            CliRunResult::LintNoFilesFound
        }
    }

    // moved into a separate function for readability, but it's only ever used
    // in one place.
    fn get_filters(
        filters_arg: Vec<(AllowWarnDeny, String)>,
    ) -> Result<Vec<LintFilter>, (CliRunResult, String)> {
        let mut filters = Vec::with_capacity(filters_arg.len());

        for (severity, filter_arg) in filters_arg {
            match LintFilter::new(severity, filter_arg) {
                Ok(filter) => {
                    filters.push(filter);
                }
                Err(InvalidFilterKind::Empty) => {
                    return Err((
                        CliRunResult::InvalidOptionSeverityWithoutFilter,
                        format!("Cannot {severity} an empty filter.\n"),
                    ));
                }
                Err(InvalidFilterKind::PluginMissing(filter)) => {
                    return Err((
                        CliRunResult::InvalidOptionSeverityWithoutPluginName,
                        format!(
                            "Failed to {severity} filter {filter}: Plugin name is missing. Expected <plugin>/<rule>\n"
                        ),
                    ));
                }
                Err(InvalidFilterKind::RuleMissing(filter)) => {
                    return Err((
                        CliRunResult::InvalidOptionSeverityWithoutRuleName,
                        format!(
                            "Failed to {severity} filter {filter}: Rule name is missing. Expected <plugin>/<rule>\n"
                        ),
                    ));
                }
            }
        }

        Ok(filters)
    }
}

pub fn print_and_flush_stdout(stdout: &mut dyn Write, message: &str) {
    stdout.write_all(message.as_bytes()).or_else(check_for_writer_error).unwrap();
    stdout.flush().or_else(check_for_writer_error).unwrap();
}

fn check_for_writer_error(error: std::io::Error) -> Result<(), std::io::Error> {
    // Do not panic when the process is killed (e.g. piping into `less`).
    if matches!(
        error.kind(),
        ErrorKind::Interrupted | ErrorKind::BrokenPipe | ErrorKind::WouldBlock
    ) {
        Ok(())
    } else {
        Err(error)
    }
}

fn render_report(handler: &GraphicalReportHandler, diagnostic: &OxcDiagnostic) -> String {
    let mut err = String::new();
    handler.render_report(&mut err, diagnostic).unwrap();
    err
}

fn render_config_builder_error(
    handler: &GraphicalReportHandler,
    error: ConfigBuilderError,
) -> String {
    match error {
        ConfigBuilderError::RuleConfigurationErrors { errors } => errors
            .iter()
            .map(|e| render_report(handler, &OxcDiagnostic::error(e.to_string())))
            .collect::<String>(),
        _ => render_report(handler, &OxcDiagnostic::error(error.to_string())),
    }
}

#[cfg(test)]
mod test {
    use std::fs;

    use crate::{DEFAULT_OXLINTRC_NAME, tester::Tester};
    use oxc_linter::rules::RULES;

    // lints the full directory of fixtures,
    // so do not snapshot it, test only
    #[test]
    fn no_arg() {
        let args = &[];
        Tester::new().test(args);
    }

    #[test]
    fn dir() {
        let args = &["fixtures/cli/linter"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn cwd() {
        let args = &["debugger.js"];
        Tester::new().with_cwd("fixtures/cli/linter".into()).test_and_snapshot(args);
    }

    #[test]
    fn file() {
        let args = &["fixtures/cli/linter/debugger.js"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn multi_files() {
        let args = &["fixtures/cli/linter/debugger.js", "fixtures/cli/linter/nan.js"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn wrong_extension() {
        let args = &["foo.asdf"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn wrong_extension_with_no_error_on_unmatched_pattern() {
        let args = &["--no-error-on-unmatched-pattern", "foo.asdf"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn ignore_pattern() {
        let args =
            &["--ignore-pattern", "**/*.js", "--ignore-pattern", "**/*.vue", "fixtures/cli/linter"];
        Tester::new().test_and_snapshot(args);
    }

    /// When a file is explicitly passed as a path and `--no-ignore`
    /// is not present, the ignore file should take precedence.
    /// See https://github.com/oxc-project/oxc/issues/1124
    #[test]
    fn ignore_file_overrides_explicit_args() {
        let args =
            &["--ignore-path", "fixtures/cli/linter/.customignore", "fixtures/cli/linter/nan.js"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn ignore_file_overrides_explicit_args_with_no_error_on_unmatched_pattern() {
        let args = &[
            "--no-error-on-unmatched-pattern",
            "--ignore-path",
            "fixtures/cli/linter/.customignore",
            "fixtures/cli/linter/nan.js",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn ignore_file_no_ignore() {
        let args = &[
            "--ignore-path",
            "fixtures/cli/linter/.customignore",
            "--no-ignore",
            "fixtures/cli/linter/nan.js",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn ignore_flow() {
        let args = &["--import-plugin", "fixtures/cli/flow/index.mjs"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    // https://github.com/oxc-project/oxc/issues/7406
    fn ignore_flow_import_plugin_directory() {
        let args = &["--import-plugin", "-A all", "-D no-cycle", "fixtures/cli/flow/"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    // https://github.com/oxc-project/oxc/issues/9023
    fn ignore_file_current_dir() {
        let args1 = &[];
        let args2 = &["."];
        Tester::new()
            .with_cwd("fixtures/cli/ignore_file_current_dir".into())
            .test_and_snapshot_multiple(&[args1, args2]);
    }

    #[test]
    // https://github.com/oxc-project/oxc/issues/13204
    fn ignore_pattern_non_glob_syntax() {
        let args1 = &[];
        let args2 = &["."];
        Tester::new()
            .with_cwd("fixtures/cli/ignore_pattern_non_glob_syntax".into())
            .test_and_snapshot_multiple(&[args1, args2]);
    }

    #[test]
    fn ignore_patterns_empty_nested() {
        let args1 = &[];
        let args2 = &["."];
        Tester::new()
            .with_cwd("fixtures/cli/ignore_patterns_empty_nested".into())
            .test_and_snapshot_multiple(&[args1, args2]);
    }

    #[test]
    fn ignore_patterns_relative() {
        let args1 = &[];
        let args2 = &["."];
        Tester::new()
            .with_cwd("fixtures/cli/ignore_patterns_relative".into())
            .test_and_snapshot_multiple(&[args1, args2]);
    }

    #[test]
    // https://github.com/oxc-project/oxc/issues/24310
    fn ignore_patterns_ancestor_config() {
        // Config is found via ancestor search; its `ignorePatterns` should be
        // rooted at the config file's directory, not CWD.
        let args = &["."];
        Tester::new()
            .with_cwd("fixtures/cli/ignore_patterns_ancestor_config/packages/foo".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn ignore_patterns_config_path_with_parent_references() {
        // `..` components in a `--config` path must be normalized before the config's
        // directory is used as the root for `ignorePatterns`, otherwise the patterns
        // silently never match.
        let args = &["-c", "./packages/../.oxlintrc.json", "."];
        Tester::new()
            .with_cwd("fixtures/cli/ignore_patterns_ancestor_config".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn ignore_patterns_with_symlink() {
        let args1 = &[];
        let args2 = &["."];
        Tester::new()
            .with_cwd("fixtures/cli/ignore_patterns_symlink".into())
            .test_and_snapshot_multiple(&[args1, args2]);
    }

    #[test]
    fn ignore_patterns_whitelist() {
        let args1 = &[];
        let args2 = &["."];
        Tester::new()
            .with_cwd("fixtures/cli/ignore_patterns_whitelist".into())
            .test_and_snapshot_multiple(&[args1, args2]);
    }

    #[test]
    fn filter_allow_all() {
        let args = &["-A", "all", "fixtures/cli/linter"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn filter_allow_one() {
        let args = &["-W", "correctness", "-A", "no-debugger", "fixtures/cli/linter/debugger.js"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn filter_error() {
        let args = &["-D", "correctness", "fixtures/cli/linter/debugger.js"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn eslintrc_error() {
        let args = &["-c", "fixtures/cli/linter/eslintrc.json", "fixtures/cli/linter/debugger.js"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn eslintrc_off() {
        let args =
            &["-c", "fixtures/cli/eslintrc_off/eslintrc.json", "fixtures/cli/eslintrc_off/test.js"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn oxlint_config_auto_detection() {
        let args = &["debugger.js"];
        Tester::new().with_cwd("fixtures/cli/auto_config_detection".into()).test_and_snapshot(args);
    }

    #[test]
    fn oxlint_config_auto_detection_jsonc() {
        let args = &["debugger.js"];
        Tester::new()
            .with_cwd("fixtures/cli/auto_config_detection_jsonc".into())
            .test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_os = "windows"))] // Skipped on Windows due to snapshot diffs from path separators (`/` vs `\`)
    fn oxlint_config_auto_detection_parse_error() {
        let args = &["debugger.js"];
        Tester::new()
            .with_cwd("fixtures/cli/auto_config_parse_error".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn eslintrc_no_undef() {
        let args = &[
            "-W",
            "no-undef",
            "-c",
            "fixtures/cli/no_undef/eslintrc.json",
            "fixtures/cli/no_undef/test.js",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn eslintrc_no_env() {
        let args = &[
            "-W",
            "no-undef",
            "-c",
            "fixtures/cli/eslintrc_env/eslintrc_no_env.json",
            "fixtures/cli/eslintrc_env/test.js",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn eslintrc_with_env() {
        let args = &[
            "-c",
            "fixtures/cli/eslintrc_env/eslintrc_env_browser.json",
            "fixtures/cli/eslintrc_env/test.js",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn no_empty_allow_empty_catch() {
        let args = &[
            "-c",
            "fixtures/cli/no_empty_allow_empty_catch/eslintrc.json",
            "-W",
            "no-empty",
            "fixtures/cli/no_empty_allow_empty_catch/test.js",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn no_empty_disallow_empty_catch() {
        let args = &[
            "-c",
            "fixtures/cli/no_empty_disallow_empty_catch/eslintrc.json",
            "-W",
            "no-empty",
            "fixtures/cli/no_empty_disallow_empty_catch/test.js",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn no_console_off() {
        let args = &[
            "-c",
            "fixtures/cli/no_console_off/eslintrc.json",
            "fixtures/cli/no_console_off/test.js",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn typescript_eslint() {
        let args = &[
            "-c",
            "fixtures/cli/typescript_eslint/eslintrc.json",
            "fixtures/cli/typescript_eslint/test.ts",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn typescript_eslint_off() {
        let args = &[
            "-c",
            "fixtures/cli/typescript_eslint/eslintrc.json",
            "--disable-typescript-plugin",
            "fixtures/cli/typescript_eslint/test.ts",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn js_and_jsx() {
        let args = &["fixtures/cli/linter/js_as_jsx.js"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn debug_timings() {
        Tester::new().test_and_snapshot(&[
            "--debug",
            "timings",
            "--threads",
            "1",
            "-A",
            "all",
            "-W",
            "no-debugger",
            "fixtures/cli/linter/debugger.js",
        ]);
    }

    #[test]
    fn debug_files() {
        Tester::new().test_and_snapshot(&["--debug", "files", "fixtures/cli/linter"]);
    }

    #[test]
    fn lint_vue_file() {
        let args = &["fixtures/cli/vue/debugger.vue"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn lint_empty_vue_file() {
        let args = &["fixtures/cli/vue/empty.vue"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn lint_invalid_vue_file() {
        let args = &["fixtures/cli/vue/invalid.vue"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn lint_astro_file() {
        let args = &["fixtures/cli/astro/debugger.astro"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn lint_svelte_file() {
        let args = &["fixtures/cli/svelte/debugger.svelte"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn lint_svelte_module_and_instance_scripts() {
        let output =
            Tester::new().test_output_verbose(&["fixtures/cli/svelte/module-script.svelte"]);

        assert_eq!(output.matches("eslint(no-debugger)").count(), 2);
        assert!(output.contains("fixtures/cli/svelte/module-script.svelte:2:2"));
        assert!(output.contains("fixtures/cli/svelte/module-script.svelte:6:2"));
    }

    #[test]
    fn lint_svelte_context_module_and_instance_scripts() {
        let output = Tester::new()
            .test_output_verbose(&["fixtures/cli/svelte/context-module-script.svelte"]);

        assert_eq!(output.matches("eslint(no-debugger)").count(), 2);
        assert!(output.contains("fixtures/cli/svelte/context-module-script.svelte:2:2"));
        assert!(output.contains("fixtures/cli/svelte/context-module-script.svelte:6:2"));
    }

    #[test]
    fn lint_svelte_context_module_and_typescript_scripts() {
        let output = Tester::new()
            .test_output_verbose(&["fixtures/cli/svelte/context-module-script-ts.svelte"]);

        assert_eq!(output.matches("eslint(no-debugger)").count(), 2);
        assert!(output.contains("fixtures/cli/svelte/context-module-script-ts.svelte:2:2"));
        assert!(output.contains("fixtures/cli/svelte/context-module-script-ts.svelte:7:2"));
    }

    #[test]
    fn test_tsconfig_option() {
        // passed
        Tester::new()
            .with_cwd("fixtures/cli/tsconfig".into())
            .test(&["--tsconfig", "tsconfig.json"]);

        // failed
        Tester::new()
            .with_cwd("fixtures/cli/tsconfig".into())
            .test_and_snapshot(&["--tsconfig", "non-exists.json"]);
    }

    #[test]
    fn test_enable_vitest_rule_without_plugin() {
        let args = &[
            "-c",
            "fixtures/cli/eslintrc_vitest_replace/eslintrc.json",
            "fixtures/cli/eslintrc_vitest_replace/foo.test.js",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn test_enable_vitest_plugin() {
        let args = &[
            "--vitest-plugin",
            "-c",
            "fixtures/cli/eslintrc_vitest_replace/eslintrc.json",
            "fixtures/cli/eslintrc_vitest_replace/foo.test.js",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn test_import_plugin_enabled_in_config() {
        let args_1 = &["-c", ".oxlintrc.json", "test.js"];
        // support import-x namespace see #8779
        let args_2 = &["-c", ".oxlintrc-import-x.json", "test.js"];
        Tester::new()
            .with_cwd("fixtures/cli/import".into())
            .test_and_snapshot_multiple(&[args_1, args_2]);
    }

    #[test]
    fn test_fix() {
        let tester = Tester::new().with_cwd("fixtures/cli/fix_argument".into());
        tester.test_fix(
            "fix.js",
            "var x = new String('Hello world');\n",
            "var x = 'Hello world';\n",
        );
        tester.test_fix(
            "fix.vue",
            "<script>var x = new String('Hello world');</script>\n<script>var y = new String('Hello world');</script>\n",
            "<script>var x = 'Hello world';</script>\n<script>var y = 'Hello world';</script>\n",
        );
    }

    #[test]
    fn test_fix_skip_suggestion() {
        let tester = Tester::new().with_cwd("fixtures/cli/fix_argument".into());
        let test_1 = "debugger\n";
        tester.test_fix("skip_suggestion.js", test_1, test_1);
        let test_2 = "<script>debugger;</script>\n<script>debugger;</script>\n";
        tester.test_fix("skip_suggestion.vue", test_2, test_2);
    }

    #[test]
    fn test_print_config_ban_all_rules() {
        let args = &["-A", "all", "--print-config"];
        Tester::new().with_cwd("fixtures".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_print_config_ban_rules() {
        let args = &[
            "-c",
            "fixtures/cli/print_config/ban_rules/eslintrc.json",
            "-A",
            "all",
            "-D",
            "eqeqeq",
            "--print-config",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn test_init_config() {
        let temp_dir = tempfile::tempdir().expect("Could not create a temp dir");
        let config_path = temp_dir.path().join(DEFAULT_OXLINTRC_NAME);
        assert!(!fs::exists(&config_path).unwrap());

        let args = &["--init"];
        Tester::new().with_cwd(temp_dir.path().to_path_buf()).test(args);

        assert!(fs::exists(&config_path).unwrap());

        let content = fs::read_to_string(config_path).unwrap();

        insta::assert_snapshot!("init_config", content);
    }

    #[test]
    fn test_overrides() {
        // This is split into three to avoid creating a snapshot with a filename too large to be usable on Windows.
        let tester = Tester::new().with_cwd("fixtures/cli/overrides".into());
        tester.test_and_snapshot(&["-c", ".oxlintrc.json", "test.js"]);
        tester.test_and_snapshot(&["-c", ".oxlintrc.json", "test.ts"]);
        tester.test_and_snapshot(&["-c", ".oxlintrc.json", "other.jsx"]);
    }

    #[test]
    fn test_rule_count_includes_override_only_rules() {
        let args = &["demo.ts"];
        Tester::new().with_cwd("fixtures/issue_19891".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_overrides_directories() {
        let args =
            &["-c", "fixtures/cli/overrides/directories-config.json", "fixtures/cli/overrides"];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn test_overrides_envs_and_global() {
        let args = &["-c", ".oxlintrc.json", "."];
        Tester::new().with_cwd("fixtures/cli/overrides_env_globals".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_ignore_patterns() {
        let args = &["-c", "./test/eslintrc.json", "--ignore-pattern", "*.ts", "."];

        Tester::new()
            .with_cwd("fixtures/cli/config_ignore_patterns/with_oxlintrc".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_config_ignore_patterns_extension() {
        let args = &[
            "-c",
            "fixtures/cli/config_ignore_patterns/ignore_extension/eslintrc.json",
            "fixtures/cli/config_ignore_patterns/ignore_extension",
        ];

        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn test_config_ignore_patterns_special_extension() {
        let args = &[
            "-c",
            "fixtures/cli/config_ignore_patterns/ignore_extension/eslintrc.json",
            "fixtures/cli/config_ignore_patterns/ignore_extension/main.js",
        ];

        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn test_config_ignore_patterns_directory() {
        let args = &["-c", "eslintrc.json"];
        Tester::new()
            .with_cwd("fixtures/cli/config_ignore_patterns/ignore_directory".into())
            .test_and_snapshot(args);
    }

    // Issue: <https://github.com/oxc-project/oxc/pull/7566>
    #[test]
    fn ignore_path_with_relative_files() {
        let args = &[
            "--ignore-path",
            "fixtures/cli/issue_7566/.oxlintignore",
            "fixtures/cli/issue_7566/tests/main.js",
            "fixtures/cli/issue_7566/tests/function/main.js",
        ];
        Tester::new().test_and_snapshot(args);
    }

    #[test]
    fn test_jest_and_vitest_alias_rules() {
        let args_1 = &["-c", "oxlint-jest.json", "test.js"];
        let args_2 = &["-c", "oxlint-vitest.json", "test.js"];
        Tester::new()
            .with_cwd("fixtures/cli/jest_and_vitest_alias_rules".into())
            .test_and_snapshot_multiple(&[args_1, args_2]);
    }

    #[test]
    fn test_eslint_and_typescript_alias_rules() {
        let args_1 = &["-c", "oxlint-eslint.json", "test.js"];
        let args_2 = &["-c", "oxlint-typescript.json", "test.js"];
        Tester::new()
            .with_cwd("fixtures/cli/eslint_and_typescript_alias_rules".into())
            .test_and_snapshot_multiple(&[args_1, args_2]);
    }

    #[test]
    // Test to ensure that a vitest rule based on the jest rule is
    // handled correctly when it has a different name.
    // e.g. `vitest/no-restricted-vi-methods` vs `jest/no-restricted-jest-methods`
    fn test_disable_vitest_rules() {
        let args =
            &["-c", ".oxlintrc-vitest.json", "--report-unused-disable-directives", "test.js"];
        Tester::new().with_cwd("fixtures/cli/disable_vitest_rules".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_two_rules_with_same_rule_name_from_different_plugins() {
        // Issue: <https://github.com/oxc-project/oxc/issues/8485>
        let args = &["-c", ".oxlintrc.json", "test.js"];
        Tester::new()
            .with_cwd("fixtures/cli/two_rules_with_same_rule_name".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_report_unused_directives() {
        let args = &["-c", ".oxlintrc.json", "--report-unused-disable-directives"];

        Tester::new()
            .with_cwd("fixtures/cli/report_unused_directives".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_report_unused_directives_from_config() {
        // Verify that `reportUnusedDisableDirectives` in the config file enables reporting
        // without needing a CLI flag.
        let args = &["-c", ".oxlintrc-with-rudd.json"];

        Tester::new()
            .with_cwd("fixtures/cli/report_unused_directives".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_report_unused_directives_cli_overrides_config() {
        // Verify that the CLI flag takes precedence over the config file value.
        // Config has `reportUnusedDisableDirectives: "warn"`, but CLI passes `off`,
        // so no unused-directive diagnostics should be reported.
        let args =
            &["-c", ".oxlintrc-with-rudd.json", "--report-unused-disable-directives-severity=off"];

        Tester::new()
            .with_cwd("fixtures/cli/report_unused_directives".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_report_unused_directives_with_oxlint_prefix_only() {
        let args = &["-c", ".oxlintrc.json", "--report-unused-disable-directives"];

        Tester::new()
            .with_cwd("fixtures/cli/report_unused_directives_oxlint_only".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_root_config_ancestor() {
        Tester::new()
            .with_cwd("fixtures/cli/root_config_ancestor/cwd".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    fn test_nested_config() {
        let args = &[];
        Tester::new().with_cwd("fixtures/cli/nested_config".into()).test_and_snapshot(args);

        let args = &["--disable-nested-config"];
        Tester::new().with_cwd("fixtures/cli/extends_config".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_nested_config_subdirectory() {
        // This tests the specific scenario from issue #10156
        // where a file is located in a subdirectory of a directory with a config file
        let args = &["package3-deep-config"];
        Tester::new().with_cwd("fixtures/cli/nested_config".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_nested_config_subdirectory_as_cwd() {
        let args = &[];
        Tester::new()
            .with_cwd("fixtures/cli/nested_config/package4-as-cwd".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_nested_config_explicit_config_precedence() {
        // `--config` takes absolute precedence over nested configs, and will be used for
        // linting all files rather than the nested configuration files.
        let args = &["--config", "oxlint-no-console.json"];
        Tester::new().with_cwd("fixtures/cli/nested_config".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_nested_config_filter_precedence() {
        // CLI arguments take precedence over nested configs, but apply over top of the nested
        // config files, rather than replacing them.
        let args = &["-A", "no-console"];
        Tester::new().with_cwd("fixtures/cli/nested_config".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_nested_config_explicit_config_and_filter_precedence() {
        // Combining `--config` and CLI filters should make the passed config file be
        // used for all files, but still override any rules specified in the config file.
        let args = &["-A", "no-console", "--config", "oxlint-no-console.json"];
        Tester::new().with_cwd("fixtures/cli/nested_config".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_extends_explicit_config() {
        // Check that referencing a config file that extends other config files works as expected
        let args = &["--config", "extends_rules_config.json", "console.js"];
        Tester::new().with_cwd("fixtures/cli/extends_config".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_extends_extends_config() {
        // Check that using a config that extends a config which extends a config works
        let args = &["--config", "relative_paths/extends_extends_config.json", "console.js"];
        Tester::new().with_cwd("fixtures/cli/extends_config".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_extends_overrides() {
        // Check that using a config with overrides works as expected
        let args = &["overrides"];
        Tester::new().with_cwd("fixtures/cli/extends_config".into()).test_and_snapshot(args);

        // Check that using a config which extends a config with overrides works as expected
        let args = &["overrides_same_directory"];
        Tester::new().with_cwd("fixtures/cli/extends_config".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_nested_config_multi_file_analysis_imports() {
        let args = &["issue_10054"];
        Tester::new().with_cwd("fixtures/cli".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_cross_modules_with_nested_config() {
        let args = &[];
        Tester::new()
            .with_cwd("fixtures/cli/cross_module_nested_config".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_cross_modules_with_extended_config() {
        let args = &[];
        Tester::new()
            .with_cwd("fixtures/cli/cross_module_extended_config".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_import_plugin_being_enabled_correctly() {
        // https://github.com/oxc-project/oxc/pull/10597
        let args = &["--import-plugin", "-D", "import/no-cycle"];
        Tester::new().with_cwd("fixtures/cli/import-cycle".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_import_plugin_detects_cycles_with_auto_discovered_tsconfig_paths() {
        let args = &["--import-plugin", "-D", "import/no-cycle", "deep/src/dep-a.ts"];
        Tester::new().with_cwd("fixtures/lsp/ts_path_alias".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_rule_config_being_enabled_correctly() {
        let args = &["-c", ".oxlintrc.json"];
        Tester::new().with_cwd("fixtures/cli/issue_11054".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_plugins_in_overrides_enabled_correctly() {
        let args = &["-c", ".oxlintrc.json"];
        Tester::new().with_cwd("fixtures/cli/overrides_with_plugin".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_plugins_inside_overrides_categories_enabled_correctly() {
        let args = &["-c", ".oxlintrc.json"];
        Tester::new().with_cwd("fixtures/cli/issue_10394".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_jsx_a11y_label_has_associated_control() {
        let args = &["-c", ".oxlintrc.json"];
        Tester::new().with_cwd("fixtures/cli/issue_11644".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_dot_folder() {
        Tester::new().with_cwd("fixtures/cli/dot_folder".into()).test_and_snapshot(&[]);
    }

    #[test]
    fn test_rules_json_output() {
        let args = &["--rules", "-f=json"];
        let (stdout, _) = Tester::new().with_cwd("fixtures".into()).test_output(args);

        // Parse output as JSON array. If parsing fails, the test will fail.
        let rules: Vec<serde_json::Value> =
            serde_json::from_str(&stdout).expect("Failed to parse JSON");
        assert!(!rules.is_empty(), "The rules list should not be empty");

        // Ensure that the number of rules matches the RULES constant, all rules should be listed.
        assert_eq!(rules.len(), RULES.len(), "The number of rules should match the RULES constant");

        // Validate the structure of JSON objects.
        for rule in &rules {
            let rule_obj = rule.as_object().unwrap();
            assert!(rule_obj.contains_key("scope"), "Rule should contain 'scope' field");
            assert!(rule_obj.contains_key("value"), "Rule should contain 'value' field");
            assert!(rule_obj.contains_key("category"), "Rule should contain 'category' field");
            assert!(rule_obj.contains_key("type_aware"), "Rule should contain 'type_aware' field");
            assert!(rule_obj.contains_key("fix"), "Rule should contain 'fix' field");
            assert!(rule_obj.contains_key("default"), "Rule should contain 'default' field");
            assert!(rule_obj.contains_key("docs_url"), "Rule should contain 'docs_url' field");
        }

        // Verify that the rules list is sorted by scope and value.
        let rule_names: Vec<(&str, &str)> = rules
            .iter()
            .map(|rule| {
                let obj = rule.as_object().unwrap();
                (obj["scope"].as_str().unwrap(), obj["value"].as_str().unwrap())
            })
            .collect();
        assert!(rule_names.is_sorted(), "The rules list should be sorted by scope and value");
    }

    #[test]
    fn test_disable_directive_issue_13311() {
        // Test that exhaustive-deps diagnostics are reported at the dependency array
        // so that disable directives work correctly
        // Issue: https://github.com/oxc-project/oxc/issues/13311
        let args = &["test.jsx", "test2.d.ts"];
        Tester::new()
            .with_cwd("fixtures/cli/disable_directive_issue_13311".into())
            .test_and_snapshot(args);
    }

    // ToDo: `tsgolint` does not support `big-endian`?
    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint() {
        let args = &["--type-aware"];
        Tester::new().with_cwd("fixtures/cli/tsgolint".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_silent() {
        let args = &["--type-aware", "--silent"];
        Tester::new().with_cwd("fixtures/cli/tsgolint".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_config() {
        // TODO: test with other rules as well once diagnostics are more stable
        let args = &["--type-aware", "-c", "config-test.json"];
        Tester::new().with_cwd("fixtures/cli/tsgolint".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_config_via_config_file() {
        let args = &["-c", "config-type-aware.json"];
        Tester::new().with_cwd("fixtures/cli/tsgolint".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_config_type_aware_applies_to_overrides() {
        let args = &["-c", "config-type-aware-with-overrides.json", "no-floating-promises.ts"];
        Tester::new().with_cwd("fixtures/cli/tsgolint".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_config_type_aware_false() {
        let args = &["-c", "config-type-aware-false.json", "no-floating-promises.ts"];
        Tester::new().with_cwd("fixtures/cli/tsgolint".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_config_type_aware_false_overridden_by_cli_flag() {
        let args =
            &["--type-aware", "-c", "config-type-aware-false.json", "no-floating-promises.ts"];
        Tester::new().with_cwd("fixtures/cli/tsgolint".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_config_type_aware_false_disables_overrides() {
        let args =
            &["-c", "config-type-aware-false-with-overrides.json", "no-floating-promises.ts"];
        Tester::new().with_cwd("fixtures/cli/tsgolint".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_type_error() {
        let args = &["--type-aware", "--type-check"];
        Tester::new().with_cwd("fixtures/cli/tsgolint_type_error".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_type_check_only() {
        let args = &["--type-check-only"];
        Tester::new().with_cwd("fixtures/cli/tsgolint_type_error".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_type_check_only_reports_syntax_errors() {
        let args = &["--type-check-only"];
        Tester::new()
            .with_cwd("fixtures/cli/tsgolint_type_check_only_syntax_error".into())
            .test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_type_check_only_reports_partial_loader_syntax_errors() {
        let args = &["--type-check-only"];
        Tester::new()
            .with_cwd("fixtures/cli/tsgolint_type_check_only_svelte_syntax_error".into())
            .test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_type_check_requires_type_aware() {
        let args = &["--type-check"];
        Tester::new().with_cwd("fixtures/cli/tsgolint_type_error".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_type_check_via_config_file() {
        let args = &["-c", "config-type-check.json"];
        Tester::new().with_cwd("fixtures/cli/tsgolint_type_error".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_type_check_with_zero_rules_enabled() {
        let args = &["-c", "config-type-check-zero-rules.json"];
        Tester::new().with_cwd("fixtures/cli/tsgolint_type_error".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_type_check_false_via_config_file() {
        let args = &["-c", "config-type-check-false.json"];
        Tester::new().with_cwd("fixtures/cli/tsgolint_type_error".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_type_check_false_overridden_by_cli_flag() {
        let args = &["--type-check", "-c", "config-type-check-false.json"];
        Tester::new().with_cwd("fixtures/cli/tsgolint_type_error".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_type_check_only_rejects_fix_flags() {
        let args = &["--type-check-only", "--fix"];
        Tester::new().with_cwd("fixtures/cli/tsgolint_type_error".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_max_warnings_via_config_file() {
        let args = &["-c", "config-max-warnings.json", "debugger.js"];
        Tester::new().with_cwd("fixtures/cli/linter".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_max_warnings_overridden_by_cli_flag() {
        let args = &["--max-warnings", "1", "-c", "config-max-warnings.json", "debugger.js"];
        Tester::new().with_cwd("fixtures/cli/linter".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_deny_warnings_via_config_file() {
        let args = &["-c", "config-deny-warnings.json", "debugger.js"];
        Tester::new().with_cwd("fixtures/cli/linter".into()).test_and_snapshot(args);
    }

    #[test]
    fn test_deny_warnings_overridden_by_cli_flag() {
        let args = &["--deny-warnings", "-c", "config-deny-warnings-false.json", "debugger.js"];
        Tester::new().with_cwd("fixtures/cli/linter".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_tsgolint_no_typescript_files() {
        // tsgolint shouldn't run when no files need type aware linting
        let args = &["--type-aware", "test.svelte"];
        Tester::new().with_cwd("fixtures/cli/tsgolint".into()).test_and_snapshot(args);
    }

    #[cfg(not(target_endian = "big"))]
    #[test]
    fn test_tsgolint_unused_disable_directives() {
        // Test that unused disable directives are reported with type-aware rules
        let args = &["--type-aware", "--report-unused-disable-directives", "unused.ts"];
        Tester::new()
            .with_cwd("fixtures/cli/tsgolint_disable_directives".into())
            .test_and_snapshot(args);
    }

    #[cfg(not(target_endian = "big"))]
    #[test]
    fn test_tsgolint_disable_directives() {
        // Test that disable directives work with type-aware rules
        let args = &["--type-aware"];
        Tester::new()
            .with_cwd("fixtures/cli/tsgolint_disable_directives".into())
            .test_and_snapshot(args);
    }

    #[test]
    #[cfg(all(not(target_os = "windows"), not(target_endian = "big")))]
    fn test_tsgolint_config_error() {
        let args = &["--type-aware"];
        Tester::new().with_cwd("fixtures/cli/tsgolint_config_error".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(all(not(target_os = "windows"), not(target_endian = "big")))]
    fn test_tsgolint_tsconfig_extends_config_err() {
        let args = &["--type-aware", "-D", "no-floating-promises"];
        Tester::new()
            .with_cwd("fixtures/cli/tsgolint_tsconfig_extends_config_err".into())
            .test_and_snapshot(args);
    }

    #[test]
    #[cfg(all(not(target_os = "windows"), not(target_endian = "big")))]
    fn test_tsgolint_rule_options() {
        // Test that rule options are correctly passed to tsgolint
        // See: https://github.com/oxc-project/oxc/issues/16182
        let args = &["--type-aware"];
        Tester::new().with_cwd("fixtures/cli/tsgolint_rule_options".into()).test_and_snapshot(args);
    }

    #[test]
    #[cfg(all(not(target_os = "windows"), not(target_endian = "big")))]
    fn test_tsgolint_fix() {
        Tester::new().test_fix_with_args(
            "fixtures/cli/tsgolint_fix/fix.ts",
            "// This file has a fixable tsgolint error: no-unnecessary-type-assertion
// The type assertion `as string` is unnecessary because str is already a string
const str: string = 'hello';
const redundant = str as string;

export { redundant };
",
            "// This file has a fixable tsgolint error: no-unnecessary-type-assertion
// The type assertion `as string` is unnecessary because str is already a string
const str: string = 'hello';
const redundant = str;

export { redundant };
",
            &["--type-aware", "-D", "no-unnecessary-type-assertion"],
        );
    }

    #[test]
    fn test_invalid_config_invalid_config_enum() {
        Tester::new().with_cwd("fixtures/cli/invalid_config_enum".into()).test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_invalid_config_extra_options() {
        Tester::new()
            .with_cwd("fixtures/cli/invalid_config_extra_options".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_rule_without_config_but_options() {
        Tester::new()
            .with_cwd("fixtures/cli/invalid_config_rules_without_config".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    // Ensure the config validation works with vitest/no-hooks, which
    // is an alias of jest/no-hooks.
    fn test_invalid_config_invalid_config_with_rule_alias() {
        Tester::new()
            .with_cwd("fixtures/cli/invalid_config_with_rule_alias".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_invalid_config_in_override() {
        Tester::new()
            .with_cwd("fixtures/cli/invalid_config_in_override".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_missing_rule_in_override() {
        Tester::new()
            .with_cwd("fixtures/cli/invalid_config_missing_rule_in_override".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_invalid_config_multiple_rules() {
        Tester::new()
            .with_cwd("fixtures/cli/invalid_config_multiple_rules".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_missing_builtin_rule() {
        Tester::new()
            .with_cwd("fixtures/cli/invalid_config_missing_builtin_rule".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_nested() {
        Tester::new().with_cwd("fixtures/cli/invalid_config_nested".into()).test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_invalid_config_type_difference() {
        Tester::new()
            .with_cwd("fixtures/cli/invalid_config_type_difference".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_invalid_config_extends() {
        Tester::new().with_cwd("fixtures/cli/extends_invalid_config".into()).test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_invalid_config_sort_imports() {
        Tester::new()
            .with_cwd("fixtures/cli/invalid_config_sort_imports".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    fn test_valid_complex_config() {
        Tester::new().with_cwd("fixtures/cli/valid_complex_config".into()).test_and_snapshot(&[]);
    }

    /// Test that rules with dummy `config = Value` declarations can accept
    /// configuration options without errors. This test should be removed in
    /// the future, once these rules have been updated to use proper config
    /// structs.
    #[test]
    fn test_valid_config_rules_with_dummy_config() {
        Tester::new()
            .with_cwd("fixtures/cli/valid_config_rules_with_dummy_config".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_invalid_config_complex_enum() {
        Tester::new()
            .with_cwd("fixtures/cli/invalid_config_complex_enum".into())
            .test_and_snapshot(&[]);
    }

    #[test]
    fn test_invalid_config_invalid_config_tuple_rules() {
        Tester::new()
            .with_cwd("fixtures/cli/invalid_config_tuple_rules".into())
            .test_and_snapshot(&[]);
    }
}

#[cfg(test)]
mod suppression {

    use crate::{
        cli::CliRunResult,
        tester::{SuppressionTester, Tester},
    };

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn file_not_detected_report_all_errors() {
        let args = &["--type-aware", "--type-check"];
        Tester::new()
            .with_cwd("fixtures/suppression/file_not_detected_report_all_errors".into())
            .test_and_snapshot(args);
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_diagnostics_filtered_if_count_is_the_same() {
        let args = &["--type-aware", "--type-check"];
        Tester::new()
            .with_cwd("fixtures/suppression/diagnostics_filtered_if_count_is_the_same".into())
            .test_and_snapshot(args);
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_suppress_all_hides_errors_with_existing_file() {
        // When --suppress-all is used on an existing suppressions file,
        // error diagnostics should be suppressed, matching ESLint behavior.
        let args = &["--suppress-all", "--type-aware", "--type-check"];
        let (stdout, result) = Tester::new()
            .with_cwd("fixtures/suppression/diagnostics_filtered_if_count_is_the_same".into())
            .test_output(args);
        assert!(
            matches!(result, CliRunResult::LintSucceeded),
            "Expected LintSucceeded with --suppress-all, got {result:?}"
        );
        // Only TS type-check errors and warnings should remain.
        assert!(
            !stdout.contains("eslint(no-console)"),
            "no-console error should be suppressed with --suppress-all.\nOutput: {stdout}"
        );
        assert!(
            !stdout.contains("typescript-eslint(await-thenable)"),
            "await-thenable error should be suppressed with --suppress-all.\nOutput: {stdout}"
        );
        assert!(
            !stdout.contains("typescript-eslint(array-type)"),
            "array-type error should be suppressed with --suppress-all.\nOutput: {stdout}"
        );
    }

    #[test]
    fn test_only_file_diffs_are_reported() {
        let args = &[];
        Tester::new()
            .with_cwd("fixtures/suppression/only_file_diffs_are_reported".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_eslint_file_format() {
        let args = &[];
        Tester::new()
            .with_cwd("fixtures/suppression/eslint_file_format".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_decreased_violations_are_reported() {
        let args = &[];
        Tester::new()
            .with_cwd("fixtures/suppression/decreased_violations_are_reported".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_no_false_positive_after_suppress_all() {
        // After --suppress-all creates a suppressions file with only error-level rules,
        // a re-run without --suppress-all should NOT report "new violations not covered"
        // just because some files have only warning-level violations.
        // This matches ESLint behavior: a clean re-run after suppression is silent with exit 0.
        let args = &[];
        let (stdout, result) = Tester::new()
            .with_cwd("fixtures/suppression/no_false_positive_after_suppress_all".into())
            .test_output(args);
        assert!(
            !stdout.contains("new violations not covered"),
            "False positive: warnings-only files should not be reported as new violations.\nOutput: {stdout}"
        );
        assert!(
            !matches!(result, CliRunResult::LintFoundErrors),
            "Expected no errors (warnings-only files should not count), got {result:?}"
        );
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_type_check_only_does_not_validate_regular_rule_suppressions() {
        let cwd = "fixtures/suppression/type_check_only_with_regular_rule";

        let (stdout, result) = Tester::new().with_cwd(cwd.into()).test_output(&["index.ts"]);
        assert!(
            matches!(result, CliRunResult::LintSucceeded),
            "Expected the suppression fixture to pass regular lint, got {result:?}.\nOutput: {stdout}"
        );

        let (stdout, result) =
            Tester::new().with_cwd(cwd.into()).test_output(&["--type-check-only", "index.ts"]);

        assert!(
            matches!(result, CliRunResult::LintSucceeded),
            "Expected LintSucceeded, got {result:?}.\nOutput: {stdout}"
        );
        assert!(
            !stdout.contains("suppressions that do not occur anymore"),
            "Regular lint suppressions must not be validated when their rules did not run.\nOutput: {stdout}"
        );
    }

    #[test]
    fn test_type_check_only_rejects_suppression_update_flags() {
        for flag in ["--suppress-all", "--prune-suppressions"] {
            let (stdout, result) = Tester::new()
                .with_cwd("fixtures/suppression/type_check_only_with_regular_rule".into())
                .test_output(&["--type-check-only", flag, "index.ts"]);

            assert!(
                matches!(result, CliRunResult::InvalidOptionTypeCheckOnlyWithSuppressionUpdate),
                "Expected {flag} to be rejected with --type-check-only, got {result:?}.\nOutput: {stdout}"
            );
            assert!(
                stdout.contains("cannot be used with suppression update flags"),
                "Expected an invalid option message for {flag}.\nOutput: {stdout}"
            );
        }
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_prune_errors_warning() {
        let args = &["--type-aware", "--type-check"];
        Tester::new()
            .with_cwd("fixtures/suppression/fixed_violations_are_reported".into())
            .test_and_snapshot(args);
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_unpruned_suppressions_exit_code() {
        // When there are stale suppressions, exit code should be 2
        let args = &["--type-aware", "--type-check"];
        let (_stdout, result) = Tester::new()
            .with_cwd("fixtures/suppression/fixed_violations_are_reported".into())
            .test_output(args);
        assert!(
            matches!(result, CliRunResult::LintUnprunedSuppressions),
            "Expected LintUnprunedSuppressions (exit code 2), got {result:?}"
        );
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_report_one_new_error_but_filter_the_rest() {
        let args = &["--type-aware", "--type-check"];
        Tester::new()
            .with_cwd("fixtures/suppression/reports_new_errors_and_filter_existing".into())
            .test_and_snapshot(args);
    }

    #[test]
    fn test_file_malformed() {
        let args = &[];
        Tester::new()
            .with_cwd("fixtures/suppression/file_malformed".into())
            .test_and_snapshot(args);
    }

    #[test]
    #[ignore = "disabled while big-endian CI runs on PRs"]
    fn test_happy_path() {
        let args = &["--suppress-all", "--type-aware", "--type-check"];
        let suppression = SuppressionTester::new()
            .with_cwd("project_without_suppression_detected")
            .with_setup_file(false)
            .with_expected_file(true);

        suppression.test(args);

        let (stdout, result) = Tester::new()
            .with_cwd("fixtures/suppression/project_without_suppression_detected".into())
            .test_output(args);
        // --suppress-all should exit with success (matching ESLint behavior)
        assert!(
            matches!(result, CliRunResult::LintSucceeded),
            "Expected LintSucceeded, got {result:?}"
        );

        // The error comes from the type check error. Type check errors aren't filtered
        assert!(stdout.contains("Found 3 warnings and 2 errors."), "Unexpected errors found");
    }

    #[test]
    fn test_no_diagnostics_with_suppress_all() {
        SuppressionTester::new()
            .with_cwd("no_diagnostics_with_suppress_all")
            .with_setup_file(false)
            .with_expected_file(true)
            .test(&["--suppress-all"]);
    }

    #[test]
    fn test_pruning_without_file_does_not_create_one() {
        // This diverges from ESLint now, but probably not a big deal either way
        SuppressionTester::new()
            .with_cwd("pruning_cannot_create_a_file")
            .with_setup_file(false)
            .with_expected_file(false)
            .test(&["--prune-suppressions", "--type-aware", "--type-check"]);
    }

    #[test]
    fn test_prune_removes_deleted_file_entries() {
        SuppressionTester::new()
            .with_cwd("prune_deleted_file")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .test(&["--prune-suppressions"]);
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_only_non_fixed_diagnostics_are_reported() {
        SuppressionTester::new()
            .with_cwd("only_non_fixed_diagnostics_are_reported")
            .with_setup_file(false)
            .with_expected_file(true)
            .with_backup_file(false)
            .with_files_fixed(true)
            .test(&["--fix", "--suppress-all", "--type-aware", "--type-check"]);
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_update_file_after_fixing() {
        SuppressionTester::new()
            .with_cwd("update_file_after_fixing")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .with_files_fixed(false)
            .test(&["--fix", "--suppress-all", "--type-aware", "--type-check"]);
    }

    #[test]
    fn test_not_update_dangerous_fix_violation_with_fix_only() {
        SuppressionTester::new()
            .with_cwd("not_update_dangerous_fix_violation_with_fix_only")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .with_files_fixed(true)
            .test(&["--fix", "--type-aware", "--type-check"]);
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_updated_dangerous_fix() {
        SuppressionTester::new()
            .with_cwd("updated_dangerous_fix")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .with_files_fixed(true)
            // Adding both fix see https://github.com/oxc-project/oxc/pull/13366, https://github.com/oxc-project/oxc/issues/12491
            .test(&[
                "--fix",
                "--fix-suggestions",
                "--fix-dangerously",
                "--type-aware",
                "--type-check",
            ]);
    }

    #[test]
    fn test_not_filtered_suggestion_fix_not_applied() {
        SuppressionTester::new()
            .with_cwd("not_update_suggestion_fix_violation_with_fix_only")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .with_files_fixed(true)
            .test(&["--fix", "--type-aware", "--type-check"]);
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_updated_suggestion_fix_applied() {
        SuppressionTester::new()
            .with_cwd("updated_suggestion_fix")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .with_files_fixed(true)
            .test(&["--fix", "--fix-suggestions", "--type-aware", "--type-check"]);
    }

    #[test]
    fn test_with_suppress_all_arg_and_pruned_errors() {
        SuppressionTester::new()
            .with_cwd("with_suppress_all_arg_and_pruned_errors")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .test(&["--suppress-all", "--type-aware", "--type-check"]);
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_prunning_errors_update_the_file_when_errors_are_pruned() {
        SuppressionTester::new()
            .with_cwd("with_arg_and_pruned_errors")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .test(&["--prune-suppressions", "--type-aware", "--type-check"]);
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_suppressiong_errors_update_the_file_when_errors_are_increased() {
        SuppressionTester::new()
            .with_cwd("with_arg_and_increased_errors")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .test(&["--suppress-all", "--type-aware", "--type-check"]);
    }

    #[test]
    fn test_prunning_errors_update_the_file_when_errors_are_increased() {
        SuppressionTester::new()
            .with_cwd("with_arg_and_increased_errors_prune")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .test(&["--prune-suppressions", "--type-aware", "--type-check"]);
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_suppressing_errors_update_the_file_when_errors_are_decreased() {
        SuppressionTester::new()
            .with_cwd("with_arg_and_decreased_errors")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .test(&["--suppress-all", "--type-aware", "--type-check"]);
    }

    #[test]
    #[cfg_attr(target_endian = "big", ignore = "disabled on big-endian")]
    fn test_prunning_errors_update_the_file_when_errors_are_decreased() {
        SuppressionTester::new()
            .with_cwd("with_arg_and_decreased_errors")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .test(&["--prune-suppressions", "--type-aware", "--type-check"]);
    }

    #[test]
    #[cfg(not(target_endian = "big"))]
    fn test_fixing_only_ts_go_errors() {
        SuppressionTester::new()
            .with_cwd("fixing_only_ts_go_errors")
            .with_setup_file(true)
            .with_expected_file(true)
            .with_backup_file(true)
            .with_files_fixed(true)
            .test(&["--fix", "--fix-suggestions", "--type-aware", "--type-check"]);
    }

    #[test]
    fn test_type_errors_not_recorded() {
        SuppressionTester::new()
            .with_cwd("type_errors_not_recorded")
            .with_setup_file(false)
            .with_expected_file(false)
            .with_backup_file(false)
            .test(&["--suppress-all", "--type-check"]);
    }
}
