Skip to content

Commit

Permalink
feat(lint): add noReactDeps
Browse files Browse the repository at this point in the history
  • Loading branch information
fireairforce committed Mar 1, 2025
1 parent 7b7ef04 commit 31adde6
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 85 deletions.
183 changes: 101 additions & 82 deletions crates/biome_configuration/src/analyzer/linter/rules.rs

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ define_categories! {
"lint/nursery/noConsole": "https://biomejs.dev/linter/rules/no-console",
"lint/nursery/noConstantBinaryExpression": "https://biomejs.dev/linter/rules/no-constant-binary-expression",
"lint/nursery/noDescendingSpecificity": "https://biomejs.dev/linter/rules/no-descending-specificity",
"lint/nursery/noDestructuredProps": "https://biomejs.dev/linter/rules/no-destructured-props",
"lint/nursery/noDocumentCookie": "https://biomejs.dev/linter/rules/no-document-cookie",
"lint/nursery/noDocumentImportInPage": "https://biomejs.dev/linter/rules/no-document-import-in-page",
"lint/nursery/noDoneCallback": "https://biomejs.dev/linter/rules/no-done-callback",
Expand Down Expand Up @@ -170,7 +171,7 @@ define_categories! {
"lint/nursery/noPackagePrivateImports": "https://biomejs.dev/linter/rules/no-package-private-imports",
"lint/nursery/noProcessEnv": "https://biomejs.dev/linter/rules/no-process-env",
"lint/nursery/noProcessGlobal": "https://biomejs.dev/linter/rules/no-process-global",
"lint/nursery/noDestructuredProps": "https://biomejs.dev/linter/rules/no-destructured-props",
"lint/nursery/noReactDeps": "https://biomejs.dev/linter/rules/no-react-deps",
"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/noRestrictedTypes": "https://biomejs.dev/linter/rules/no-restricted-types",
Expand Down
3 changes: 2 additions & 1 deletion crates/biome_js_analyze/src/lint/nursery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub mod no_octal_escape;
pub mod no_package_private_imports;
pub mod no_process_env;
pub mod no_process_global;
pub mod no_react_deps;
pub mod no_restricted_imports;
pub mod no_restricted_types;
pub mod no_secrets;
Expand Down Expand Up @@ -55,4 +56,4 @@ pub mod use_sorted_classes;
pub mod use_strict_mode;
pub mod use_trim_start_end;
pub mod use_valid_autocomplete;
declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_await_in_loop :: NoAwaitInLoop , self :: no_common_js :: NoCommonJs , self :: no_constant_binary_expression :: NoConstantBinaryExpression , self :: no_destructured_props :: NoDestructuredProps , self :: no_document_cookie :: NoDocumentCookie , self :: no_document_import_in_page :: NoDocumentImportInPage , self :: no_duplicate_else_if :: NoDuplicateElseIf , self :: no_dynamic_namespace_import_access :: NoDynamicNamespaceImportAccess , self :: no_enum :: NoEnum , self :: no_exported_imports :: NoExportedImports , self :: no_floating_promises :: NoFloatingPromises , self :: no_global_dirname_filename :: NoGlobalDirnameFilename , self :: no_head_element :: NoHeadElement , self :: no_head_import_in_document :: NoHeadImportInDocument , self :: no_img_element :: NoImgElement , self :: no_import_cycles :: NoImportCycles , self :: no_irregular_whitespace :: NoIrregularWhitespace , self :: no_nested_ternary :: NoNestedTernary , self :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractions , self :: no_octal_escape :: NoOctalEscape , self :: no_package_private_imports :: NoPackagePrivateImports , self :: no_process_env :: NoProcessEnv , self :: no_process_global :: NoProcessGlobal , self :: no_restricted_imports :: NoRestrictedImports , self :: no_restricted_types :: NoRestrictedTypes , self :: no_secrets :: NoSecrets , self :: no_static_element_interactions :: NoStaticElementInteractions , self :: no_substr :: NoSubstr , self :: no_template_curly_in_string :: NoTemplateCurlyInString , self :: no_ts_ignore :: NoTsIgnore , self :: no_unwanted_polyfillio :: NoUnwantedPolyfillio , self :: no_useless_escape_in_regex :: NoUselessEscapeInRegex , self :: no_useless_string_raw :: NoUselessStringRaw , self :: no_useless_undefined :: NoUselessUndefined , self :: use_adjacent_overload_signatures :: UseAdjacentOverloadSignatures , self :: use_aria_props_supported_by_role :: UseAriaPropsSupportedByRole , self :: use_at_index :: UseAtIndex , self :: use_collapsed_if :: UseCollapsedIf , self :: use_component_export_only_modules :: UseComponentExportOnlyModules , self :: use_consistent_curly_braces :: UseConsistentCurlyBraces , self :: use_consistent_member_accessibility :: UseConsistentMemberAccessibility , self :: use_consistent_object_definition :: UseConsistentObjectDefinition , self :: use_explicit_type :: UseExplicitType , self :: use_exports_last :: UseExportsLast , self :: use_google_font_display :: UseGoogleFontDisplay , self :: use_google_font_preconnect :: UseGoogleFontPreconnect , self :: use_guard_for_in :: UseGuardForIn , self :: use_parse_int_radix :: UseParseIntRadix , self :: use_sorted_classes :: UseSortedClasses , self :: use_strict_mode :: UseStrictMode , self :: use_trim_start_end :: UseTrimStartEnd , self :: use_valid_autocomplete :: UseValidAutocomplete ,] } }
declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_await_in_loop :: NoAwaitInLoop , self :: no_common_js :: NoCommonJs , self :: no_constant_binary_expression :: NoConstantBinaryExpression , self :: no_destructured_props :: NoDestructuredProps , self :: no_document_cookie :: NoDocumentCookie , self :: no_document_import_in_page :: NoDocumentImportInPage , self :: no_duplicate_else_if :: NoDuplicateElseIf , self :: no_dynamic_namespace_import_access :: NoDynamicNamespaceImportAccess , self :: no_enum :: NoEnum , self :: no_exported_imports :: NoExportedImports , self :: no_floating_promises :: NoFloatingPromises , self :: no_global_dirname_filename :: NoGlobalDirnameFilename , self :: no_head_element :: NoHeadElement , self :: no_head_import_in_document :: NoHeadImportInDocument , self :: no_img_element :: NoImgElement , self :: no_import_cycles :: NoImportCycles , self :: no_irregular_whitespace :: NoIrregularWhitespace , self :: no_nested_ternary :: NoNestedTernary , self :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractions , self :: no_octal_escape :: NoOctalEscape , self :: no_package_private_imports :: NoPackagePrivateImports , self :: no_process_env :: NoProcessEnv , self :: no_process_global :: NoProcessGlobal , self :: no_react_deps :: NoReactDeps , self :: no_restricted_imports :: NoRestrictedImports , self :: no_restricted_types :: NoRestrictedTypes , self :: no_secrets :: NoSecrets , self :: no_static_element_interactions :: NoStaticElementInteractions , self :: no_substr :: NoSubstr , self :: no_template_curly_in_string :: NoTemplateCurlyInString , self :: no_ts_ignore :: NoTsIgnore , self :: no_unwanted_polyfillio :: NoUnwantedPolyfillio , self :: no_useless_escape_in_regex :: NoUselessEscapeInRegex , self :: no_useless_string_raw :: NoUselessStringRaw , self :: no_useless_undefined :: NoUselessUndefined , self :: use_adjacent_overload_signatures :: UseAdjacentOverloadSignatures , self :: use_aria_props_supported_by_role :: UseAriaPropsSupportedByRole , self :: use_at_index :: UseAtIndex , self :: use_collapsed_if :: UseCollapsedIf , self :: use_component_export_only_modules :: UseComponentExportOnlyModules , self :: use_consistent_curly_braces :: UseConsistentCurlyBraces , self :: use_consistent_member_accessibility :: UseConsistentMemberAccessibility , self :: use_consistent_object_definition :: UseConsistentObjectDefinition , self :: use_explicit_type :: UseExplicitType , self :: use_exports_last :: UseExportsLast , self :: use_google_font_display :: UseGoogleFontDisplay , self :: use_google_font_preconnect :: UseGoogleFontPreconnect , self :: use_guard_for_in :: UseGuardForIn , self :: use_parse_int_radix :: UseParseIntRadix , self :: use_sorted_classes :: UseSortedClasses , self :: use_strict_mode :: UseStrictMode , self :: use_trim_start_end :: UseTrimStartEnd , self :: use_valid_autocomplete :: UseValidAutocomplete ,] } }
155 changes: 155 additions & 0 deletions crates/biome_js_analyze/src/lint/nursery/no_react_deps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use biome_analyze::{context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, RuleSourceKind};
use biome_console::markup;
use biome_js_syntax::{JsCallExpression, JsIdentifierBinding};
use biome_rowan::AstNode;

