Skip to content

Commit

Permalink
feat: add reserveFirst option
Browse files Browse the repository at this point in the history
  • Loading branch information
vohoanglong0107 committed Apr 30, 2024
1 parent 0d48b11 commit 870e6bf
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 3 deletions.
86 changes: 83 additions & 3 deletions crates/biome_js_analyze/src/lint/nursery/use_jsx_sort_props.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use biome_deserialize::{
Deserializable, DeserializableValue, DeserializationDiagnostic, VisitableType,
};
use biome_js_factory::make::jsx_attribute_list;
use biome_rowan::BatchMutationExt;
use std::cmp::Ordering;
use std::{cmp::Ordering, str::FromStr};

use biome_analyze::{
context::RuleContext, declare_rule, ActionCategory, Ast, FixKind, Rule, RuleDiagnostic,
Expand All @@ -11,7 +14,7 @@ use biome_deserialize_macros::Deserializable;
use biome_diagnostics::Applicability;
use biome_js_syntax::{AnyJsxAttribute, JsxAttribute, JsxAttributeList};
use biome_rowan::{AstNode, TextRange};
use serde::{Deserialize, Serialize};
use serde::{de::IntoDeserializer, Deserialize, Serialize};

use crate::JsRuleAction;

Expand Down Expand Up @@ -122,7 +125,8 @@ pub struct UseJsxSortPropsOptions {
ignore_case: bool,
#[serde(default, skip_serializing_if = "is_default")]
no_sort_alphabetically: bool,
// TODO: add reserved_first and locale options
#[serde(default, skip_serializing_if = "is_default")]
reserved_first: ReservedFirstBehavior,
}

#[derive(Clone, Debug, Default, Deserializable, Serialize, Deserialize, PartialEq, Eq)]
Expand All @@ -145,6 +149,53 @@ pub enum ShorthandBehavior {
Last,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum ReservedFirstBehavior {
Enabled(bool),
ReservedProps(Vec<ReservedProps>),
}

impl Default for ReservedFirstBehavior {
fn default() -> Self {
ReservedFirstBehavior::Enabled(false)
}
}

impl Deserializable for ReservedFirstBehavior {
fn deserialize(
value: &impl DeserializableValue,
name: &str,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) -> Option<Self> {
if value.visitable_type()? == VisitableType::ARRAY {
Deserializable::deserialize(value, name, diagnostics).map(Self::ReservedProps)
} else {
Deserializable::deserialize(value, name, diagnostics).map(Self::Enabled)
}
}
}

#[derive(Clone, Debug, Serialize, Deserializable, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub enum ReservedProps {
Children,
DangerouslySetInnerHTML,
Key,
Ref,
}

impl FromStr for ReservedProps {
type Err = serde::de::value::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
<Self as Deserialize>::deserialize(s.into_deserializer())
}
}

fn is_default<T: Default + Eq>(value: &T) -> bool {
value == &T::default()
}
Expand Down Expand Up @@ -275,6 +326,24 @@ fn compare_props(
};
let (a_name, b_name) = (a_name.text(), b_name.text());

if options.reserved_first == ReservedFirstBehavior::Enabled(true) {
if is_reserved(a, None) && !is_reserved(b, None) {
return Ordering::Less;
}
if !is_reserved(a, None) && is_reserved(b, None) {
return Ordering::Greater;
}
}

if let ReservedFirstBehavior::ReservedProps(reserved) = &options.reserved_first {
if is_reserved(a, Some(reserved)) && !is_reserved(b, Some(reserved)) {
return Ordering::Less;
}
if !is_reserved(a, Some(reserved)) && is_reserved(b, Some(reserved)) {
return Ordering::Greater;
}
}

if options.callbacks_last {
if is_callback(a) && !is_callback(b) {
return Ordering::Greater;
Expand Down Expand Up @@ -331,6 +400,17 @@ fn compare_props(
}
}

fn is_reserved(prop: &JsxAttribute, reserved: Option<&[ReservedProps]>) -> bool {
let Ok(prop_name) = prop.name() else {
return false;
};
let prop_name = prop_name.text();
let Ok(prop_name) = ReservedProps::from_str(&prop_name) else {
return false;
};
reserved.map_or(true, |reserved| reserved.contains(&prop_name))
}

fn is_shorthand(prop: &JsxAttribute) -> bool {
prop.initializer().is_none()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/* should not generate diagnostics */
<Hello key={0} ref={johnRef} name="John">
<div dangerouslySetInnerHTML={{__html: 'ESLint Plugin React!'}} ref={dangerDivRef} />
</Hello>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
expression: sortedReservedFirst.jsx
---
# Input
```jsx
/* should not generate diagnostics */
<Hello key={0} ref={johnRef} name="John">
<div dangerouslySetInnerHTML={{__html: 'ESLint Plugin React!'}} ref={dangerDivRef} />
</Hello>

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json",
"linter": {
"rules": {
"nursery": {
"useJsxSortProps": {
"level": "error",
"options": {
"reservedFirst": true
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* should not generate diagnostics */
<Hello ref={johnRef} key={0} name="John" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
expression: sortedReservedFirstArray.jsx
---
# Input
```jsx
/* should not generate diagnostics */
<Hello ref={johnRef} key={0} name="John" />

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json",
"linter": {
"rules": {
"nursery": {
"useJsxSortProps": {
"level": "error",
"options": {
"reservedFirst": ["ref"]
}
}
}
}
}
}
7 changes: 7 additions & 0 deletions packages/@biomejs/backend-jsonrpc/src/workspace.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions packages/@biomejs/biome/configuration_schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 870e6bf

Please sign in to comment.