Skip to content

feat: add JS configuration file support#492

Merged
fansenze merged 1 commit intomainfrom
feat/js-config-support
Mar 16, 2026
Merged

feat: add JS configuration file support#492
fansenze merged 1 commit intomainfrom
feat/js-config-support

Conversation

@fansenze
Copy link
Copy Markdown
Contributor

@fansenze fansenze commented Mar 5, 2026

Summary

Add support for rslint.config.{ts|mts|js|mjs} using ESLint flat config style. rslint.json/jsonc remains backward-compatible but deprecated.

Usage

Quick start

# Generate a default JS config in the current directory
npx rslint --init

This creates a config file with a recommended preset. The file extension depends on the project setup (following the ESLint convention):

  • Has tsconfig.jsonrslint.config.ts
  • Has "type": "module" in package.jsonrslint.config.js (ESM syntax)
  • Otherwise → rslint.config.mjs (ESM syntax, .mjs ensures Node treats it as ESM)
// rslint.config.mjs
import { defineConfig, ts } from '@rslint/core';

export default defineConfig([
  // Global ignores — entry with only `ignores` excludes matching files from all rules
  // (same semantics as ESLint flat config)
  { ignores: ['dist/**', 'build/**', 'coverage/**', 'node_modules/**'] },

  // Use the recommended preset for TypeScript projects
  // Includes @typescript-eslint/recommended + eslint:recommended overrides
  ts.configs.recommended,

  // Per-project rule overrides
  {
    rules: {
      '@typescript-eslint/no-explicit-any': 'off',
    },
  },
]);

Available presets

Preset Description
js.configs.recommended ESLint eslint:recommended (JS files only)
ts.configs.recommended @typescript-eslint/recommended + eslint:recommended override layer (TS files only)
reactPlugin.configs.recommended eslint-plugin-react recommended (JSX/TSX files)
importPlugin.configs.recommended eslint-plugin-import recommended

Each preset lists all official rules — implemented ones are active, unimplemented ones are commented out for visibility.

Config file priority

rslint.config.js > .mjs > .ts > .mts > rslint.json > rslint.jsonc

Use --config <path> to specify a custom config file path.

TypeScript config files

.ts/.mts config files are supported via:

  1. Native import() on Node.js ≥ 22.6 (detected via process.features.typescript)
  2. jiti fallback (install as dev dependency: npm install -D jiti)

Architecture

JS handles config loading, Go handles rule execution — both share identical rule resolution logic.

  • JS side loads config file → serializes to JSON → pipes to Go via --config-stdin
  • stdout/stderr inherited — Go outputs directly to terminal, no IPC changes
  • JSON config: normalizeJSONConfig injects core/plugin rules at load time, so GetConfigForFile processes both config types identically

Config merging (ESLint flat config semantics)

  • rules / settings: shallow merge, later entries override
  • languageOptions: deep merge (parserOptions fields merged independently; ProjectService uses *bool to distinguish unset from explicit false)
  • Entry with only ignores acts as a global ignore
  • No matching entry → file is not linted (GetConfigForFile returns nil)

Monorepo per-file config resolution (LSP)

