Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/tidy-markdoc-dashed-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/markdoc': patch
---

Fixes custom `transform` functions being incorrectly dropped for tags and nodes whose names require bracket access (e.g. `side-note`). The check that detects whether a transform respects a custom `render` component now recognizes bracket notation, optional chaining and whitespace, not only dot notation.
12 changes: 8 additions & 4 deletions packages/integrations/markdoc/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,15 @@ function syncTagNodeAttributes(config: MergedConfig): void {
*/
function transformRespectsRender(transform: { toString(): string }, configKey: string): boolean {
const source = transform.toString();
// Astro's transforms check config.nodes?.X?.render or config.tags?.X?.render
return (
source.includes(`config.nodes?.${configKey}?.render`) ||
source.includes(`config.tags?.${configKey}?.render`)
// `configKey` may not be a valid identifier (e.g. `side-note`), so a render-respecting
// transform can read `render` via dot or bracket access. Escape the key and match either.
const key = configKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const member = (prop: string) =>
`(?:\\??\\.\\s*${prop}|\\??\\.?\\s*\\[\\s*['"\`]${prop}['"\`]\\s*\\])`;
const pattern = new RegExp(
`config\\s*\\??\\.\\s*(?:nodes|tags)\\s*${member(key)}\\s*${member('render')}`,
);
return pattern.test(source);
}

export function resolveComponentImports(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import type { AstroInstance } from 'astro';
import { resolveComponentImports } from '../dist/runtime.js';

type MarkdocConfig = Parameters<typeof resolveComponentImports>[0];
type NodeComponentMap = Parameters<typeof resolveComponentImports>[2];

// Minimal stand-in for an Astro component; only referential identity matters here.
const Component = (() => null) as unknown as AstroInstance['default'];
const noNodeComponents = {} as NodeComponentMap;

/**
* Regression test for issue #17118: tag/node names containing characters that
* require bracket access (e.g. `side-note`) were always treated as not respecting
* `render`, because the detection only matched dot notation.
*/
describe('Markdoc - resolveComponentImports', () => {
it('keeps a render-respecting transform for tag names that need bracket access (e.g. dashes)', () => {
const markdocConfig: MarkdocConfig = {
tags: {
'side-note': {
// A dashed key can only read `render` through bracket notation.
transform(node, config) {
if (config.tags?.['side-note']?.render) return [];
return node.transformChildren(config);
},
},
},
nodes: {},
};

const resolved = resolveComponentImports(
markdocConfig,
{ 'side-note': Component },
noNodeComponents,
);

assert.equal(resolved.tags['side-note'].render, Component);
assert.equal(
typeof resolved.tags['side-note'].transform,
'function',
'a render-respecting transform should be preserved for dashed tag names',
);
});

it('removes a transform that does not respect render so the custom component wins', () => {
const markdocConfig: MarkdocConfig = {
tags: {
'side-note': {
transform(node, config) {
return node.transformChildren(config);
},
},
},
nodes: {},
};

const resolved = resolveComponentImports(
markdocConfig,
{ 'side-note': Component },
noNodeComponents,
);

assert.equal(resolved.tags['side-note'].render, Component);
assert.equal(
resolved.tags['side-note'].transform,
undefined,
'a non-render-respecting transform should be removed so `render` wins',
);
});
});
Loading