web-infra-dev / web-infra-dev/rslint

[Bug]: languageOptions.parserOptions.jsx related options (jsxPragma, jsxFragmentName) are ignored/broken

Open
#1,230 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
459
Forks
33
Avg merge
1d 2h
Merged PRs (30d)
376

Description

### System Info
```text
System:
OS: Linux (x64)
npmPackages:
@rslint/core: 0.6.5
```

### Details
When configuring custom JSX pragmas (e.g., `jsxPragma: 'h'` and `jsxFragmentName: 'Fragment'` for Preact or custom JSX frameworks) under `languageOptions.parserOptions`, Rslint fails to respect them. This results in false-positive unused variable/import warnings (e.g. `'h' is defined but never used`) because Rslint incorrectly defaults the JSX pragma name to `'React'`.

We traced this behavior to two distinct issues in the codebase:

1. **Scope Analyzer Option Drop (JS/TS worker)**:
In `packages/rslint/src/eslint-plugin/linter/ecma-language-plugin.ts` (lines 271–284), the scope manager factory is instantiated without `jsxPragma` or `jsxFragmentName`:
```typescript
const scopeManagerFactory = (() => {
const inner = makeScopeManagerFactory(ast, {
filePath: req.filePath,
sourceType: req.languageOptions?.sourceType ?? 'module',
ecmaVersion: req.languageOptions?.ecmaVersion,
globals,
impliedStrict: ecmaFeatures?.impliedStrict,
globalReturn: ecmaFeatures?.globalReturn,
// missing jsxPragma and jsxFragmentName!
});
```
This causes `ts-scope-manager` in the Node/JS worker to fall back to `'React'`, which impacts any scope/variable checking rules running inside the worker (like `react/jsx-uses-vars`).

2. **Go Config Merge Key Override**:
In `internal/config/config.go` (lines 569–583), `mergeLanguageOptions` only performs a shallow merge of the raw `parserOptions` map. If a base config configures `parserOptions.project` and a sub-config override configures `parserOptions.jsxPragma`, the `parserOptions` map is replaced entirely, throwing away `project` configurations on the wire:
```go
if len(override.Raw) > 0 {
mergedRaw := make(map[string]any, len(base.Raw)+len(override.Raw))
for k, v := range base.Raw {
mergedRaw[k] = v
}
for k, v := range override.Raw {
mergedRaw[k] = v // completely overwrites nested objects like parserOptions
}
merged.Raw = mergedRaw
}
```

3. **Fallback Program compiler options**:
In `cmd/rslint/programs.go`, the directory-scan/fallback `compiler.Program` options do not map `jsxPragma` or `jsxFragmentName` to `core.CompilerOptions.JsxFactory`. Go's native rules (like `@typescript-eslint/no-unused-vars`) therefore fall back to looking for `React`.

### Reproduce Steps

1. Create a minimal project with a single JSX file `src/index.jsx` (no `tsconfig.json` present to ensure pure-linter config mapping is tested):
```jsx
// src/index.jsx
import { h } from 'preact';

export function App() {
return

Hello Preact
;
}
```

2. Create identical configurations for ESLint and Rslint targeting this file:
* **`eslint.config.js`**:
```javascript
import tsParser from '@typescript-eslint/parser';
import tsPlugin from '@typescript-eslint/eslint-plugin';

export default [
{
files: ['src/**/*.jsx'],
plugins: {
'@typescript-eslint': tsPlugin,
},
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaFeatures: { jsx: true },
jsxPragma: 'h',
jsxFragmentName: 'Fragment',
}
},
rules: {
'@typescript-eslint/no-unused-vars': 'error'
}
}
];
```
* **`rslint.config.mjs`**:
```javascript
import { defineConfig } from '@rslint/core';

export default defineConfig([
{
files: ['src/**/*.jsx'],
plugins: ['@typescript-eslint'],
languageOptions: {
parserOptions: {
ecmaFeatures: { jsx: true },
jsxPragma: 'h',
jsxFragmentName: 'Fragment',
}
},
rules: {
'@typescript-eslint/no-unused-vars': 'error'
}
}
]);
```

3. **Run ESLint**:
```bash
npx eslint src/index.jsx
```
* **Result**: `0` problems (passes cleanly, because `h` is recognized as the JSX pragma).

4. **Run Rslint**:
```bash
npx rslint --config rslint.config.mjs src/index.jsx
```
* **Result**:
```text
@typescript-eslint/no-unused-vars — [error] 'h' is defined but never used.
╭─┴──────────( src/index.jsx:1:10 )─────
│ 1 │ import { h } from 'preact';
│ 2 │
╰────────────────────────────────
```

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the JSX pragma warning with the provided src/index.jsx and rslint.config.mjs, then inspect packages/rslint/src/eslint-plugin/linter/ecma-language-plugin.ts, internal/config/config.go, and cmd/rslint/programs.go. Done means jsxPragma and jsxFragmentName reach scope analysis and fallback compiler options, while nested parserOptions values survive config merging without the false-positive unused-variable warning.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, javascript, typescript
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.