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
4 changes: 2 additions & 2 deletions packages/vscode/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ One extension replacing the standalone `rstack.rslint` and `rstack.rstest` exten

- `stacks/lint` and `stacks/test` are deliberate near-verbatim copies of the upstream extensions, kept close to upstream so changes can be synced by diffing. Do NOT deduplicate or refactor across the two stacks — the duplication is the point; consolidation is a later, explicit phase.
- The copies diverge from upstream in exactly nine ways (the "adaptations" below). When syncing upstream, preserve them. A tenth divergence is either a bug or must be added to this list.
- **Tracked upstream state.** `stacks/lint` is synced to web-infra-dev/rslint `packages/vscode-extension` at **39536fd6** (#1617 — per-document core resolution, `CoreResolver` + `RuntimeManager`, `corePath`, PnP removed) and **892482e0** (#1630 — `configPath` on `rslint/configRefresh`). `CoreResolver.ts` / `RuntimeManager.ts` / `WorkspaceDocumentRouter.ts` / `Rslint.ts` are the files to diff when syncing further; record the new commits here when you do.
- **Tracked upstream state.** `stacks/lint` is synced to web-infra-dev/rslint `packages/vscode-extension` at **39536fd6** (#1617 — per-document core resolution, `CoreResolver` + `RuntimeManager`, `corePath`, PnP removed) and **892482e0** (#1630 — `configPath` on `rslint/configRefresh`). Targeted later ports are **84f9c9b5** (#1967 — languageclient-owned live LSP tracing) and **b7176723** (#1951 — remove legacy JSON config watching); the Unicode BOM E2E comes from **5fc197a5** (#1560), with its native-config fixture shape from **b7176723**. `CoreResolver.ts` / `RuntimeManager.ts` / `WorkspaceDocumentRouter.ts` / `Rslint.ts` are the files to diff when syncing further; record the new commits here when you do.
- **Ahead of upstream — offer these back when syncing** (bug fixes, not adaptations): (1) `RuntimeManager.reconcile` resolves the document's core **before** sweeping pending uses (`planDocumentCore`), so a reconcile landing on the key a pending start is already producing adopts that start instead of tearing it down mid-`initialize` — the teardown made vscode-languageclient force-notify ("couldn't create connection to server") whenever the register-time pass, a detection change and `didOpen` landed inside one worker startup window (`tests/stacks/lint/runtimeManager.test.ts`). (2) `Rslint.close()` gives a still-Starting language client a bounded chance to settle before tearing down its transport, so a legitimate mid-start close (document closed during start, core key changed) stops cleanly instead of triggering the same force-notified toasts.

## The nine adaptations
Expand All @@ -30,7 +30,7 @@ One extension replacing the standalone `rstack.rslint` and `rstack.rstest` exten
- The shell always activates; per-folder config detection decides which stacks start, and re-runs on config/lockfile changes without a window reload. Enable-settings are coarse kill switches only.
- Reconciles and restarts share one serialized queue (`enqueue`); a reconcile leaves a live stack alone, so the restart path — the commands, and the full pass any relevant settings change triggers — is the only thing that rebuilds one. Do not add a second queue.
- Restart is a shell concern, not a stack one: `rstack.restart` rebuilds every controller, `rstack.<stack>.restart` rebuilds one. A stack must never register its own restart command — a shallower "bounce the tool's process" restart keeps that controller's stale package resolution and version check, which is the bug the command exists to clear.
- A relevant settings change (a gate key, or a key a live controller declares in `restartOnSettings`) triggers **one full restart pass**, never a targeted one. Per-stack selectivity was removed deliberately: settings edits are rare, and deciding per stack between "reconcile handles the gate" and "restart handles the setting" swallowed the restart when one save wrote a gate key at its already-effective value alongside a shared setting. The full pass re-evaluates every gate, so flips in either direction need no special casing.
- A relevant settings change (a gate key, or a key a live controller declares in `restartOnSettings`) triggers **one full restart pass**, never a targeted one. Per-stack selectivity was removed deliberately: settings edits are rare, and deciding per stack between "reconcile handles the gate" and "restart handles the setting" swallowed the restart when one save wrote a gate key at its already-effective value alongside a shared setting. The full pass re-evaluates every gate, so flips in either direction need no special casing. `rstack.rslint.trace.server` and `rstack.fmt.trace.server` are deliberately not restart triggers: vscode-languageclient applies both to running clients through `$/setTrace` and includes the current value when a later client initializes.
- Deprecated `rslint.json` / `rslint.jsonc` are unsupported by decision, not omission — never make them detection signals.
- Never share a child process across stacks: the tools have incompatible cwd semantics (lint LSP anchors on spawn cwd; test worker pins to project root; the `rs fmt` server takes its config root from the workspace folder the client reports, falling back to spawn cwd, with no upward walk either way). The lint and fmt servers now happen to stand in the same directory — the folder root — which changes nothing: they are different CLIs, different protocols and different version gates.
- In Restricted Mode (workspace trust), only the status bar runs — no process spawns, no project code loaded.
Expand Down
15 changes: 15 additions & 0 deletions packages/vscode/e2e/lint/fixtures/unicode-bom/rslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default [
{
files: ['**/*.ts'],
languageOptions: {
parserOptions: {
projectService: false,
project: ['./tsconfig.json'],
},
},
rules: {
'unicode-bom': 'error',
'no-var': 'error',
},
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const unmarked = 1;
8 changes: 8 additions & 0 deletions packages/vscode/e2e/lint/fixtures/unicode-bom/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true
},
"include": ["src/**/*.ts"]
}
10 changes: 10 additions & 0 deletions packages/vscode/e2e/lint/runTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ async function main(): Promise<void> {
workspace: fixture('jsconfig'),
tests: suiteDir('suite-jsconfig'),
},
{
name: 'Trace output channel tests',
workspace: fixture('jsconfig'),
tests: suiteDir('suite-trace'),
},
{
name: 'Monorepo config tests',
workspace: fixture('monorepo'),
Expand Down Expand Up @@ -279,6 +284,11 @@ async function main(): Promise<void> {
workspace: fixture('eslint-plugins'),
tests: suiteDir('suite-eslint-plugins'),
},
{
name: 'unicode-bom tests',
workspace: fixture('unicode-bom'),
tests: suiteDir('suite-unicode-bom'),
},
{
name: 'Generated rule-option-types tests',
workspace: fixture('rule-option-types'),
Expand Down
31 changes: 26 additions & 5 deletions packages/vscode/e2e/lint/suite-jsconfig/config-transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
// `CONFIG_DISCOVERY_PROTOCOL_VERSION` as a constructor argument. The tests
// inject the devDependency's constant; the fixtures currently pin the same
// 0.9.0 release line.
// - The watch-glob test asserts upstream's glob is kept verbatim, lockfiles
// included.
// - The watch-glob test asserts upstream's current JS/TS config list is kept
// verbatim, with this extension's existing lockfile additions.
import * as assert from 'node:assert';

import {
Expand All @@ -34,6 +34,7 @@ import {
RelativePattern,
Uri,
type DocumentFilter,
type OutputChannel,
type WorkspaceFolder,
} from 'vscode';

Expand All @@ -49,11 +50,31 @@ suite('initial config refresh retry classification', () => {
name: 'second-root',
uri: Uri.file('/workspace/second-root'),
};
const firstOptions = createLanguageClientOptions(firstFolder, undefined);
const secondOptions = createLanguageClientOptions(secondFolder, undefined);
const traceOutputChannel: OutputChannel = {
name: 'Rslint client-options test',
append() {},
appendLine() {},
replace() {},
clear() {},
show() {},
hide() {},
dispose() {},
};
const firstOptions = createLanguageClientOptions(
firstFolder,
undefined,
traceOutputChannel,
);
const secondOptions = createLanguageClientOptions(
secondFolder,
undefined,
traceOutputChannel,
);

assert.strictEqual(firstOptions.workspaceFolder, firstFolder);
assert.strictEqual(secondOptions.workspaceFolder, secondFolder);
assert.strictEqual(firstOptions.traceOutputChannel, traceOutputChannel);
assert.strictEqual(secondOptions.traceOutputChannel, traceOutputChannel);
for (const [options, folder] of [
[firstOptions, firstFolder],
[secondOptions, secondFolder],
Expand Down Expand Up @@ -240,7 +261,7 @@ suite('LSP config discovery transactions', () => {
assert.match(CONFIG_REFRESH_WATCH_GLOB, /rslint\.config\.ts/);
assert.match(CONFIG_REFRESH_WATCH_GLOB, /rslint\.config\.mts/);
assert.doesNotMatch(CONFIG_REFRESH_WATCH_GLOB, /rslint\.config\.\*/);
assert.match(CONFIG_REFRESH_WATCH_GLOB, /rslint\.jsonc/);
assert.doesNotMatch(CONFIG_REFRESH_WATCH_GLOB, /rslint\.json/);
assert.match(CONFIG_REFRESH_WATCH_GLOB, /pnpm-lock\.yaml/);
assert.doesNotMatch(CONFIG_REFRESH_WATCH_GLOB, /\.gitignore/);
assert.strictEqual(
Expand Down
3 changes: 3 additions & 0 deletions packages/vscode/e2e/lint/suite-trace/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createRun } from '../runSuite';

export const run = createRun();
Loading