VS Code extension discovers all rslint.config.{js,mjs,ts,mts} files in the workspace via workspace.findFiles and sends the full set to the LSP server via rslint/configUpdate notification. Config files are loaded in parallel via Promise.allSettled — a broken config in one sub-package does not prevent other configs from loading. Config keys use URI strings (file:///...) throughout, matching LSP protocol convention — this eliminates cross-platform path separator issues by design. The rslint/configUpdate handler is registered as a blocking method to ensure config updates are serialized on the dispatch loop, preventing data races with concurrent diagnostics.

When linting a file, getConfigForURI walks up the URI hierarchy from the file's directory to find the nearest JS/TS config (ESLint v10 flat config behavior), returning both the config entries and the config's own directory as cwd for glob matching. This ensures monorepo sub-package patterns like files: ['src/**/*.ts'] resolve relative to the sub-package root, not the workspace root. If no JS config matches, it falls back to the JSON config with s.cwd (workspace root) as cwd.

uriToPath correctly handles Windows drive letter URIs (file:///C:/Users/...C:/Users/...) to ensure the returned cwd uses a DOS root consistent with os.Getwd(), avoiding root-type mismatches in tspath.ConvertToRelativePath.

The handleConfigUpdate handler distinguishes nil payloads (malformed — preserves existing configs) from explicitly empty configs arrays (legitimate "all configs deleted" — clears state).

CLI error handling

loadConfigFile and normalizeConfig failures are caught independently with user-friendly error messages (e.g., "Error: failed to load config ...", "Error: invalid config ..."), rather than exposing raw stack traces.

Pure JS project support

  • When no parserOptions.project is specified, auto-detect tsconfig.json in cwd
  • If no tsconfig found, generate a temporary one with allowJs: true for JS-only projects
  • LSP accepts JSON configs without parserOptions.project (session discovers tsconfig files independently via projectService)

Config export structure

Config presets are exported as named exports from the main entry point:

import { ts, js, reactPlugin, importPlugin } from '@rslint/core';
// ts.configs.recommended, js.configs.recommended, reactPlugin.configs.recommended, importPlugin.configs.recommended

TS config loading strategy

  • Prefer native import() (Node 22.6+, detected via process.features.typescript), fall back to jiti (optional peer dep)
  • jiti is initialized with path.dirname(configPath) as base to ensure relative imports in config files resolve correctly

Scope of Changes

  • Go: --config-stdin flag with 50MB size guard, unified GetConfigForFile(filePath, cwd) merge logic with explicit config directory, normalizeJSONConfig for JSON default rules, --init generates .js/.mjs based on package.json "type" field (ESLint convention), deprecation warning for JSON config, pure JS project tsconfig fallback, isFileMatched/isFileIgnored take cwd parameter, RegisterAllRules wrapped with sync.Once to fix concurrent map writes, textDocument/codeAction added to blocking methods to prevent data race on s.diagnostics
  • JS: cli.ts (config discovery + node:util parseArgs with --config=value and -- separator support + stdin pipe + user-friendly error handling for config load/validation failures), config-loader.ts (load/normalize with files/ignores type validation, jiti uses config file directory as base), define-config.ts, preset configs (ts.configs.recommended, js.configs.recommended, reactPlugin.configs.recommended, importPlugin.configs.recommended) aligned with official eslint/typescript-eslint presets (added missing no-with off in TS overrides, commented unimplemented rules: no-new-symbol, no-unassigned-vars, preserve-caught-error)
  • LSP: handleConfigUpdate notification handler with multi-config support (URI-keyed jsConfigs map), registered as blocking method for concurrency safety, nil vs empty payload guard, getConfigForURI returns (config, cwd) tuple — walks URI hierarchy for nearest config and returns per-config directory as cwd for correct glob matching, uriToPath handles Windows drive letter URIs (file:///C:/...C:/...), uriDirname helper for platform-independent URI manipulation, reloadConfig no longer validates tsConfigs (session handles discovery), clears JSON config path to prevent watcher override
  • VSCode: loadAndSendConfig discovers all JS configs via workspace.findFiles, loads in parallel via Promise.allSettled (one broken config doesn't block others), sends as URI-keyed array, ESM cache busting for hot reload (.js/.mjs directly, .ts/.mts with native import fallback to jiti), configWatcher for **/rslint.config.{ts,mts,js,mjs}
  • Tests:
    • Go unit: config merge logic (30+ tests), handleConfigUpdate including malformed/nil payload tests, getConfigForURI with monorepo multi-package (4 packages), 3-level nesting, Windows URI + cwd assertion, uriToPath cross-platform (Unix/Windows drive letter/UNC/edge cases), uriDirname edge cases, isFileIgnored/isFileMatched with cwd parameter, GetConfigForFile cwd-based matching and ignores including Windows paths (forward-slash cwd, backslash cwd, monorepo sub-package), --init with type:module/.mjs/.ts variants, reloadConfig accepts config without tsconfigs, isBlockingMethod unit test (including codeAction) + dispatch loop serialization test for rslint/configUpdate, RegisterAllRules concurrent safety test with -race
    • JS unit: normalizeConfig validation, loadConfigFile with Promise export, defineConfig identity check, config presets importability and structure
    • CLI e2e: JS config auto-detection (.js/.ts), --config flag (equals/space format), flag passthrough (--fix/--quiet/--format), --init generation (.ts/esm .js/non-esm .mjs/no-pkg .mjs), JS-over-JSON priority, global ignores, rules off, src/**/*.ts pattern matching with correct cwd, user-friendly error for syntax errors and non-array configs
    • VS Code e2e: single-project JS config (diagnostics, priority over JSON, hot reload, delete, create), monorepo multi-config (root/foo/bar resolution with src/**/*.ts pattern, sub-package config change, sub-package config deletion fallback, root config change, broken config resilience — valid configs still work alongside broken sibling, broken package falls back to root, corrupted config at runtime doesn't break other configs, dynamic sub-package config creation with fallback on delete, root config deletion isolation — sub-packages with own config unaffected), no-config fallback, full config lifecycle (no config → JSON created → JS override → JS deleted → JSON restored → JSON deleted)

Known Limitations

  • LSP cannot load JS/TS config files directly (requires VS Code extension to send resolved config via rslint/configUpdate notification)
  • js.configs.recommended for pure JS projects generates a temporary tsconfig; users with complex setups should provide their own

Checklist

  • Tests updated
  • Documentation updated (or not required)

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades rslint's configuration system by introducing support for JavaScript and TypeScript configuration files, adopting the modern ESLint flat config style. This change provides greater flexibility and type safety for users defining their linting rules. The core linter remains in Go, with a new JavaScript layer handling the loading and serialization of these dynamic configurations. Existing JSON configurations are still supported but are now considered deprecated, guiding users towards the more powerful JS/TS options.

Highlights

  • JS/TS Configuration Support: Introduced support for rslint.config.{ts|mts|js|mjs} files, allowing configuration to be defined using JavaScript or TypeScript, aligning with ESLint's flat config style.
  • Backward Compatibility & Deprecation: Maintained backward compatibility for existing rslint.json/jsonc files, but marked them as deprecated, with a warning message encouraging migration to the new JS/TS format.
  • Hybrid Architecture: Implemented a hybrid architecture where the JavaScript layer loads and preprocesses the JS/TS configuration, serializes it to JSON, and then pipes it via stdin to the Go-based linter, which remains the core processing engine.
  • Enhanced Configuration Merging Logic: Refined the configuration merging logic to handle rules (shallow merge), settings (shallow merge), and languageOptions (deep merge, with parserOptions fields independently merged), following ESLint flat config semantics.
  • Improved Rule Resolution: Updated rule resolution to differentiate between core ESLint rules (enabled by default in JSON config) and plugin rules (explicitly listed in JS config, auto-enabled in JSON config via plugins field), using GetPluginRules and GetCoreRules.
  • Dynamic Config Initialization: Modified the rslint --init command to intelligently generate either rslint.config.ts or rslint.config.js based on the presence of a tsconfig.json file.
  • TypeScript Config Loading: Implemented TypeScript configuration loading with a preference for native Node.js import() (Node 22.6+) and a fallback to jiti for broader compatibility.
Changelog
  • cmd/rslint/api.go
    • Updated ProjectService field in ParserOptions to use *bool for clearer true/false/unset semantics.
  • cmd/rslint/cmd.go
    • Added io package import.
    • Updated --config flag description to remove default value.
    • Introduced a new --config-stdin flag for internal use by the JS config loader.
    • Implemented logic to read configuration from stdin when --config-stdin is present.
    • Modified the call to GetEnabledRules to pass the isJSConfig flag for correct rule resolution.
  • internal/config/config.go
    • Added Settings type for shared rule settings.
    • Modified ConfigEntry fields to be omitempty for better JSON serialization.
    • Updated ParserOptions to use *bool for ProjectService to distinguish unset from explicit false.
    • Removed TypedRules struct and GetAllRulesForPlugin function.
    • Removed the GetRulesForFile method from RslintConfig.
    • Introduced BoolPtr helper function.
    • Added MergedConfig struct to represent the final computed configuration for a file.
    • Implemented GetConfigForFile method for RslintConfig to compute merged configurations based on ESLint flat config semantics, handling global ignores, file matching, and rule/setting/languageOptions merging.
    • Added helper functions isGlobalIgnoreEntry, isFileMatched, mergeLanguageOptions, GetPluginRules, and GetCoreRules.
    • Updated InitDefaultConfig to generate rslint.config.ts or rslint.config.js based on tsconfig.json presence, and to check for existing JS/TS config files.
  • internal/config/config_test.go
    • Added TestParserOptionsProjectServicePtr to verify the correct handling of *bool for ProjectService.
  • internal/config/init_test.go
    • Added new test file init_test.go to cover InitDefaultConfig functionality for TypeScript and JavaScript projects, and existing config scenarios.
  • internal/config/loader.go
    • Imported strings package.
    • Added a deprecation warning to LoadRslintConfig for JSON/JSONC configuration files.
  • internal/config/merge_test.go
    • Added new test file merge_test.go with comprehensive tests for GetConfigForFile covering JS/JSON config differences, plugin auto-enabling, global/entry ignores, file matching, rule/settings shallow merge, language options deep merge, and rule array configuration.
  • internal/config/rule_registry.go
    • Modified GetEnabledRules to accept an isJSConfig parameter and return the MergedConfig along with configured rules, reflecting the new configuration merging strategy.
  • internal/lsp/service.go
    • Updated the call to GetEnabledRules to pass false for isJSConfig in the LSP context, ensuring JSON config behavior.
  • packages/rslint/bin/rslint.cjs
    • Converted main function to async.
    • Delegated CLI execution to the new cli.js module, importing its run function.
  • packages/rslint/package.json
    • Added configs export mapping to src/configs/index.ts.
    • Included src/ directory in the files array for publishing.
    • Added jiti as an optional peer dependency with peerDependenciesMeta.
  • packages/rslint/src/cli.ts
    • Added new file cli.ts to implement the JavaScript CLI logic.
    • Includes functions for argument parsing, finding JS config files, and orchestrating the loading and piping of JS configurations to the Go binary.
  • packages/rslint/src/config-loader.ts
    • Added new file config-loader.ts to handle loading and normalizing JS/TS configuration files.
    • Implements logic for native import() for JS/MJS and a fallback to jiti for TS/MTS files.
  • packages/rslint/src/configs/index.ts
    • Added new file index.ts to export ts and js configuration presets.
  • packages/rslint/src/configs/javascript.ts
    • Added new file javascript.ts defining a recommended configuration preset for JavaScript projects, including core ESLint rules.
  • packages/rslint/src/configs/typescript.ts
    • Added new file typescript.ts defining a recommended configuration preset for TypeScript projects, including core ESLint and @typescript-eslint rules.
  • packages/rslint/src/define-config.ts
    • Added new file define-config.ts providing a defineConfig helper function for type-safe definition of RslintConfigEntry arrays.
  • packages/rslint/src/index.ts
    • Exported defineConfig function and RslintConfigEntry interface for public API usage.
Activity
  • The author fansenze initiated this pull request to introduce JavaScript configuration file support.
  • Extensive changes were made across Go and TypeScript codebases to integrate the new configuration system.
  • Over 20 new Go tests were added to validate the complex configuration merging logic, global ignore handling, *bool semantics, and the --init command's behavior.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces comprehensive support for JavaScript-based configuration files (rslint.config.{ts|mts|js|mjs}), aligning with ESLint's flat config style. The changes involve significant refactoring on both the Go and JavaScript sides to handle config loading, merging, and rule resolution. The Go binary now accepts configuration via stdin, enabling the JavaScript frontend to preprocess and serialize config files. The cmd/rslint package has been updated to include a new --config-stdin flag and logic to parse the incoming JSON payload. The internal/config package sees major architectural changes with the introduction of MergedConfig, GetConfigForFile, and refined rule resolution helpers (GetPluginRules, GetCoreRules), replacing older, less flexible methods. The ParserOptions.ProjectService field has been updated to use a pointer to a boolean (*bool) to correctly distinguish between unset and explicitly false values, which is a good practice for optional boolean fields in Go structs that are unmarshaled from JSON. New test files (internal/config/init_test.go and internal/config/merge_test.go) have been added to thoroughly cover the new initialization and config merging logic, which is excellent for maintaining code quality. Additionally, the packages/rslint directory now includes the necessary JavaScript files (cli.ts, config-loader.ts, define-config.ts, and preset configs) to implement the client-side config loading and interaction with the Go binary. The deprecation warning for rslint.json/jsonc files is a thoughtful addition for user migration. Overall, the changes are well-structured, thoroughly tested, and effectively implement the new feature as described in the pull request.

@fansenze fansenze force-pushed the feat/js-config-support branch 17 times, most recently from 49cd3ae to 2b98a70 Compare March 13, 2026 01:56
@fansenze fansenze force-pushed the feat/js-config-support branch 9 times, most recently from 4ec13ee to 7d4e998 Compare March 13, 2026 09:22
@fansenze fansenze marked this pull request as ready for review March 13, 2026 09:25
@fansenze fansenze force-pushed the feat/js-config-support branch 13 times, most recently from ced00b7 to 571fbbd Compare March 16, 2026 09:26
@fansenze fansenze force-pushed the feat/js-config-support branch from 571fbbd to e0baba6 Compare March 16, 2026 09:28
@fansenze fansenze merged commit d6d9375 into main Mar 16, 2026
15 checks passed
@fansenze fansenze deleted the feat/js-config-support branch March 16, 2026 10:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants