From 4fe9dc37e04a84f2b22315c7b1c5e157bb3260c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 03:52:14 +0000 Subject: [PATCH 1/2] Initial plan From 2d5c808d6418b32dbb2a95b086bd9c7a5d52d52e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 04:03:44 +0000 Subject: [PATCH 2/2] fix(emit): normalize extra-extension declaration paths (issue #63) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tsgo emits `Button.vue.d.vue.ts` for .vue source files with allowArbitraryExtensions, but the host convention (stock TypeScript + Volar) is `Button.vue.d.ts`. Add `fixExtraExtDeclarationPath` to strip the redundant `.{ext}` from `.d.{ext}.ts` declaration output paths in Program.emit, keyed on the registered extraFileExtensions — no framework literals. Add witness `triage-vue-tsc-decl-emit` to gate the fix in CI (TOTAL 72→73). Co-authored-by: johnsoncodehk <16279759+johnsoncodehk@users.noreply.github.com> --- .../overlay/src/compiler/tsgoChecker.ts | 20 ++++- tools/ci-witness-groups.mjs | 3 +- tools/triage-vue-tsc-decl-emit.mjs | 84 +++++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 tools/triage-vue-tsc-decl-emit.mjs diff --git a/patches/typescript/overlay/src/compiler/tsgoChecker.ts b/patches/typescript/overlay/src/compiler/tsgoChecker.ts index c54e475..553243e 100644 --- a/patches/typescript/overlay/src/compiler/tsgoChecker.ts +++ b/patches/typescript/overlay/src/compiler/tsgoChecker.ts @@ -5925,6 +5925,22 @@ function extensionFromPathOrTs(fileName: string): any { } return ts.Extension.Ts; } +/** + * Issue #63: tsgo emits `{name}.d.{ext}.ts` for allowArbitraryExtensions + * source files (e.g. Button.vue → Button.vue.d.vue.ts), but the host + * convention (stock TypeScript + Volar) is `{name}.d.ts` (Button.vue.d.ts). + * Strip the redundant extra-extension segment from declaration output paths. + */ +function fixExtraExtDeclarationPath(fileName: string, extraExts: { extension: string }[] | undefined): string { + if (!extraExts?.length) return fileName; + for (const { extension } of extraExts) { + const suffix = `.d${extension}.ts`; + if (fileName.endsWith(suffix)) { + return fileName.slice(0, -suffix.length) + ".d.ts"; + } + } + return fileName; +} /** * Builds the `extraFileExtensions` field sent with tsgo snapshots. * @@ -8501,7 +8517,9 @@ export function createTsgoProgram( for (const o of outputs) { // Go-computed output path — crosses the wire boundary before // reaching writeFile/emittedFiles consumers. - const outFileName = wireFileNameToHost(o.fileName); + // Issue #63: strip the redundant extra-extension segment from + // declaration paths (e.g. Button.vue.d.vue.ts → Button.vue.d.ts). + const outFileName = fixExtraExtDeclarationPath(wireFileNameToHost(o.fileName), programCtx.pendingExtraFileExtensions); // Builder wrappers mutate data (data.skippedDtsWrite on the // dts-unchanged skip path in builder.ts), so a data object // must always ride along — stock emitter threads one too. diff --git a/tools/ci-witness-groups.mjs b/tools/ci-witness-groups.mjs index 49d2af7..5e95eae 100644 --- a/tools/ci-witness-groups.mjs +++ b/tools/ci-witness-groups.mjs @@ -40,7 +40,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; -const TOTAL = 72; +const TOTAL = 73; // Witnesses intentionally NOT in the matrix — run on demand (reasons above). const LOCAL_ONLY = [ @@ -158,6 +158,7 @@ const groups = [ 'triage-completion-span-i55', // ~6s (npm install @types/node best-effort + tsserver session) 'triage-prototype-refresh', // ~1s 'triage-declared-type-hostonly', // ~1s + 'triage-vue-tsc-decl-emit', // ~0s ], }, { diff --git a/tools/triage-vue-tsc-decl-emit.mjs b/tools/triage-vue-tsc-decl-emit.mjs new file mode 100644 index 0000000..a8d0d6b --- /dev/null +++ b/tools/triage-vue-tsc-decl-emit.mjs @@ -0,0 +1,84 @@ +#!/usr/bin/env node +/** + * Witness for issue #63: declaration emit for `.vue` source files must produce + * `Button.vue.d.ts`, not `Button.vue.d.vue.ts`. + * + * tsgo emits `{name}.d.{ext}.ts` for allowArbitraryExtensions source files + * (e.g. Button.vue → Button.vue.d.vue.ts); the bridge must normalize this to + * `{name}.d.ts` (Button.vue.d.ts) before handing the path to the host's + * writeFile, matching stock TypeScript + Volar convention. + * + * The fixture mirrors the minimal reproduction from the issue: a .vue SFC + * with plain-TS content (Volar's virtual TS injection role) and an index.ts + * that re-exports it, compiled with declaration + emitDeclarationOnly. + * + * Exit 0 = PASS, exit 1 = FAIL. + */ +import { createRequire } from 'node:module'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; + +const require = createRequire(import.meta.url); +const repoRoot = path.resolve(import.meta.dirname, '..'); +const ts = require(path.join(repoRoot, 'lib', 'typescript.js')); + +const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tnb-vue-tsc-decl-emit-')); +fs.mkdirSync(path.join(dir, 'src', 'components'), { recursive: true }); + +// .vue file with plain-TS content — mirrors what Volar injects as virtual TS +// for an SFC with a