Skip to content

Commit

Permalink
refactor(linter): add schemars and serde traits to AllowWarnDeny and …
Browse files Browse the repository at this point in the history
…RuleCategories (#6119)
  • Loading branch information
DonIsaac committed Sep 29, 2024
1 parent 40bd919 commit 82b8f21
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 10 deletions.
134 changes: 125 additions & 9 deletions crates/oxc_linter/src/options/allow_warn_deny.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
use std::{convert::From, fmt};
use std::{
convert::From,
fmt::{self, Display},
};

use oxc_diagnostics::{OxcDiagnostic, Severity};
use schemars::{schema::SchemaObject, JsonSchema};
use serde::{de, Deserialize, Serialize};
use serde_json::{Number, Value};

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum AllowWarnDeny {
Allow, // Off
Warn, // Warn
Expand Down Expand Up @@ -65,18 +70,89 @@ impl TryFrom<&Value> for AllowWarnDeny {
}
}

fn invalid_int_severity<D: Display>(value: D) -> OxcDiagnostic {
OxcDiagnostic::error(format!(
r#"Failed to parse rule severity, expected one of `0`, `1` or `2`, but got {value}"#
))
}

impl TryFrom<u64> for AllowWarnDeny {
type Error = OxcDiagnostic;
fn try_from(value: u64) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Allow),
1 => Ok(Self::Warn),
2 => Ok(Self::Deny),
x => Err(invalid_int_severity(x)),
}
}
}

impl TryFrom<i64> for AllowWarnDeny {
type Error = OxcDiagnostic;

fn try_from(value: i64) -> Result<Self, Self::Error> {
if value < 0 {
return Err(invalid_int_severity("a negative number"));
}
#[allow(clippy::cast_sign_loss)]
Self::try_from(value as u64)
}
}

impl TryFrom<&Number> for AllowWarnDeny {
type Error = OxcDiagnostic;

fn try_from(value: &Number) -> Result<Self, Self::Error> {
match value.as_i64() {
Some(0) => Ok(Self::Allow),
Some(1) => Ok(Self::Warn),
Some(2) => Ok(Self::Deny),
_ => Err(OxcDiagnostic::error(format!(
r#"Failed to parse rule severity, expected one of `0`, `1` or `2`, but got {value:?}"#
))),
let value = value.as_i64().ok_or_else(|| invalid_int_severity(value))?;
Self::try_from(value)
}
}

impl<'de> Deserialize<'de> for AllowWarnDeny {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct AllowWarnDenyVisitor;

impl<'de> de::Visitor<'de> for AllowWarnDenyVisitor {
type Value = AllowWarnDeny;

fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
"an int between 0 and 2 or a string".fmt(f)
}

fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
Self::Value::try_from(v).map_err(de::Error::custom)
}

fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
Self::Value::try_from(v).map_err(de::Error::custom)
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Self::Value::try_from(v).map_err(de::Error::custom)
}

fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: de::Error,
{
Self::Value::try_from(v.as_str()).map_err(de::Error::custom)
}
}

deserializer.deserialize_any(AllowWarnDenyVisitor)
}
}

Expand Down Expand Up @@ -128,3 +204,43 @@ impl From<AllowWarnDeny> for Severity {
}
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_serialize() {
let tests = [
(AllowWarnDeny::Allow, r#""allow""#),
(AllowWarnDeny::Warn, r#""warn""#),
(AllowWarnDeny::Deny, r#""deny""#),
];
for (input, expected) in tests {
assert_eq!(serde_json::to_string(&input).unwrap(), expected);
}
}

#[test]
fn test_deserialize() {
let tests = [
// allow
(r#""allow""#, AllowWarnDeny::Allow),
(r#""off""#, AllowWarnDeny::Allow),
("0", AllowWarnDeny::Allow),
// warn
(r#""warn""#, AllowWarnDeny::Warn),
("1", AllowWarnDeny::Warn),
// deny
(r#""error""#, AllowWarnDeny::Deny),
(r#""deny""#, AllowWarnDeny::Deny),
("2", AllowWarnDeny::Deny),
];

for (input, expected) in tests {
let msg = format!("input: {input}");
let actual: AllowWarnDeny = serde_json::from_str(input).expect(&msg);
assert_eq!(actual, expected);
}
}
}
27 changes: 26 additions & 1 deletion crates/oxc_linter/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use std::{
};

use oxc_semantic::SymbolId;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::{
context::{ContextHost, LintContext},
Expand Down Expand Up @@ -61,7 +63,8 @@ pub trait RuleMeta {
}

/// Rule categories defined by rust-clippy
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum RuleCategory {
/// Code that is outright wrong or useless
Correctness,
Expand Down Expand Up @@ -277,6 +280,7 @@ impl RuleWithSeverity {
mod test {
use markdown::{to_html_with_options, Options};

use super::RuleCategory;
use crate::rules::RULES;

#[test]
Expand All @@ -295,4 +299,25 @@ mod test {
assert!(!html.is_empty());
}
}

#[test]
fn test_deserialize_rule_category() {
let tests = [
("correctness", RuleCategory::Correctness),
("suspicious", RuleCategory::Suspicious),
("restriction", RuleCategory::Restriction),
("perf", RuleCategory::Perf),
("pedantic", RuleCategory::Pedantic),
("style", RuleCategory::Style),
("nursery", RuleCategory::Nursery),
];

for (input, expected) in tests {
let de: RuleCategory = serde_json::from_str(&format!("{input:?}")).unwrap();
// deserializes to expected value
assert_eq!(de, expected, "{input}");
// try_from on a str produces the same value as deserializing
assert_eq!(de, RuleCategory::try_from(input).unwrap(), "{input}");
}
}
}

0 comments on commit 82b8f21

Please sign in to comment.