close

JavaScript API

The JavaScript API lets you run rslint programmatically — lint files or in-memory source from a JavaScript runtime script, an editor integration, or a build tool. It is designed for JavaScript runtime hosts such as Node.js, Bun, or Deno when they can load npm packages and provide the Node-compatible filesystem and process APIs that @rslint/core uses. Its surface is aligned with ESLint's v10 programmatic API shape, so most ESLint API code ports over with minimal changes.

With automatic discovery, the native engine selects config candidates and file ownership while the JavaScript host evaluates and normalizes the selected JS or TS modules. Explicit config files and inline overrides use the same public API.

This guide focuses on common workflows. For complete method signatures and lifecycle details, see the Rslint reference.

Getting started

import { Rslint } from '@rslint/core';

const rslint = new Rslint();
const results = await rslint.lintFiles(['src/**/*.ts']);

for (const result of results) {
  console.log(result.filePath, result.errorCount, result.warningCount);
}

new Rslint(options) creates a linter instance. Both lintFiles and lintText are async and return an ESLint-shaped LintResult[].

Linting files

lintFiles takes one or more glob patterns resolved against cwd. It keeps supported source-file extensions that are not excluded by global config ignores or .gitignore. With automatic discovery, each selected file is routed to its nearest loadable config, so files in different monorepo packages can use different configs.

const results = await rslint.lintFiles(['src/**/*.ts', 'test/**/*.ts']);

Results are ordered by the linted file's path (deterministic), not by glob-walk order.

If no file matches the patterns, lintFiles returns an empty array rather than throwing — unlike ESLint v10, whose default errorOnUnmatchedPattern throws on an unmatched glob.

Linting a string

lintText lints an in-memory string as if it lived at filePath:

const [result] = await rslint.lintText('const x = 1', {
  filePath: 'example.ts',
});

lintText always returns exactly one result — for the linted buffer. If you omit filePath, the result's filePath is the "<text>" sentinel (matching ESLint).

In-memory linting

See In-memory projects for the complete constructor contract and path behavior.

By default lintText still reads the config and tsconfig from disk. To provide the source, config, tsconfig, and project files from memory, combine overrideConfigFile: true (use only the inline config), an inline overrideConfig, and a virtualFiles overlay:

const rslint = new Rslint({
  cwd: '/', // stable root for the virtual paths below
  overrideConfigFile: true, // use only overrideConfig — skip config discovery
  overrideConfig: [
    {
      files: ['**/*.ts'],
      // The tsconfig + parserOptions.project below are needed ONLY for
      // type-aware rules (like no-for-in-array). Other rules need neither.
      languageOptions: { parserOptions: { project: ['./tsconfig.json'] } },
      plugins: ['@typescript-eslint'],
      rules: { '@typescript-eslint/no-for-in-array': 'error' },
    },
  ],
  virtualFiles: {
    'tsconfig.json': JSON.stringify({
      compilerOptions: { strict: true },
      files: ['./a.ts'],
    }),
  },
});

const [result] = await rslint.lintText(
  'const a = [1];\nfor (const k in a) {}\n',
  { filePath: 'a.ts' },
);

virtualFiles is an in-memory file overlay (path → content) — an rslint extension; ESLint has no in-memory file map. Put the tsconfig.json that parserOptions.project names, plus any dependency files, in the overlay. The overlay does not disable filesystem fallback: rslint may still consult disk for .gitignore and TypeScript resolution, so this API is not a filesystem sandbox.

