Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/rstack/src/cli/commands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { join, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { getConfigState } from '../config.ts';
import { CLI_CONFIG_PATH_ENV, getConfigState } from '../config.ts';
import { insertConfigArg, parseArgs, parseCliArgs } from './args.ts';
import { hasHelpFlag, printCommandHelp } from './help.ts';

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

// Rslint's JS plugin workers inherit `process.env`. Clear an inherited path so
// that a run without `--config` still searches cwd.
const { configPath } = getConfigState();
if (configPath === undefined) {
delete process.env[CLI_CONFIG_PATH_ENV];
} else {
process.env[CLI_CONFIG_PATH_ENV] = configPath;
}

const argv = [
process.execPath,
'rslint',
Expand Down
7 changes: 7 additions & 0 deletions packages/rstack/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ export const getConfigState = (): ConfigState => {
return globalThis.__rstackCliState;
};

/**
* Passes the `--config` path to Rslint's JS plugin workers, which import the
* internal lint config in a fresh realm without the CLI state above. Only that
* config reads it.
*/
export const CLI_CONFIG_PATH_ENV = 'RSTACK_CLI_CONFIG_PATH';

type Define = {
/**
* Inherits shared configs from left to right, followed by the project's own
Expand Down
13 changes: 10 additions & 3 deletions packages/rstack/src/rslintConfig.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { loadRstackConfig, type LoadedRstackConfig } from './config.ts';
import {
CLI_CONFIG_PATH_ENV,
loadRstackConfig,
type LoadedRstackConfig,
} from './config.ts';
import type { RslintConfig } from '@rslint/core';
import { color, logger } from 'rslog';
import { resolveRslintConfig } from './configLayers.ts';

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

Expand Down
47 changes: 47 additions & 0 deletions packages/rstack/tests/cli/specify-config-lint/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import path from 'node:path';
import { test } from '#test-helpers';
import { expect } from 'rstack/test';

// Rslint runs JS plugin rules in worker threads, which must also load the
// config selected by `--config` instead of searching cwd.
test('should run JS plugin rules from --config outside cwd', async ({
execCli,
logHelper,
}) => {
expect(() =>
execCli('lint -c workspace/app/rstack.config.ts workspace/app/src'),
).toThrow();
await logHelper.expectLog('foo is not allowed');
});

test('should prefer --config JS plugin rules over the cwd config', async ({
cwd,
execCli,
logHelper,
}) => {
expect(() =>
execCli('lint -c app/rstack.config.ts app/src', {
cwd: path.join(cwd, 'workspace'),
}),
).toThrow();
await logHelper.expectLog('foo is not allowed');
logHelper.expectNoLog('rule from the cwd config');
});

test('should ignore an inherited config path without --config', async ({
cwd,
execCli,
logHelper,
}) => {
const workspace = path.join(cwd, 'workspace');
expect(() =>
execCli('lint app/src', {
cwd: workspace,
env: {
RSTACK_CLI_CONFIG_PATH: path.join(workspace, 'app/rstack.config.ts'),
},
}),
).toThrow();
await logHelper.expectLog('rule from the cwd config');
logHelper.expectNoLog('foo is not allowed');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { define } from 'rstack';

type Context = { report: (descriptor: object) => void };

define.lint([
{
files: ['**/*.js'],
plugins: {
local: {
rules: {
'no-foo': {
meta: { messages: { foo: 'foo is not allowed' } },
create: (context: Context) => ({
Identifier(node: { name: string }) {
if (node.name === 'foo') {
context.report({ node, messageId: 'foo' });
}
},
}),
},
},
},
},
rules: { 'local/no-foo': 'error' },
},
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo = 1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { define } from 'rstack';

type Context = { report: (descriptor: object) => void };

// Registers the same plugin name as `app/rstack.config.ts` with another rule.
define.lint([
{
plugins: {
local: {
rules: {
'no-foo': {
meta: { messages: { cwd: 'rule from the cwd config' } },
create: (context: Context) => ({
Program(node: object) {
context.report({ node, messageId: 'cwd' });
},
}),
},
},
},
},
rules: { 'local/no-foo': 'error' },
},
]);
Loading