Rslint Documentation

repository·main·Indexed 19 days ago

https://github.com/web-infra-dev/rslint

A high-performance, ESLint-compatible linter for JavaScript and TypeScript powered by typescript-go. Documentation covers the core linting engine, the rslint-wasm browser package, the VS Code extension, and the internal inspector module used for AST, type, symbol, and data flow analysis.

Tokens
278.3K
Snippets
875
Records
1.1K
Agent score
60%

What's inside Rslint

  1. Overview of Rslint

    main

    Rslint is a high-performance, ESLint-compatible linter for JavaScript and TypeScript. It is powered by typescript-go, providing 20-40x faster performance compared to traditional ESLint setups.

    Key features include:

    • Type-aware linting: Enabled by default with minimal setup.
    • ESLint Compatibility: Designed to be a drop-in replacement for most ESLint and typescript-eslint configurations.
    • Project-Level Analysis: Performs cross-module analysis by default, making it suitable for large-scale monorepos and TypeScript project references.
    • Extensibility: Allows writing custom rules that access AST, type information, and global checker data for complex semantic analysis.
  2. What is Rslint and its core goals

    main

    Rslint is a high-performance JavaScript and TypeScript linter designed as a drop-in replacement for ESLint and TypeScript-ESLint. It is built to provide 20-40x speedup over traditional ESLint setups by using a Go implementation and integrating with typescript-go for native parsing and direct TypeScript AST usage.

    Key Goals

    • Lightning Fast Performance: Achieved via Go and typescript-go integration.
    • ESLint Compatibility: Provides best-effort compatibility with existing ESLint and TypeScript-ESLint configurations and rules.
    • TypeScript First: Uses TypeScript Compiler semantics as the single source of truth for 100% consistency.
    • Project-Level Analysis: Supports cross-module analysis by default.
    • Monorepo Ready: Designed for large-scale monorepos using TypeScript project references.
    • Batteries Included: Includes all existing TypeScript-ESLint rules and common ESLint rules.

    Non-Goals

    • Complete Third-Party Plugin Compatibility: While the Node worker supports third-party ESLint plugins on a best-effort basis, it does not support every parser, processor, or ESLint runtime API.
    • Runtime Performance Optimization: The tool is optimized for build-time linting, not for improving the performance of your application at runtime.
    • Custom Parser Support: The system is standardized on the TypeScript parser via typescript-go.
  3. Use the no-standalone-expect rule to prevent invalid Jest assertions

    main

    The no-standalone-expect rule disallows using expect calls outside of it or test blocks. This prevents assertions that sit directly in a describe block, at module scope, or in other locations where Jest will not execute them as part of a test case.

    What is allowed:

    • expect calls inside helper functions (even if the helper is defined outside the it/test callback), provided the helper is invoked from within a test.
    • Static expect APIs at module scope, such as expect.any() and expect.extend().

    What is disallowed:

    • expect calls directly inside a describe block.
    • expect calls sitting at the top-level module scope.
    • expect.hasAssertions() called at the module scope.
    // Incorrect
    describe('a test', () => {
      expect(1).toBe(1);
    });
    
    expect(1).toBe(1);
    
    // Correct
    describe('a test', () => {
      it('an it', () => {
        expect(1).toBe(1);
      });
    });
    
    const helper = () => {
      expect(1).toBe(1);
    };
    
    describe('a test', () => {
      it('an it', () => {
        helper();
      });
    });
    
    expect.any(String);
    expect.extend({});
  4. Use the no-hooks rule to disallow Jest lifecycle hooks

    main

    The no-hooks rule prevents the use of Jest lifecycle hooks (beforeEach, afterEach, beforeAll, afterAll). This rule is used to enforce isolated and explicit tests, reducing reliance on shared setup/teardown behavior that can make test order and failures difficult to reason about.

    Incorrect Usage

    Using hooks for setup or teardown:

    beforeEach(() => {
      setupDatabase();
    });
    
    afterAll(() => {
      cleanup();
    });

    Correct Usage

    Performing setup explicitly within the test or using describe blocks for grouping without hooks:

    test("works with explicit setup", () => {
      const db = createTestDatabase();
      expect(runWith(db)).toBe(true);
    });
    
    describe("suite", () => {
      test("case", () => {
        expect(1 + 1).toBe(2);
      });
    });
    // Incorrect
    beforeEach(() => {
      setupDatabase();
    });
    
    // Correct
    test("works with explicit setup", () => {
      const db = createTestDatabase();
      expect(runWith(db)).toBe(true);
    });
  5. Understand the `role-supports-aria-props` rule

    main

    The role-supports-aria-props rule ensures that JSX elements using ARIA roles only utilize aria-* properties that are valid for that specific role.

    How it works

    1. Role Resolution: The rule identifies the role of an element by looking at an explicit role attribute. If no explicit role is provided, it uses an implicit role table (e.g., <a href="#" /> is treated as role="link").
    2. Attribute Validation: It iterates through JSX attributes (excluding spreads and null/undefined values) and checks if any aria-* attribute is supported by the resolved role.
    3. Context Sensitivity: Some roles are context-dependent. For example, <a> only acquires the link role if an href attribute is present, and <img> loses its img role if alt="" is used or if the src contains .svg.

    Important Limitations

    • Case Sensitivity: Both role names (e.g., role="BUTTON") and ARIA prop names (e.g., aria-Checked) must be lowercase. Mixed-case values will not be validated.
    • Spread Attributes: Attributes passed via spreads (e.g., {...props}) are opaque and are not validated, even if the object contains aria-* keys.
  6. Understand Autofix behavior across different interfaces

    main

    The way autofixes are applied depends on how you are using Rslint:

    InterfaceBehavior
    CLI (default)Requests diagnostics only; no fixes are constructed.
    CLI --fixRequests native autofixes; can run multiple passes; uses a final diagnostics-only pass to verify.
    LSP Quick FixReturns direct text edits for a single diagnostic.
    LSP Fix-allRuns repeated lint-fix cycles, then returns a single whole-document replacement.
    LSP APIRequests all native edits (fixes and suggestions) as metadata.
    API (lint({ fix: true }))Applies fixes in a single pass and returns the fixed source in output. Does not re-lint across passes.
  7. Understand the `no-direct-mutation-state` rule

    main

    The no-direct-mutation-state rule prevents developers from directly modifying this.state in React components. Direct mutation bypasses React's ability to schedule re-renders and reconcile the UI.

    Key constraints:

    • State must only be updated via this.setState() (for class components) or the setter returned by useState (for functional components).
    • Direct assignment to this.state is only permitted inside a component's constructor when seeding the initial state.
    • This rule applies to ES6 class components (extending Component or PureComponent) and ES5 components created via createReactClass.
  8. Understand the `no-misleading-character-class` rule

    main

    The no-misleading-character-class rule disallows characters whose visual rendering is composed of multiple code points (such as combining marks, surrogate pairs, regional indicators, emoji-modifier sequences, or joined ZWJ sequences) from appearing inside a regex character class [...].

    Because these sequences cannot be matched as a single unit by the regex engine, they often produce surprising or incorrect matches. To fix this, you should use the u or v flags, or use Unicode property escapes (e.g., \q{...}) when using the v flag.

    // Incorrect: multiple code points in a class without proper flags/handling
    /^[Á]$/u;        // a + combining acute
    /^[👶🏻]$/u;     // base emoji + skin tone modifier
    /^[👨‍👩‍👦]$/u;     // ZWJ-joined family sequence
    
    // Correct: using the v-flag to preserve sequences
    /^[\q{👶🏻}]$/v; 
    
    // Correct: using the u-flag for astral characters
    /^[👍]$/u;
  9. Understand `jsx-no-target-blank` behavior and limitations

    main

    This rule is designed to be a 1:1 implementation of the eslint-plugin-react rule. Note the following implementation details:

    • No Autofix for Forms: When forms: true is enabled, the rule does not provide automatic fixes, matching upstream behavior.
    • Unanalyzable Expressions: If the rel attribute uses an expression the rule cannot statically analyze (e.g., rel={getRel()}), a diagnostic will be reported, but no autofix will be provided.
    • Form Security Strictness: For <form> elements, the rule always treats allowReferrer as false. Even if the rule-level option is true, forms using only rel="noopener" will still trigger a violation.
  10. Understand the `no-useless-default-assignment` rule

    main

    The no-useless-default-assignment rule disallows default values in default parameters or destructuring that will never be used.

    In TypeScript, default values are only triggered when the value is undefined. If the source type guarantees a non-undefined value, the default is unreachable code and can be misleading regarding the value's nullability.

    Incorrect Code Examples

    // foo is guaranteed to be a string, so the default '' is unreachable
    function Bar({ foo = '' }: { foo: string }) {
      return foo;
    }
    
    // The object literal guarantees 'foo' is 'bar'
    const { foo = '' } = { foo: 'bar' };
    
    // The array element is guaranteed to be 'bar'
    const [foo = ''] = ['bar'];
    
    // The parameter 'a' is guaranteed to be a number from the map
    [1, 2, 3].map((a = 42) => a + 1);
    
    // Explicitly assigning undefined as a default is redundant
    function f(a = undefined) {}
    
    const { a = undefined } = {};
    
    function g(p: number | undefined = undefined) {}

    Correct Code Examples

    // foo is optional, so the default is reachable
    function Bar({ foo = '' }: { foo?: string }) {
      return foo;
    }
    
    // The source value is explicitly undefined
    const { foo = '' } = { foo: undefined };
    
    // The array contains undefined
    const [foo = ''] = [undefined];
    
    // The map includes undefined elements
    [1, 2, 3, undefined].map((a = 42) => a + 1);
    
    // Using optional parameters
    function f(a?: number) {}
    
    function g(p?: number | undefined) {}