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 @@ -50,6 +50,7 @@ All changes included in 1.9:
- ([#13825](https://github.com/quarto-dev/quarto-cli/issues/13825)): Fix `column: margin` not working with `renderings: [light, dark]` option. Column classes are now preserved when applying theme classes to cell outputs.
- ([#13883](https://github.com/quarto-dev/quarto-cli/issues/13883)): Fix unequal top/bottom spacing in simple untitled callouts.
- ([#13900](https://github.com/quarto-dev/quarto-cli/issues/13900)): Warn when `renderings` cell option contains duplicate names. Previously, duplicate names like `[dark, light, dark, light]` would silently use only the last output for each name.
- ([#14065](https://github.com/quarto-dev/quarto-cli/issues/14065)): Fix `SCSSParsingError` when custom SCSS themes contain non-ASCII characters in selectors (e.g., `#présentation`).

### `typst`

Expand Down
13 changes: 12 additions & 1 deletion src/core/sass/add-css-vars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ import { getVariableDependencies } from "./analyzer/get-dependencies.ts";

const { getSassAst } = makeParserModule(parse);

// Reverse the _u<hex>_ encoding applied in parse.ts so that
// variable names emitted into the CSS vars block match the
// original SCSS source that Dart Sass compiles against.
// Non-ASCII codepoints are valid in CSS custom property names since they
// follow the <ident> production (see spec references in parse.ts).
const decodeScssName = (name: string) =>
name.replace(/_u([0-9a-f]+)_/g, (_, hex: string) =>
String.fromCodePoint(parseInt(hex, 16))
);

export class SCSSParsingError extends Error {
constructor(message: string) {
super(`SCSS Parsing Error: ${message}`);
Expand All @@ -38,7 +48,8 @@ export const cssVarsBlock = (scssSource: string) => {
for (const [dep, _] of deps) {
const decl = ast.get(dep);
if (decl.valueType === "color") {
output.push(`--quarto-scss-export-${dep}: #{$${dep}};`);
const originalName = decodeScssName(dep);
output.push(`--quarto-scss-export-${originalName}: #{$${originalName}};`);
}
}
output.push("}");
Expand Down
15 changes: 15 additions & 0 deletions src/core/sass/analyzer/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ export const makeParserModule = (
"$1: $2",
);

// scss-parser's tokenizer only handles ASCII identifier characters.
// Non-ASCII codepoints are valid in both CSS and SCSS identifiers:
// - CSS Syntax L3 §4.2 defines "ident code point" as including any
// codepoint >= U+0080 (https://www.w3.org/TR/css-syntax-3/#ident-code-point)
// - CSS2 grammar includes `nonascii` in `nmstart`/`nmchar` productions
// (https://www.w3.org/TR/CSS2/grammar.html#scanner)
// - Sass inherits CSS's <ident-token> grammar for identifiers
// (https://github.com/sass/sass/blob/main/spec/syntax.md)
// Dart Sass handles them correctly, so we encode here as ASCII
// placeholders for analysis only, then decode in add-css-vars.ts.
contents = contents.replaceAll(
/[^\x00-\x7F]/g,
(ch) => `_u${ch.codePointAt(0)!.toString(16)}_`,
);

// This is relatively painful, because unfortunately the error message of scss-parser
// is not helpful.

Expand Down
2 changes: 2 additions & 0 deletions tests/docs/smoke-all/2025/03/31/issue-12338/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
7 changes: 7 additions & 0 deletions tests/docs/smoke-all/2026/02/20/custom-14065-var.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*-- scss:defaults --*/
$présentation-bg: #ff0000;

/*-- scss:rules --*/
#test-unicode-var {
background-color: $présentation-bg;
}
5 changes: 5 additions & 0 deletions tests/docs/smoke-all/2026/02/20/custom-14065.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/*-- scss:rules --*/

#présentation p {
line-height: 2;
}
15 changes: 15 additions & 0 deletions tests/docs/smoke-all/2026/02/20/issue-14065-var.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
title: "Unicode SCSS Variable Test"
format:
html:
theme:
- default
- custom-14065-var.scss
_quarto:
tests:
html:
ensureCssRegexMatches:
- ['#test-unicode-var', '--quarto-scss-export-présentation-bg']
---

Content with a unicode-named color variable in SCSS.
17 changes: 17 additions & 0 deletions tests/docs/smoke-all/2026/02/20/issue-14065.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: "Unicode SCSS Test"
format:
html:
theme:
- default
- custom-14065.scss
_quarto:
tests:
html:
ensureCssRegexMatches:
- ['#présentation p', '--quarto-scss-export-']
---

## Présentation

Unicode characters in headings become CSS selectors.
Loading