From 59986ae7bf0d1dc46b8a4e6d3f2f958e9101c19a Mon Sep 17 00:00:00 2001 From: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:47:02 +0530 Subject: [PATCH 1/4] fix(lint): resolve snippet typechecking failures on Windows due to mixed path separators --- packages/leadtype/src/lint/snippet-typecheck.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/leadtype/src/lint/snippet-typecheck.ts b/packages/leadtype/src/lint/snippet-typecheck.ts index 6af71669..aee5624a 100644 --- a/packages/leadtype/src/lint/snippet-typecheck.ts +++ b/packages/leadtype/src/lint/snippet-typecheck.ts @@ -3,6 +3,7 @@ import { resolve } from "node:path"; import type { Root } from "mdast"; import type * as TS from "typescript"; import { visit } from "unist-util-visit"; +import { normalizeDocsPath } from "../internal/docs-url"; import { loadTypeScript } from "./snippet-lint"; /** @@ -229,7 +230,7 @@ export function typecheckSnippets( for (const [index, snippet] of options.snippets.entries()) { const virtualDir = `${projectRoot}/.leadtype-snippet-${index}`; for (const file of toVirtualFiles(snippet, virtualDir)) { - virtualFiles.set(file.path, file); + virtualFiles.set(normalizeDocsPath(file.path), file); } } @@ -245,16 +246,17 @@ export function typecheckSnippets( const baseGetSourceFile = host.getSourceFile.bind(host); const baseDirectoryExists = host.directoryExists?.bind(host); host.fileExists = (fileName) => - virtualFiles.has(fileName) || baseFileExists(fileName); + virtualFiles.has(normalizeDocsPath(fileName)) || baseFileExists(fileName); host.readFile = (fileName) => - virtualFiles.get(fileName)?.content ?? baseReadFile(fileName); + virtualFiles.get(normalizeDocsPath(fileName))?.content ?? + baseReadFile(fileName); // Module resolution probes the containing directory; virtual dirs never // exist on disk, so sibling imports (`./helpers`) need this to resolve. host.directoryExists = (directoryName) => - virtualDirs.has(directoryName) || + virtualDirs.has(normalizeDocsPath(directoryName)) || (baseDirectoryExists?.(directoryName) ?? false); host.getSourceFile = (fileName, languageVersion, ...rest) => { - const virtual = virtualFiles.get(fileName); + const virtual = virtualFiles.get(normalizeDocsPath(fileName)); if (virtual) { return ts.createSourceFile(fileName, virtual.content, languageVersion); } @@ -287,7 +289,9 @@ export function typecheckSnippets( continue; } const fileName = diagnostic.file?.fileName; - const virtual = fileName ? virtualFiles.get(fileName) : undefined; + const virtual = fileName + ? virtualFiles.get(normalizeDocsPath(fileName)) + : undefined; if (!virtual) { continue; // diagnostics from real project files aren't lint's business } From e1062ea68f3a707e902807d79446c5ebbb981532 Mon Sep 17 00:00:00 2001 From: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com> Date: Sun, 12 Jul 2026 18:22:26 +0530 Subject: [PATCH 2/4] refactor(lint): use local toPosixPath helper and add regression test --- .../src/lint/snippet-typecheck.test.ts | 9 +++++++++ .../leadtype/src/lint/snippet-typecheck.ts | 18 ++++++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/leadtype/src/lint/snippet-typecheck.test.ts b/packages/leadtype/src/lint/snippet-typecheck.test.ts index 789343a0..63caf023 100644 --- a/packages/leadtype/src/lint/snippet-typecheck.test.ts +++ b/packages/leadtype/src/lint/snippet-typecheck.test.ts @@ -3,6 +3,7 @@ import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { lintDocs } from "./runner"; +import { toPosixPath } from "./snippet-typecheck"; const tempDirs: string[] = []; @@ -188,4 +189,12 @@ describe("snippet typechecking", () => { ); expect(violations).toEqual([]); }); + + describe("toPosixPath", () => { + it("converts Windows backslash paths to POSIX forward slash paths", () => { + expect( + toPosixPath("C:\\project\\root\\.leadtype-snippet-0\\main.ts") + ).toBe("C:/project/root/.leadtype-snippet-0/main.ts"); + }); + }); }); diff --git a/packages/leadtype/src/lint/snippet-typecheck.ts b/packages/leadtype/src/lint/snippet-typecheck.ts index aee5624a..49268ab3 100644 --- a/packages/leadtype/src/lint/snippet-typecheck.ts +++ b/packages/leadtype/src/lint/snippet-typecheck.ts @@ -3,7 +3,6 @@ import { resolve } from "node:path"; import type { Root } from "mdast"; import type * as TS from "typescript"; import { visit } from "unist-util-visit"; -import { normalizeDocsPath } from "../internal/docs-url"; import { loadTypeScript } from "./snippet-lint"; /** @@ -218,6 +217,10 @@ function isUnresolvedPackageImport( return !(specifier.startsWith(".") || specifier.startsWith("/")); } +export function toPosixPath(filePath: string): string { + return filePath.replace(/\\/g, "/"); +} + export function typecheckSnippets( options: TypecheckSnippetsOptions ): SnippetTypecheckIssue[] { @@ -230,7 +233,7 @@ export function typecheckSnippets( for (const [index, snippet] of options.snippets.entries()) { const virtualDir = `${projectRoot}/.leadtype-snippet-${index}`; for (const file of toVirtualFiles(snippet, virtualDir)) { - virtualFiles.set(normalizeDocsPath(file.path), file); + virtualFiles.set(toPosixPath(file.path), file); } } @@ -246,17 +249,16 @@ export function typecheckSnippets( const baseGetSourceFile = host.getSourceFile.bind(host); const baseDirectoryExists = host.directoryExists?.bind(host); host.fileExists = (fileName) => - virtualFiles.has(normalizeDocsPath(fileName)) || baseFileExists(fileName); + virtualFiles.has(toPosixPath(fileName)) || baseFileExists(fileName); host.readFile = (fileName) => - virtualFiles.get(normalizeDocsPath(fileName))?.content ?? - baseReadFile(fileName); + virtualFiles.get(toPosixPath(fileName))?.content ?? baseReadFile(fileName); // Module resolution probes the containing directory; virtual dirs never // exist on disk, so sibling imports (`./helpers`) need this to resolve. host.directoryExists = (directoryName) => - virtualDirs.has(normalizeDocsPath(directoryName)) || + virtualDirs.has(toPosixPath(directoryName)) || (baseDirectoryExists?.(directoryName) ?? false); host.getSourceFile = (fileName, languageVersion, ...rest) => { - const virtual = virtualFiles.get(normalizeDocsPath(fileName)); + const virtual = virtualFiles.get(toPosixPath(fileName)); if (virtual) { return ts.createSourceFile(fileName, virtual.content, languageVersion); } @@ -290,7 +292,7 @@ export function typecheckSnippets( } const fileName = diagnostic.file?.fileName; const virtual = fileName - ? virtualFiles.get(normalizeDocsPath(fileName)) + ? virtualFiles.get(toPosixPath(fileName)) : undefined; if (!virtual) { continue; // diagnostics from real project files aren't lint's business From 4d861f59e10838db18f7edd90f4f788a3cc23a52 Mon Sep 17 00:00:00 2001 From: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com> Date: Sun, 12 Jul 2026 22:09:31 +0530 Subject: [PATCH 3/4] test(lint): add integration regression test for Windows path separator check --- .../src/lint/snippet-typecheck.test.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/leadtype/src/lint/snippet-typecheck.test.ts b/packages/leadtype/src/lint/snippet-typecheck.test.ts index 63caf023..fb3fde7a 100644 --- a/packages/leadtype/src/lint/snippet-typecheck.test.ts +++ b/packages/leadtype/src/lint/snippet-typecheck.test.ts @@ -190,6 +190,34 @@ describe("snippet typechecking", () => { expect(violations).toEqual([]); }); + it("catches typecheck errors when projectRoot contains Windows-style backslashes (Windows regression integration test)", async () => { + const { projectRoot, srcDir } = await createTypecheckProject(); + + // Create a path containing backslashes to simulate a Windows environment path separator mismatch + const backslashedProjectRoot = `${projectRoot}${path.sep}subdir\\subsubdir`; + + const violations = await lintWithTypecheck( + backslashedProjectRoot, + srcDir, + [ + "```ts", + "// @check", + 'const x: number = "this is a string";', + "```", + "", + ].join("\n") + ); + + // This should successfully detect the type error + expect(violations).toEqual([ + expect.objectContaining({ + rule: "snippet:types", + severity: "error", + message: expect.stringContaining("string"), + }), + ]); + }); + describe("toPosixPath", () => { it("converts Windows backslash paths to POSIX forward slash paths", () => { expect( From 1c15bd014521d5bb91d86e80cf78dc67ad53d814 Mon Sep 17 00:00:00 2001 From: Kaylee <65376239+KayleeWilliams@users.noreply.github.com> Date: Wed, 15 Jul 2026 11:03:55 +0100 Subject: [PATCH 4/4] chore: add changeset for Windows snippet typechecking fix --- .changeset/calm-paths-agree.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/calm-paths-agree.md diff --git a/.changeset/calm-paths-agree.md b/.changeset/calm-paths-agree.md new file mode 100644 index 00000000..3bf24900 --- /dev/null +++ b/.changeset/calm-paths-agree.md @@ -0,0 +1,5 @@ +--- +"leadtype": patch +--- + +Fix snippet typechecking on Windows by normalizing virtual file paths before TypeScript compiler host lookups.