Skip to content

Commit

Permalink
Escape {{ after the rehype phase instead of during the remark phase (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
NullVoxPopuli authored Mar 11, 2024
1 parent ae7e596 commit 3cb1179
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 39 deletions.
45 changes: 20 additions & 25 deletions packages/ember-repl/addon/src/compile/formats/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,6 @@ const ALLOWED_LANGUAGES = ['gjs', 'hbs'] as const;
type AllowedLanguage = (typeof ALLOWED_LANGUAGES)[number];
type RelevantCode = Omit<Code, 'lang'> & { lang: AllowedLanguage };

const escapeCurlies = (node: Text | Parent) => {
if ('value' in node && node.value) {
node.value = node.value.replace(/{{/g, '\\{{');
}

if ('children' in node && node.children) {
node.children.forEach((child) => escapeCurlies(child as Parent));
}

if (!node.data) {
return;
}

if ('hChildren' in node.data && Array.isArray(node.data['hChildren'])) {
node.data['hChildren'].forEach(escapeCurlies);

return;
}
};

function isLive(meta: string) {
return meta.includes('live');
}
Expand Down Expand Up @@ -218,6 +198,24 @@ function liveCodeExtraction(options: Options = {}) {
};
}

function sanitizeForGlimmer(/* options */) {
return (tree: Parent) => {
visit(tree, 'element', (node: Parent) => {
if ('tagName' in node) {
if (node.tagName !== 'pre') return;

visit(node, 'text', (textNode: Text) => {
if ('value' in textNode && textNode.value) {
textNode.value = textNode.value.replace(/{{/g, '\\{{');
}
});

return 'skip';
}
});
};
}

function buildCompiler(options: ParseMarkdownOptions) {
let compiler = unified().use(remarkParse).use(remarkGfm);

Expand Down Expand Up @@ -261,9 +259,6 @@ function buildCompiler(options: ParseMarkdownOptions) {
let properties = (node as any).properties;

if (properties?.[GLIMDOWN_PREVIEW]) {
// Have to sanitize anything Glimmer could try to render
escapeCurlies(node as Parent);

return 'skip';
}

Expand All @@ -274,8 +269,6 @@ function buildCompiler(options: ParseMarkdownOptions) {
return;
}

escapeCurlies(node as Parent);

return 'skip';
}

Expand Down Expand Up @@ -314,6 +307,8 @@ function buildCompiler(options: ParseMarkdownOptions) {
});
});

compiler = compiler.use(sanitizeForGlimmer);

// Finally convert to string! oofta!
compiler = compiler.use(rehypeStringify, {
collapseEmptyAttributes: true,
Expand Down
3 changes: 2 additions & 1 deletion packages/ember-repl/test-app/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import config from 'test-app/config/environment';
// But they aren't used.... so.. that's fun.
Object.assign(window, {
process: { env: {} },
Buffer: {},
// Polyfilled in webpack
// Buffer: {},
});

export default class App extends Application {
Expand Down
1 change: 1 addition & 0 deletions packages/ember-repl/test-app/ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ module.exports = function (defaults) {
resolve: {
fallback: {
path: 'path-browserify',
buffer: require.resolve('buffer/'),
},
},
},
Expand Down
2 changes: 2 additions & 0 deletions packages/ember-repl/test-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
"lint:prettier": "pnpm -w exec lint prettier"
},
"dependencies": {
"@shikijs/rehype": "^1.1.7",
"@types/unist": "^3.0.2",
"buffer": "^6.0.3",
"common-tags": "^1.8.2",
"ember-repl": "workspace:*",
"ember-resources": "^7.0.0",
Expand Down
81 changes: 68 additions & 13 deletions packages/ember-repl/test-app/tests/unit/markdown-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { module, test } from 'qunit';

import rehypeShiki from '@shikijs/rehype';
import { stripIndent } from 'common-tags';
import { invocationOf, nameFor } from 'ember-repl';
import { parseMarkdown } from 'ember-repl/formats/markdown';
Expand All @@ -10,13 +11,7 @@ import { visit } from 'unist-util-visit';
* indentation are stripped
*/
function assertOutput(actual: string, expected: string) {
let _actual = actual
.split('\n')
.filter(Boolean)
.join('\n')
.trim()
.replace(/<div class="glimdown-render">/, '')
.replace(/<\/div>/, '');
let _actual = actual.split('\n').filter(Boolean).join('\n').trim();
let _expected = expected.split('\n').filter(Boolean).join('\n').trim();

QUnit.assert.equal(_actual, _expected);
Expand Down Expand Up @@ -60,7 +55,7 @@ module('Unit | parseMarkdown()', function () {
<h1>Title</h1>
<div class=\"glimdown-snippet relative\"><pre><code class=\"language-js\"> const two = 2;
</code></pre><CopyMenu />
</code></pre><CopyMenu /></div>
`
);

Expand Down Expand Up @@ -178,6 +173,38 @@ module('Unit | parseMarkdown()', function () {

assert.deepEqual(result.blocks, []);
});

test('rehypePlugins retain {{ }} escaping', async function () {
let result = await parseMarkdown(
stripIndent`
# Title
\`\`\`gjs
const two = 2
<template>
{{two}}
</template>
\`\`\`
`,
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rehypePlugins: [[rehypeShiki as any, { theme: 'github-dark' }]],
}
);

assertOutput(
result.templateOnlyGlimdown,
`<h1>Title</h1>
<div class="glimdown-snippet relative"><pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> two</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">template</span><span style="color:#E1E4E8">></span></span>
<span class="line"><span style="color:#F97583"> \\{{</span><span style="color:#79B8FF">two</span><span style="color:#F97583">}}</span></span>
<span class="line"><span style="color:#E1E4E8">&#x3C;/</span><span style="color:#85E89D">template</span><span style="color:#E1E4E8">></span></span>
<span class="line"></span></code></pre></div>
`
);
});
});

module('hbs', function () {
Expand All @@ -199,7 +226,7 @@ module('Unit | parseMarkdown()', function () {
stripIndent`
<h1>Title</h1>
${invocationOf(name)}
<div class=\"glimdown-render\">${invocationOf(name)}</div>
`
);

Expand Down Expand Up @@ -232,7 +259,7 @@ module('Unit | parseMarkdown()', function () {
<h1>Title</h1>
<div class=\"glimdown-snippet relative\"><pre><code class=\"language-gjs\"> const two = 2;
</code></pre><CopyMenu />
</code></pre><CopyMenu /></div>
`
);

Expand All @@ -255,7 +282,7 @@ module('Unit | parseMarkdown()', function () {
stripIndent`
<h1>Title</h1>
${invocationOf(name)}
<div class=\"glimdown-render\">${invocationOf(name)}</div>
`
);

Expand All @@ -268,6 +295,34 @@ module('Unit | parseMarkdown()', function () {
]);
});

test('Code with preview fence has {{ }} tokens escaped', async function () {
let result = await parseMarkdown(stripIndent`
# Title
\`\`\`gjs
const two = 2
<template>
{{two}}
</template>
\`\`\`
`);

assertOutput(
result.templateOnlyGlimdown,
stripIndent`
<h1>Title</h1>
<div class=\"glimdown-snippet relative\"><pre><code class=\"language-gjs\">const two = 2
&#x3C;template>
\\{{two}}
&#x3C;/template>
</code></pre></div>
`
);
});

test('Can invoke a component again when defined in a live fence', async function (assert) {
let snippet = `const two = 2`;
let name = nameFor(snippet);
Expand All @@ -285,7 +340,7 @@ module('Unit | parseMarkdown()', function () {
stripIndent`
<h1>Title</h1>
${invocationOf(name)}
<div class=\"glimdown-render\">${invocationOf(name)}</div>
<Demo />
`
);
Expand Down Expand Up @@ -319,7 +374,7 @@ module('Unit | parseMarkdown()', function () {
stripIndent`
<p>hi</p>
${invocationOf(name)}
<div class=\"glimdown-render\">${invocationOf(name)}</div>
<div class=\"glimdown-snippet relative\"><pre><code class=\"language-gjs\">import Component from '@glimmer/component';
import { on } from '@ember/modifier';
&#x3C;template>
Expand Down
39 changes: 39 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3cb1179

Please sign in to comment.