diff --git a/packages/vscode/AGENTS.md b/packages/vscode/AGENTS.md index a42f958..f5f74b2 100644 --- a/packages/vscode/AGENTS.md +++ b/packages/vscode/AGENTS.md @@ -27,7 +27,7 @@ One extension replacing the standalone `rstack.rslint` and `rstack.rstest` exten - **Pre-1.0.0 the extension breaks freely.** No compatibility is owed with earlier unpublished states of this extension — settings, command ids and behavior may change without deprecation paths, and dead compat code for them is removed, not kept. No settings migration exists either — not for earlier states of this extension, and not for the two retired standalone extensions (removed in #15; users re-enter their settings under `rstack.*`). Testing and fixtures track only the latest published releases, pinned exactly and bumped by Renovate; a green E2E run speaks only for those releases. `SUPPORT_MATRIX` floors are the minimum versions the extension accepts: each entry is the lowest release evidence shows works with the current code, and its comment records that evidence. Move a floor only when a change makes older releases stop working, never because a devDependency or fixture moved. Raising a floor needs no transition story; the status names the required version. - **The three tools are treated uniformly by default.** Detection, dependency-change retry, restart semantics, version gating and status reporting follow one shared pattern across the lint/test/fmt stacks; a stack diverges only when its tool forces it, and the divergence is recorded here as a gotcha. When adding behavior to one stack, first ask whether it belongs to all three. This is about behavior, not code — the upstream copies still must not be deduplicated. -- **Not installed is a state, not an error — uniformly.** A folder or project whose dependencies are not installed (no `rstack`, no `@rstest/core`, no `@rslint/core`, a config importing a package that is not there) is the normal state of a fresh clone and of scaffolded templates beside their generator (`create-rstack`'s `template-*`, which declare their own dependencies and are never installed). Every stack reports it the same way: a `disabled` status whose reason keeps the restart command as an explicit fallback, one `warn` line per unresolved episode in the output channel without a stack trace, never a `crashed` status and never a notification. The shell owns one 60-second recursive poll while any controller's raw folder/project/runtime state is disabled, crashed or version-mismatched; it enters the existing serialized queue, forces the same detection notification as a lockfile event, and stops when no failed state remains (ADR 0005). A mid-install retry can read half-written `node_modules` and produce a real syntax error; continuing through failed states makes that transient harmless without a provisional-error heuristic. Real errors remain visible in status and Output, deduplicated by message rather than logged every minute. The words come from one place, `shared/notInstalled.ts` (the `formatVersionMismatch` precedent) — each stack keeps its own status machinery, none its own wording; the restart hint is derived from `stackCommandTitle`, which `tests/extension.test.ts` checks against the manifest. Rstest classifies config-import failures in its worker (`missingDependencyCauseOf`: Node's `code`, a bare package specifier, and for a subpath a walk-up proving the package really is absent) because IPC drops the `code`; Rslint makes the same code-gated decision where its worker still has structured loader results and sends a dedicated verdict to the editor; fmt intercepts only the exact `rs fmt cannot format this workspace:` Error notification and applies the shared message classifier. A typo'd relative import or a missing subpath of an installed package stays a real error in all three. +- **Not installed is a state, not an error — uniformly.** A folder or project whose dependencies are not installed (no `rstack`, no `@rstest/core`, no `@rslint/core`, a config importing a package that is not there) is the normal state of a fresh clone and of scaffolded templates beside their generator (`create-rstack`'s `template-*`, which declare their own dependencies and are never installed). Every stack reports it the same way: a `disabled` status whose reason keeps the restart command as an explicit fallback, one `warn` line per unresolved episode in the output channel without a stack trace, never a `crashed` status and never a notification. The shell owns one 60-second recursive poll while any controller's raw folder/project/runtime state is disabled, crashed or version-mismatched; it enters the existing serialized queue, forces the same detection notification as a lockfile event, and stops when no failed state remains (ADR 0005). A mid-install retry can read half-written `node_modules` and produce a real syntax error; continuing through failed states makes that transient harmless without a provisional-error heuristic. Real errors remain visible in status and Output, deduplicated by message rather than logged every minute. The words come from one place, `shared/notInstalled.ts` (the `formatVersionMismatch` precedent) — each stack keeps its own status machinery, none its own wording; the restart hint is derived from `stackCommandTitle`, which `tests/extension.test.ts` checks against the manifest. Rstest classifies config-import failures in its worker because IPC drops the `code`; Rslint makes the same code-gated decision where its worker still has structured loader results and sends a dedicated verdict to the editor; fmt intercepts only the exact `rs fmt cannot format this workspace:` Error notification and applies the shared message classifier. Any Node module-not-found on a bare specifier is the not-installed state, subpath included; the classifier never touches the filesystem (`shared/missingDependency.ts`, #52). Only relative, absolute and `file:` specifiers stay real errors. - One stack failing to register or crashing must never take another stack (or the shell) down. - The shell always activates; per-folder config detection decides which stacks start, and re-runs on config/lockfile changes without a window reload. The per-stack 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. diff --git a/packages/vscode/e2e/lint/suite-jsconfig/config-transaction.test.ts b/packages/vscode/e2e/lint/suite-jsconfig/config-transaction.test.ts index a17e9dd..b63752d 100644 --- a/packages/vscode/e2e/lint/suite-jsconfig/config-transaction.test.ts +++ b/packages/vscode/e2e/lint/suite-jsconfig/config-transaction.test.ts @@ -255,6 +255,13 @@ function loadRequest(transactionId = 'tx-1'): LoadConfigsRequest { } suite('LSP config discovery transactions', () => { + const rejectsConfigDependencies: ConstructorParameters< + typeof LspConfigTransactionAdapter + >[4] = { + report: () => assert.fail('unexpected missing dependency'), + reportError: () => assert.fail('unexpected config error'), + }; + test('the extension watcher leaves gitignore ownership to Go', () => { assert.match(CONFIG_REFRESH_WATCH_GLOB, /rslint\.config\.js/); assert.match(CONFIG_REFRESH_WATCH_GLOB, /rslint\.config\.mjs/); @@ -282,11 +289,7 @@ suite('LSP config discovery transactions', () => { pool, () => 'fingerprint-1', CONFIG_DISCOVERY_PROTOCOL_VERSION, - { - resolveFrom: (candidate) => candidate.configDirectory, - report: () => assert.fail('unexpected missing dependency'), - reportError: () => assert.fail('unexpected config error'), - }, + rejectsConfigDependencies, ); const loaded = await adapter.loadConfigs(loadRequest()); @@ -332,11 +335,7 @@ suite('LSP config discovery transactions', () => { pool, () => 'fingerprint-1', CONFIG_DISCOVERY_PROTOCOL_VERSION, - { - resolveFrom: (candidate) => candidate.configDirectory, - report: () => assert.fail('unexpected missing dependency'), - reportError: () => assert.fail('unexpected config error'), - }, + rejectsConfigDependencies, ); await adapter.loadConfigs(loadRequest('tx-abort')); @@ -399,11 +398,7 @@ suite('LSP config discovery transactions', () => { pool, () => 'fingerprint-degraded', CONFIG_DISCOVERY_PROTOCOL_VERSION, - { - resolveFrom: (candidate) => candidate.configDirectory, - report: () => assert.fail('unexpected missing dependency'), - reportError: () => assert.fail('unexpected config error'), - }, + rejectsConfigDependencies, ); await adapter.loadConfigs(loadRequest('tx-degraded')); @@ -439,11 +434,7 @@ suite('LSP config discovery transactions', () => { pool, () => 'fingerprint-before-prepare', CONFIG_DISCOVERY_PROTOCOL_VERSION, - { - resolveFrom: (candidate) => candidate.configDirectory, - report: () => assert.fail('unexpected missing dependency'), - reportError: () => assert.fail('unexpected config error'), - }, + rejectsConfigDependencies, ); await adapter.loadConfigs(loadRequest('tx-prepare-race')); @@ -468,11 +459,7 @@ suite('LSP config discovery transactions', () => { pool, () => 'fingerprint-1', CONFIG_DISCOVERY_PROTOCOL_VERSION, - { - resolveFrom: (candidate) => candidate.configDirectory, - report: () => assert.fail('unexpected missing dependency'), - reportError: () => assert.fail('unexpected config error'), - }, + rejectsConfigDependencies, ); await adapter.loadConfigs(loadRequest('tx-response-lost')); @@ -510,11 +497,7 @@ suite('LSP config discovery transactions', () => { new TestPluginPool(), () => 'fingerprint-1', CONFIG_DISCOVERY_PROTOCOL_VERSION, - { - resolveFrom: (candidate) => candidate.configDirectory, - report: () => assert.fail('unexpected missing dependency'), - reportError: () => assert.fail('unexpected config error'), - }, + rejectsConfigDependencies, ); await assert.rejects(adapter.loadConfigs(loadRequest()), /load failed/); diff --git a/packages/vscode/src/shared/missingDependency.ts b/packages/vscode/src/shared/missingDependency.ts index 675d7e0..1a29828 100644 --- a/packages/vscode/src/shared/missingDependency.ts +++ b/packages/vscode/src/shared/missingDependency.ts @@ -1,29 +1,24 @@ import path from 'node:path'; -import { findPackageJsonUncached } from './packageResolve'; -export function isMissingDependencyCode( +function isMissingDependencyCode( code: unknown, ): code is 'ERR_MODULE_NOT_FOUND' | 'MODULE_NOT_FOUND' { return code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND'; } /** - * The classifier behind the "config imports a package that is not installed" - * verdict of the uniform not-installed policy (AGENTS.md). Nothing in it is - * Rstest-specific — it reads Node's loader errors — and lint/fmt will need - * the same verdict where their configs load (#30), which is why it lives in - * `shared/` beside the walk-up it uses rather than in one stack. - * - * Returns the one-line cause when a config evaluation failed on a package - * that is not installed, or `undefined` for a real error. Only a bare - * specifier — a package name, read from the message since CJS carries no - * structured one — counts, and anything unrecognized fails towards the full - * error report. Only the first line comes back: the rest of a CJS message is - * the require stack, and the not-installed state is one warn line without one. + * The "config import cannot be resolved" verdict of the not-installed policy + * (AGENTS.md); in `shared/` because lint/fmt need it too (#30). Returns the + * loader's first line for a bare specifier — the policy's one warn line, with + * the CJS require stack dropped — or `undefined` for a real error. A bare + * specifier failed in the dependency graph, where an install, a lockfile + * event or the poll can change the answer; a relative, absolute or `file:` + * specifier failed inside the user's own source, where nothing external will. + * #52 removed a filesystem walk-up: it could not see a pnpm-isolated private + * dependency. */ export function classifyMissingDependencyMessage( message: string, - resolveFrom: string, ): string | undefined { const [firstLine] = message.split('\n', 1); const specifier = /^Cannot find (?:package|module) '([^']+)'/.exec( @@ -37,35 +32,21 @@ export function classifyMissingDependencyMessage( ) { return undefined; } - // `installed-package/missing-subpath` wears the same bare shape, but the - // package itself is there — installing dependencies cannot fix it either, - // so a subpath is checked against the physical `node_modules` with the - // same uncached walk-up every stack resolves packages with. - const packageName = specifier.startsWith('@') - ? specifier.split('/').slice(0, 2).join('/') - : specifier.split('/', 1)[0]; - if ( - packageName !== specifier && - findPackageJsonUncached(packageName, resolveFrom) !== undefined - ) { - return undefined; - } return firstLine; } -/** - * Error-object entry point used where Node's loader code survives. The code is - * still required there: arbitrary user errors may contain loader-like prose. - * Worker/protocol boundaries that already carry a separately checked code use - * `classifyMissingDependencyMessage` directly because serialization can drop - * custom Error fields. - */ -export function missingDependencyCauseOf( - error: unknown, - resolveFrom: string, +/** Use for a (code, message) pair; bare messages (fmt) use the classifier directly. */ +export function missingDependencyCause( + code: unknown, + message: string, ): string | undefined { + if (!isMissingDependencyCode(code)) return undefined; + return classifyMissingDependencyMessage(message); +} + +/** Use when the caller holds an Error rather than a (code, message) pair. */ +export function missingDependencyCauseOf(error: unknown): string | undefined { if (!(error instanceof Error)) return undefined; const { code } = error as NodeJS.ErrnoException; - if (!isMissingDependencyCode(code)) return undefined; - return classifyMissingDependencyMessage(error.message, resolveFrom); + return missingDependencyCause(code, error.message); } diff --git a/packages/vscode/src/shared/notInstalled.ts b/packages/vscode/src/shared/notInstalled.ts index 7ebd976..96a5827 100644 --- a/packages/vscode/src/shared/notInstalled.ts +++ b/packages/vscode/src/shared/notInstalled.ts @@ -29,20 +29,18 @@ export const formatNotInstalledStatus = ( `${packageName} is not installed (node_modules missing) — install it, ${restartHint(stack)}`; /** - * The `disabled` reason for a config that evaluates but imports a package - * that is not there. `configPath` is workspace-relative: the status has no - * room for more. + * The `disabled` reason for a config with an import Node cannot resolve. + * `configPath` is workspace-relative: the status has no room for more. */ export const formatConfigDependencyMissingStatus = ( stack: StackId, configPath: string, ): string => - `${configPath} imports a package that is not installed — install the project dependencies, ${restartHint(stack)}`; + `${configPath} has an import Node cannot resolve — install the project dependencies, ${restartHint(stack)}`; /** - * The output-channel line for a config that imports a package that is not - * installed. `cause` is the loader's own first line, which names the - * specifier and the importer. + * The output-channel line for the same verdict. `cause` is the loader's own + * first line, which names the specifier and the importer. */ export const formatConfigDependencyMissingLog = ( stack: StackId, diff --git a/packages/vscode/src/stacks/fmt/index.ts b/packages/vscode/src/stacks/fmt/index.ts index 20c7089..b2c9472 100644 --- a/packages/vscode/src/stacks/fmt/index.ts +++ b/packages/vscode/src/stacks/fmt/index.ts @@ -239,7 +239,6 @@ class FmtFolderRuntime { if (configPath !== undefined) { const cause = classifyMissingDependencyMessage( firstLine.replace(/^Error(?: \[[A-Z_]+\])?: /, ''), - this.folderPath, ); if (cause !== undefined) { this.#sessionError.clear(); diff --git a/packages/vscode/src/stacks/lint/worker/ConfigTransactionAdapter.ts b/packages/vscode/src/stacks/lint/worker/ConfigTransactionAdapter.ts index 4576c31..58c0705 100644 --- a/packages/vscode/src/stacks/lint/worker/ConfigTransactionAdapter.ts +++ b/packages/vscode/src/stacks/lint/worker/ConfigTransactionAdapter.ts @@ -2,20 +2,15 @@ import type { ActivateConfigsRequest, ActivateConfigsResponse, ConfigModuleActivationPlan, - ConfigModuleCandidate, ConfigModuleEslintPluginEntry, ConfigModulePluginDescriptor, LoadConfigsRequest, LoadConfigsResponse, } from '@rslint/core/config-loader'; -import { - classifyMissingDependencyMessage, - isMissingDependencyCode, -} from '../../../shared/missingDependency'; +import { missingDependencyCause } from '../../../shared/missingDependency'; import type { ConfigDependencyFailure } from '../../../shared/notInstalled'; interface ConfigDependencyObserver { - resolveFrom(candidate: ConfigModuleCandidate): string; report(failure: ConfigDependencyFailure): void; reportError(message: string): void; } @@ -128,14 +123,10 @@ export class LspConfigTransactionAdapter { results: response.results.map((result, index) => { if (result.status !== 'failed') return result; const candidate = request.candidates[index]; - const cause = - candidate !== undefined && - isMissingDependencyCode(result.error.code) - ? classifyMissingDependencyMessage( - result.error.message, - this.configDependencyObserver.resolveFrom(candidate), - ) - : undefined; + const cause = missingDependencyCause( + result.error.code, + result.error.message, + ); // Scan every failure: a later real error must not be hidden by the // first missing dependency, even though only that result is rewritten. if (cause === undefined || candidate === undefined) { diff --git a/packages/vscode/src/stacks/lint/worker/index.ts b/packages/vscode/src/stacks/lint/worker/index.ts index 772fd22..4d559da 100644 --- a/packages/vscode/src/stacks/lint/worker/index.ts +++ b/packages/vscode/src/stacks/lint/worker/index.ts @@ -234,10 +234,6 @@ export async function runLintWorker( (activation) => fingerprinter.compute(activation), installation.protocolVersion, { - resolveFrom: (candidate) => - candidate.configPath === options.configPath - ? process.cwd() - : candidate.configDirectory, report: (failure) => { configDependencyFailure ??= failure; }, diff --git a/packages/vscode/src/stacks/test/project.ts b/packages/vscode/src/stacks/test/project.ts index bebb5bc..0618685 100644 --- a/packages/vscode/src/stacks/test/project.ts +++ b/packages/vscode/src/stacks/test/project.ts @@ -691,8 +691,9 @@ export class Project implements vscode.Disposable { return `config-deps:${this.sourceUri.toString()}`; } - // The config imports a package that is not installed: the not-installed - // state (AGENTS.md), one step past a missing `@rstest/core` — some install + // The config imports a package Node cannot resolve — a missing package + // or a missing subpath of an installed one: the not-installed state + // (AGENTS.md), one step past a missing `@rstest/core` — some install // *above* the project satisfied the shim, so the config itself is what // failed. A scaffolded template beside its generator is the usual shape. // Latched under this project's key, which `dispose` forgets. diff --git a/packages/vscode/src/stacks/test/types.ts b/packages/vscode/src/stacks/test/types.ts index 660a49a..9725d5a 100644 --- a/packages/vscode/src/stacks/test/types.ts +++ b/packages/vscode/src/stacks/test/types.ts @@ -9,9 +9,9 @@ export type WorkerInitOptions = RstestConfig & { }; /** - * What the worker answers `getNormalizedConfig` with. A config that fails to - * evaluate because a dependency is not installed is a result, not a rejection: - * the IPC channel would strip the error's `code` (see + * What the worker answers `getNormalizedConfig` with. A config that fails + * to evaluate because Node cannot resolve one of its imports is a result, + * not a rejection: the IPC channel would strip the error's `code` (see * `missingDependencyCauseOf`), so the worker classifies it and reports the * loader's own first line as data. */ diff --git a/packages/vscode/src/stacks/test/worker/index.ts b/packages/vscode/src/stacks/test/worker/index.ts index 430f978..fed7381 100644 --- a/packages/vscode/src/stacks/test/worker/index.ts +++ b/packages/vscode/src/stacks/test/worker/index.ts @@ -92,9 +92,7 @@ export class Worker { // Classified here and not in the master: `code` does not survive the // IPC round-trip. Only this unprompted, per-config evaluation gets the // treatment — a run or list the user asked for reports its failure. - // The worker's spawn cwd is the project root (adaptation #5), which is - // where the config's dependencies are installed. - const cause = missingDependencyCauseOf(error, process.cwd()); + const cause = missingDependencyCauseOf(error); if (cause !== undefined) { return { ok: false, message: cause }; } diff --git a/packages/vscode/tests/shared/missingDependency.test.ts b/packages/vscode/tests/shared/missingDependency.test.ts index 7036aac..85fbf37 100644 --- a/packages/vscode/tests/shared/missingDependency.test.ts +++ b/packages/vscode/tests/shared/missingDependency.test.ts @@ -20,11 +20,6 @@ const resolveError = (specifier: string, from: string): unknown => { }; describe('missingDependencyCauseOf', () => { - // The classifier resolves from the worker's cwd — the project root; the - // test directory stands in for it. - const classify = (error: unknown, from = __dirname) => - missingDependencyCauseOf(error, from); - // Same reasoning as `resolveError`: the errors come from Node's own // loaders. const importError = async (specifier: string): Promise => { @@ -38,12 +33,14 @@ describe('missingDependencyCauseOf', () => { it('should name a package an ESM config failed to import', async () => { expect( - classify(await importError('@rstest/definitely-not-installed')), + missingDependencyCauseOf( + await importError('@rstest/definitely-not-installed'), + ), ).toContain("'@rstest/definitely-not-installed'"); }); it('should keep a CJS failure to one line, without the require stack', () => { - const cause = classify( + const cause = missingDependencyCauseOf( resolveError('@rstest/definitely-not-installed', __dirname), ); expect(cause).toContain("'@rstest/definitely-not-installed'"); @@ -58,11 +55,11 @@ describe('missingDependencyCauseOf', () => { // installing dependencies cannot fix it, so it must not be classified as // the not-installed state. The ESM loader reports relative imports as // absolute paths, which the absolute case stands in for. - expect(classify(resolveError('./definitely-missing', __dirname))).toBe( - undefined, - ); expect( - classify( + missingDependencyCauseOf(resolveError('./definitely-missing', __dirname)), + ).toBe(undefined); + expect( + missingDependencyCauseOf( resolveError( path.join(os.tmpdir(), 'definitely-missing.js'), os.tmpdir(), @@ -71,37 +68,36 @@ describe('missingDependencyCauseOf', () => { ).toBe(undefined); }); - it('should tell a missing subpath of an installed package from a missing one', () => { - // `require('installed-package/missing')` fails with the same code and a - // bare-looking specifier, but the package is there — that is a source - // error, not the not-installed state. The same subpath under a package - // that is really absent still is. + it('treats a missing subpath of an installed package as not installed', () => { + // No filesystem lookup since #52; the fixture only proves Node still reports MODULE_NOT_FOUND for a present package's missing subpath. const root = fs.mkdtempSync(path.join(os.tmpdir(), 'rstest-vscode-')); try { const pkgDir = path.join(root, 'node_modules', 'installed-package'); fs.mkdirSync(pkgDir, { recursive: true }); fs.writeFileSync( path.join(pkgDir, 'package.json'), - '{"name":"installed-package","version":"1.0.0","main":"./index.js"}', + '{"name":"installed-package","version":"1.0.0"}', ); - fs.writeFileSync(path.join(pkgDir, 'index.js'), 'module.exports = {};\n'); expect( - classify(resolveError('installed-package/missing', root), root), - ).toBe(undefined); - expect( - classify(resolveError('not-installed-package/missing', root), root), - ).toContain("'not-installed-package/missing'"); + missingDependencyCauseOf( + resolveError('installed-package/missing', root), + ), + ).toContain("'installed-package/missing'"); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); it('should leave every other failure to the full error report', () => { - expect(classify(new SyntaxError('Unexpected token'))).toBe(undefined); - expect(classify(new Error("Cannot find package 'x'"))).toBe(undefined); - expect(classify("Cannot find package 'x'")).toBe(undefined); - expect(classify(undefined)).toBe(undefined); + expect(missingDependencyCauseOf(new SyntaxError('Unexpected token'))).toBe( + undefined, + ); + expect(missingDependencyCauseOf(new Error("Cannot find package 'x'"))).toBe( + undefined, + ); + expect(missingDependencyCauseOf("Cannot find package 'x'")).toBe(undefined); + expect(missingDependencyCauseOf(undefined)).toBe(undefined); }); }); @@ -110,24 +106,16 @@ describe('classifyMissingDependencyMessage', () => { expect( classifyMissingDependencyMessage( "Cannot find package '@scope/missing' imported from /project/config.mjs", - __dirname, ), ).toBe( "Cannot find package '@scope/missing' imported from /project/config.mjs", ); - expect( - classifyMissingDependencyMessage( - "Cannot find module 'missing-package'\nRequire stack:\n- /project/config.cjs", - __dirname, - ), - ).toBe("Cannot find module 'missing-package'"); }); it('rejects non-loader messages even without the Error-code gate', () => { expect( classifyMissingDependencyMessage( "Configuration says Cannot find package 'missing'", - __dirname, ), ).toBe(undefined); }); diff --git a/packages/vscode/tests/shared/notInstalled.test.ts b/packages/vscode/tests/shared/notInstalled.test.ts index 0c81480..bd974c0 100644 --- a/packages/vscode/tests/shared/notInstalled.test.ts +++ b/packages/vscode/tests/shared/notInstalled.test.ts @@ -29,7 +29,7 @@ describe('not-installed wording', () => { 'templates/app/rstack.config.ts', ), ).toBe( - 'templates/app/rstack.config.ts imports a package that is not installed — install the project dependencies, then run "Rstack: Restart Rstest" if this status stays', + 'templates/app/rstack.config.ts has an import Node cannot resolve — install the project dependencies, then run "Rstack: Restart Rstest" if this status stays', ); }); diff --git a/packages/vscode/tests/stacks/lint/worker.test.ts b/packages/vscode/tests/stacks/lint/worker.test.ts index 228ab00..917dde1 100644 --- a/packages/vscode/tests/stacks/lint/worker.test.ts +++ b/packages/vscode/tests/stacks/lint/worker.test.ts @@ -261,7 +261,6 @@ describe('lint worker config dependency classification', () => { let missing: { configPath: string; cause: string } | undefined; let configError: string | undefined; const observer = { - resolveFrom: () => '/project', report: (failure: NonNullable) => { missing = failure; }, @@ -392,7 +391,6 @@ describe('lint worker config dependency classification', () => { (_plan: ConfigModuleActivationPlan) => 'fingerprint', 3, { - resolveFrom: (candidate) => candidate.configDirectory, report: (failure) => failures.push(failure), reportError: () => { throw new Error('unexpected config error'); @@ -473,7 +471,6 @@ describe('lint worker config dependency classification', () => { () => 'fingerprint', 3, { - resolveFrom: () => '/project', report: (failure) => failures.push(failure), reportError: (message) => expect(message).toBe("Cannot find package './relative.js'"), diff --git a/packages/vscode/tests/stacks/test/project.test.ts b/packages/vscode/tests/stacks/test/project.test.ts index a03c40e..5c5ca6c 100644 --- a/packages/vscode/tests/stacks/test/project.test.ts +++ b/packages/vscode/tests/stacks/test/project.test.ts @@ -1,5 +1,6 @@ import path from 'node:path'; import { afterEach, beforeEach, describe, expect, it, rs } from '@rstest/core'; +import { formatConfigDependencyMissingStatus } from '../../../src/shared/notInstalled'; import { ReportedRstestResolutionError } from '../../../src/stacks/test/coreResolution'; import { logger } from '../../../src/stacks/test/logger'; import { status } from '../../../src/stacks/test/status'; @@ -557,8 +558,10 @@ describe('Project config/cwd/package-resolution decoupling', () => { expect(reported).toEqual([ { kind: 'disabled', - reason: - 'templates/app/rstack.config.ts imports a package that is not installed — install the project dependencies, then run "Rstack: Restart Rstest" if this status stays', + reason: formatConfigDependencyMissingStatus( + 'rstest', + 'templates/app/rstack.config.ts', + ), }, ]);