Skip to content

Commit

Permalink
feat(rule/solidjs): add no-react-specific-props (#2427)
Browse files Browse the repository at this point in the history
Co-authored-by: Emanuele Stoppa <[email protected]>
  • Loading branch information
marvin-j97 and ematipico committed Apr 18, 2024
1 parent 4164331 commit fd7ba41
Show file tree
Hide file tree
Showing 14 changed files with 336 additions and 8 deletions.
6 changes: 6 additions & 0 deletions crates/biome_analyze/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ pub enum RuleSource {
EslintReact(&'static str),
/// Rules from [Eslint Plugin React Hooks](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/README.md)
EslintReactHooks(&'static str),
/// Rules from [Eslint Plugin Solid](https://github.com/solidjs-community/eslint-plugin-solid)
EslintSolid(&'static str),
/// Rules from [Eslint Plugin Sonar](https://github.com/SonarSource/eslint-plugin-sonarjs)
EslintSonarJs(&'static str),
/// Rules from [Eslint Plugin Stylistic](https://eslint.style)
Expand Down Expand Up @@ -108,6 +110,7 @@ impl std::fmt::Display for RuleSource {
RuleSource::EslintJsxA11y(_) => write!(f, "eslint-plugin-jsx-a11y"),
RuleSource::EslintReact(_) => write!(f, "eslint-plugin-react"),
RuleSource::EslintReactHooks(_) => write!(f, "eslint-plugin-react-hooks"),
RuleSource::EslintSolid(_) => write!(f, "eslint-plugin-solid"),
RuleSource::EslintSonarJs(_) => write!(f, "eslint-plugin-sonarjs"),
RuleSource::EslintStylistic(_) => write!(f, "eslint-plugin-stylistic"),
RuleSource::EslintTypeScript(_) => write!(f, "typescript-eslint"),
Expand Down Expand Up @@ -153,6 +156,7 @@ impl RuleSource {
| Self::EslintReact(rule_name)
| Self::EslintReactHooks(rule_name)
| Self::EslintTypeScript(rule_name)
| Self::EslintSolid(rule_name)
| Self::EslintSonarJs(rule_name)
| Self::EslintStylistic(rule_name)
| Self::EslintUnicorn(rule_name)
Expand All @@ -172,6 +176,7 @@ impl RuleSource {
Self::EslintReact(rule_name) => format!("react/{rule_name}"),
Self::EslintReactHooks(rule_name) => format!("react-hooks/{rule_name}"),
Self::EslintTypeScript(rule_name) => format!("@typescript-eslint/{rule_name}"),
Self::EslintSolid(rule_name) => format!("solidjs/{rule_name}"),
Self::EslintSonarJs(rule_name) => format!("sonarjs/{rule_name}"),
Self::EslintStylistic(rule_name) => format!("@stylistic/{rule_name}"),
Self::EslintUnicorn(rule_name) => format!("unicorn/{rule_name}"),
Expand All @@ -192,6 +197,7 @@ impl RuleSource {
Self::EslintReact(rule_name) => format!("https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/{rule_name}.md"),
Self::EslintReactHooks(_) => "https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/README.md".to_string(),
Self::EslintTypeScript(rule_name) => format!("https://typescript-eslint.io/rules/{rule_name}"),
Self::EslintSolid(rule_name) => format!("https://github.com/solidjs-community/eslint-plugin-solid/blob/main/docs/{rule_name}.md"),
Self::EslintSonarJs(rule_name) => format!("https://github.com/SonarSource/eslint-plugin-sonarjs/blob/HEAD/docs/rules/{rule_name}.md"),
Self::EslintStylistic(rule_name) => format!("https://eslint.style/rules/default/{rule_name}"),
Self::EslintUnicorn(rule_name) => format!("https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/{rule_name}.md"),
Expand Down
10 changes: 10 additions & 0 deletions crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

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

35 changes: 27 additions & 8 deletions crates/biome_configuration/src/linter/rules.rs

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

1 change: 1 addition & 0 deletions crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ define_categories! {
"lint/nursery/noFlatMapIdentity": "https://biomejs.dev/linter/rules/no-flat-map-identity",
"lint/nursery/noMisplacedAssertion": "https://biomejs.dev/linter/rules/no-misplaced-assertion",
"lint/nursery/noNodejsModules": "https://biomejs.dev/linter/rules/no-nodejs-modules",
"lint/nursery/noReactSpecificProps": "https://biomejs.dev/linter/rules/no-react-specific-props",
"lint/nursery/noRestrictedImports": "https://biomejs.dev/linter/rules/no-restricted-imports",
"lint/nursery/noTypeOnlyImportAttributes": "https://biomejs.dev/linter/rules/no-type-only-import-attributes",
"lint/nursery/noUndeclaredDependencies": "https://biomejs.dev/linter/rules/no-undeclared-dependencies",
Expand Down
2 changes: 2 additions & 0 deletions crates/biome_js_analyze/src/lint/nursery.rs

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::JsRuleAction;
use biome_analyze::context::RuleContext;
use biome_analyze::{declare_rule, ActionCategory, Ast, Rule, RuleDiagnostic, RuleSource};
use biome_console::markup;
use biome_diagnostics::Applicability;
use biome_js_factory::make::{jsx_ident, jsx_name};
use biome_js_syntax::{AnyJsxAttributeName, JsxAttribute};
use biome_rowan::{AstNode, BatchMutationExt, TextRange};

declare_rule! {
/// Prevents React-specific JSX properties from being used.
///
/// This rule is intended for use in JSX-based frameworks (mainly Solid.js)
/// that do not use React-style prop names.
///
/// ## Examples
///
/// ### Invalid
///
/// ```js,expect_diagnostic
/// <Hello className="John" />
/// ```
///
/// ### Valid
///
/// ```js
/// <Hello class="Doe" />
/// ```
pub NoReactSpecificProps {
version: "next",
name: "noReactSpecificProps",
sources: &[RuleSource::EslintSolid("no-react-specific-props")],
recommended: false,
}
}

const REACT_SPECIFIC_JSX_PROPS: &[&str] = &["className", "htmlFor"];

fn get_replacement_for_react_prop(str: &str) -> Option<&'static str> {
match str {
"className" => Some("class"),
"htmlFor" => Some("for"),
_ => None,
}
}

impl Rule for NoReactSpecificProps {
type Query = Ast<JsxAttribute>;
type State = (TextRange, &'static str);
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let attribute = ctx.query();
let name = attribute.name().ok()?;
let range = name.range();
let name = name.text();

if REACT_SPECIFIC_JSX_PROPS.contains(&name.as_str()) {
let replacement = get_replacement_for_react_prop(&name)?;
Some((range, replacement))
} else {
None
}
}

fn diagnostic(_: &RuleContext<Self>, (range, _): &Self::State) -> Option<RuleDiagnostic> {
Some(
RuleDiagnostic::new(
rule_category!(),
range,
markup!("This JSX attribute is specific to React."),
)
.detail(
range,
"This attribute may not be supported by non-React frameworks, as it is not native to HTML.",
),
)
}

fn action(ctx: &RuleContext<Self>, (_, replacement): &Self::State) -> Option<JsRuleAction> {
let mut mutation = ctx.root().begin();
let original_name_node = ctx.query().name().ok()?;

let new_name_node = AnyJsxAttributeName::JsxName(jsx_name(jsx_ident(replacement)));
mutation.replace_node(original_name_node, new_name_node);

Some(JsRuleAction {
category: ActionCategory::QuickFix,
applicability: Applicability::Always,
message: markup! {
{format!("Replace this attribute name with {replacement:?}")}
}
.to_owned(),
mutation,
})
}
}
2 changes: 2 additions & 0 deletions crates/biome_js_analyze/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ pub type NoPrototypeBuiltins =
<lint::suspicious::no_prototype_builtins::NoPrototypeBuiltins as biome_analyze::Rule>::Options;
pub type NoReExportAll =
<lint::performance::no_re_export_all::NoReExportAll as biome_analyze::Rule>::Options;
pub type NoReactSpecificProps =
<lint::nursery::no_react_specific_props::NoReactSpecificProps as biome_analyze::Rule>::Options;
pub type NoRedeclare =
<lint::suspicious::no_redeclare::NoRedeclare as biome_analyze::Rule>::Options;
pub type NoRedundantAlt =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//Case with className
<div className="greeting">Hello world!</div>;

//Case with className with expression
<div className={"greeting"}>Hello world!</div>;

//Case with htmlFor
<div htmlFor="greeting">Hello world!</div>;
Loading

0 comments on commit fd7ba41

Please sign in to comment.