fix(components): prevent infinite recursion from circular transcludes (#2187)

* fix(components): prevent infinite recursion from circular transcludes

* chore(components): modify warning message

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
This commit is contained in:
Michael Thomason
2025-11-01 06:22:29 -04:00
committed by GitHub
parent 8bc6cb9061
commit 2fdc8129b6

View File

@@ -9,6 +9,7 @@ import { visit } from "unist-util-visit"
import { Root, Element, ElementContent } from "hast" import { Root, Element, ElementContent } from "hast"
import { GlobalConfiguration } from "../cfg" import { GlobalConfiguration } from "../cfg"
import { i18n } from "../i18n" import { i18n } from "../i18n"
import { styleText } from "util"
interface RenderComponents { interface RenderComponents {
head: QuartzComponent head: QuartzComponent
@@ -68,6 +69,7 @@ function renderTranscludes(
cfg: GlobalConfiguration, cfg: GlobalConfiguration,
slug: FullSlug, slug: FullSlug,
componentData: QuartzComponentProps, componentData: QuartzComponentProps,
visited: Set<FullSlug>,
) { ) {
// process transcludes in componentData // process transcludes in componentData
visit(root, "element", (node, _index, _parent) => { visit(root, "element", (node, _index, _parent) => {
@@ -76,6 +78,30 @@ function renderTranscludes(
if (classNames.includes("transclude")) { if (classNames.includes("transclude")) {
const inner = node.children[0] as Element const inner = node.children[0] as Element
const transcludeTarget = (inner.properties["data-slug"] ?? slug) as FullSlug const transcludeTarget = (inner.properties["data-slug"] ?? slug) as FullSlug
if (visited.has(transcludeTarget)) {
console.warn(
styleText(
"yellow",
`Warning: Skipping circular transclusion: ${slug} -> ${transcludeTarget}`,
),
)
node.children = [
{
type: "element",
tagName: "p",
properties: { style: "color: var(--secondary);" },
children: [
{
type: "text",
value: `Circular transclusion detected: ${transcludeTarget}`,
},
],
},
]
return
}
visited.add(transcludeTarget)
const page = componentData.allFiles.find((f) => f.slug === transcludeTarget) const page = componentData.allFiles.find((f) => f.slug === transcludeTarget)
if (!page) { if (!page) {
return return
@@ -196,7 +222,8 @@ export function renderPage(
// make a deep copy of the tree so we don't remove the transclusion references // make a deep copy of the tree so we don't remove the transclusion references
// for the file cached in contentMap in build.ts // for the file cached in contentMap in build.ts
const root = clone(componentData.tree) as Root const root = clone(componentData.tree) as Root
renderTranscludes(root, cfg, slug, componentData) const visited = new Set<FullSlug>([slug])
renderTranscludes(root, cfg, slug, componentData, visited)
// set componentData.tree to the edited html that has transclusions rendered // set componentData.tree to the edited html that has transclusions rendered
componentData.tree = root componentData.tree = root