Skip to content

ensure replaceAll() is called with function parameter when str ing is not known to be free of $ (#12853) #12916

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 12, 2025
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.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,4 @@ All changes included in 1.8:

- ([#11321](https://github.com/quarto-dev/quarto-cli/issues/11321)): Follow [recommendation from LaTeX project](https://latex-project.org/news/latex2e-news/ltnews40.pdf) and use `lualatex` instead of `xelatex` as the default PDF engine.
- ([#12782](https://github.com/quarto-dev/quarto-cli/pull/12782)): fix bug on `safeRemoveDirSync`'s detection of safe directory boundaries.
- ([#12853](https://github.com/quarto-dev/quarto-cli/issues/12853)): fix replaceAll() escaping issue with embedded notebooks containing `$` in their Markdown.
28 changes: 14 additions & 14 deletions package/src/common/update-html-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -820,63 +820,63 @@ async function updateBootstrapFromBslib(
for (let line of varContents) {
line = line.replaceAll(
"var(--#{$prefix}font-sans-serif)",
"$font-family-sans-serif"
"$$font-family-sans-serif"
);
line = line.replaceAll(
"var(--#{$prefix}font-monospace)",
"$font-family-monospace"
"$$font-family-monospace"
);
line = line.replaceAll(
"var(--#{$prefix}success-rgb)",
"$success"
"$$success"
);
line = line.replaceAll(
"var(--#{$prefix}danger-rgb)",
"$danger"
"$$danger"
);
line = line.replaceAll(
"var(--#{$prefix}body-color-rgb)",
"$body-color"
"$$body-color"
);
line = line.replaceAll(
"var(--#{$prefix}body-bg-rgb)",
"$body-bg"
"$$body-bg"
);
line = line.replaceAll(
"var(--#{$prefix}emphasis-color-rgb)",
"$body-emphasis-color"
"$$body-emphasis-color"
);
line = line.replaceAll(
/RGBA?\(var\(--#\{\$prefix\}emphasis-color-rgb,(.*?)\).*?\)/gm,
"$body-emphasis-color"
"$$body-emphasis-color"
);
line = line.replaceAll(
"var(--#{$prefix}secondary-color)",
"$body-secondary-color"
"$$body-secondary-color"
);
line = line.replaceAll(
"var(--#{$prefix}secondary-bg)",
"$body-secondary-bg"
"$$body-secondary-bg"
);
line = line.replaceAll(
"var(--#{$prefix}tertiary-bg)",
"$body-tertiary-bg"
"$$body-tertiary-bg"
);
line = line.replaceAll(
"var(--#{$prefix}tertiary-color)",
"$body-tertiary-color"
);
line = line.replaceAll(
"var(--#{$prefix}emphasis-bg)",
"$body-emphasis-bg"
"$$body-emphasis-bg"
);
line = line.replaceAll(
"var(--#{$prefix}emphasis-color)",
"$body-emphasis-color"
"$$body-emphasis-color"
);
line = line.replaceAll(
"$emphasis-color-rgb",
"$body-emphasis-color"
"$$body-emphasis-color"
);

line = line.replaceAll(/var\(--#\{\$prefix\}(.*?)\)/gm, "$$$1");
Expand Down
9 changes: 4 additions & 5 deletions src/core/handlers/embed.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/*
* embed.ts
*
* Copyright (C) 2022 by Posit, PBC
*
*/
* embed.ts
*
* Copyright (C) 2022-2025 by Posit, PBC
*/

import { LanguageCellHandlerContext, LanguageHandler } from "./types.ts";
import { baseHandler, install } from "./base.ts";
Expand Down
6 changes: 3 additions & 3 deletions src/core/handlers/mermaid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ mermaid.initialize(${JSON.stringify(mermaidOpts)});
const oldId = svg.getAttribute("id") as string;
svg.setAttribute("id", newMermaidId);
const style = svg.querySelector("style")!;
style.innerHTML = style.innerHTML.replaceAll(oldId, newMermaidId);
style.innerHTML = style.innerHTML.replaceAll(oldId, () => newMermaidId);

for (const defNode of svg.querySelectorAll("defs")) {
const defEl = defNode as Element;
Expand Down Expand Up @@ -296,11 +296,11 @@ mermaid.initialize(${JSON.stringify(mermaidOpts)});
// this string substitution is fraught, but I don't know how else to fix the problem.
oldSvgSrc = oldSvgSrc.replaceAll(
`"${idToPatch}"`,
`"${to}"`,
() => `"${to}"`,
);
oldSvgSrc = oldSvgSrc.replaceAll(
`#${idToPatch}`,
`#${to}`,
() => `#${to}`,
);
}
svg = mappedDiff(svg, oldSvgSrc);
Expand Down
9 changes: 8 additions & 1 deletion src/core/jupyter/jupyter-embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,14 @@ export async function replaceNotebookPlaceholders(
}

// Replace the placeholders with the rendered markdown
markdown = markdown.replaceAll(match[0], nbMarkdown || "");
markdown = markdown.replaceAll(
match[0],
// https://github.com/quarto-dev/quarto-cli/issues/12853
// we use a function here to avoid
// escaping issues with $ in the markdown
// (e.g. $x$ in math mode)
() => nbMarkdown ?? "",
);
}
match = regex.exec(markdown);
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/jupyter/jupyter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1039,7 +1039,7 @@ export function mdFromContentCell(
for (let i = 0; i < source.length; i++) {
source[i] = source[i].replaceAll(
`attachment:${file}`,
imageFile,
() => imageFile,
);
}
// only process one supported mime type
Expand Down
5 changes: 4 additions & 1 deletion src/execute/rmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ export const knitrEngine: ExecutionEngine = {
options.quiet,
// fixup .rmarkdown file references
(output) => {
output = output.replaceAll(`${inputStem}.rmarkdown`, inputBasename);
output = output.replaceAll(
`${inputStem}.rmarkdown`,
() => inputBasename,
);

const m = output.match(/^Quitting from lines (\d+)-(\d+)/m);
if (m) {
Expand Down
2 changes: 1 addition & 1 deletion src/format/jats/format-jats-postprocess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const renderSubarticlePostProcessor = (
// Replace the placeholder with the rendered subarticle
outputContents = outputContents.replaceAll(
placeholder,
subArtLines.join("\n"),
() => subArtLines.join("\n"),
);

// Move supporting and resource files into place
Expand Down
221 changes: 221 additions & 0 deletions tests/docs/smoke-all/2025/06/11/issue-12853/quarto-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints
.jupyter_cache

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
.pypirc

# MACOS
.DS_Store

# Files generated by invoking Julia with --code-coverage
*.jl.cov
*.jl.*.cov
*.jl.mem

Manifest.toml

.vscode

# System-specific files and directories generated by the BinaryProvider and BinDeps packages
# They contain absolute paths specific to the host computer, and so should not be committed
deps/deps.jl
deps/build.log
deps/downloads/
deps/usr/
deps/src/

# these are all temporary files that will be generated during Quarto render
/.quarto/
/_freeze/
/_site/
/_output/
/_book/
/index_files/
*.embed_files/
*.html
*.gif
*.json
/site_libs/
_tmp_fig.svg

# Files generated from Binder
.TinyTeX
.code-server
.config
.local
.yarn
.jupyter-server-log.txt
.profile
.bashrc
.bash_logout
.wget-hsts
Loading
Loading