web-infra-dev / web-infra-dev/rslint
[Bug]: languageOptions.parserOptions.jsx related options (jsxPragma, jsxFragmentName) are ignored/broken
- 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
}
```
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
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