declare_lint_rule! {
/// Disallow usage of dependency arrays in `createEffect` and `createMemo`.
///
/// In Solid, `createEffect` and `createMemo` track dependencies automatically, it's no need to add dependency arrays.
///
/// ## Examples
///
/// ### Invalid
///
/// ```js,expect_diagnostic
/// import { createEffect } from "solid-js";
/// createEffect(() => {
/// console.log(signal());
/// }, [signal()]);
/// ```
///
/// ```js,expect_diagnostic
/// import { createEffect } from "solid-js";
/// createEffect(() => {
/// console.log(signal());
/// }, [signal]);
/// ```
///
/// ```js,expect_diagnostic
/// import { createEffect } from "solid-js";
/// const deps = [signal];
/// createEffect(() => {
/// console.log(signal());
/// }, deps)
/// ```
///
/// ```js,expect_diagnostic
/// import { createMemo } from "solid-js";
/// const value = createMemo(() => computeExpensiveValue(a(), b()), [a(), b()]);
/// ```
///
/// ```js,expect_diagnostic
/// import { createMemo } from "solid-js";
/// const value = createMemo(() => computeExpensiveValue(a(), b()), [a, b]);
/// ```
///
/// ```js,expect_diagnostic
/// import { createMemo } from "solid-js";
/// const value = createMemo(() => computeExpensiveValue(a(), b()), [a, b()]);
/// ```
///
/// ```js,expect_diagnostic
/// import { createMemo } from "solid-js";
/// const deps = [a, b];
/// const value = createMemo(() => computeExpensiveValue(a(), b()), deps);
/// ```
///
/// ```js,expect_diagnostic
/// import { createMemo } from "solid-js";
/// const deps = [a, b];
/// const memoFn = () => computeExpensiveValue(a(), b());
/// const value = createMemo(memoFn, deps);
/// ```
///
/// ### Valid
///
/// ```js
/// import { createEffect } from "solid-js";
/// createEffect(() => {
/// console.log(signal());
/// });
/// ```
///
/// ```js
/// import { createEffect } from "solid-js";
/// createEffect((prev) => {
/// console.log(signal());
/// return prev + 1;
/// }, 0);
/// ```
///
/// ```js
/// import { createEffect } from "solid-js";
/// createEffect((prev) => {
/// console.log(signal());
/// return (prev || 0) + 1;
/// });
/// ```
///
/// ```js
/// import { createEffect } from "solid-js";
/// createEffect((prev) => {
/// console.log(signal());
/// return prev ? prev + 1 : 1;
/// }, undefined);
/// ```
///
/// ```js
/// import { createMemo } from "solid-js";
/// const value = createMemo(() => computeExpensiveValue(a(), b()));
/// ```
///
/// ```js
/// import { createMemo } from "solid-js";
/// const sum = createMemo((prev) => input() + prev, 0);
/// ```
///
/// ```js
/// import { createEffect } from "solid-js";
/// const args = [
/// () => {
/// console.log(signal());
/// },
/// [signal()],
/// ];
/// createEffect(...args);
/// ```
pub NoReactDeps {
version: "next",
name: "noReactDeps",
language: "js",
domains: &[RuleDomain::Solid],
recommended: false,
sources: &[RuleSource::EslintSolid("no-react-deps")],
source_kind: RuleSourceKind::Inspired,
}
}

