# How to Implement Code Fixes with .withFix() and Refactorings with .withRefactor() in TSSLint Rules

> Learn to implement code fixes with TSSLint .withFix() and refactorings with .withRefactor(). Modify source code efficiently using FileTextChanges directly in your TSSLint rules.

- Repository: [Johnson Chu/tsslint](https://github.com/johnsoncodehk/tsslint)
- Tags: how-to-guide
- Published: 2026-03-04

---

**Use the `Reporter` object returned by `report()` to chain `.withFix()` for quick fixes or `.withRefactor()` for refactoring actions, providing a title and a callback that returns `FileTextChanges[]` to modify source code.**

TSSLint is a TypeScript linter that operates as a language service plugin, enabling real-time feedback in your editor. When building custom rules in the `johnsoncodehk/tsslint` repository, you can enhance diagnostics with automated solutions by implementing code fixes with `.withFix()` and refactorings with `.withRefactor()` in TSSLint rules.

## Understanding the TSSLint Reporter API

When a rule detects an issue, it calls `report(message, start, end)`, which returns a **`Reporter`** object. This object acts as a mutable builder that lets you attach code actions before the rule finishes execution.

### Where the API Lives

The type definitions and implementation reside in two key locations:

- **[`packages/types/index.ts`](https://github.com/johnsoncodehk/tsslint/blob/main/packages/types/index.ts)** (lines 50-60): Defines the `Reporter` interface with `withFix` and `withRefactor` methods
- **[`packages/core/index.ts`](https://github.com/johnsoncodehk/tsslint/blob/main/packages/core/index.ts)** (lines 69-80): Contains the concrete reporter implementation that collects fixes and refactors into internal arrays

When you chain `.withFix()` or `.withRefactor()`, the core stores your supplied `getEdits` callback along with a title. Later, during the code-action phase, TSSLint invokes these callbacks to obtain **`FileTextChanges[]`** and exposes them through `getCodeFixes` and `getRefactors`.

## Implementing Code Fixes with .withFix()

Code fixes appear as "quick fixes" in your editor (the lightbulb icon). Use `.withFix()` when you want to provide an immediate solution to a diagnostic.

### The Fix Signature

```typescript
.withFix(title: string, getEdits: () => FileTextChanges[])

```

The `getEdits` callback must return an array of objects matching TypeScript's `FileTextChanges` structure:

```typescript
{
  fileName: string;               // absolute path of the file
  textChanges: ts.TextChange[];   // { newText: string, span: { start: number, length: number } }
}

```

### Real-World Example: Remove Console Calls

The [`fixtures/noConsoleRule.ts`](https://github.com/johnsoncodehk/tsslint/blob/main/fixtures/noConsoleRule.ts) file (lines 10-26) demonstrates a complete implementation:

```typescript
import { defineRule } from '@tsslint/config';

export default defineRule(({ typescript: ts, file, report }) => {
  ts.forEachChild(file, function walk(node) {
    if (
      ts.isPropertyAccessExpression(node) &&
      ts.isIdentifier(node.expression) &&
      node.expression.text === 'console'
    ) {
      // Report the diagnostic
      report(
        `Calls to 'console.${node.name.text}' are not allowed.`,
        node.parent.getStart(file),
        node.parent.getEnd(),
      )
        // Attach a fix that replaces the entire call with a comment
        .withFix(
          `Remove console.${node.name.text}`,
          () => [
            {
              fileName: file.fileName,
              textChanges: [
                {
                  newText: '/* deleted */',
                  span: {
                    start: node.parent.getStart(file),
                    length: node.parent.getWidth(file),
                  },
                },
              ],
            },
          ],
        );
    }
    ts.forEachChild(node, walk);
  });
});

```

This rule detects `console` calls and offers a quick fix that replaces the entire call expression with `/* deleted */`.

## Implementing Refactorings with .withRefactor()

Refactorings appear in the "Refactor..." menu in supported editors. Use `.withRefactor()` for optional transformations that improve code quality but aren't necessarily fixing errors.

### The Refactor Signature

```typescript
.withRefactor(title: string, getEdits: () => FileTextChanges[])

```

The API is identical to `.withFix()`, but TSSLint categorizes these actions separately, exposing them through `getRefactors` instead of `getCodeFixes`.

### Example: Convert var to let

```typescript
import { defineRule } from '@tsslint/config';

export default defineRule(({ typescript: ts, file, report }) => {
  ts.forEachChild(file, function walk(node) {
    if (ts.isVariableDeclarationList(node) && (node.flags & ts.NodeFlags.Var)) {
      const start = node.getStart(file);
      const length = node.getWidth(file);

      report(
        'Prefer let/const over var.',
        start,
        start + length,
      )
        .withRefactor(
          'Convert var to let',
          () => [
            {
              fileName: file.fileName,
              textChanges: [
                {
                  newText: 'let',
                  span: { start, length: 3 }, // replace the keyword "var"
                },
              ],
            },
          ],
        );
    }
    ts.forEachChild(node, walk);
  });
});

```

When users trigger the refactor command in their editor, they will see "Convert var to let" as an available action.

## How Fixes and Refactorings Flow to the Editor

Understanding the lifecycle helps debug why an action might not appear:

1. **Rule Execution**: When `report()` is called, TSSLint creates a reporter instance (implemented in [`packages/core/index.ts`](https://github.com/johnsoncodehk/tsslint/blob/main/packages/core/index.ts) lines 69-80)
2. **Action Registration**: Chaining `.withFix()` or `.withRefactor()` pushes `{ title, getEdits }` into internal `fixes` or `refactors` arrays
3. **Result Mapping**: After all rules run, the core builds a `lintResults` map linking files to their diagnostics and associated actions
4. **Editor Integration**: When the editor requests code actions, `getCodeFixes` (for fixes) or `getRefactors` (for refactorings) in [`packages/core/index.ts`](https://github.com/johnsoncodehk/tsslint/blob/main/packages/core/index.ts) iterates stored callbacks, invokes `getEdits()` to obtain concrete `FileTextChanges`, and returns `ts.CodeFixAction` or `ts.RefactorAction` objects

## ESLint Compatibility Layer

If you're migrating ESLint rules to TSSLint, the compatibility layer in [`packages/compat-eslint/index.ts`](https://github.com/johnsoncodehk/tsslint/blob/main/packages/compat-eslint/index.ts) (lines 120-135) demonstrates how to map ESLint's `suggest` entries to TSSLint's refactor API:

```typescript
// Inside packages/compat-eslint/index.ts
reporter.withRefactor(
  suggest.message,               // title shown to the user
  () => [
    {
      fileName: file.fileName,
      textChanges: getTextChanges(file, suggest.fix as ESLint.Rule.ReportFixer),
    },
  ],
);

```

This pattern allows you to expose ESLint suggestions as native TSSLint refactoring actions.

## Summary

- **Reporter Object**: Calling `report()` returns a mutable builder that accepts `.withFix()` and `.withRefactor()` chains
- **FileTextChanges**: Both methods require a callback returning `FileTextChanges[]`, specifying `fileName` and `textChanges` with `newText` and `span`
- **Categorization**: `.withFix()` creates quick fixes (lightbulb), while `.withRefactor()` creates refactoring actions (refactor menu)
- **Implementation**: The core logic resides in [`packages/core/index.ts`](https://github.com/johnsoncodehk/tsslint/blob/main/packages/core/index.ts), while type definitions are in [`packages/types/index.ts`](https://github.com/johnsoncodehk/tsslint/blob/main/packages/types/index.ts)
- **ESLint Migration**: Use [`packages/compat-eslint/index.ts`](https://github.com/johnsoncodehk/tsslint/blob/main/packages/compat-eslint/index.ts) as a reference for mapping ESLint suggestions to TSSLint refactorings

## Frequently Asked Questions

### What's the difference between a code fix and a refactoring in TSSLint?

Code fixes are intended to correct errors or violations reported by your rule, appearing as quick fixes (lightbulb icon) in the editor. Refactorings are optional transformations that improve code structure or style, appearing in the "Refactor..." menu. While both use the same `FileTextChanges[]` return type, TSSLint routes fixes through `getCodeFixes` and refactorings through `getRefactors`, allowing editors to present them in appropriate contexts.

### What must the callback passed to .withFix() return?

The callback must return an array of `FileTextChanges` objects, where each object contains a `fileName` (absolute path) and a `textChanges` array. Each `textChange` must specify `newText` (the replacement string) and `span` (an object with `start` and `length` numbers indicating the position in the file). This matches TypeScript's native `FileTextChanges` interface used by the language service.

### How does TSSLint expose these actions to VS Code?

TSSLint operates as a TypeScript language service plugin. When you register fixes or refactorings in a rule, the core implementation in [`packages/core/index.ts`](https://github.com/johnsoncodehk/tsslint/blob/main/packages/core/index.ts) stores these actions. When VS Code requests code fixes or refactorings via the TypeScript language service protocol, TSSLint's `getCodeFixes` and `getRefactors` methods invoke the stored callbacks, convert the returned `FileTextChanges` into standard `ts.CodeFixAction` or `ts.RefactorAction` objects, and return them to the editor.

### Can I use these APIs in ESLint-compatible rules?

Yes. The [`packages/compat-eslint/index.ts`](https://github.com/johnsoncodehk/tsslint/blob/main/packages/compat-eslint/index.ts) compatibility layer demonstrates how to map ESLint's `suggest` API to TSSLint's `.withRefactor()` method. When migrating ESLint rules, you can extract the fix logic from ESLint's `suggest` entries and wrap them in TSSLint's refactor callbacks, returning the appropriate `FileTextChanges` array to provide the same functionality within TSSLint's architecture.