Skip to content

Commit

Permalink
refactor(linter): move OxlintConfig to Oxlintrc (#5707)
Browse files Browse the repository at this point in the history
  • Loading branch information
DonIsaac committed Sep 17, 2024
1 parent b5ac5a6 commit a438743
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 199 deletions.
195 changes: 9 additions & 186 deletions crates/oxc_linter/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,70 +1,15 @@
mod env;
mod globals;
mod oxlintrc;
mod rules;
mod settings;

use std::path::Path;

use oxc_diagnostics::OxcDiagnostic;
use rustc_hash::FxHashSet;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

pub use self::{
env::OxlintEnv,
globals::OxlintGlobals,
rules::OxlintRules,
oxlintrc::Oxlintrc,
settings::{jsdoc::JSDocPluginSettings, OxlintSettings},
};
use crate::{
rules::RuleEnum,
utils::{is_jest_rule_adapted_to_vitest, read_to_string},
AllowWarnDeny, RuleWithSeverity,
};

/// Oxlint Configuration File
///
/// This configuration is aligned with ESLint v8's configuration schema (`eslintrc.json`).
///
/// Usage: `oxlint -c oxlintrc.json --import-plugin`
///
/// ::: danger NOTE
///
/// Only the `.json` format is supported. You can use comments in configuration files.
///
/// :::
///
/// Example
///
/// `.oxlintrc.json`
///
/// ```json
/// {
/// "env": {
/// "browser": true
/// },
/// "globals": {
/// "foo": "readonly"
/// },
/// "settings": {
/// },
/// "rules": {
/// "eqeqeq": "warn",
/// "import/no-cycle": "error"
/// }
/// }
/// ```
#[derive(Debug, Default, Deserialize, Serialize, JsonSchema)]
#[serde(default)]
pub struct OxlintConfig {
/// See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html).
pub rules: OxlintRules,
pub settings: OxlintSettings,
/// Environments enable and disable collections of global variables.
pub env: OxlintEnv,
/// Enabled or disabled specific global variables.
pub globals: OxlintGlobals,
}

#[derive(Debug, Default)]
pub(crate) struct LintConfig {
Expand All @@ -75,134 +20,12 @@ pub(crate) struct LintConfig {
pub(crate) globals: OxlintGlobals,
}

impl From<OxlintConfig> for LintConfig {
fn from(config: OxlintConfig) -> Self {
impl From<Oxlintrc> for LintConfig {
fn from(config: Oxlintrc) -> Self {
Self { settings: config.settings, env: config.env, globals: config.globals }
}
}

impl OxlintConfig {
/// # Errors
///
/// * Parse Failure
pub fn from_file(path: &Path) -> Result<Self, OxcDiagnostic> {
let mut string = read_to_string(path).map_err(|e| {
OxcDiagnostic::error(format!("Failed to parse config {path:?} with error {e:?}"))
})?;

// jsonc support
json_strip_comments::strip(&mut string).map_err(|err| {
OxcDiagnostic::error(format!("Failed to parse jsonc file {path:?}: {err:?}"))
})?;

let json = serde_json::from_str::<serde_json::Value>(&string).map_err(|err| {
let guess = mime_guess::from_path(path);
let err = match guess.first() {
// syntax error
Some(mime) if mime.subtype() == "json" => err.to_string(),
Some(_) => "Only json configuration is supported".to_string(),
None => {
format!(
"{err}, if the configuration is not a json file, please use json instead."
)
}
};
OxcDiagnostic::error(format!("Failed to parse eslint config {path:?}.\n{err}"))
})?;

let config = Self::deserialize(&json).map_err(|err| {
OxcDiagnostic::error(format!("Failed to parse config with error {err:?}"))
})?;

Ok(config)
}

#[allow(clippy::option_if_let_else)]
pub fn override_rules(
&self,
rules_for_override: &mut FxHashSet<RuleWithSeverity>,
all_rules: &[RuleEnum],
) {
use itertools::Itertools;
let mut rules_to_replace: Vec<RuleWithSeverity> = vec![];
let mut rules_to_remove: Vec<RuleWithSeverity> = vec![];

// Rules can have the same name but different plugin names
let lookup = self.rules.iter().into_group_map_by(|r| r.rule_name.as_str());

for (name, rule_configs) in &lookup {
match rule_configs.len() {
0 => unreachable!(),
1 => {
let rule_config = &rule_configs[0];
let (rule_name, plugin_name) = transform_rule_and_plugin_name(
&rule_config.rule_name,
&rule_config.plugin_name,
);
let severity = rule_config.severity;
match severity {
AllowWarnDeny::Warn | AllowWarnDeny::Deny => {
if let Some(rule) = all_rules
.iter()
.find(|r| r.name() == rule_name && r.plugin_name() == plugin_name)
{
let config = rule_config.config.clone().unwrap_or_default();
let rule = rule.read_json(config);
rules_to_replace.push(RuleWithSeverity::new(rule, severity));
}
}
AllowWarnDeny::Allow => {
if let Some(rule) = rules_for_override
.iter()
.find(|r| r.name() == rule_name && r.plugin_name() == plugin_name)
{
let rule = rule.clone();
rules_to_remove.push(rule);
}
}
}
}
_ => {
// For overlapping rule names, use the "error" one
// "no-loss-of-precision": "off",
// "@typescript-eslint/no-loss-of-precision": "error"
if let Some(rule_config) =
rule_configs.iter().find(|r| r.severity.is_warn_deny())
{
if let Some(rule) = rules_for_override.iter().find(|r| r.name() == *name) {
let config = rule_config.config.clone().unwrap_or_default();
rules_to_replace
.push(RuleWithSeverity::new(rule.read_json(config), rule.severity));
}
} else if rule_configs.iter().all(|r| r.severity.is_allow()) {
if let Some(rule) = rules_for_override.iter().find(|r| r.name() == *name) {
rules_to_remove.push(rule.clone());
}
}
}
}
}

for rule in rules_to_remove {
rules_for_override.remove(&rule);
}
for rule in rules_to_replace {
rules_for_override.replace(rule);
}
}
}

fn transform_rule_and_plugin_name<'a>(
rule_name: &'a str,
plugin_name: &'a str,
) -> (&'a str, &'a str) {
if plugin_name == "vitest" && is_jest_rule_adapted_to_vitest(rule_name) {
return (rule_name, "jest");
}

(rule_name, plugin_name)
}

#[cfg(test)]
mod test {
use std::env;
Expand All @@ -211,19 +34,19 @@ mod test {
use rustc_hash::FxHashSet;
use serde::Deserialize;

use super::OxlintConfig;
use super::Oxlintrc;
use crate::rules::RULES;

#[test]
fn test_from_file() {
let fixture_path = env::current_dir().unwrap().join("fixtures/eslint_config.json");
let config = OxlintConfig::from_file(&fixture_path).unwrap();
let config = Oxlintrc::from_file(&fixture_path).unwrap();
assert!(!config.rules.is_empty());
}

#[test]
fn test_deserialize() {
let config = OxlintConfig::deserialize(&serde_json::json!({
let config = Oxlintrc::deserialize(&serde_json::json!({
"rules": {
"no-console": "off",
"no-debugger": 2,
Expand Down Expand Up @@ -253,7 +76,7 @@ mod test {
}));
assert!(config.is_ok());

let OxlintConfig { rules, settings, env, globals } = config.unwrap();
let Oxlintrc { rules, settings, env, globals } = config.unwrap();
assert!(!rules.is_empty());
assert_eq!(
settings.jsx_a11y.polymorphic_prop_name.as_ref().map(CompactStr::as_str),
Expand All @@ -267,7 +90,7 @@ mod test {
fn test_vitest_rule_replace() {
let fixture_path: std::path::PathBuf =
env::current_dir().unwrap().join("fixtures/eslint_config_vitest_replace.json");
let config = OxlintConfig::from_file(&fixture_path).unwrap();
let config = Oxlintrc::from_file(&fixture_path).unwrap();
let mut set = FxHashSet::default();
config.override_rules(&mut set, &RULES);

Expand Down
Loading

0 comments on commit a438743

Please sign in to comment.