Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 25 additions & 23 deletions crates/oxc_linter/src/rules/eslint/sort_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ use serde::{Deserialize, Serialize};
use crate::{AstNode, context::LintContext, rule::Rule};

#[derive(Debug, Default, Clone)]
pub struct SortKeys(Box<SortKeysOptions>);
pub struct SortKeys(Box<SortKeysConfig>);

#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
/// Sorting order for keys. Accepts "asc" for ascending or "desc" for descending.
pub enum SortOrder {
Desc,
#[default]
Expand All @@ -26,8 +27,6 @@ pub enum SortOrder {
#[derive(Debug, Clone, JsonSchema)]
#[serde(rename_all = "camelCase", default)]
pub struct SortKeysOptions {
/// Sorting order for keys. Accepts "asc" for ascending or "desc" for descending.
sort_order: SortOrder,
/// Whether the sort comparison is case-sensitive (A < a when true).
case_sensitive: bool,
/// Use natural sort order so that, for example, "a2" comes before "a10".
Expand All @@ -42,7 +41,6 @@ impl Default for SortKeysOptions {
fn default() -> Self {
// we follow the eslint defaults
Self {
sort_order: SortOrder::Asc,
case_sensitive: true,
natural: false,
min_keys: 2,
Expand All @@ -51,11 +49,16 @@ impl Default for SortKeysOptions {
}
}

impl std::ops::Deref for SortKeys {
type Target = SortKeysOptions;
#[derive(Debug, Default, Clone, JsonSchema)]
#[serde(default)]
pub struct SortKeysConfig(SortOrder, SortKeysOptions);

fn deref(&self) -> &Self::Target {
&self.0
impl SortKeys {
fn sort_order(&self) -> &SortOrder {
&(*self.0).0
}
fn options(&self) -> &SortKeysOptions {
&(*self.0).1
}
}

Expand Down Expand Up @@ -94,7 +97,7 @@ declare_oxc_lint!(
eslint,
style,
conditional_fix,
config = SortKeysOptions
config = SortKeysConfig
);

impl Rule for SortKeys {
Expand Down Expand Up @@ -130,20 +133,19 @@ impl Rule for SortKeys {
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);

Self(Box::new(SortKeysOptions {
Self(Box::new(SortKeysConfig(
sort_order,
case_sensitive,
natural,
min_keys,
allow_line_separated_groups,
}))
SortKeysOptions { case_sensitive, natural, min_keys, allow_line_separated_groups },
)))
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::ObjectExpression(dec) = node.kind() {
if dec.properties.len() < self.min_keys {
let options = self.options();
if dec.properties.len() < options.min_keys {
return;
}
let sort_order = self.sort_order().clone();

let mut property_groups: Vec<Vec<String>> = vec![vec![]];

Expand All @@ -157,7 +159,7 @@ impl Rule for SortKeys {
}
ObjectPropertyKind::ObjectProperty(obj) => {
let Some(key) = obj.key.static_name() else { continue };
if i != dec.properties.len() - 1 && self.allow_line_separated_groups {
if i != dec.properties.len() - 1 && options.allow_line_separated_groups {
let text_between = extract_text_between_spans(
source_text,
prop.span(),
Expand All @@ -175,7 +177,7 @@ impl Rule for SortKeys {
}
}

if !self.case_sensitive {
if !options.case_sensitive {
for group in &mut property_groups {
*group = group
.iter()
Expand All @@ -186,13 +188,13 @@ impl Rule for SortKeys {

let mut sorted_property_groups = property_groups.clone();
for group in &mut sorted_property_groups {
if self.natural {
if options.natural {
natural_sort(group);
} else {
alphanumeric_sort(group);
}

if self.sort_order == SortOrder::Desc {
if sort_order == SortOrder::Desc {
group.reverse();
}
}
Expand Down Expand Up @@ -259,7 +261,7 @@ impl Rule for SortKeys {
let keys_for_cmp: Vec<String> = props
.iter()
.map(|(k, _)| {
if self.case_sensitive {
if options.case_sensitive {
k.clone()
} else {
k.cow_to_ascii_lowercase().to_string()
Expand All @@ -270,12 +272,12 @@ impl Rule for SortKeys {
// Compute the sorted key order using the same helpers as the main rule
// so the autofix ordering matches the diagnostic ordering.
let mut sorted_keys = keys_for_cmp.clone();
if self.natural {
if options.natural {
natural_sort(&mut sorted_keys);
} else {
alphanumeric_sort(&mut sorted_keys);
}
if self.sort_order == SortOrder::Desc {
if sort_order == SortOrder::Desc {
sorted_keys.reverse();
}

Expand Down
20 changes: 15 additions & 5 deletions tasks/website/src/linter/json_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ struct Root {
pub(super) struct Section {
level: String,
title: String,
instance_type: Option<String>,
description: String,
pub(super) instance_type: Option<String>,
pub(super) description: String,
pub(super) default: Option<String>,
sections: Vec<Section>,
}
Expand Down Expand Up @@ -144,13 +144,23 @@ impl Renderer {
return array
.items
.iter()
.map(|item| match item {
.flat_map(|item| match item {
// array
SingleOrVec::Single(schema) => {
let schema_object = Self::get_schema_object(schema);
let key = parent_key.map_or_else(String::new, |k| format!("{k}[n]"));
self.render_schema_impl(depth + 1, &key, schema_object)
vec![self.render_schema_impl(depth + 1, &key, schema_object)]
}
SingleOrVec::Vec(_) => panic!(),
// tuple
SingleOrVec::Vec(schema) => schema
.iter()
.enumerate()
.map(|(i, schema)| {
let schema_object = Self::get_schema_object(schema);
let key = parent_key.map_or_else(String::new, |k| format!("{k}[{i}]"));
self.render_schema_impl(depth + 1, &key, schema_object)
})
.collect(),
})
.collect();
}
Expand Down
48 changes: 48 additions & 0 deletions tasks/website/src/linter/rules/doc_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{
path::PathBuf,
};

use itertools::Itertools;
use oxc_linter::{LintPlugins, table::RuleTableRow};
use schemars::{
JsonSchema, SchemaGenerator,
Expand Down Expand Up @@ -122,6 +123,40 @@ const source = `{}`;
}

fn rule_config(&self, schema: &SchemaObject) -> String {
if let Some(array) = &schema.array
&& let Some(SingleOrVec::Vec(options)) = &array.items
{
// multiple options
return options
.iter()
.enumerate()
.map(|(i, schema)| match schema {
Schema::Object(schema_object) => {
let section = self.renderer.render_schema(3, "", schema_object);
let title = format!("\n### The {} option\n", ordinal(i + 1));
let instance_type = section.instance_type.as_ref().map_or_else(
String::new,
|instance_type| {
if instance_type == "object" {
"\nThis option is an object with the following properties:\n"
.to_string()
} else {
format!("\ntype: `{instance_type}`\n")
}
},
);
let rendered = section.to_md(&self.renderer);
let description = if section.description.is_empty() {
section.description
} else {
format!("\n{}\n", section.description)
};
format!("{title}{instance_type}{description}{rendered}")
}
Schema::Bool(_) => panic!(),
})
.join("");
}
let mut section = self.renderer.render_schema(2, "", schema);
if section.default.is_none()
&& let Some(SingleOrVec::Single(ty)) = &schema.instance_type
Expand Down Expand Up @@ -262,3 +297,16 @@ To **enable** this rule in the CLI or using the config file, you can use:
"
)
}

fn ordinal(n: usize) -> String {
let suffix = match n % 100 {
11..=13 => "th",
_ => match n % 10 {
1 => "st",
2 => "nd", // spellchecker:disable-line
3 => "rd",
_ => "th",
},
};
format!("{n}{suffix}")
}
Loading