Declaring plugins. A rule from a plugin (@typescript-eslint/*, unicorn/*, and so on) runs only when that plugin is listed in plugins — rslint enforces this exactly like ESLint. Core rules (no / prefix) need no declaration.

Type-aware vs non-type-aware rules. The tsconfig.json and parserOptions.project matter only for type-aware rules, which need a real TypeScript program (see Type Checking). If your config has only rules that do not require type information, you can drop both — no tsconfig, no parserOptions.project.

Use relative paths in virtualFiles keys and inside the tsconfig:

  • virtualFiles keys: prefer relative paths ('tsconfig.json'). Keys are always resolved against cwd. An absolute key like '/tsconfig.json' happens to match only when cwd is /; with any other cwd it lands at the filesystem root.
  • parserOptions.project: relative paths resolve from the config entry's effective base. In this override-only example that base is cwd; a basePath changes it. For a discovered config module it is normally the module directory.
  • Inside the tsconfig (files and include): relative paths resolve from the tsconfig's own directory. A bare POSIX-absolute path (such as /a.ts) has no drive letter on Windows, so it won't match the overlay.

Pin the tsconfig to explicit files — a broad include glob is expanded against the real filesystem and scans from the tsconfig's directory (which is cwd in this example).

Auto-fixing

Pass fix: true. A result whose file received at least one applied fix carries an output string — the full final source, even if later fixes restored the input; results with no applied fix have no output.

Rslint repeats linting and fixing until no fix is produced, a fix cycle restores the input, or ten writable rounds have run. messages and all diagnostic counts describe the final source in output, so successfully fixed findings are no longer reported. If the round limit leaves a fixable finding, its message.fix range also targets that final source.

Write fixes to disk with the static Rslint.outputFixes:

const rslint = new Rslint({ fix: true });
const results = await rslint.lintFiles(['src/**/*.ts']);
await Rslint.outputFixes(results); // writes fixed files back to disk

Rslint.outputFixes writes back only results whose filePath is absolute. A lintText result is absolute — and so will be written — when you pass a filePath; only a result with no filePath (the non-absolute "<text>" sentinel) is skipped.

Apply fixes in memory — to fix without touching disk, read output directly and don't call outputFixes:

const rslint = new Rslint({ fix: true /* + your in-memory config */ });
const [result] = await rslint.lintText('let x = foo!!.bar', {
  filePath: 'a.ts',
});
const fixed = result.output ?? 'let x = foo!!.bar'; // fixed source, or the original if nothing changed

lintText with fix: true never writes to disk — the fixed source comes back as result.output. For edit-level control, each result.messages[].fix is a { range: [start, end], text } edit (UTF-16 offsets) you can splice into the source yourself; for more than one fix prefer output, which is already the safely merged whole-file result.

Lifecycle

Each Rslint instance owns a long-lived rslint engine child process. You don't need to call close() — like ESLint, a one-off script exits cleanly on its own (the idle child is unref'd, so it never blocks the event loop).

Call close() only in a long-running host (an editor server, a watch process) that creates many instances, to free each child promptly:

const rslint = new Rslint();
try {
  await rslint.lintFiles(['src/**/*.ts']);
} finally {
  await rslint.close();
}

Or use await using for automatic disposal at the end of scope:

await using rslint = new Rslint();
await rslint.lintFiles(['src/**/*.ts']);

Native await using needs a runtime with explicit resource management support, which Node.js 22 lacks (a bare .mjs throws a SyntaxError). Compile with a using-aware toolchain such as TypeScript 5.2+, or use the try / finally form above, which does not rely on native using syntax.

Result shape

Both methods resolve to LintResult[]:

FieldTypeDescription
filePathstringAbsolute path, or "<text>" for lintText called with no filePath
messagesLintMessage[]Diagnostics for this file
errorCountnumberNumber of error-severity messages
warningCountnumberNumber of warning-severity messages
fixableErrorCountnumberErrors that have an auto-fix
fixableWarningCountnumberWarnings that have an auto-fix
outputstring?Final source — present when fix: true applied at least one fix

Each LintMessage:

FieldTypeDescription
ruleIdstring | nullThe rule that produced the message (null if none)
severity1 | 22 = error, 1 = warning
messagestringHuman-readable message
messageIdstring?Stable message id, when the rule provides one
line / columnnumber1-based position (column counts UTF-16 code units)
endLine / endColumnnumber?1-based end position, when available
fix{ range: [number, number]; text: string }?Flat UTF-16 offset range + replacement text
suggestionsLintSuggestion[]?Suggested fixes (each with desc, fix, and optional messageId / data)

Options

new Rslint(options) accepts:

OptionTypeDefaultDescription
cwdstringcurrent cwdWorking directory for targets and discovery; also the authored base of inline entries without basePath
overrideConfigRslintConfigEntry | RslintConfig | nullExtra config appended after the resolved/discovered config; basePath inherits the ConfigArray base, while entries without it keep Rslint's existing cwd-relative behavior
overrideConfigFilestring | true | nullnullstring: use this module; basePath resolves from cwd, while entries without it retain the module-directory base. true: use only the inline override; otherwise discover
fixbooleanfalseApply rule auto-fixes; results carry output
virtualFilesRecord<string, string>In-memory file overlay (path → content); unresolved reads may fall back to disk

See the basePath configuration reference for the full source matrix.