diff --git a/packages/vscode/AGENTS.md b/packages/vscode/AGENTS.md index 9a14a94f..ec852322 100644 --- a/packages/vscode/AGENTS.md +++ b/packages/vscode/AGENTS.md @@ -68,6 +68,7 @@ One extension replacing the standalone `rstack.rslint` and `rstack.rstest` exten ## Testing - E2E suites ported from upstream keep upstream's assertion semantics; every intentional deviation is documented in a comment in the test itself. A failing ported test is a regression, not a test to adjust. +- **No upstream-internal coupling in tests.** A test may depend on an upstream package's internal detail (wire protocol, dist layout, log or error text) only when `src/` depends on that same detail; otherwise assert the user-visible behavior. Documented public upstream APIs and importing our own `src` are allowed. - E2E fixtures install exact versions of published npm packages (not workspace links): the extension must work against what users actually install. Renovate updates the exact toolchain pins; generated fixture lockfiles and `node_modules` remain disposable and uncommitted. - Prefer running the E2E slice that covers the change over the full chain: `pnpm test:e2e ` (or the `test:e2e:` aliases). Slices are declared in the `SLICES` table in `e2e/run.mjs` (name, fixtures, entry) — the package.json scripts are thin forwards and carry no slice knowledge. `RSTACK_LINT_E2E_SUITES=` filters lint suites. - Run E2E locally as `VSCODE_CLI=1 pnpm test:e2e `. Without it, the launched VS Code overwrites the extension host's `PATH` with a login-shell snapshot; on a machine whose login-shell `node` is below the runtime floor, the User Node preflight (correctly) refuses and every fmt test times out waiting for a server. CI is unaffected — its PATH `node` is new enough either way. The heavier alternative, `--force-disable-user-env` in `e2e/runTest.ts`, was deliberately not taken: it would change env fidelity for every slice. diff --git a/packages/vscode/e2e/fixtures/rslint/local-plugin.mjs b/packages/vscode/e2e/fixtures/rslint/local-plugin.mjs index 69e16d3e..b492bc35 100644 --- a/packages/vscode/e2e/fixtures/rslint/local-plugin.mjs +++ b/packages/vscode/e2e/fixtures/rslint/local-plugin.mjs @@ -1,15 +1,13 @@ /** * An object-form ESLint plugin, mounted by `rslint.config.mjs`. * - * Object-form plugin rules run in JS, not in the Go linter: the server sends a - * reverse `rslint/pluginLint` request back to the client, which answers it from - * a worker pool owned by `@rslint/core/eslint-plugin`. That is exactly the path - * the plugin-host regression smoke test exercises, so the rule has to come from a - * plugin rather than from Rslint's native rule set. + * Object-form plugin rules run in the project's own plugin runtime, so this + * rule has to come from a plugin rather than from the built-in rule set. That + * is exactly the path the plugin-host regression smoke test exercises. * * `.mjs` on purpose: a `.ts` plugin/config would drag `jiti` (or native type - * stripping) into a test that is about module resolution, not about config - * loaders. + * stripping) into a test that is about module resolution, not about loading + * TypeScript configs. */ export default { meta: { name: 'rstack-editor-e2e-local-plugin', version: '1.0.0' }, diff --git a/packages/vscode/e2e/lint/fixtures/package.json b/packages/vscode/e2e/lint/fixtures/package.json index 5596405a..292e640c 100644 --- a/packages/vscode/e2e/lint/fixtures/package.json +++ b/packages/vscode/e2e/lint/fixtures/package.json @@ -2,7 +2,7 @@ "name": "rstack-lint-e2e-fixtures", "version": "0.0.0", "private": true, - "description": "Shared install root for the Rslint E2E fixture workspaces. The extension ships no binary: every fixture resolves @rslint/core - including its native Go binary, config-loader and eslint-plugin host - from this one published-npm install. jiti backs the config-file-loader's TypeScript-config fallback.", + "description": "Shared install root for the Rslint E2E fixture workspaces. The extension ships no binary: every fixture resolves @rslint/core - including the binary and the plugin runtime - from this one published-npm install. jiti backs TypeScript config loading.", "dependencies": { "@rslint/core": "0.10.0", "jiti": "^2.7.0" diff --git a/packages/vscode/e2e/lint/fixtures/plugin-host-failure/local-plugin.mjs b/packages/vscode/e2e/lint/fixtures/plugin-host-failure/local-plugin.mjs index c83f6448..c1214cb7 100644 --- a/packages/vscode/e2e/lint/fixtures/plugin-host-failure/local-plugin.mjs +++ b/packages/vscode/e2e/lint/fixtures/plugin-host-failure/local-plugin.mjs @@ -4,6 +4,9 @@ import { isMainThread } from 'node:worker_threads'; // Config discovery must succeed; only the plugin-host worker import fails. // A non-config marker keeps a file-watch refresh or restart from recovering // the state early; the dependency poll retries the generation. +// Known exception to the test-coupling rule: no public surface can fail only +// the plugin host, so if the host stops using worker threads this fixture must +// be updated. if (!isMainThread) { const state = fs.readFileSync(new URL('./host-state.txt', import.meta.url), 'utf8').trim(); if (state === 'crash') throw new Error('fixture plugin import exploded'); diff --git a/packages/vscode/e2e/lint/suite-ancestor/ancestor.test.ts b/packages/vscode/e2e/lint/suite-ancestor/ancestor.test.ts index f5d91a3d..85efe676 100644 --- a/packages/vscode/e2e/lint/suite-ancestor/ancestor.test.ts +++ b/packages/vscode/e2e/lint/suite-ancestor/ancestor.test.ts @@ -1,7 +1,6 @@ // The opened workspace folder is `packages/app`; the fixture's only -// `rslint.config.*` sits two levels above it. The rslint CLI and its Go server -// walk up to the filesystem root for a config, so native lint detection must -// light this folder, and its ancestor watchers must keep that decision fresh. +// `rslint.config.*` sits two levels above it, so native lint detection must +// light this folder from the ancestor config and keep that decision fresh. import * as vscode from 'vscode'; import path from 'node:path'; import fs from 'node:fs'; @@ -66,9 +65,8 @@ suite('rslint config above the workspace folder', function () { await waitForDiagnostics(document, hasNoDebugger); }); - // The live runtime keeps its key; the extension's ancestor watchers send - // configRefresh, as do the Go server's own (`ancestorJSConfigFileWatchers`, - // registered through the worker) once they are live. + // The live runtime keeps its key; edits above the folder are picked up by + // the extension's own config watchers and applied without a restart. test('applies a nearer config created above the folder while linting', async () => { const document = await openIndex(); await waitForDiagnostics(document, hasNoDebugger); diff --git a/packages/vscode/e2e/lint/suite-eslint-plugins/eslint-plugins.test.ts b/packages/vscode/e2e/lint/suite-eslint-plugins/eslint-plugins.test.ts index b010c236..8451fa85 100644 --- a/packages/vscode/e2e/lint/suite-eslint-plugins/eslint-plugins.test.ts +++ b/packages/vscode/e2e/lint/suite-eslint-plugins/eslint-plugins.test.ts @@ -16,11 +16,9 @@ import { temporaryFilePath, } from '../utils/documents'; -// End-to-end VS Code coverage for the object-form `plugins` reverse-dispatch -// path: the LSP server lints natively but dispatches rules mounted via a -// config's object-form `plugins` to the extension-side worker pool -// (PluginLintPool), then merges + publishes. The Go merge/dispatch units are -// covered in internal/lsp/eslint_plugin_test.go; this exercises the full loop. +// End-to-end VS Code coverage for the object-form `plugins` path: rules +// mounted through a config's `plugins` run in the extension-side worker pool +// (PluginLintPool) and their diagnostics are published alongside native rules. // // Fixture: rslint.config.mjs mounts ./local-plugin.mjs under object-form // `plugins` with rules local/no-null + local/prefer-array-some, plus native diff --git a/packages/vscode/e2e/lint/suite-eslint-plugins/plugin-pool.test.ts b/packages/vscode/e2e/lint/suite-eslint-plugins/plugin-pool.test.ts index d8830f3d..eb2f2aad 100644 --- a/packages/vscode/e2e/lint/suite-eslint-plugins/plugin-pool.test.ts +++ b/packages/vscode/e2e/lint/suite-eslint-plugins/plugin-pool.test.ts @@ -191,7 +191,7 @@ suite('PluginLintPool generations', () => { assert.strictEqual( hostB.lintCalls, 1, - 'Go can route an accepted generation before Node observes the ack', + 'a caller can route an accepted generation before the pool observes the ack', ); assert.strictEqual(await pool.commit('b'), true); @@ -209,7 +209,7 @@ suite('PluginLintPool generations', () => { assert.strictEqual( hostA.shutdownCalls, 0, - 'the predecessor remains rollback-capable until a later commit proves Go accepted b', + 'the predecessor remains rollback-capable until a later commit proves the newer generation was accepted', ); assert.strictEqual( @@ -239,9 +239,9 @@ suite('PluginLintPool generations', () => { await pool.prepare(descriptor('b'), 'fingerprint-b', 'b'); await pool.commit('b'); - // Node already switched to b, but Go did not receive/accept the commit - // response and compensates with abort. The pool must return to a rather - // than leaving both processes on different generations. + // The pool already switched to b, but the caller did not receive/accept + // the commit response and compensates with abort. The pool must return to a + // rather than leaving both sides on different generations. await pool.abort('b'); await pool.lint(request('a')); assert.strictEqual(hosts.get('/a.mjs')!.lintCalls, 1); @@ -252,7 +252,7 @@ suite('PluginLintPool generations', () => { assert.strictEqual(hosts.get('/a.mjs')!.shutdownCalls, 1); }); - test('default grace retains at most two old WorkerPools', async () => { + test('default grace retains at most two old hosts', async () => { const hosts = new Map(); const pool = new PluginLintPool(testLogger(), async (configs) => { const host = new TestHost(); diff --git a/packages/vscode/e2e/lint/suite-import-cycle/import-cycle.test.ts b/packages/vscode/e2e/lint/suite-import-cycle/import-cycle.test.ts index fd8f3f6d..1077118b 100644 --- a/packages/vscode/e2e/lint/suite-import-cycle/import-cycle.test.ts +++ b/packages/vscode/e2e/lint/suite-import-cycle/import-cycle.test.ts @@ -187,7 +187,7 @@ suite('rslint import/no-cycle over LSP', function () { ); // a.ts is unchanged in meaning, so only a fresh cross-file answer — built - // from the Program that holds c.ts's edited buffer — can clear its report. + // from the edited buffer for c.ts — can clear its report. const clearedA = await touchAndWait( docA, (all) => isLintedPass(all) && cycleDiagnostics(all).length === 0, 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 4962be24..16946526 100644 --- a/packages/vscode/e2e/lint/suite-jsconfig/config-transaction.test.ts +++ b/packages/vscode/e2e/lint/suite-jsconfig/config-transaction.test.ts @@ -6,10 +6,9 @@ // constant: `@rslint/core/config-loader` is resolved from the project at // runtime, so the adapter takes the loader's // `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 current JS/TS config list is kept -// verbatim, with this extension's existing lockfile additions. +// inject the devDependency's constant. +// - The watch-glob test asserts the JS/TS config names the extension watches, +// with this extension's existing lockfile additions. import * as assert from 'node:assert'; import { @@ -263,7 +262,7 @@ suite('LSP config discovery transactions', () => { reportError: () => assert.fail('unexpected config error'), }; - test('the extension watcher leaves gitignore ownership to Go', () => { + test('the extension watcher covers configs and lockfiles only', () => { assert.match(CONFIG_REFRESH_WATCH_GLOB, /rslint\.config\.js/); assert.match(CONFIG_REFRESH_WATCH_GLOB, /rslint\.config\.mjs/); assert.match(CONFIG_REFRESH_WATCH_GLOB, /rslint\.config\.ts/); @@ -327,7 +326,7 @@ suite('LSP config discovery transactions', () => { assert.deepStrictEqual(host.deletedTransactions, ['tx-1']); }); - test('a rejected commit retains the session until Go aborts it', async () => { + test('a rejected commit retains the session until the caller aborts it', async () => { const host = new TestConfigHost(); const pool = new TestPluginPool(); pool.commitResult = false; @@ -366,7 +365,7 @@ suite('LSP config discovery transactions', () => { assert.deepStrictEqual(host.deletedTransactions, ['tx-abort']); }); - test('a later Running state requests a fresh initial catalog', async () => { + test('a later Running state requests a fresh initial refresh', async () => { const events: string[] = []; const recovery = recoverConfigDiscoveryOnServerState( State.Running, diff --git a/packages/vscode/e2e/lint/suite-jsconfig/jsconfig.test.ts b/packages/vscode/e2e/lint/suite-jsconfig/jsconfig.test.ts index 8cda4534..40efb712 100644 --- a/packages/vscode/e2e/lint/suite-jsconfig/jsconfig.test.ts +++ b/packages/vscode/e2e/lint/suite-jsconfig/jsconfig.test.ts @@ -74,8 +74,8 @@ suite('rslint JS config support', function () { const doc = await openFixture('index.ts'); await vscode.window.showTextDocument(doc); - // Wait specifically for JS config diagnostics. The startup snapshot may - // publish JSON fallback results before JS config activation commits. + // Wait specifically for JS config diagnostics: the JSON config may apply + // first, and the JS config must win once it loads. const diagnostics = await waitForDiagnostics(doc, (diags) => diags.some((d) => diagnosticRuleIdIncludes(d, 'no-unsafe-member-access')), ); @@ -96,8 +96,8 @@ suite('rslint JS config support', function () { await vscode.window.showTextDocument(doc); // Wait specifically for JS config diagnostics (no-unsafe-member-access). - // JSON config may load first with no-explicit-any, but the committed JS - // config catalog should override it. + // JSON config may load first with no-explicit-any, but the JS config must + // take priority. const diagnostics = await waitForDiagnostics(doc, (diags) => diags.some((d) => diagnosticRuleIdIncludes(d, 'no-unsafe-member-access')), ); @@ -230,7 +230,7 @@ export default [{ assert.strictEqual( fs.readFileSync(markerPath, 'utf8'), 'x', - 'one edit must evaluate one config-discovery transaction', + 'one edit must evaluate the config once', ); }, async () => { @@ -505,14 +505,13 @@ export default []; ); }); - test('parent global ignores remove nested configs from the effective catalog', async () => { + test('parent global ignores disable nested configs', async () => { const root = getWorkspaceRoot(); const rootConfigPath = path.join(root, 'rslint.config.js'); const originalRootConfig = fs.readFileSync(rootConfigPath, 'utf8'); const nestedDir = path.join(root, 'parent-ignore-catalog-probe'); const nestedConfigPath = path.join(nestedDir, 'rslint.config.mjs'); const nestedFilePath = path.join(nestedDir, 'index.ts'); - const loadMarkerPath = path.join(nestedDir, 'config-loads.txt'); const rootDoc = await openFixture('index.ts'); const ignoredRootConfig = originalRootConfig @@ -531,9 +530,7 @@ export default []; fs.writeFileSync(nestedFilePath, 'console.log("nested");\n', 'utf8'); fs.writeFileSync( nestedConfigPath, - `import fs from 'node:fs'; -fs.appendFileSync(${JSON.stringify(loadMarkerPath)}, 'x'); -export default [{ files: ['**/*.ts'], rules: { 'no-console': 'error' } }]; + `export default [{ files: ['**/*.ts'], rules: { 'no-console': 'error' } }]; `, 'utf8', ); @@ -559,9 +556,6 @@ export default [{ files: ['**/*.ts'], rules: { 'no-console': 'error' } }]; fs.appendFileSync(nestedConfigPath, '\n'); } } - // Every server-side evaluation of the nested config appends to the - // marker; the retry nudges above may legitimately cause more than one. - const evaluationsBeforeIgnore = fs.readFileSync(loadMarkerPath, 'utf8'); await vscode.window.showTextDocument(rootDoc); const parentApplied = waitForDiagnostics(rootDoc, (diagnostics) => @@ -580,18 +574,17 @@ export default [{ files: ['**/*.ts'], rules: { 'no-console': 'error' } }]; fs.writeFileSync(rootConfigPath, ignoredRootConfig, 'utf8'); await Promise.all([parentApplied, nestedCleared]); - assert.strictEqual( - fs.readFileSync(loadMarkerPath, 'utf8'), - evaluationsBeforeIgnore, - 'The ignored nested candidate must not be evaluated again', - ); + // Deviation from upstream: upstream also asserted that the ignored + // nested candidate is never evaluated again. Which config candidates + // get evaluated is the linter's concern, not this extension's, so this + // port asserts only the visible outcome below. assert.ok( !vscode.languages .getDiagnostics(nestedDoc.uri) .some((diagnostic) => diagnostic.message.includes('Unexpected console statement'), ), - 'The ignored nested config must not be sent in the effective catalog', + 'The ignored nested config must not apply to its directory', ); }, async () => { diff --git a/packages/vscode/e2e/lint/suite-monorepo/monorepo.test.ts b/packages/vscode/e2e/lint/suite-monorepo/monorepo.test.ts index af178e3f..ab32a7d3 100644 --- a/packages/vscode/e2e/lint/suite-monorepo/monorepo.test.ts +++ b/packages/vscode/e2e/lint/suite-monorepo/monorepo.test.ts @@ -46,17 +46,16 @@ suite('rslint monorepo multi-config support', function () { /** * Give `packages/foo` its own **physical** `@rslint/core` copy, so the * monorepo exercises two Lint runtimes in one workspace folder (rslint - * #1617). Upstream's fixture does the same by copying `package.json` + `dist` - * out of the workspace-resolved core and symlinking that core's own - * `node_modules` next to the copy. + * #1617). The workspace-resolved package is copied as a whole, so the + * fixture does not encode the package's internal file layout; the + * dependencies it needs are linked next to the copy. * * Adaptation: this repo installs published npm packages into one shared * fixture root (`e2e/lint/fixtures/package.json`), and with pnpm's isolated - * store the core's dependencies (`picomatch`, `jiti`, the platform - * `@rslint/native-*`) live in the `node_modules` **two levels above** the - * package directory, not inside it. Those entries are linked individually — - * `@rslint/core` itself is skipped, because a link to the original would - * shadow the copy the Lint worker is meant to load. + * store the core's dependencies live in the `node_modules` **two levels + * above** the package directory, not inside it. Those entries are linked + * individually — `@rslint/core` itself is skipped, because a link to the + * original would shadow the copy the Lint worker is meant to load. */ suiteSetup(async function () { this.timeout(120000); @@ -72,18 +71,11 @@ suite('rslint monorepo multi-config support', function () { getWorkspaceRoot(), 'packages/foo/node_modules/@rslint/core', ); - await fs.promises.mkdir(nestedCore, { recursive: true }); - await Promise.all([ - fs.promises.copyFile( - path.join(sourceCore, 'package.json'), - path.join(nestedCore, 'package.json'), - ), - fs.promises.cp( - path.join(sourceCore, 'dist'), - path.join(nestedCore, 'dist'), - { recursive: true, force: false, errorOnExist: true }, - ), - ]); + await fs.promises.cp(sourceCore, nestedCore, { + recursive: true, + force: false, + errorOnExist: true, + }); const dependencyRoot = path.resolve(sourceCore, '..', '..'); const nestedModules = path.join(nestedCore, 'node_modules'); diff --git a/packages/vscode/e2e/lint/suite-noconfig/noconfig.test.ts b/packages/vscode/e2e/lint/suite-noconfig/noconfig.test.ts index bfe5e7a9..570396bf 100644 --- a/packages/vscode/e2e/lint/suite-noconfig/noconfig.test.ts +++ b/packages/vscode/e2e/lint/suite-noconfig/noconfig.test.ts @@ -16,7 +16,8 @@ // register the stack without a window reload, and deleting the last one // must deregister it (the config-glob + lockfile detection watcher). // - Upstream's "a broken discovered JS config must not fall back to JSON" -// step is preserved: the Go server owns that behavior and is unchanged. +// step is preserved: a broken config yields no diagnostics, never the +// legacy JSON rules. import * as assert from 'assert'; import * as vscode from 'vscode'; import path from 'node:path'; @@ -228,10 +229,9 @@ suite('rslint no config fallback', function () { }); test('a broken JS config does not fall back to rslint.json', async () => { - // Upstream's lifecycle step 4: a *freshly discovered* broken JS config - // (no last-good catalog for its path) must yield an explicit no-lint - // state instead of falling back to JSON. A broken rewrite of an already - // loaded config would instead keep the last-good catalog active — that + // Upstream's lifecycle step 4: a freshly discovered broken JS config must + // yield no lint state instead of falling back to JSON. A broken rewrite of + // an already loaded config keeps linting with the last good config — that // scenario belongs to the jsconfig suite ("broken higher-priority config // preserves last-good"). await withConfigFilesAbsent(async ({ json, js }) => { @@ -256,15 +256,15 @@ suite('rslint no config fallback', function () { ); // Delete the JS config: the folder is un-detected (rslint.json does - // not count), the stack deregisters and its last-good - // catalog dies with the server. + // not count), the stack deregisters and the previous config's results + // are gone. fs.rmSync(js); await waitForLintStackRegistration(false); await waitForDiagnostics(doc, (ds) => ds.length === 0); // Create a broken JS config fresh. Detection lights the stack again; - // the new server evaluates the module (observable via the marker), - // fails, and has no last-good to keep — nor a JSON fallback to take. + // the module is evaluated (observable via the marker), fails, and + // there is no earlier config to keep — nor a JSON fallback to take. fs.writeFileSync( js, `import fs from 'node:fs'; diff --git a/packages/vscode/e2e/lint/suite-rule-option-types/rule-option-types.test.ts b/packages/vscode/e2e/lint/suite-rule-option-types/rule-option-types.test.ts index 8d09dfa3..d8fdc2dc 100644 --- a/packages/vscode/e2e/lint/suite-rule-option-types/rule-option-types.test.ts +++ b/packages/vscode/e2e/lint/suite-rule-option-types/rule-option-types.test.ts @@ -6,12 +6,9 @@ import * as vscode from 'vscode'; import path from 'node:path'; import { waitForRslintDiagnostics } from '../utils/diagnostics'; -// Exercises the types generated by the generate-rule-option-types rslib -// plugin (packages/rslint/plugins/generate-rule-option-types.ts) end to end: -// the built dist/index.d.ts must actually constrain a rule's options in the -// editor (TypeScript diagnostics on rslint.config.ts), and a value that -// type-checks must actually reach the Go rule at lint time (rslint -// diagnostics on the linted source file). +// Exercises the rule-option types published by `@rslint/core` end to end: a +// value that type-checks must reach the lint rule at lint time, and an invalid +// one must be rejected by TypeScript in the config file. suite('rslint generated rule-option types', function () { this.timeout(120_000); @@ -45,7 +42,7 @@ suite('rslint generated rule-option types', function () { return diagnostics; } - test('a valid typed rule option actually configures the Go rule at lint time', async () => { + test('a valid typed rule option actually configures the rule at lint time', async () => { const filePath = path.join(getWorkspaceRoot(), 'src', 'index.ts'); const doc = await vscode.workspace.openTextDocument(filePath); await vscode.window.showTextDocument(doc); @@ -94,7 +91,7 @@ suite('rslint generated rule-option types', function () { !diagnostics.some((d) => String(d.message).includes('Cannot find module'), ), - `'@rslint/core' must resolve so its generated types are actually in effect. Diagnostics: ${diagnostics.map((d) => d.message).join(' | ')}`, + `'@rslint/core' must resolve so its published types are actually in effect. Diagnostics: ${diagnostics.map((d) => d.message).join(' | ')}`, ); // Every `@ts-expect-error` in the fixture marks an option value that the // rule's JSON Schema rejects, so each one must be triggered. An unused one diff --git a/packages/vscode/e2e/lint/suite-type-aware-scope/type-aware-scope.test.ts b/packages/vscode/e2e/lint/suite-type-aware-scope/type-aware-scope.test.ts index a56b686a..31036515 100644 --- a/packages/vscode/e2e/lint/suite-type-aware-scope/type-aware-scope.test.ts +++ b/packages/vscode/e2e/lint/suite-type-aware-scope/type-aware-scope.test.ts @@ -14,8 +14,8 @@ import { closeTextEditor } from '../utils/documents'; // // Fixture: rslint.config.js has project: // ['./packages/core/tsconfig.lint.json']. The default tsconfig.json deliberately -// excludes index.ts, so tsgo's main Session cannot own the declared lint -// project. This exercises rslint's standalone Program path. +// excludes index.ts, so the declared lint project is the only project that +// covers it and the rule must still run there. // - packages/core/src/index.ts: IN tsconfig.lint.json, type-aware rules fire // - packages/cli/src/preview.ts: NOT in tsconfig.lint.json, type-aware rules do not fire suite('rslint type-aware rule scope', function () { @@ -114,7 +114,7 @@ suite('rslint type-aware rule scope', function () { ); assert.ok( !updated.some((d) => diagnosticRuleIdIncludes(d, 'require-await')), - 'Incremental standalone Program retained a stale require-await diagnostic', + 'An incremental edit retained a stale require-await diagnostic', ); await closeTextEditor(doc); @@ -170,7 +170,7 @@ suite('rslint type-aware rule scope', function () { !updated.some((d) => diagnosticRuleIdIncludes(d, 'no-unsafe-member-access'), ), - 'External dependency change left stale standalone Program diagnostics', + 'External dependency change left stale type-aware diagnostics', ); } finally { await vscode.workspace.fs.writeFile(dependencyUri, originalDependency); @@ -183,7 +183,7 @@ suite('rslint type-aware rule scope', function () { restored.some((d) => diagnosticRuleIdIncludes(d, 'no-unsafe-member-access'), ), - 'Restored dependency did not restore standalone Program diagnostics', + 'Restored dependency did not restore type-aware diagnostics', ); }); }); diff --git a/packages/vscode/e2e/smoke/rslintPluginHost.mjs b/packages/vscode/e2e/smoke/rslintPluginHost.mjs index 47124496..0eec1ddd 100644 --- a/packages/vscode/e2e/smoke/rslintPluginHost.mjs +++ b/packages/vscode/e2e/smoke/rslintPluginHost.mjs @@ -2,15 +2,11 @@ * Regression smoke test — "rslint eslint-plugin host path". * * This is a *verified non-requirement*: with a plain project install of - * `@rslint/core`, a bare-specifier `import('@rslint/core/eslint-plugin')` - * resolves to the project's own copy and exports `createPluginLintHost`, and - * the full plugin pipeline runs (Go projects each file's ESTree frame, the - * host's sibling `lint-worker.js` answers the reverse `pluginLint` request, - * the native byte transport resolves by walking up `node_modules`, an - * object-form plugin rule produces a diagnostic, clean shutdown). Nothing - * rslint-related ships in the VSIX, so if that ever stops being true, this - * extension's plugin-lint path is dead — hence a regression test rather than - * a one-off manual verification. + * `@rslint/core`, the project's own copy resolves, runs an object-form plugin + * rule to a diagnostic, and shuts down cleanly. Nothing rslint-related ships + * in the VSIX, so if that ever stops being true, this extension's plugin-lint + * path is dead — hence a regression test rather than a one-off manual + * verification. * * It needs no VS Code: it is a plain Node script, run by `pnpm test:e2e:smoke` * as well as by the full `pnpm test:e2e`. It deliberately mirrors the @@ -22,15 +18,11 @@ * entry and `@rslint/core/eslint-plugin` — self-reference resolution, * pinning both subpaths to that same install; * 3. the one-resolution-root assertion; - * 4. dynamic `import()`es of the resolved paths — the host is ESM and spawns - * its sibling worker via `import.meta.url`, so it can never be `require`d. + * 4. dynamic `import()` of the resolved paths. * - * Since `@rslint/core` 0.10.0 Go projects the ESTree frame itself and the host - * only consumes it (`EslintPluginLintRequest.files[].frame` is required), so a - * hand-built `host.lint()` request with just a path is no longer a valid - * request. Driving the pipeline through the public `Rslint` class exercises - * the same reverse `pluginLint` request — real frames included — against the - * entry asserted below, and keeps this test valid across core versions. + * The project's installed `@rslint/core` lints the fixture and reports the + * plugin rule, so this exercises the package a user actually installs and + * stays valid across core versions. */ import assert from 'node:assert/strict'; import { createRequire } from 'node:module'; @@ -117,10 +109,9 @@ const main = async () => { `the linter failed fatally: ${JSON.stringify(fatal)}`, ); - // `local/no-null` is an object-form plugin rule: Go cannot run it, so this - // diagnostic proves the project install's plugin host answered the reverse - // request with a Go-projected frame. The VS Code `eslintPlugins` E2E suite - // covers the same path through the extension's own worker. + // `local/no-null` is an object-form plugin rule, so this diagnostic proves + // the project install runs plugin rules. The VS Code `eslintPlugins` E2E + // suite covers the same path through the extension's own worker. const diagnostics = messages.filter( (message) => message.ruleId === RULE_NAME, ); @@ -135,7 +126,7 @@ const main = async () => { `[smoke] diagnostic: ${diagnostics[0].ruleId} — ${String(diagnostics[0].message)} (${diagnostics[0].line}:${diagnostics[0].column})`, ); console.log( - '[smoke] OK — project-resolved plugin lint host produced a diagnostic', + "[smoke] OK — the project's @rslint/core ran a plugin rule and produced a diagnostic", ); }; diff --git a/packages/vscode/tests/stacks/lint/start.test.ts b/packages/vscode/tests/stacks/lint/start.test.ts index 43779828..ee946ad3 100644 --- a/packages/vscode/tests/stacks/lint/start.test.ts +++ b/packages/vscode/tests/stacks/lint/start.test.ts @@ -119,7 +119,8 @@ rs.mock('vscode-languageclient/node', () => ({ refreshOutcome === 'unconfigured-rejected' ) { this.notification?.({ kind: 'unconfigured' }); - // Go rejects instead of committing when a last-good catalog exists. + // The server rejects instead of committing while an earlier config + // stays active. if (refreshOutcome === 'unconfigured-rejected') { throw new Error('config refresh failed at last-good boundary'); } @@ -399,8 +400,8 @@ it('reports a bridged runtime as not-detected with a detail when the shim finds // Not a failure: the dependency poll has nothing to retry. expect(runtime.retryConfigDependency()).toBeUndefined(); - // An unchanged episode logs once; Go rejecting with a last-good catalog is - // not reported as a failure either. + // An unchanged episode logs once; the server rejecting a refresh while an + // earlier config stays active is not reported as a failure either. await refreshConfig(runtime, 'config-change'); refreshOutcome = 'unconfigured-rejected'; await refreshConfig(runtime, 'config-change'); diff --git a/packages/vscode/tests/stacks/lint/worker.test.ts b/packages/vscode/tests/stacks/lint/worker.test.ts index 0f2f1538..3c71bc90 100644 --- a/packages/vscode/tests/stacks/lint/worker.test.ts +++ b/packages/vscode/tests/stacks/lint/worker.test.ts @@ -140,7 +140,7 @@ describe('lint worker config refresh', () => { }); }); - it('stamps requests sent to Go and preserves parameterless messages', async () => { + it('stamps requests sent to the server and preserves parameterless messages', async () => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'lint-worker-go-')); const stubPath = path.join(directory, 'fake-go.cjs'); fs.writeFileSync(stubPath, fakeGoSource);