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
69 changes: 69 additions & 0 deletions __tests__/lib/stripComments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,75 @@ end"`);
expect(output).not.toContain('<!-- bottom comment -->');
});

describe('HTMLBlock handling in mdxish mode', () => {
it('does not crash on multiline HTMLBlock content', async () => {
const input = `<HTMLBlock>{\`
<div>hello</div>
\`}</HTMLBlock>`;

const output = await stripComments(input, { mdxish: true });
expect(output).toContain('<HTMLBlock>');
expect(output).toContain('<div>hello</div>');
});

it('preserves HTMLBlock with attributes', async () => {
const input = `<HTMLBlock id="test">{\`
<p>content</p>
\`}</HTMLBlock>`;

const output = await stripComments(input, { mdxish: true });
expect(output).toContain('<HTMLBlock');
expect(output).toContain('<p>content</p>');
});

it('strips comments outside HTMLBlocks while preserving the block', async () => {
const input = `<!-- top comment -->

<HTMLBlock>{\`
<div>hello</div>
\`}</HTMLBlock>

<!-- bottom comment -->`;

const output = await stripComments(input, { mdxish: true });
expect(output).toContain('<HTMLBlock>');
expect(output).not.toContain('<!-- top comment -->');
expect(output).not.toContain('<!-- bottom comment -->');
});

it('strips HTML comments inside HTMLBlock content', async () => {
const input = `<HTMLBlock>{\`
<!-- authored HTML comment -->
<div>hello</div>
\`}</HTMLBlock>`;

const output = await stripComments(input, { mdxish: true });
expect(output).not.toContain('<!-- authored HTML comment -->');
expect(output).toContain('<div>hello</div>');
});

it('strips all comments including inside HTMLBlocks', async () => {
const input = `<div>
<!-- strip me -->
<HTMLBlock>{\`<!-- also strip me --><p>hi</p>\`}</HTMLBlock>
</div>`;

const output = await stripComments(input, { mdxish: true });
expect(output).not.toContain('<!-- also strip me -->');
expect(output).not.toContain('<!-- strip me -->');
expect(output).toContain('<HTMLBlock>');
});

it('handles nested HTMLBlocks', async () => {
const input = `<HTMLBlock>{\`
<HTMLBlock>{\`<div>nested</div>\`}</HTMLBlock>
\`}</HTMLBlock>`;

const output = await stripComments(input, { mdxish: true });
expect(output).toContain('<HTMLBlock>');
});
});

describe('strip comments edge cases', () => {
it.each([
['should return empty for empty string', '', undefined, ''],
Expand Down
53 changes: 53 additions & 0 deletions lib/mdast-util/html-block-component/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { Html } from 'mdast';
import type { CompileContext, Extension as FromMarkdownExtension, Handle, Token } from 'mdast-util-from-markdown';

const contextMap = new WeakMap<Token, { chunks: string[]; lastEndLine: number }>();

function findHtmlBlockComponentToken(this: CompileContext): Token | undefined {
const events = this.tokenStack;
for (let i = events.length - 1; i >= 0; i -= 1) {
if (events[i][0].type === 'htmlBlockComponent') return events[i][0];
}
return undefined;
}

function enterHtmlBlockComponent(this: CompileContext, token: Parameters<Handle>[0]): void {
contextMap.set(token, { chunks: [], lastEndLine: token.start.line });
this.enter({ type: 'html', value: '' } as Html, token);
}

function exitHtmlBlockComponentData(this: CompileContext, token: Parameters<Handle>[0]): void {
const componentToken = findHtmlBlockComponentToken.call(this);
if (!componentToken) return;
const ctx = contextMap.get(componentToken);
if (ctx) {
const gap = token.start.line - ctx.lastEndLine;
if (ctx.chunks.length > 0 && gap > 0) {
ctx.chunks.push('\n'.repeat(gap));
}
ctx.chunks.push(this.sliceSerialize(token));
ctx.lastEndLine = token.end.line;
}
}

function exitHtmlBlockComponent(this: CompileContext, token: Parameters<Handle>[0]): void {
const ctx = contextMap.get(token);
const node = this.stack[this.stack.length - 1] as Html;
if (ctx) {
node.value = ctx.chunks.join('');
contextMap.delete(token);
}
this.exit(token);
}

export function htmlBlockComponentFromMarkdown(): FromMarkdownExtension {
return {
enter: {
htmlBlockComponent: enterHtmlBlockComponent,
},
exit: {
htmlBlockComponentData: exitHtmlBlockComponentData,
htmlBlockComponent: exitHtmlBlockComponent,
},
};
}
1 change: 1 addition & 0 deletions lib/micromark/html-block-component/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { htmlBlockComponent } from './syntax';
Loading