eslint-plugin-solid

repository·main·Indexed 42 days ago

https://github.com/solidjs-community/eslint-plugin-solid

ESLint plugin providing SolidJS-specific linting rules to help developers avoid reactivity pitfalls and migrate React patterns to Solid. Includes recommended configurations for JavaScript, TypeScript, and Solid 2.0, as well as a standalone package for web-based linting in browsers or web workers.

Tokens
43K
Snippets
98
Records
130
Agent score
49%

What's inside eslint-plugin-solid

  1. Use eslint-solid-standalone for web-based linting

    main

    The eslint-solid-standalone package provides a bundled environment containing ESLint, eslint-plugin-solid, and required tooling. It is designed to run linting logic directly in the browser or within a web worker, making it ideal for web-based IDEs or playgrounds (like the Solid playground).

    It supports linting for both TypeScript and JSX source code.

  2. Understand the solid/no-write-in-pure-computation rule

    main

    The solid/no-write-in-pure-computation rule prevents writing to signals or stores inside pure owned scopes. In Solid 2.0, these scopes are intended to be pure; writing to reactive state from within them creates update cycles and triggers a REACTIVE_WRITE_IN_OWNED_SCOPE runtime error in development mode.

    Flagged Scopes:

    • createMemo callbacks (the compute phase).
    • The compute half of createEffect(compute, effect).
    • Component bodies (the setup phase).

    Exemptions:

    • Setter calls inside nested functions (e.g., event handlers, onSettled, createTrackedEffect, or the effect half of createEffect) are considered imperative scopes and are allowed.
    • Signals initialized with the { ownedWrite: true } option are exempt. Note that this exemption applies to signals only; store setters do not have this exemption in Solid core.

    This rule is enabled as an error in the v2 and v2-strict configurations.

    // ✗ Deriving state by writing a signal from a computation
    const bad = createMemo(() => {
      setDouble(count() * 2);
      return count();
    });
    
    // ✓ Derive it — no second signal needed
    const double = createMemo(() => count() * 2);
  3. Understand the `solid/valid-use-server` rule

    main

    The solid/valid-use-server rule ensures that "use server" directives are placed in positions where the Solid 2.0 compiler actually honors them. Because the compiler ignores misplaced directives silently, this rule prevents sensitive code (like database access or secrets) from accidentally being shipped to the client browser.

    This rule catches several types of mistakes:

    1. Misplaced Directives

    • Not in the prologue: The directive must be part of the directive prologue (the very top of a function body or module). It will not work if it follows any other statement or is placed inside a block like if or for.
    • Template literals: Only plain string literals ("use server") are valid; template literals (`use server`) are ignored by the compiler.

    2. Ineligible Positions

    The compiler cannot extract server functions from:

    • Object-literal methods
    • Getters or setters
    • Class methods

    Valid positions include standalone functions, function-valued properties in objects, and block-bodied arrow functions.

    3. Module-level Directive File Errors

    In files starting with a module-level "use server" directive, every export is treated as a server function. The rule validates:

    • Non-function exports: Exporting constants (e.g., export const LIMIT = 10) will fail because the value is expected to be a callable server function.
    • Client wrapper calls: Using wrappers like GET, live, withMeta (from @solidjs/web), or query, action, and liveQuery (from @solidjs/router) inside a directive module is invalid. The client build replaces these exports with bare server references, causing the client-side behavior of the wrapper to never run. To fix this, wrap the server function in a shared (non-directive) module instead, or use a function-level directive inside the wrapper call.
  4. The `solid/reactivity` rule

    main

    The solid/reactivity rule enforces that reactivity (props, signals, memos, etc.) is used correctly so that changes are tracked and the view updates as expected. This rule is a warning by default.

    It prevents common mistakes where reactive values are accessed in non-tracked scopes (like the component body), which causes the UI to fail to update when those values change because Solid components only run once.

    // This rule is a warning by default.
    // It targets improper usage of props, signals, and memos.
    ```js
    // Example of what it catches:
    function Greeting(props) {
      const text = `Hello, ${props.name}!`; // Warning: props.name used in component body
      return <span class="greeting">{text}</span>;
    }
  5. Understand rule confidence and error levels in Solid ESLint Plugin

    main

    The plugin categorizes rules by Confidence and Severity Level to help you interpret lint reports:

    Confidence Classes

    • Certain / Near-certain: The rule identifies a structural fact or a mechanical error (e.g., duplicate props, invalid server function signatures). These are highly reliable.
    • Heuristic: The rule infers intent or analyzes reactivity patterns (e.g., reactivity, components-return-once). These may produce false positives and are often set to warn level. Behavioral confirmation should be done via runtime diagnostics.

    Severity Levels

    • Error: Used for rules that catch definitive bugs, security risks, or breaking API changes (especially in Solid 2.0).
    • Warn: Used for stylistic preferences, heuristic-based reactivity checks, or rules where the analysis might be imperfect.

    Rule Audit (v2 and v2-strict)

    Rules are grouped into v2 (standard) and v2-strict (stricter enforcement). Some rules are specifically designed for the Solid 2.0 ecosystem, such as no-react-specific-props (flagging className/htmlFor on DOM elements) and removed-api (flagging deprecated 1.x APIs like classList).

  6. Understand the solid/no-boolean-enumerated-attribute rule

    main

    The solid/no-boolean-enumerated-attribute rule prevents bugs caused by using boolean values on HTML attributes that actually expect specific string tokens.

    In Solid, passing false to an attribute removes it from the DOM entirely. For many enumerated attributes, removing the attribute results in a different state than setting it to a string like "false" or "no".

    Affected Attributes and their expected tokens:

    • draggable, spellcheck, contenteditable: Expects "true" or "false".
    • translate: Expects "yes" or "no".
    • Tristate ARIA states (aria-checked, aria-expanded, aria-hidden, aria-pressed, aria-selected): Expects "true" or "false". Absence of these attributes is treated as undefined by assistive technologies, which is different from an explicit "false" state.

    Rule Behavior:

    • Reporting: It reports boolean literals, bare attributes (like draggable), and expressions that are provably boolean (like count() > 0 or !locked()).
    • Autofixing: Boolean literals are automatically fixed to the correct string token.
    • Suggestions: For boolean expressions, the rule provides an editor suggestion to map the expression through a ternary operator.
    • Default Status: This rule is off by default in standard configs but is enabled as an error in the v2 and v2-strict configurations. It applies to both Solid 1.x and 2.0.
    // ✗ Incorrect: results in attribute removal or invalid state
    <div draggable={false} />          // behaves as "auto", not "false"
    <button aria-expanded={false} />   // state becomes "undefined", not collapsed
    
    // ✓ Correct: uses explicit string tokens
    <div draggable="false" />
    <button aria-expanded={open() ? "true" : "false"} />
  7. Avoid reactivity pitfalls in SolidJS

    main

    The solid/reactivity rule identifies common mistakes that break SolidJS's fine-grained reactivity model. Key patterns to avoid include:

    • Accessing signals in the component body: Calling a signal (e.g., signal()) directly in the component function body instead of inside an effect, memo, or JSX expression. This causes the component to run only once and fail to update.
    • Destructuring props: Destructuring props (e.g., const { value } = props) breaks reactivity because it captures the value at the time of destructuring rather than tracking the property access.
    • Initializing state from props: Using createSignal(props.value) inside a component. This only sets the initial value and won't update when props.value changes. Use createMemo or track the prop directly.
    • Stale captures in returned functions: Returning a function that captures a value from a previous render or scope instead of accessing the reactive source (e.g., const val = props.val; return () => val;).
    • Async reactivity issues: Accessing signals after an await or yield in an async computation, which can lead to stale data or lost tracking.
    • Derived signals in unexpected scopes: Creating functions that act as derived signals but are called in ways that bypass the intended reactive tracking.
  8. Understand the solid/no-module-scope-reactive-primitive rule

    main

    The solid/no-module-scope-reactive-primitive rule disallows the creation of reactive primitives (like createSignal, createStore, createMemo, or createEffect) at the module scope.

    Why this rule exists

    In Server-Side Rendering (SSR) environments, reactive state created at the module scope is shared across every incoming request. This causes a critical bug where one user's state can leak into another user's response.

    How to fix violations

    1. Move state into a component or provider: Instead of declaring state at the top level of a file, create it inside a function (like a Solid component) so that a new instance is created for every request/render.
    2. Use createRoot for intentional global state: If you are building a client-only application and truly need a global store, wrap the primitive creation inside a createRoot callback. This documents the intent and provides an owner for the state, which prevents the rule from flagging it.

    Configuration

    This rule is off by default. It is only enabled as an error when using the v2-strict configuration preset.

  9. Understand the `solid/components-return-once` rule

    main

    The solid/components-return-once rule disallows early returns within SolidJS components.

    Rationale: Solid components only run once during initialization. If you use an if statement to return different JSX elements early in the component body, that logic will not react to changes. To ensure reactivity, conditional logic must be placed inside the JSX using Solid's control flow components (like <Show /> or <Switch />) or within reactive functions/render functions.

    This rule is a warning by default.

  10. Understand the `solid/no-unused-signal` rule

    main

    The solid/no-unused-signal rule identifies signals that are either never written to (making them effectively constants) or never read from (making them dead state).

    This rule is useful for cleaning up code where a signal is used unnecessarily, such as leftover scaffolding from Solid 1.x to 2.0 migrations. It applies to createSignal, createStore, and createOptimistic tuples.

    Key Behaviors:

    • Never-written signals: If the setter is never referenced, the signal is treated as a constant. For signals with literal initial values, the rule provides an editor suggestion to rewrite them as a plain accessor.
    • Never-read signals: If the accessor is never referenced, the state is considered dead code.
    • Exports: Exported signal declarations are skipped because the rule cannot guarantee they aren't used in other modules.
    • Configuration: This rule is off by default. It is enabled as a warning in the v2 config and as an error in the v2-strict config.
    // ✗ Never written — this is a constant wearing a signal costume
    const [limit, setLimit] = createSignal(10);
    return <List max={limit()} />;
    
    // ✓ A plain accessor keeps call sites identical
    const limit = () => 10;
    
    // ✓ Or createMemo, if the value derives from reactive state
    const limit = createMemo(() => props.pageSize ?? 10);
  11. Understand the solid/no-restated-default-options rule

    main

    The solid/no-restated-default-options rule disallows explicitly passing a prop or option value that is already the default for a Solid component. This helps reduce noise, particularly in AI-generated code.

    Default Behaviors Verified by the Rule:

    • <For>: keyed is true by default.
    • <Show>: keyed is false by default.
    • <Match>: keyed is false by default.

    Scope of the Rule:

    • The rule applies to components imported from solid-js (including aliases) and unbound names (since the compiler auto-imports control-flow built-ins).
    • It does not affect components bound to other imports or local declarations, as those may have different defaults.

    Configuration:

    • This rule is off by default.
    • It is enabled as an error only when using the v2-strict configuration.
  12. Understand the rule confidence policy

    main

    The eslint-plugin-solid plugin follows a strict confidence policy to prevent false positives, especially to avoid misleading AI agents that might 'fix' correct code based on incorrect lint reports.

    Rules are categorized based on three principles:

    1. Errors are for structural facts: A rule only reports at the error level if the code is certainly wrong according to Solid's semantics or is a security hazard. Heuristics are never used for errors.
    2. Heuristics are warnings: If a rule must infer intent (e.g., component detection or naming conventions), it reports at the warn level and provides instructions in the error message on how to opt out (e.g., using a specific naming prefix or option).
    3. Runtime behavior vs. Static analysis: Lint rules assert structure. They do not speculate on runtime behavior (like whether a computation re-runs or goes stale). For behavior-based issues, you should rely on Solid's dev-mode diagnostics (DEV.diagnostics or @solidjs/diagnostics).