Skip to content

Commit 4e46c31

Browse files
authored
Fix quarto preview subdir/file.qmd crashing with doubled path (#14150)
* Fix `quarto preview subdir/file.qmd` crashing with doubled path `quarto preview subdir/page.qmd` in a website project crashes with `readfile subdir\subdir\page.qmd`. The `projectPath` function was designed for output filenames (resolving relative to source directory via dirname+join), but was also called with the source path itself. When that source path was relative, dirname+join doubled the subdirectory. Split into two functions: `projectRelativeInput` for source paths (normalizes directly) and `projectOutputPath` for output filenames (preserves the dirname+join logic). * Add missing test for relative/absolute path cache convergence The FileInformationCacheMap (added in #13955) normalizes keys so that relative and absolute paths to the same file share one cache entry. This adds the test case that was missing from the original test suite.
1 parent 0f97e05 commit 4e46c31

2 files changed

Lines changed: 44 additions & 5 deletions

File tree

src/command/render/render.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,24 @@ export async function renderPandoc(
385385
));
386386
}
387387

388-
// if there is a project context then return paths relative to the project
389-
const projectPath = (path: string) => {
388+
// Compute the project-relative path for the input source file.
389+
// Uses normalizePath to handle both relative and absolute source paths.
390+
const projectRelativeInput = (sourcePath: string) => {
391+
if (context.project) {
392+
return relative(
393+
normalizePath(context.project.dir),
394+
normalizePath(sourcePath),
395+
);
396+
}
397+
return sourcePath;
398+
};
399+
400+
// Resolve an output file path to a project-relative path.
401+
// Output paths (like "page.html") are relative to the source file's
402+
// directory, so we join with dirname(target.source) before computing
403+
// the project-relative result. Absolute output paths pass through
404+
// normalizePath directly.
405+
const projectOutputPath = (path: string) => {
390406
if (context.project) {
391407
if (isAbsolute(path)) {
392408
return relative(
@@ -411,7 +427,7 @@ export async function renderPandoc(
411427

412428
const result: RenderedFile = {
413429
isTransient: recipe.isOutputTransient,
414-
input: projectPath(context.target.source),
430+
input: projectRelativeInput(context.target.source),
415431
markdown: executeResult.markdown,
416432
format,
417433
supporting: supporting
@@ -421,7 +437,7 @@ export async function renderPandoc(
421437
: undefined,
422438
file: recipe.isOutputTransient
423439
? finalOutput!
424-
: projectPath(finalOutput!),
440+
: projectOutputPath(finalOutput!),
425441
resourceFiles: {
426442
globs: pandocResult.resources,
427443
files,

tests/unit/project/file-information-cache.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import { unitTest } from "../../test.ts";
1111
import { assert } from "testing/asserts";
12-
import { join } from "../../../src/deno_ral/path.ts";
12+
import { join, relative } from "../../../src/deno_ral/path.ts";
1313
import {
1414
ensureFileInformationCache,
1515
FileInformationCacheMap,
@@ -107,3 +107,26 @@ unitTest(
107107
);
108108
},
109109
);
110+
111+
// deno-lint-ignore require-await
112+
unitTest(
113+
"fileInformationCache - relative and absolute paths share same entry",
114+
async () => {
115+
const project = createMockProjectContext();
116+
117+
const absolutePath = join(project.dir, "subdir", "page.qmd");
118+
const relativePath = relative(Deno.cwd(), absolutePath);
119+
120+
const entry1 = ensureFileInformationCache(project, relativePath);
121+
const entry2 = ensureFileInformationCache(project, absolutePath);
122+
123+
assert(
124+
entry1 === entry2,
125+
"Relative and absolute paths to same file should share a cache entry",
126+
);
127+
assert(
128+
project.fileInformationCache.size === 1,
129+
"Should have exactly one cache entry",
130+
);
131+
},
132+
);

0 commit comments

Comments
 (0)