Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/changelog-1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,4 @@ All changes included in 1.9:
- ([#13890](https://github.com/quarto-dev/quarto-cli/issues/13890)): Fix render failure when using `embed-resources: true` with input path through a symlinked directory. The cleanup now resolves symlinks before comparing paths.
- ([#13907](https://github.com/quarto-dev/quarto-cli/issues/13907)): Ignore AI assistant configuration files (`CLAUDE.md`, `AGENTS.md`) when scanning for project input files and in extension templates, similar to how `README.md` is handled.
- ([#13935](https://github.com/quarto-dev/quarto-cli/issues/13935)): Fix `quarto install`, `quarto update`, and `quarto uninstall` interactive tool selection.
- ([#13998](https://github.com/quarto-dev/quarto-cli/issues/13998)): Fix YAML validation error with CR-only line terminators (old Mac format). Documents using `\r` line endings no longer fail with "Expected YAML front matter to contain at least 2 lines".
2 changes: 1 addition & 1 deletion src/core/lib/ranged-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function rangedLines(
text: string,
includeNewLines = false,
): RangedSubstring[] {
const regex = /\r?\n/g;
const regex = /\r\n?|\n/g;
const result: RangedSubstring[] = [];

let startOffset = 0;
Expand Down
6 changes: 3 additions & 3 deletions src/core/lib/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { InternalError } from "./error.ts";
import { quotedStringColor } from "./errors.ts";

export function lines(text: string): string[] {
return text.split(/\r?\n/);
return text.split(/\r\n?|\n/);
}

export function normalizeNewlines(text: string) {
Expand Down Expand Up @@ -62,13 +62,13 @@ export function* matchAll(text: string, regexp: RegExp) {

export function* lineOffsets(text: string) {
yield 0;
for (const match of matchAll(text, /\r?\n/g)) {
for (const match of matchAll(text, /\r\n?|\n/g)) {
yield match.index + match[0].length;
}
}

export function* lineBreakPositions(text: string) {
for (const match of matchAll(text, /\r?\n/g)) {
for (const match of matchAll(text, /\r\n?|\n/g)) {
yield match.index;
}
}
Expand Down
24 changes: 24 additions & 0 deletions tests/smoke/yaml/yaml-cr-line-endings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* yaml-cr-line-endings.test.ts
*
* Test YAML validation with CR-only line endings (old Mac format)
* See: https://github.com/quarto-dev/quarto-cli/issues/13998
*
* Copyright (C) 2025 Posit Software, PBC
*/

import { testRender } from "../render/render.ts";
import { noErrorsOrWarnings } from "../../verify.ts";
import { join } from "../../../src/deno_ral/path.ts";

// Create test file with CR-only line endings programmatically
const dir = Deno.makeTempDirSync({ prefix: "quarto-cr-test-" });
const crContent = "---\rtitle: \"CR Test\"\rauthor: \"Test Author\"\r---\r\rContent here.\r";
const inputFile = join(dir, "cr-only.qmd");
Deno.writeFileSync(inputFile, new TextEncoder().encode(crContent));

testRender(inputFile, "html", false, [noErrorsOrWarnings], {
teardown: async () => {
Deno.removeSync(dir, { recursive: true });
},
});
33 changes: 32 additions & 1 deletion tests/unit/core/lib/text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { unitTest } from "../../../test.ts";
import { assertEquals } from "testing/asserts";
import { getEndingNewlineCount } from "../../../../src/core/lib/text.ts";
import { getEndingNewlineCount, lines } from "../../../../src/core/lib/text.ts";

unitTest("core/lib/text.ts - getEndingNewlineCount", async () => {
// Test case 1: No trailing newlines
Expand Down Expand Up @@ -64,3 +64,34 @@ unitTest("core/lib/text.ts - getEndingNewlineCount", async () => {
3,
);
});

// Test for lines() function with different line endings
// See: https://github.com/quarto-dev/quarto-cli/issues/13998
unitTest("core/lib/text.ts - lines() with different line endings", async () => {
// LF (Unix/Linux)
assertEquals(lines("a\nb\nc"), ["a", "b", "c"]);

// CRLF (Windows)
assertEquals(lines("a\r\nb\r\nc"), ["a", "b", "c"]);

// CR-only (old Mac) - the fix for #13998
assertEquals(lines("a\rb\rc"), ["a", "b", "c"]);

// Mixed endings
assertEquals(lines("a\rb\nc\r\nd"), ["a", "b", "c", "d"]);

// YAML front matter with CR-only
const yaml = "---\rtitle: \"Test\"\r---";
assertEquals(lines(yaml), ["---", "title: \"Test\"", "---"]);

// Empty string
assertEquals(lines(""), [""]);

// Single line without newline
assertEquals(lines("single"), ["single"]);

// Trailing newlines
assertEquals(lines("a\n"), ["a", ""]);
assertEquals(lines("a\r"), ["a", ""]);
assertEquals(lines("a\r\n"), ["a", ""]);
});
Loading