Skip to content

Commit fc5f80f

Browse files
committed
fix(vscode): refresh bridge attribution on detection
1 parent 8f255d6 commit fc5f80f

3 files changed

Lines changed: 112 additions & 11 deletions

File tree

packages/vscode/src/stacks/lint/Rslint.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ export class Rslint implements Disposable {
309309
public readonly workspaceFolder: WorkspaceFolder;
310310
private readonly router: WorkspaceDocumentRouter;
311311
private readonly reportStatus: RslintStatusSink;
312-
private readonly bridgeConfigPath: string | undefined;
312+
private bridgeConfigPath: string | undefined;
313313
private readonly installation: CoreInstallation;
314314
private readonly lspOutputChannel: OutputChannel;
315315
private readonly outputChannel: OutputChannel;
@@ -343,6 +343,11 @@ export class Rslint implements Disposable {
343343
this.onClosed = options.onClosed;
344344
}
345345

346+
public setBridgeConfigPath(configPath: string | undefined): void {
347+
if (this.installation.mode === 'bridged')
348+
this.bridgeConfigPath = configPath;
349+
}
350+
346351
private report(state: StackState): void {
347352
this.reportStatus(state);
348353
}

packages/vscode/src/stacks/lint/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ class RslintController implements StackController {
9494
this.#subscriptions.push(
9595
context.onDidChangeDetection((snapshot) => {
9696
this.#snapshot = snapshot;
97+
for (const runtime of this.#runtimes.values()) {
98+
runtime.setBridgeConfigPath(
99+
snapshot.forFolder(runtime.workspaceFolder)?.rootRstackConfigPath,
100+
);
101+
}
97102
this.pruneDepartedFolders();
98103
// A detection pass fires on config topology and lockfile changes —
99104
// exactly the moments a document's core may have appeared, moved or

packages/vscode/tests/stacks/lint/start.test.ts

Lines changed: 101 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,54 @@
11
import { expect, it, rs } from '@rstest/core';
2-
import type { StackState } from '../../../src/types';
2+
import type {
3+
DetectionSnapshot,
4+
StackContext,
5+
StackState,
6+
} from '../../../src/types';
37
import type { RslintOptions } from '../../../src/stacks/lint/Rslint';
8+
import type { ResolvedCoreRuntime } from '../../../src/stacks/lint/CoreResolver';
49
import { registerEditorProxy } from '../../../src/stacks/lint/worker/index';
510

611
let refreshOutcome:
712
'missing' | 'broken' | 'fixed' | 'changed' | 'changed-once' = 'missing';
813

9-
rs.mock('vscode', () => ({
10-
RelativePattern: class {},
11-
workspace: {
12-
createFileSystemWatcher: () => ({
13-
onDidCreate() {},
14-
onDidChange() {},
15-
onDidDelete() {},
16-
}),
14+
rs.mock('vscode', () => {
15+
const api = {
16+
RelativePattern: class {},
17+
workspace: {
18+
textDocuments: [],
19+
onDidChangeWorkspaceFolders: () => ({ dispose() {} }),
20+
onDidOpenTextDocument: () => ({ dispose() {} }),
21+
onDidCloseTextDocument: () => ({ dispose() {} }),
22+
createFileSystemWatcher: () => ({
23+
onDidCreate() {},
24+
onDidChange() {},
25+
onDidDelete() {},
26+
}),
27+
},
28+
env: {},
29+
};
30+
return { ...api, default: api };
31+
});
32+
let runtimeFactory: (resolved: ResolvedCoreRuntime) => Rslint;
33+
rs.mock('../../../src/stacks/lint/RuntimeManager', () => ({
34+
RuntimeManager: class {
35+
constructor(
36+
_router: unknown,
37+
_resolver: unknown,
38+
create: typeof runtimeFactory,
39+
) {
40+
runtimeFactory = create;
41+
}
42+
initialize() {}
43+
clearResolutionCache() {}
44+
async reconcileOpenDocuments() {}
1745
},
18-
env: {},
46+
}));
47+
rs.mock('../../../src/stacks/lint/CoreResolver', () => ({
48+
CoreResolver: class {},
49+
}));
50+
rs.mock('../../../src/stacks/lint/ruleDocumentationProviders', () => ({
51+
registerRuleDocumentationProviders: () => [],
1952
}));
2053
rs.mock('../../../src/shared/nodeExecutableSetting', () => ({
2154
getConfiguredNodeExecutable: () => undefined,
@@ -89,6 +122,64 @@ rs.mock('vscode-languageclient/node', () => ({
89122
}));
90123

91124
import { Rslint } from '../../../src/stacks/lint/Rslint';
125+
import { createRslintController } from '../../../src/stacks/lint';
126+
127+
it('updates a surviving bridge runtime attribution before the next config failure', async () => {
128+
const folder = {
129+
name: 'project',
130+
uri: { fsPath: '/project', toString: () => 'file:///project' },
131+
};
132+
const snapshot = (configPath: string): DetectionSnapshot => {
133+
const entry = {
134+
folder,
135+
rootRstackConfigPath: configPath,
136+
stacks: { rslint: { mode: 'bridged' } },
137+
};
138+
return {
139+
forFolder: () => entry,
140+
foldersFor: () => [entry],
141+
} as unknown as DetectionSnapshot;
142+
};
143+
let onDetection!: (snapshot: DetectionSnapshot) => void;
144+
const warnings: string[] = [];
145+
const states: StackState[] = [];
146+
const controller = createRslintController();
147+
await controller.register({
148+
detection: snapshot('/project/rstack.config.js'),
149+
onDidChangeDetection: (listener: typeof onDetection) => {
150+
onDetection = listener;
151+
return { dispose() {} };
152+
},
153+
output: { warn: (message: string) => warnings.push(message) },
154+
status: { report: (state: StackState) => states.push(state) },
155+
} as unknown as StackContext);
156+
const shimPath = '/project/node_modules/rstack/dist/rslintConfig.js';
157+
const runtime = runtimeFactory({
158+
key: 'bridge',
159+
workspaceFolder: folder,
160+
installation: {
161+
mode: 'bridged',
162+
packageDirectory: '/project/core',
163+
shimPath,
164+
},
165+
} as unknown as ResolvedCoreRuntime);
166+
167+
onDetection(snapshot('/project/rstack.config.ts'));
168+
// Deliver the next worker verdict to the same runtime, not a replacement.
169+
(
170+
runtime as unknown as { handleConfigDependencyStatus(value: unknown): void }
171+
).handleConfigDependencyStatus({
172+
kind: 'missing',
173+
failure: { configPath: shimPath, cause: "Cannot find package 'missing'" },
174+
});
175+
expect(states.at(-1)).toMatchObject({
176+
kind: 'disabled',
177+
reason: expect.stringContaining('rstack.config.ts'),
178+
});
179+
expect(warnings).toHaveLength(1);
180+
expect(warnings[0]).toContain('Cannot load rstack.config.ts:');
181+
expect(warnings[0]).not.toContain('rstack.config.js');
182+
});
92183

93184
function createRuntime() {
94185
const states: StackState[] = [];

0 commit comments

Comments
 (0)