impl Rule for NoReactDeps {
type Query = Ast<JsCallExpression>;
type State = ();
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let _binding = ctx.query();
Some(())
}

fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
let node = ctx.query();
Some(
RuleDiagnostic::new(
rule_category!(),
node.range(),
markup! {
"Variable is read here."
},
)
.note(markup! {
"This note will give you more information."
}),
)
}
}
1 change: 1 addition & 0 deletions crates/biome_js_analyze/src/options.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,27 @@
import { createEffect, createMemo } from "solid-js";

createEffect(() => {
console.log(signal());
}, [signal()]);

createEffect(() => {
console.log(signal());
}, [signal]);

const deps = [signal];
createEffect(() => {
console.log(signal());
}, deps);

const value = createMemo(() => computeExpensiveValue(a(), b()), [a(), b()]);

const value = createMemo(() => computeExpensiveValue(a(), b()), [a, b]);

const value = createMemo(() => computeExpensiveValue(a(), b()), [a, b()]);

const deps = [a, b];
const value = createMemo(() => computeExpensiveValue(a(), b()), deps);

const deps = [a, b];
const memoFn = () => computeExpensiveValue(a(), b());
const value = createMemo(memoFn, deps);
27 changes: 27 additions & 0 deletions crates/biome_js_analyze/tests/specs/nursery/noReactDeps/valid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createEffect, createMemo } from "solid-js";

createEffect(() => {
console.log(signal());
});

createEffect((prev) => {
console.log(signal());
return prev + 1;
}, 0);

createEffect((prev) => {
console.log(signal());
return (prev || 0) + 1;
});

createEffect((prev) => {
console.log(signal());
return prev ? prev + 1 : 1;
}, undefined);

const value = createMemo(() => computeExpensiveValue(a(), b()));

const sum = createMemo((prev) => input() + prev, 0);

const args = [() => { console.log(signal()); }, [signal()]];
createEffect(...args);
7 changes: 6 additions & 1 deletion packages/@biomejs/backend-jsonrpc/src/workspace.ts

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

7 changes: 7 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 31adde6

Please sign in to comment.