tsl

repository·main·Indexed 40 days ago

https://github.com/arnaudbarre/tsl

A high-performance, type-aware linter extension for tsc (version 1.0.30) designed to provide fast linting alongside tools like Oxlint or Biome, or as a replacement for typescript-eslint. It supports a subset of type-aware rules, custom rule definition via defineRule and createRulesSet, and IDE integration through a TypeScript plugin. It requires TS >=5.8 as a peer dependency.

Tokens
13.8K
Snippets
41
Records
59
Agent score
50%

What's inside tsl

  1. Ignore rules with comments

    main

    You can suppress rule reports using line comments or block comments:

    • /* tsl-ignore */: Ignores the next line for all rules.
    • /* tsl-ignore org/ruleA */: Ignores the next line for a specific rule.
    • // tsl-ignore org/ruleB: Ignores the next line for a specific rule.
    • // tsl-ignore org/ruleC, org/ruleD: reason: Ignores multiple rules with an optional reason.
    /* tsl-ignore */
    /* tsl-ignore org/ruleA */
    
    // tsl-ignore org/ruleB
    const a = 1;
    
    // tsl-ignore org/ruleC, org/ruleD: reason
    const b = 2;
  2. Configure tsl with tsl.config.ts

    main

    Create a tsl.config.ts file in your project root to define your linting rules. If no config file is present, all core rules are enabled for CLI usage, but editor integration requires a configuration file.

    You can enable all core rules and selectively disable or configure them, or pick specific rules to enable.

    // tsl.config.ts
    import { core, defineConfig } from "tsl";
    
    export default defineConfig({
      rules: [
        ...core.all(),
        core.strictBooleanExpressions("off"),
        core.switchExhaustivenessCheck({
          considerDefaultExhaustiveForUnions: true,
        }),
      ],
    });
  3. Apply type patches in tsconfig.json

    main

    Some rules (like prefer-promise-reject-errors) require overriding built-in types via patches. Add the following to your tsconfig.json to enable all patches, or specify them individually.

    Available patches:

    • tsl/patches (all)
    • tsl/patches/promiseRejectError
    • tsl/patches/unknowninCatchCallbacks
    {
      "compilerOptions": {
        "types": ["tsl/patches"]
      }
    }
  4. Enable IDE support with the TypeScript plugin

    main

    To display diagnostics and suggestions directly in your editor, add the tsl/plugin to your tsconfig.json.

    Important for VS Code users: After adding the plugin, run the TypeScript: Select TypeScript Version command and choose Use Workspace Version.

    {
      "compilerOptions": {
        "plugins": [{ "name": "tsl/plugin" }]
      }
    }
  5. Understand the `noConfusingVoidExpression` rule

    main

    The noConfusingVoidExpression rule ensures that void expressions are used in a way that is clear and unambiguous. It targets AwaitExpression, CallExpression, and TaggedTemplateExpression nodes that evaluate to a void-like type.

    Common Violations and Fixes

    ScenarioViolation MessageSuggested Fix
    Inside another expressionPlacing a void expression inside another expression is forbidden. Move it to its own statement instead.Move the expression to its own statement or wrap it with the void operator.
    Arrow function shorthandReturning a void expression from an arrow function shorthand is forbidden. Please add braces to the arrow function.Add braces { ... } to the arrow function.
    Arrow function shorthand (unwrapped)Void expressions returned from an arrow function shorthand must be marked explicitly with the void operator.Mark the expression with the void operator.
    Return statement (general)Returning a void expression from a function is forbidden. Please move it before the return statement.Move the expression before the return keyword.
    Return statement (last line)Returning a void expression from a function is forbidden. Please remove the return statement.Remove the return keyword.
    Return statement (unwrapped)Void expressions returned from a function must be marked explicitly with the void operator.Mark the expression with the void operator.
  6. Understand strictBooleanExpressions error messages and fixes

    main

    When the strictBooleanExpressions rule triggers, it provides specific error messages and often suggests automated fixes to make the boolean expression explicit. Common error patterns include:

    • Nullable types: If a value might be null or undefined (e.g., string | null), the rule requires an explicit check like value != null or value !== "".
    • Numbers/Strings: If a number or string is used directly (e.g., if (count)), the rule may suggest count !== 0 or count > 0.
    • Async Predicates: In array methods like .filter() or .find(), the rule will error if the predicate function is async, as these methods expect a synchronous boolean return.
    • Explicit Return Types: For array predicates, the rule may suggest adding an explicit : boolean return type annotation.
  7. Use the `preferNullishCoalescing` rule

    main

    The preferNullishCoalescing rule identifies patterns where the nullish coalescing operator is a safer or cleaner alternative to logical OR or ternary logic.

    It flags the following patterns:

    1. Logical OR (||) or Assignment (||=): Replaces a || b or a ||= b with a ?? b or a ??= b when a is nullish or undefined.
    2. Ternary Expressions: Replaces patterns like a ? a : b or !a ? b : a with a ?? b.
    3. If Statements: Replaces if (!a) a = b; with a ??= b;.

    Note on Type Safety: The rule uses TypeScript's type checker to ensure that the conversion is safe. It avoids flagging expressions where the type is any or unknown to prevent accidental logic changes, and it respects your ignorePrimitives configuration.

  8. Enable performance timing and profiling in tsl

    main

    To debug performance bottlenecks in your linting process, you can use the --timing and --profile flags.

    Timing

    Using --timing (or -t) will output the duration of various stages:

    • Boot time
    • TS program creation
    • Configuration loading
    • Typechecking duration
    • Linting duration
    • A summary table of rule and file execution times, sorted by the most time-consuming entries.

    Profiling

    Using --profile will trigger a CPU profile session. When the process completes, the profile is saved to tsl-profile.cpuprofile in the current working directory.

    # View timing statistics for rules and files
    tsl --timing
    
    # Generate a CPU profile for deep performance analysis
    tsl --profile
  9. Write custom rules in TypeScript

    main

    Custom rules are written by defining a visitor object within the rules array of defineConfig. Rules run on the TypeScript AST.

    Key capabilities:

    • Type Information: Access the type checker via context.checker.getTypeAtLocation(node).
    • AST Utilities: context.utils provides helpers like typeOrUnionHasFlag.
    • Improved Types: tsl provides rewritten AST types for better type narrowing and exhaustive switches.

    Use ast-explorer.dev to explore the AST and ts-ast-viewer to explore type information.

    import { type AST, core, defineConfig } from "tsl";
    import { SyntaxKind, TypeFlags } from "typescript";
    
    export default defineConfig({
      rules: [
        ...core.all(),
        {
          name: "org/useLogger",
          visitor: {
            CallExpression(context, node) {
              if (
                node.expression.kind === SyntaxKind.PropertyAccessExpression
                && node.expression.expression.kind === SyntaxKind.Identifier
                && node.expression.expression.text === "console"
              ) {
                node.expression.expression satisfies AST.Identifier;
                context.report({ node, message: "Use logger instead" });
              }
            },
          },
        },
        {
          name: "org/jsxNoNumberTruthiness",
          visitor: {
            BinaryExpression(context, node) {
              if (
                node.parent.kind === SyntaxKind.JsxExpression
                && node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken
              ) {
                const type = context.checker.getTypeAtLocation(node.left);
                if (context.utils.typeOrUnionHasFlag(type, TypeFlags.NumberLike)) {
                  context.report({
                    node,
                    message: "Don't use logical expression on a number inside JSX...",
                  });
                }
              }
            },
          },
        },
      ],
    });
  10. Create sharable rules with defineRule and createRulesSet

    main
    You can create your own sharable rules using the defineRule and createRulesSet APIs. These are the same primitives used to build the core rules of tsl. To verify your custom rules, you can use the ruleTester utility, though note that it is currently considered a raw/unstable API.