Skip to content

Commit bd2406e

Browse files
committed
fix(lint): respect --config in Rslint plugin workers
Rslint runs JS plugin rules in worker threads that import the internal lint config again in a fresh realm. The --config path only lived in the main thread's global state, so the workers searched cwd instead. From a directory without rstack.config.*, linting failed with "worker exited during init", and when a config in cwd registered the same plugin name, the workers silently ran that config's implementation. Before starting Rslint, set an environment variable to the resolved --config path and pass it to loadRstackConfig() in the internal lint config. Only that config reads it, so other commands and programmatic loadRstackConfig() calls are unchanged. Clear the variable when --config is absent so an inherited value is ignored.
1 parent 5fe9881 commit bd2406e

7 files changed

Lines changed: 125 additions & 4 deletions

File tree

‎packages/rstack/src/cli/commands.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { join, resolve } from 'node:path';
22
import { pathToFileURL } from 'node:url';
3-
import { getConfigState } from '../config.ts';
3+
import { CLI_CONFIG_PATH_ENV, getConfigState } from '../config.ts';
44
import { insertConfigArg, parseArgs, parseCliArgs } from './args.ts';
55
import { hasHelpFlag, printCommandHelp } from './help.ts';
66

@@ -153,6 +153,15 @@ async function runRslintCLI(args: string[]): Promise<void> {
153153
return printCommandHelp('lint');
154154
}
155155

156+
// Rslint's JS plugin workers inherit `process.env`. Clear an inherited path so
157+
// that a run without `--config` still searches cwd.
158+
const { configPath } = getConfigState();
159+
if (configPath === undefined) {
160+
delete process.env[CLI_CONFIG_PATH_ENV];
161+
} else {
162+
process.env[CLI_CONFIG_PATH_ENV] = configPath;
163+
}
164+
156165
const argv = [
157166
process.execPath,
158167
'rslint',

‎packages/rstack/src/config.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,13 @@ export const getConfigState = (): ConfigState => {
140140
return globalThis.__rstackCliState;
141141
};
142142

143+
/**
144+
* Passes the `--config` path to Rslint's JS plugin workers, which import the
145+
* internal lint config in a fresh realm without the CLI state above. Only that
146+
* config reads it.
147+
*/
148+
export const CLI_CONFIG_PATH_ENV = 'RSTACK_CLI_CONFIG_PATH';
149+
143150
type Define = {
144151
/**
145152
* Inherits shared configs from left to right, followed by the project's own

‎packages/rstack/src/rslintConfig.ts‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import { existsSync } from 'node:fs';
22
import { join } from 'node:path';
3-
import { loadRstackConfig, type LoadedRstackConfig } from './config.ts';
3+
import {
4+
CLI_CONFIG_PATH_ENV,
5+
loadRstackConfig,
6+
type LoadedRstackConfig,
7+
} from './config.ts';
48
import type { RslintConfig } from '@rslint/core';
59
import { color, logger } from 'rslog';
610
import { resolveRslintConfig } from './configLayers.ts';
711

812
// Expose the loaded config so `rs check` can pass it to fmt instead of loading
9-
// and executing the Rstack config a second time.
10-
export const loadedConfig: LoadedRstackConfig = await loadRstackConfig();
13+
// and executing the Rstack config a second time. Rslint also imports this
14+
// module in its JS plugin workers, which get `--config` from the environment.
15+
export const loadedConfig: LoadedRstackConfig = await loadRstackConfig({
16+
configFilePath: process.env[CLI_CONFIG_PATH_ENV],
17+
});
1118
const { configs } = loadedConfig;
1219
const lintConfig = await resolveRslintConfig([configs]);
1320

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import path from 'node:path';
2+
import { test } from '#test-helpers';
3+
import { expect } from 'rstack/test';
4+
5+
// Rslint runs JS plugin rules in worker threads, which must also load the
6+
// config selected by `--config` instead of searching cwd.
7+
test('should run JS plugin rules from --config outside cwd', async ({
8+
execCli,
9+
logHelper,
10+
}) => {
11+
expect(() =>
12+
execCli('lint -c workspace/app/rstack.config.ts workspace/app/src'),
13+
).toThrow();
14+
await logHelper.expectLog('foo is not allowed');
15+
});
16+
17+
test('should prefer --config JS plugin rules over the cwd config', async ({
18+
cwd,
19+
execCli,
20+
logHelper,
21+
}) => {
22+
expect(() =>
23+
execCli('lint -c app/rstack.config.ts app/src', {
24+
cwd: path.join(cwd, 'workspace'),
25+
}),
26+
).toThrow();
27+
await logHelper.expectLog('foo is not allowed');
28+
logHelper.expectNoLog('rule from the cwd config');
29+
});
30+
31+
test('should ignore an inherited config path without --config', async ({
32+
cwd,
33+
execCli,
34+
logHelper,
35+
}) => {
36+
const workspace = path.join(cwd, 'workspace');
37+
expect(() =>
38+
execCli('lint app/src', {
39+
cwd: workspace,
40+
env: {
41+
RSTACK_CLI_CONFIG_PATH: path.join(workspace, 'app/rstack.config.ts'),
42+
},
43+
}),
44+
).toThrow();
45+
await logHelper.expectLog('rule from the cwd config');
46+
logHelper.expectNoLog('foo is not allowed');
47+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { define } from 'rstack';
2+
3+
type Context = { report: (descriptor: object) => void };
4+
5+
define.lint([
6+
{
7+
files: ['**/*.js'],
8+
plugins: {
9+
local: {
10+
rules: {
11+
'no-foo': {
12+
meta: { messages: { foo: 'foo is not allowed' } },
13+
create: (context: Context) => ({
14+
Identifier(node: { name: string }) {
15+
if (node.name === 'foo') {
16+
context.report({ node, messageId: 'foo' });
17+
}
18+
},
19+
}),
20+
},
21+
},
22+
},
23+
},
24+
rules: { 'local/no-foo': 'error' },
25+
},
26+
]);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo = 1;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { define } from 'rstack';
2+
3+
type Context = { report: (descriptor: object) => void };
4+
5+
// Registers the same plugin name as `app/rstack.config.ts` with another rule.
6+
define.lint([
7+
{
8+
plugins: {
9+
local: {
10+
rules: {
11+
'no-foo': {
12+
meta: { messages: { cwd: 'rule from the cwd config' } },
13+
create: (context: Context) => ({
14+
Program(node: object) {
15+
context.report({ node, messageId: 'cwd' });
16+
},
17+
}),
18+
},
19+
},
20+
},
21+
},
22+
rules: { 'local/no-foo': 'error' },
23+
},
24+
]);

0 commit comments

Comments
 (0)